package policy import ( "os" "path/filepath" "strings" "testing" ) func argvs(s ShellScript) string { var parts []string for _, c := range s.Commands { parts = append(parts, strings.Join(c.Argv, " ")) } return strings.Join(parts, " | ") } func TestParseShell(t *testing.T) { cases := []struct{ in, want string }{ {`ls -la /etc`, `ls -la /etc`}, {`cat a | grep "x y" && echo 'z;w'`, `cat a | grep x y | echo z;w`}, {`FOO=1 BAR=2 env`, `env`}, {`echo $(rm -rf /tmp/x)`, `rm -rf /tmp/x | echo $(...)`}, {"echo `whoami`", `whoami | echo $(...)`}, {`diff <(ls a) <(ls b)`, `ls a | ls b | diff <(...) <(...)`}, {`if systemctl is-active nginx; then echo ok; fi`, `systemctl is-active nginx | echo ok`}, {`for f in a b c; do rm "$f"; done`, `rm $f`}, {"cat <<'EOF' > /etc/x\n$(evil)\nEOF\nls", `cat | ls`}, {`echo $((1+2))`, `echo $((...))`}, {`# comment only`, ``}, {"a \\\n b", `a b`}, {`[[ -f /x && -d /y ]] && echo yes`, `[[ | echo yes`}, } for _, c := range cases { got := argvs(ParseShell(c.in)) if got != c.want { t.Errorf("ParseShell(%q) = %q, want %q", c.in, got, c.want) } } } func TestParseShellRedirects(t *testing.T) { s := ParseShell(`echo hi > /etc/motd 2>&1; sort < in.txt >> out.txt; cmd &> /dev/null`) var got []string for _, c := range s.Commands { for _, r := range c.Redirects { got = append(got, r.Op+r.Target) } } want := ">/etc/motd >&1 >out.txt &>/dev/null" if strings.Join(got, " ") != want { t.Errorf("redirects = %q, want %q", strings.Join(got, " "), want) } } func TestParseShellHeredocUnquotedExpands(t *testing.T) { s := ParseShell("cat < out.txt", "systemctl restart nginx && systemctl is-active nginx", "ls /etc 2>&1 | grep ssh", "rm -f build/*.o", } for _, s := range ok { if _, err := g.CheckScript(s, dir); err != nil { t.Errorf("%q: unexpected %v", s, err) } } bad := []string{ "echo x > /etc/motd", // escritura fuera sin ALLOW_OUTSIDE_PROJECT_WRITE "cd /etc && rm motd", // cd seguido: rm afecta a /etc/motd "ls; reboot", // ALLOW_REBOOT no declarado "echo $(shutdown -h now)", // sustitución "bash -c 'dd if=/dev/zero of=/dev/sda'", // destructivo anidado "rm -rf /", "echo evil >> agent.md", "cat init.conf", "apt-get install -y x", } for _, s := range bad { if _, err := g.CheckScript(s, dir); !IsDenied(err) { t.Errorf("%q: expected denial, got %v", s, err) } } } func TestGuardUnrestrictedStillRespectsReboot(t *testing.T) { g, dir := guardFor(t, "EXECUTION_MODE=unrestricted\nALLOW_DELETE=true\nALLOW_OUTSIDE_PROJECT_WRITE=true") if _, err := g.CheckArgv([]string{"mkfs.ext4", "/dev/sdz9"}, dir); err != nil { t.Errorf("unrestricted should allow mkfs: %v", err) } if _, err := g.CheckArgv([]string{"systemctl", "reboot"}, dir); !IsDenied(err) { t.Errorf("ALLOW_REBOOT=false must block reboot even in unrestricted: %v", err) } if _, err := g.CheckArgv([]string{"rm", "-rf", "/usr"}, dir); !IsDenied(err) { t.Errorf("critical path deletion must always be denied: %v", err) } }