返回 DeepSeek-Reasonix
shellsafe_test.go
根目录 / internal / shellsafe / shellsafe_test.go
1 package shellsafe
2
3 import "testing"
4
5 func TestCommandIsReadOnly(t *testing.T) {
6 readOnly := []string{
7 // git read-only subcommands (the #5341 set, beyond status/diff/log/show).
8 "git status", "git diff HEAD", "git log --oneline", "git show",
9 "git rev-parse HEAD", "git describe --tags", "git reflog",
10 "git for-each-ref", "git cat-file -p HEAD", "git ls-tree HEAD",
11 "git rev-list --count HEAD", "git shortlog", "git name-rev HEAD",
12 `git log "2>/dev/null"`, `git log 2\>/dev/null`,
13 // general read-only commands.
14 "ls -la", "cat go.mod", "grep -r foo .", "pwd", "head -n5 x", "stat x", "du -sh .",
15 `grep 'a|b' file`, `printf "%s\n" "a && b"`,
16 // tooling probes.
17 "go version", "go env", "go list ./...", "go doc fmt",
18 "npm view react version", "npm outdated", "cargo check",
19 "docker ps", "docker images", "kubectl get pods",
20 "node -v", "node --version", "python --version", "python3 --version",
21 // PowerShell permission-safe inspection commands.
22 `Get-Process -Name mongod`, `Get-ChildItem -Path .`,
23 `Get-NetTCPConnection -LocalPort 6379`, `Resolve-Path .`,
24 // Narrow, recursively proven read-only command substitution.
25 `basename "$(pwd)"`, `dirname "$(realpath .)"`,
26 }
27 for _, c := range readOnly {
28 if _, _, ok := CommandIsReadOnly(c); !ok {
29 t.Errorf("CommandIsReadOnly(%q) = false, want true", c)
30 }
31 }
32
33 notReadOnly := []string{
34 // write-capable commands / subcommands.
35 "rm -rf /", "git push", "git commit -m x", "git checkout main",
36 "git reset --hard", "git branch -d feature", "git remote add o url",
37 "go build ./...", "go test ./...", "npm install", "docker rm x",
38 "kubectl apply -f x.yaml", "mv a b", "chmod 777 x",
39 // shell syntax can smuggle a write past a read-only base word.
40 "git status && rm -rf /", "cat a | tee b", "echo $(rm x)",
41 "git status > out.txt", "ls; rm x", "git log `whoami`", "echo $HOME",
42 `basename "$(touch out)"`, `basename "$(pwd; touch out)"`,
43 `basename "$(date --set tomorrow)"`, `basename "$(find . -delete)"`,
44 `basename $(pwd)`, `basename "$HOME"`, `find . "$(printf -- -delete)"`,
45 // unknown command.
46 "frobnicate --all",
47 // Network probes do not write the workspace, but they are not safe to
48 // auto-allow through the permission-layer read-only classifier.
49 `Test-NetConnection -ComputerName example.com -Port 443`,
50 // PowerShell mutators stay fail-closed.
51 `Start-Process mongod`, `Stop-Process -Name mongod`,
52 `Set-Content style.css bad`, `Remove-Item style.css`,
53 }
54 for _, c := range notReadOnly {
55 if _, _, ok := CommandIsReadOnly(c); ok {
56 t.Errorf("CommandIsReadOnly(%q) = true, want false", c)
57 }
58 }
59 }
60
61 func TestCommandIsWorkspaceNonMutatingKeepsNetworkProbeOutOfPermissionReaders(t *testing.T) {
62 tests := []struct {
63 command string
64 want bool
65 }{
66 {command: `Test-NetConnection -ComputerName example.com -Port 443`, want: true},
67 {command: `Get-Process -Name mongod`, want: true},
68 {command: `Test-NetConnection example.com; Set-Content out.txt bad`},
69 {command: `Set-Content out.txt bad`},
70 }
71 for _, tt := range tests {
72 t.Run(tt.command, func(t *testing.T) {
73 _, _, got := CommandIsWorkspaceNonMutating(tt.command)
74 if got != tt.want {
75 t.Fatalf("CommandIsWorkspaceNonMutating(%q) = %t, want %t", tt.command, got, tt.want)
76 }
77 })
78 }
79 }
80
81 func TestContainsShellSyntax(t *testing.T) {
82 for _, c := range []string{"a && b", "a || b", "a | b", "a; b", "a > f", "a < f", "a & ", "$(x)", "`x`", "a\nb"} {
83 if !ContainsShellSyntax(c) {
84 t.Errorf("ContainsShellSyntax(%q) = false, want true", c)
85 }
86 }
87 for _, c := range []string{"git status", "ls -la", "grep foo bar.go", `grep 'a|b' file`, `echo "a && b"`} {
88 if ContainsShellSyntax(c) {
89 t.Errorf("ContainsShellSyntax(%q) = true, want false", c)
90 }
91 }
92 }
93
93 lines GO