import { describe, expect, it } from "vitest"; import { isProtectedComputerLifecycleCommand } from "./executor.js"; import { isProtectedComputerLifecycleCommandReadingScripts, protectedScriptWorkspacePath, } from "./executor-shell-guard.js"; describe("computer lifecycle command guard", () => { it("rejects commands that can destroy a graphical bot's desktop", () => { for (const command of [ "pkill chromium", "killall chrome", "kill -9 1234", "k\\ill -9 1234", "xkill", "systemctl restart chromium", "systemctl --user restart chromium", "service chromium restart", "rm -rf ~/.browser-profiles/chromium", 'rm -rf "$HOME/.browser-profiles/chromium"', "rm -f /tmp/.X1-lock", "bash -c 'pkill chromium'", 'bash -lc "killall chrome"', "bash -o posix -c 'pkill chromium'", "bash --rcfile /tmp/bashrc -c 'pkill chromium'", "sh -c 'systemctl restart chromium'", `bash -c 'eval "pkill chromium"'`, 'bash -lc "source /tmp/kill-chrome.sh"', "printf 'pkill chromium\\n' > /tmp/x; . /tmp/x", "bash -c `pkill chromium`", 'bash <<< "pkill chromium"', "$KILLER chromium", 'rm -rf "$TARGET/.browser-profiles/chromium"', ]) { expect(isProtectedComputerLifecycleCommand(command)).toBe(true); } }); it("rejects kill/pkill invoked through interpreter inline programs", () => { for (const command of [ `python -c 'import os; os.system("pkill chromium")'`, `python3 -c 'import subprocess; subprocess.call(["pkill", "chromium"])'`, `python -c 'import os; os.kill(1234, 9)'`, `python3 -Oc 'import os; os.system("killall chrome")'`, `node -e 'require("child_process").execSync("pkill chromium")'`, `node --eval 'process.kill(1234, "SIGKILL")'`, `node --eval=process.kill(1,"SIGKILL")`, `nodejs -e 'require("child_process").spawnSync("xkill")'`, `perl -e 'system("pkill chromium")'`, `ruby -e 'system("pkill chromium")'`, `php -r 'system("pkill chromium");'`, `bash -c 'python -c "import os; os.system(\\"pkill chromium\\")"'`, ]) { expect(isProtectedComputerLifecycleCommand(command)).toBe(true); } }); it("rejects kill-family teardown in interpreter script files", () => { const files = new Map([ ["/tmp/kill.py", "import os\nos.system('pkill chromium')\n"], ["evil.js", "process.kill(1, 'SIGKILL')\n"], ["./scripts/stop.py", "import subprocess\nsubprocess.call(['killall', 'chrome'])\n"], ]); const readScript = (scriptPath: string) => files.get(scriptPath); for (const command of [ "python /tmp/kill.py", "node evil.js", "python3 ./scripts/stop.py", "python3.12 ./scripts/stop.py", "bash -c 'python evil.js'", "bash -lc 'node evil.js'", "node -r evil.js -e 'console.log(1)'", ]) { expect(isProtectedComputerLifecycleCommand(command, readScript)).toBe(true); } // Filename heuristic when the executor cannot read the script (container /tmp). expect(isProtectedComputerLifecycleCommand("python /tmp/kill.py")).toBe(true); expect(isProtectedComputerLifecycleCommand("perl /tmp/pkill.pl")).toBe(true); expect(isProtectedComputerLifecycleCommand("node /tmp/killall.js")).toBe(true); expect(isProtectedComputerLifecycleCommand("ruby xkill.rb")).toBe(true); expect(isProtectedComputerLifecycleCommand("/usr/bin/python3 /tmp/kill.py")).toBe(true); }); it("prefers script contents over the filename heuristic", async () => { await expect( isProtectedComputerLifecycleCommandReadingScripts( "python kill.py", async () => "print('hello')\n", ), ).resolves.toBe(false); await expect( isProtectedComputerLifecycleCommandReadingScripts("python kill.py", async () => undefined), ).resolves.toBe(true); await expect( isProtectedComputerLifecycleCommandReadingScripts( "bash -c 'node evil.js'", async () => "process.kill(1, 'SIGKILL')\n", ), ).resolves.toBe(true); }); it("keeps ordinary interpreter inline programs available", () => { expect(isProtectedComputerLifecycleCommand(`python -c 'print("hello")'`)).toBe(false); expect(isProtectedComputerLifecycleCommand(`python3 -c 'value = "$HOME"; print(value)'`)).toBe( false, ); expect(isProtectedComputerLifecycleCommand(`node -e 'console.log(1 + 1)'`)).toBe(false); expect( isProtectedComputerLifecycleCommand(`node -e 'const x = "kill switch"; console.log(x)'`), ).toBe(false); }); it("keeps ordinary interpreter scripts available", () => { const files = new Map([ ["scripts/check.js", "console.log('ok')\n"], ["scripts/train.py", "print('epochs')\n"], ["kill.py", "print('hello')\n"], ["notes/harmless.py", "value = 'kill switch'\nprint(value)\n"], ["evil.js", "console.log('not a lifecycle command')\n"], ]); const readScript = (scriptPath: string) => files.get(scriptPath); expect(isProtectedComputerLifecycleCommand("node scripts/check.js", readScript)).toBe(false); expect( isProtectedComputerLifecycleCommand("python3 scripts/train.py --epochs 2", readScript), ).toBe(false); expect( isProtectedComputerLifecycleCommand("python -W ignore scripts/train.py", readScript), ).toBe(false); expect(isProtectedComputerLifecycleCommand("python kill.py", readScript)).toBe(false); expect(isProtectedComputerLifecycleCommand("python notes/harmless.py", readScript)).toBe(false); expect(isProtectedComputerLifecycleCommand("node evil.js", readScript)).toBe(false); expect(isProtectedComputerLifecycleCommand("echo python /tmp/kill.py")).toBe(false); expect(isProtectedComputerLifecycleCommand("node /tmp/plot.js")).toBe(false); expect(isProtectedComputerLifecycleCommand("python -c 'print(\"hello\")'")).toBe(false); }); it.each([ `python -m timeit 'os.kill(1, 9)'`, `python3 -m timeit 'import os; os.system("pkill chromium")'`, `python -m runpy -c 'import os; os.kill(1, 9)'`, `python3 -m trace -c 'os.system("pkill chromium")'`, `python -m timeit 'subprocess.call(["pkill", "chromium"])'`, `python -m timeit 'subprocess.call(["kill", "-9", "1"])'`, "python -m scripts.kill", "python -m kill.main", "python3 -m pkg.pkill", "/usr/bin/python3 -m os.kill", `python -W ignore -m timeit 'os.kill(1, 9)'`, `python3.12 -O -m timeit 'os.kill(1, 9)'`, "command python3 -m os.kill", "FOO=1 python -m os.kill", `echo 'import os; os.kill(1, 9)' | python -`, `echo 'pkill chromium' | python3 -`, `printf '%s\\n' 'import os; os.system("pkill chromium")' | python3 -`, `echo 'process.kill(1)' | node -`, `echo 'process.kill(1)' | node --require preload.js -`, "python - <<'PY'\np = \"pkill chromium\"\nPY", `echo 'os.kill(1)' | python > /tmp/out -`, `echo 'system("pkill chromium")' | perl -`, `echo 'system("pkill chromium")' | perl -I lib -`, `echo 'system("pkill chromium")' | ruby -`, `deno eval 'Deno.kill(1, "SIGKILL")'`, `deno eval 'process.kill(1, "SIGKILL")'`, `deno eval --ext=ts 'Deno.kill(1, "SIGKILL")'`, `echo 'Deno.kill(1)' | deno run -`, `bun -e 'process.kill(1, "SIGKILL")'`, `bun --eval 'require("child_process").execSync("pkill chromium")'`, `bun --eval='process.kill(1)'`, `bun -e'process.kill(1)'`, "deno run kill.ts", "deno run --allow-net kill.ts", "deno run --config deno.json kill.ts", "deno --quiet run kill.ts", "deno run -- kill.ts", "deno test kill.ts", "bun /tmp/pkill.ts", "bun run ./kill.ts", "bun src/xkill.js", `eval "pkill chromium"`, `bash -c 'python -m timeit "os.kill(1, 9)"'`, `bash -c 'echo "os.kill(1)" | python -'`, `bash -c 'deno run kill.ts'`, ])("rejects kill-family python -m, stdin, and deno/bun bypasses: %s", (command) => { expect(isProtectedComputerLifecycleCommand(command)).toBe(true); }); it.each([ "python -m pip", "python3 -m pip install", "python -O -m http.server", "python3 -q -m json.tool", "python -m os", "python -m os.path", "python -Ompip install", "python -W ignore -m pip", "python3.12 -m pip", "python -m pytest kill.py", `python -m timeit 'print("kill switch")'`, `python -m timeit 'print("process.killer")'`, "python -m myapp.kill_switch", "python -", `echo 'print("hello")' | python -`, `echo 'print("kill switch")' | python3 -`, "python - <<'PY'\nprint hello\nPY", `echo 'os.kill(1)' | python -m pip`, `echo 'os.kill(1)' | python script.py`, `python -m pip && echo 'os.kill(1)'`, "python3 scripts/train.py", "node scripts/check.js", `echo 'console.log(1)' | node -`, `node -e 'console.log("kill switch")'`, `python -c 'print("kill switch")'`, `deno eval 'console.log(1)'`, `deno eval 'eval(1)'`, `deno eval 'console.log("kill switch")'`, `deno eval --ext=ts 'console.log("kill switch")'`, "deno run src/main.ts", "deno run --allow-net=localhost src/main.ts", "deno run src/main.ts -- kill.ts", "deno fmt kill.ts", "deno fmt src/kill.ts", "bun src/index.ts", "bun run dev", "bun run src/index.ts -- kill.ts", `bun -e 'console.log(1)'`, `bun -e 'console.log("kill switch")'`, "bun -p '1+1'", "bun install", "echo python -m os.kill", "echo deno run kill.ts", `echo 'puts "hello"' | ruby -`, "ruby scripts/check.rb", `perl -e 'print "kill switch"'`, "command python3 -m http.server", ])("keeps ordinary python -m, stdin, and deno/bun work: %s", (command) => { expect(isProtectedComputerLifecycleCommand(command)).toBe(false); }); it("maps interpreter scripts onto the workspace file API", () => { expect(protectedScriptWorkspacePath("./scripts/stop.py", undefined)).toBe("scripts/stop.py"); expect(protectedScriptWorkspacePath("evil.js", "notes")).toBe("notes/evil.js"); expect(protectedScriptWorkspacePath("stop.py", "bots/bot-1")).toBe("bots/bot-1/stop.py"); expect(protectedScriptWorkspacePath("evil.js", "/home/rakazo/notes")).toBe("notes/evil.js"); expect(protectedScriptWorkspacePath("/home/rakazo/scripts/stop.py", undefined)).toBe( "scripts/stop.py", ); expect(protectedScriptWorkspacePath("/home/user/rakazo-home/evil.js", undefined)).toBe( "evil.js", ); expect(protectedScriptWorkspacePath("evil.js", "/tmp")).toBe("tmp/evil.js"); expect(protectedScriptWorkspacePath("/tmp/kill.py", undefined)).toBeUndefined(); expect(protectedScriptWorkspacePath("../secret.py", "notes")).toBeUndefined(); expect(protectedScriptWorkspacePath("evil.js", "/home/user")).toBe("evil.js"); }); it("keeps ordinary shell work available", () => { expect(isProtectedComputerLifecycleCommand("pwd && ls -la")).toBe(false); expect(isProtectedComputerLifecycleCommand("node scripts/check.js")).toBe(false); expect(isProtectedComputerLifecycleCommand("systemctl status chromium")).toBe(false); expect(isProtectedComputerLifecycleCommand('rm -f "$WORKSPACE/tmp.txt"')).toBe(false); expect(isProtectedComputerLifecycleCommand("printf '%s\\n' *.txt && pwd")).toBe(false); }); it.each([ "find . -maxdepth 2 -type d -name .git -print", "git -C . status --short", "git add .", "git add then .", "ls . && git -C . worktree list --porcelain", "set -eu\npwd\nfind . -maxdepth 2 -type d -name .git -print", "git worktree add ../review-worktree origin/main", "find /tmp/. -maxdepth 1 -type d", "git diff -- .", "printf '%s' 'pk\\\nill chromium'", "printf '%s' 'pk\\\nill' chromium", "printf '%s' 'line one\nline two'", ])("allows repository paths without treating dot arguments as sourcing: %s", (command) => { expect(isProtectedComputerLifecycleCommand(command)).toBe(false); }); it.each([ ". /tmp/script.sh", "pwd; . /tmp/script.sh", "pwd\n. /tmp/script.sh", "find . -maxdepth 1 && . /tmp/script.sh", "command . /tmp/script.sh", "builtin . /tmp/script.sh", "command -p . /tmp/script.sh", "true && > /tmp/output . /tmp/script.sh", "2> /tmp/output . /tmp/script.sh", "if true; then . /tmp/script.sh; fi", "bash -c 'pwd\n. /tmp/script.sh'", "pk\\\nill chromium", "! . /tmp/script.sh", "if false; then :; elif . /tmp/script.sh; then :; fi", "{ . /tmp/script.sh; }", "coproc . /tmp/script.sh", "coproc worker . /tmp/script.sh", "function f { . /tmp/script.sh; }", "function f { . /tmp/script.sh; }; f", ])("continues blocking executable sourcing and lifecycle operations: %s", (command) => { expect(isProtectedComputerLifecycleCommand(command)).toBe(true); }); });