{"id":16382311,"url":"https://github.com/pingbird/tangent","last_synced_at":"2025-10-27T08:35:59.359Z","repository":{"id":150337548,"uuid":"189383122","full_name":"pingbird/tangent","owner":"pingbird","description":"Discord bot with a secure, interactive virtual machine","archived":false,"fork":false,"pushed_at":"2021-06-02T03:37:12.000Z","size":127,"stargazers_count":17,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-18T17:24:45.620Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pingbird.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-30T09:15:40.000Z","updated_at":"2024-08-17T17:21:12.000Z","dependencies_parsed_at":"2023-05-04T00:48:28.565Z","dependency_job_id":null,"html_url":"https://github.com/pingbird/tangent","commit_stats":{"total_commits":97,"total_committers":1,"mean_commits":97.0,"dds":0.0,"last_synced_commit":"c49a698532ba058d3b22c9280a0471fafb5d3015"},"previous_names":["pingbird/tangent"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingbird%2Ftangent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingbird%2Ftangent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingbird%2Ftangent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingbird%2Ftangent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pingbird","download_url":"https://codeload.github.com/pingbird/tangent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245052655,"owners_count":20553162,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T04:04:55.821Z","updated_at":"2025-10-27T08:35:54.291Z","avatar_url":"https://github.com/pingbird.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](https://i.tst.sh/9gQQ5.png)\n\n### Tangent - A discord bot with access to a full Linux VM\n\n## How it works\nTangent uses [libvirt](https://libvirt.org/) to manage a qemu controlled Debian 9 virtual machine and communicates to it through a custom json rpc like protocol.\n\n`tangent-server` contains the server that runs as an unprivelaged user on the VM, allowing the bot on the host machine to start processes, read stdin/stdout, and access files safely.\n\nIn it's current configuration the VM is on a closed virtual network with only the host machine being routable (`192.168.69.1`), iptables are set up so requests from the VM to the host are blocked to prevent it from attempting to connect to SSH or other services it should not have access to.\n\nThe only way for information to go in and out of the VM is through connections initiated by the host.\n\nSystem resources are also heavily limited with 1 thread, 256MB of memory, and a 16GB virtual disk.\n\nIf you manage to put the VM in an unusable state for example by killing the server process, Tangent will automatically use virsh to reboot the VM which only takes around 4 seconds.\n\nAs a last resort if someone obtains root and bricks the system a qcow2 snapshot can restore the system state to brand new using the `qclean` command.\n\nThe bot itself is a Dart application and is designed to be as fault tolerant as possible, all buffers that the vm send to are capped, any malformed packets will instantly terminate the connection, and all of the async wrappers for files and processes are destroyed properly when closed.\n\n## Usage\n\n### Prefixes\nCurrently there are 3 command prefixes for convenience:\n`.`, `α`, `@Tangent` (Will have per-server configuration later)\n\nAny of these prefixes can be used to execute commands\n\n### Misc commands\n`purge \u003cn\u003e` - Purges a number of messages *(admin only)*\n\n### Managment\n`qstart` - Start the VM *(trusted only)*\\\n`qrestart` - Restart the VM *(trusted only)*\\\n`qclean` - Revert the VM state to a clean snapshot *(trusted only)*\n\n### Upload / Download\n`upload [file/dir]` - Upload an attachment to the VM at the specified directory or file name, defaults to the home directory\\\n`download \u003cfile\u003e` - Download a specific file from the VM to Discord\n\n### Languages\nLanguage commands support discord's code block formatting, for example:\n````\nlua ```lua\nprint(\"Hello, World!\")\n```\n````\nwill interpret the contents of the code block.\n\nYou can provide compiler arguments and program arguments by putting text before and after the code block:\n````\n.c -ldl ```c\n#include \u003cstdio.h\u003e\nint main(int argc, char** argv) {\n    printf(\"Hello\\n\");\n    for (int i = 0; i \u003c argc; i++) {\n        printf(\"%i: %s\\n\", i, argv[i]);\n    }\n}\n```\nextra arguments\n````\nOutputs:\n```\nHello\n0: ./tangent\n1: extra\n2: arguments\n```\n\nThe bot is interactive, if you edit your message with a command it will re-run your command and update it's message.\nWhen a user enters a new command or edit a previous one it will kill the previous process to prevent anything from lingering.\n\n#### Commands:\n\n`sh \u003ccode\u003e` - Standard /bin/sh\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```sh\necho Hello from /bin/sh\n```\n\u003c/details\u003e\n\n`bash \u003ccode\u003e` - Standard /bin/bash\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```sh\necho Hello from /bin/bash\n```\n\u003c/details\u003e\n\n`arm \u003ccode\u003e` - ARM Assembly\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```arm\n.globl main\nmain:\n    stmfd sp!, {lr}\n    ldr r0, =hello_text\n    bl puts\n    mov r0, #0\n    ldmfd sp!, {lr}\n    bx lr\nhello_text:\n    .string \"Hello from ARM\\n\"\n```\n\u003c/details\u003e\n\n`x86 \u003ccode\u003e` - x86_64 Assembly\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```asm\n.intel_syntax noprefix\n.globl main\nmain:\n    push rax\n    mov edi, offset hello_text\n    xor eax, eax\n    call puts\n    xor eax, eax\n    pop rcx\n    ret\nhello_text:\n    .string \"Hello from x86\\n\"\n```\n\u003c/details\u003e\n\n`c \u003ccode\u003e` - GCC 6.3.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```c\n#include \u003cstdio.h\u003e\nint main() {\n    puts(\"Hello from C\\n\");\n}\n```\n\u003c/details\u003e\n\n`cpp \u003ccode\u003e` - G++ 6.3.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```cpp\n#include \u003ciostream\u003e\nint main() {\n    std::cout \u003c\u003c \"Hello from C++\\n\";\n}\n```\n\u003c/details\u003e\n\n`lua \u003ccode\u003e` - Lua 5.3 Reference interpreter\n\n`lua5.2 \u003ccode\u003e` - Lua 5.2 Reference interpreter\n\n`lua5.1 \u003ccode\u003e` - Lua 5.1 Reference interpreter\n\n`luajit \u003ccode\u003e` - LuaJIT 2.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```lua\nprint(\"Hello from \" .. _VERSION)\n```\n\u003c/details\u003e\n\n`py \u003ccode\u003e` - Python 3\n\n`py2 \u003ccode\u003e` - Python 2\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```py\nfrom platform import python_version\nprint('Hello from Python {}'.format(python_version()))\n```\n\u003c/details\u003e\n\n`js \u003ccode\u003e` - Node.JS v4.8.2\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```js\nconsole.log(\"Hello from Node.js \" + process.version);\n```\n\u003c/details\u003e\n\n`pl \u003ccode\u003e` - Perl \n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```pl\nprint \"Hello from Perl\\n\";\n```\n\u003c/details\u003e\n\n`java \u003ccode\u003e` - OpenJDK 8\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```java\npublic class Tangent {\n    public static void main(String[] args) {\n        System.out.println(\"Hello from Java\");\n    }\n}\n```\n\u003c/details\u003e\n\n`lisp \u003ccode\u003e` - SBCL\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```lisp\n(format t \"Hello from Lisp\")\n```\n\u003c/details\u003e\n\n`bf \u003ccode\u003e` - Brainfuck\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```bf\n++++++++++[\u003e+\u003e+++\u003e+++++++\u003e++++++++++\u003c\u003c\u003c\u003c-]\u003e\u003e\u003e++.\u003e+.+++++++..+++.\u003c\u003c++.\u003e\u003e---------.++++++++++++.---.--.\u003c\u003c.\u003e------.\u003e+++++.-----------------.++++++++.+++++.--------.+++++++++++++++.------------------.++++++++.\n```\n\u003c/details\u003e\n\n`cs \u003ccode\u003e` - .NET Core C#\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```cs\nclass Program {\n    static void Main() {\n        System.Console.WriteLine(\"Hello from C#\");\n    }\n}\n```\n\u003c/details\u003e\n\n`fs \u003ccode\u003e` - .NET Core F#\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```fs\n[\u003cEntryPoint\u003e]\nlet main argv =\n    printfn \"Hello from F#\"\n    0\n```\n\u003c/details\u003e\n\n`haskell \u003ccode\u003e` - GHC\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```hs\nmain = putStrLn \"Hello from Haskell\"\n```\n\u003c/details\u003e\n\n`php \u003ccode\u003e` - PHP7\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```php\n\u003c?php\necho('Hello from PHP');\n```\n\u003c/details\u003e\n\n`cobol \u003ccode\u003e` - OpenCOBOL\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```cob\nPROGRAM-ID. HELLO.\nPROCEDURE DIVISION.\n    DISPLAY 'Hello from COBOL'.\n    STOP RUN.\n```\n\u003c/details\u003e\n\n`go \u003ccode\u003e` - Go 1.7.4\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```go\npackage main\nimport \"fmt\"\nfunc main() {\n    fmt.Println(\"Hello from Go\")\n}\n```\n\u003c/details\u003e\n\n`ruby \u003ccode\u003e` - Ruby 2.3.3\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```rb\nputs 'Hello from Ruby'\n```\n\u003c/details\u003e\n\n`apl \u003ccode\u003e` - GNU APL\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```apl\n\"Hello from APL\"\n```\n\u003c/details\u003e\n\n`prolog \u003ccode\u003e` - SWI Prolog\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```pl\n:- initialization hello, halt.\nhello :-\n    write('Hello from Prolog'), nl.\n```\n\u003c/details\u003e\n\n`ocaml \u003ccode\u003e` - OCaml 4.02.3\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```ml\nprint_string \"Hello from OCaml\\n\";;\n```\n\u003c/details\u003e\n\n`sml \u003ccode\u003e` - SML-NJ v110.79\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```sml\nprint \"Hello from SML\\n\";\n```\n\u003c/details\u003e\n\n`crystal \u003ccode\u003e` - Crystal 0.28.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```cr\nputs \"Hello from Crystal\"\n```\n\u003c/details\u003e\n\n`ada \u003ccode\u003e` - GNAT\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```ada\nwith Ada.Text_IO; use Ada.Text_IO;\nprocedure Hello is\nbegin\n   Put_Line (\"Hello from Ada\");\nend Hello;\n```\n\u003c/details\u003e\n\n`d \u003ccode\u003e` - GDC\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```d\nimport std.stdio;\nvoid main() {\n    writeln(\"Hello from D\");\n}\n```\n\u003c/details\u003e\n\n`groovy \u003ccode\u003e` - Groovy 2.4.8\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```groovy\nprintln \"Hello from Groovy\"\n```\n\u003c/details\u003e\n\n`dart \u003ccode\u003e` - Dart 2.3.1\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```dart\nmain() {\n\tprint(\"Hello from Dart\");\n}\n```\n\u003c/details\u003e\n\n`erlang \u003ccode\u003e` - Erlang\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```erl\n-module(tangent).\n-export([main/0]).\nmain() -\u003e io:fwrite(\"Hello from Erlang\\n\").\n```\n\u003c/details\u003e\n\n`forth \u003ccode\u003e` - GNU FORTH\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```\n.( Hello from FORTH) CR\n```\n\u003c/details\u003e\n\n`pascal \u003ccode\u003e` - Free Pascal Compiler 3.0.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```pascal\nprogram Tangent(output);\nbegin\n  writeln('Hello from Pascal');\nend.\n```\n\u003c/details\u003e\n\n`hack \u003ccode\u003e` - HipHop VM 4.8.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```hack\necho 'Hello from Hack';\n```\n\u003c/details\u003e\n\n`julia \u003ccode\u003e` - Julia 0.4.7\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```julia\nprintln(\"Hello from Julia\")\n```\n\u003c/details\u003e\n\n`kotlin \u003ccode\u003e` - Kotlin 1.3.31\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\nfun main(args : Array\u003cString\u003e) {\n    println(\"Hello from Kotlin\")\n}\n```\n\u003c/details\u003e\n\n`scala \u003ccode\u003e` - Scala 2.12.8\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```scala\nobject Tangent {\n  def main(args: Array[String]): Unit = {\n    println(\"Hello from Scala\")\n  }\n}\n```\n\u003c/details\u003e\n\n`swift \u003ccode\u003e` - Swift 4.1\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```swift\nimport Swift\nprint(\"Hello from Swift\")```\n```\n\u003c/details\u003e\n\n`typescript \u003ccode\u003e` - Typescript 2.1.5 on Node.JS v4.8.2\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```ts\nconsole.log(\"Hello from TypeScript\");\n```\n\u003c/details\u003e\n\n`verilog \u003ccode\u003e` - Icarus Verilog\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```v\nmodule test;\n  initial begin\n    $display(\"Hello from Verilog\");\n  end\nendmodule\n```\n\u003c/details\u003e\n\n`wasm \u003ccode\u003e` - Webassembly WAVM\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```scheme\n(module\n  (import \"env\" \"_fwrite\" (func $__fwrite (param i32 i32 i32 i32) (result i32)))\n  (import \"env\" \"_stdout\" (global $stdoutPtr i32))\n  (import \"env\" \"memory\" (memory 1))\n  (export \"main\" (func $main))\n  (data (i32.const 8) \"Hello from WASM\\n\")\n  (func (export \"establishStackSpace\") (param i32 i32) (nop))\n  (func $main (result i32)\n    (local $stdout i32)\n    (local.set $stdout (i32.load align=4 (global.get $stdoutPtr)))\n    (call $__fwrite\n       (i32.const 8)\n       (i32.const 1)\n       (i32.const 16)\n       (local.get $stdout)\n    )\n    (return (i32.const 0))\n  )\n)\n```\n\u003c/details\u003e\n\n`scheme \u003ccode\u003e` - MIT Scheme 9.1.1\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```scheme\n(begin\n  (display \"Hello from Scheme\")\n  (newline))\n```\n\u003c/details\u003e\n\n`awk \u003ccode\u003e` - GNU Awk 4.1.4\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```awk\nBEGIN { print \"Hello from AWK\" }\n```\n\u003c/details\u003e\n\n`clojure \u003ccode\u003e` - Clojure 1.8.0\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```clojure\n(println \"Hello from Clojure\")\n```\n\u003c/details\u003e\n\n`tibasic \u003ccode\u003e` - Limited TI-BASIC interpreter from [patrickfeltes/ti-basic-interpreter](https://github.com/patrickfeltes/ti-basic-interpreter/)\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```js\nDisp \"Hello from TI-BASIC\"\n```\n\u003c/details\u003e\n\n`batch \u003ccode\u003e` - WINE's cmd.exe\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```bat\n@echo off\necho Hello from Batch\n```\n\u003c/details\u003e\n\n`racket \u003ccode\u003e` - Racket 6.7\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```lisp\n#lang racket/base\n(print \"Hello from Racket\")\n```\n\u003c/details\u003e\n\n`Rust \u003ccode\u003e` - rustc 1.24.1\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```rust\nfn main() {\n\tprintln!(\"Hello from Rust\");\n}\n```\n\u003c/details\u003e\n\n`bc \u003ccode\u003e` - GNU Basic Calculator\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```lua\nprint \"Hello from BC\\n\"\n```\n\u003c/details\u003e\n\n`ir \u003ccode\u003e` - LLVM IR\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```\n@.str = private unnamed_addr constant [20 x i8] c\"Hello from LLVM IR\\0A\\00\"\n\ndefine i32 @main() #0 {\n  %1 = call i32 @puts(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str, i32 0, i32 0))\n  ret i32 0\n}\n\ndeclare i32 @puts(i8*) #1\n```\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpingbird%2Ftangent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpingbird%2Ftangent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpingbird%2Ftangent/lists"}