返回 DeepSeek-Reasonix
bash_redirect_test.go
根目录 / internal / permission / bash_redirect_test.go
1 package permission
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 func TestNormalizeBashSafeRedirectsForMatch(t *testing.T) {
9 tests := []struct {
10 name string
11 in string
12 want string
13 ok bool
14 }{
15 {
16 name: "stderr to dev null",
17 in: "git log 2>/dev/null",
18 want: "git log",
19 ok: true,
20 },
21 {
22 name: "stdout to dev null with space",
23 in: "git log > /dev/null",
24 want: "git log",
25 ok: true,
26 },
27 {
28 name: "append to dev null",
29 in: "git log >>/dev/null",
30 want: "git log",
31 ok: true,
32 },
33 {
34 name: "combined stdout stderr redirect",
35 in: "git log &>/dev/null",
36 want: "git log",
37 ok: true,
38 },
39 {
40 name: "combined append redirect",
41 in: "git log &>> /dev/null",
42 want: "git log",
43 ok: true,
44 },
45 {
46 name: "powershell null sink",
47 in: "git log >$null",
48 want: "git log",
49 ok: true,
50 },
51 {
52 name: "powershell null sink is case-insensitive",
53 in: "git log 2> $NULL",
54 want: "git log",
55 ok: true,
56 },
57 {
58 name: "windows nul sink",
59 in: "git log > NUL",
60 want: "git log",
61 ok: true,
62 },
63 {
64 name: "fd duplication",
65 in: "git log 2>&1",
66 want: "git log",
67 ok: true,
68 },
69 {
70 name: "fd duplication target must be a full word",
71 in: "git log 2>&1rm",
72 ok: false,
73 },
74 {
75 name: "fd close",
76 in: "git log 2>&-",
77 want: "git log",
78 ok: true,
79 },
80 {
81 name: "fd close target must be a full word",
82 in: "git log 2>&-x",
83 ok: false,
84 },
85 {
86 name: "file output stays unsafe",
87 in: "git log > changes.patch",
88 ok: false,
89 },
90 {
91 name: "dev null prefix is not enough",
92 in: "git log >/dev/null.log",
93 ok: false,
94 },
95 {
96 name: "powershell null prefix is not enough",
97 in: "git log >$nullish",
98 ok: false,
99 },
100 {
101 name: "windows nul prefix is not enough",
102 in: "git log >nul.txt",
103 ok: false,
104 },
105 {
106 name: "input redirection remains conservative",
107 in: "git log < /dev/null",
108 ok: false,
109 },
110 {
111 name: "quoted redirect text is not removed",
112 in: "printf '%s\\n' '>/dev/null'",
113 want: "printf '%s\\n' '>/dev/null'",
114 ok: true,
115 },
116 {
117 name: "heredoc fails closed",
118 in: "cat <<'EOF'\n>/dev/null\nEOF",
119 ok: false,
120 },
121 }
122
123 for _, tt := range tests {
124 t.Run(tt.name, func(t *testing.T) {
125 got, ok := normalizeBashSafeRedirectsForMatch(tt.in)
126 if ok != tt.ok {
127 t.Fatalf("ok = %v, want %v (got %q)", ok, tt.ok, got)
128 }
129 if ok && got != tt.want {
130 t.Fatalf("normalized = %q, want %q", got, tt.want)
131 }
132 })
133 }
134 }
135
136 func TestNormalizeBashSafeRedirectsPreservesControlSyntax(t *testing.T) {
137 got, ok := normalizeBashSafeRedirectsForMatch("git log >/dev/null\nrm -rf /tmp/x")
138 if !ok {
139 t.Fatal("safe redirect should normalize while preserving the newline")
140 }
141 if !strings.Contains(got, "\n") {
142 t.Fatalf("normalized command lost the control newline: %q", got)
143 }
144 if !containsShellSyntax(got) {
145 t.Fatalf("normalized command should still be treated as shell syntax: %q", got)
146 }
147 }
148
148 lines GO