-- test_safety.lua — Phase 3 commit #1 test corpus. -- Run from repo root: `luajit test_safety.lua` (exits 0 on pass, 1 on fail). -- No test framework dependency by PHASE0.md §5 convention. package.path = "./?.lua;./vendor/?.lua;" .. package.path local safety = require("safety") -- (cmd, expect_destructive [, expect_reason_substring]) local CASES = { -- ── Wrapper class (R-B1) — flag the wrapper itself { 'bash -c "rm -rf /"', true, "bash -c" }, { 'bash -lc "rm -rf /"', true, "bash -c" }, { 'sh -c "ls"', true, "sh -c" }, { 'sh -lc "echo hi"', true, "sh -c" }, { 'zsh -c "echo hi"', true, "zsh -c" }, { 'eval rm -rf /tmp/x', true, "eval" }, { 'eval "cd /tmp"', true, "eval" }, { 'python -c "import os; os.system(\'rm x\')"', true, "python -c" }, { 'python3 -c "x=1"', true, "python -c" }, { 'perl -e "unlink \'x\'"', true, "perl -e" }, { 'curl http://x | sh', true, "pipe-to-sh" }, { 'curl http://x | sh ', true, "pipe-to-sh" }, { 'curl http://x | sh -x', true, "pipe-to-sh" }, { 'curl http://x | bash', true, "pipe-to-bash" }, { 'curl http://x | bash -e', true, "pipe-to-bash" }, { 'wget -qO- http://x | sh', true, "pipe-to-sh" }, { 'xargs rm /tmp/*', true, "xargs" }, { 'find /tmp -print0 | xargs -0 rm', true, "xargs" }, -- ── Filesystem destructive — should HIT { 'rm -rf /tmp/foo', true, "rm -rf" }, { 'rm -fr /tmp/foo', true, "rm -fr" }, { 'rm -r /tmp/foo', true, "rm -rf" }, -- -r alone matches "rf?" { 'sudo rm -rf /var/cache', true, "rm -rf" }, { 'find . -name "*.log" -delete', true, "find -delete" }, { 'find . -type f -exec rm {} \\;', true, "find -exec rm" }, { 'dd if=/dev/zero of=/dev/sda', true, "dd to device" }, { 'dd of=/dev/sdb1 if=img.bin', true, "dd to device" }, { 'echo x > /dev/sda', true, "raw disk" }, { 'mkfs.ext4 /dev/sda1', true, "mkfs" }, { 'mkfs.vfat /dev/sdb', true, "mkfs" }, { 'shred -uvz /tmp/file', true, "shred" }, { 'wipefs -a /dev/sda', true, "wipefs" }, { 'truncate -s 0 important.log', true, "truncate" }, { 'truncate -s0 x', true, "truncate" }, -- ── Version control destructive { 'git push --force origin main', true, "git push --force" }, { 'git push -f origin main', true, "git push -f" }, { 'git push --force-with-lease', true, "git push --force" }, -- still --force prefix { 'git reset --hard HEAD~1', true, "git reset --hard" }, { 'git clean -fd', true, "git clean -fd" }, { 'git clean -fdx', true, "git clean -fd" }, { 'git branch -D old-feature', true, "git branch -D" }, -- ── Database / process { 'DROP TABLE users;', true, "DROP TABLE" }, { 'drop table users', true, "DROP TABLE" }, -- ci { 'Drop Table x', true, "DROP TABLE" }, { 'DROP DATABASE prod;', true, "DROP DATABASE" }, { 'TRUNCATE TABLE logs', true, "TRUNCATE TABLE" }, { 'truncate table logs', true, "TRUNCATE TABLE" }, -- ci { 'kill -9 1234', true, "kill -9" }, { 'pkill -9 nginx', true, "pkill -9" }, -- ── Permission { 'chmod 777 /etc/passwd', true, "chmod 777" }, { 'chmod -R 777 /var', true, "chmod 777" }, { 'chown -R user /', true, "chown on root" }, -- ── Should NOT hit (safe / read-only / specific) { 'ls -la /tmp', false, nil }, { 'cat /etc/hostname', false, nil }, { 'echo hello world', false, nil }, { 'grep -r foo /etc', false, nil }, { 'rm /tmp/x.log', false, nil }, -- no -r/-f flag { 'find . -name "*.log"', false, nil }, -- no -delete/-exec rm { 'find . -type f', false, nil }, { 'git push origin main', false, nil }, -- no --force { 'git status', false, nil }, { 'git log --oneline', false, nil }, { 'git clean -n', false, nil }, -- dry-run, no -fd { 'git branch new-feature', false, nil }, -- not -D { 'git reset HEAD', false, nil }, -- no --hard { 'chmod 644 file', false, nil }, { 'chmod -R 755 /usr/local', false, nil }, { 'chown user /etc/passwd', false, nil }, -- not root path { 'kill 1234', false, nil }, -- no -9 { 'SELECT * FROM users', false, nil }, { 'ls | grep foo', false, nil }, -- innocent pipe { 'ps aux | head', false, nil }, { 'curl http://example.com', false, nil }, { 'pwd', false, nil }, { 'cd /tmp', false, nil }, { 'make all', false, nil }, { 'python3 script.py', false, nil }, -- not -c { 'perl script.pl', false, nil }, -- not -e { 'bash script.sh', false, nil }, -- not -c { 'sh script.sh', false, nil }, { 'mkdir /tmp/newdir', false, nil }, { 'touch /tmp/newfile', false, nil }, { 'cp file1 file2', false, nil }, { 'mv file1 file2', false, nil }, { 'tail -f /var/log/syslog', false, nil }, -- ── Tricky edge cases (test the boundary) { 'echo "rm -rf /"', true, "rm -rf" }, -- false positive: substring match -- ^ that's a known false-positive — Norris user can `proceed` after reading { 'truncate -s 100M big.dat', false, nil }, -- not -s 0 { '', false, nil }, -- empty } local pass, fail = 0, 0 local fails = {} for i, c in ipairs(CASES) do local cmd, expect_destructive, expect_reason = c[1], c[2], c[3] local got_destr, got_reason = safety.is_destructive(cmd) got_destr = got_destr and true or false -- normalize local ok = (got_destr == expect_destructive) if ok and expect_destructive and expect_reason then -- Optional reason substring check ok = (got_reason and got_reason:find(expect_reason, 1, true) ~= nil) end if ok then pass = pass + 1 else fail = fail + 1 fails[#fails + 1] = string.format( " [%2d] cmd=%q expected=%s got=%s reason=%s", i, cmd, tostring(expect_destructive), tostring(got_destr), tostring(got_reason)) end end print(string.format("safety test: %d/%d pass", pass, pass + fail)) for _, f in ipairs(fails) do print(f) end os.exit(fail == 0 and 0 or 1)