a2e4f27b409d479caa9d70aec80b41d1111b1858
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/AGENTS.md b/AGENTS.md
2index 30ee8f8a9516588aab9777aa8f422dcc10d383a1..50b377c7b54386ddfdfd6055113e8f910387a232 100644
3--- a/AGENTS.md
4+++ b/AGENTS.md
5@@ -16,7 +16,7 @@ Your general goal is to keep this code simple. Don't add things you're not asked
6
7 ## Policy flow
8
9diff --git a/README.md b/README.md
10index 6a75e02dd865b305a14ac3836e7fddfb8e45b2cf..829855dce0379221f30aa1dd8c2e1258a81fb4bc 100644
11--- a/README.md
12+++ b/README.md
13@@ -93,7 +93,7 @@ Pi uses a root-level `tools` map and `externalDirectories` list. Any tool name c
14
15diff --git a/src/core/deterministic.ts b/src/core/deterministic.ts
16index 11cf1ecff320c7ee680544ffe4bab64aab96a03b..63e4aa2c374488f346b7e70acd8004b63ce3d338 100644
17--- a/src/core/deterministic.ts
18+++ b/src/core/deterministic.ts
19@@ -113,39 +113,6 @@ const SYSTEM_DIRECTORIES = [
20 "/var",
21 ];
22 const MUTATIONS = new Set(["chmod", "chown", "cp", "ln", "mkdir", "mv", "rm", "rmdir", "tee", "touch"]);
23-const READ_ONLY = new Set([
24- "base64",
25- "cat",
26- "command",
27- "cut",
28- "date",
29- "dirname",
30- "echo",
31- "file",
32- "find",
33- "getent",
34- "grep",
35- "head",
36- "id",
37- "ls",
38- "nl",
39- "printf",
40- "ps",
41- "pwd",
42- "readlink",
43- "rocm-smi",
44- "rg",
45- "sed",
46- "sleep",
47- "stat",
48- "strings",
49- "tail",
50- "true",
51- "type",
52- "wc",
53- "whereis",
54- "which",
55-]);
56 const FIND_OPERATORS = new Set(["!", "-not", "-a", "-and", "-o", "-or", "(", ")"]);
57 const FIND_UNARY = new Set([
58 "-depth",
59@@ -343,7 +310,7 @@ function safeFind(args: string[]): boolean {
60 return true;
61 }
62
63-type ArgumentRoles = { paths: string[]; review?: boolean };
64+type ArgumentRoles = { paths: string[]; searchRoots?: string[]; review?: boolean };
65
66 function searchPaths(program: string, args: string[]): ArgumentRoles {
67 const paths: string[] = [];
68@@ -423,6 +390,8 @@ function searchPaths(program: string, args: string[]): ArgumentRoles {
69 "-j",
70 ]);
71 let expression = false;
72+ let recursive = program === "rg";
73+ let files = false;
74 let options = true;
75 for (let index = 0; index < args.length; index += 1) {
76 const arg = args[index];
77@@ -435,6 +404,8 @@ function searchPaths(program: string, args: string[]): ArgumentRoles {
78 continue;
79 }
80 if (arg === "--pre" || arg.startsWith("--pre=")) return { paths, review: true };
81+ if (["--recursive", "--dereference-recursive"].includes(arg)) recursive = true;
82+ if (arg === "--files") files = true;
83 const equals = arg.indexOf("=");
84 const option = equals < 0 ? arg : arg.slice(0, equals);
85 if (values.has(option)) {
86@@ -449,6 +420,7 @@ function searchPaths(program: string, args: string[]): ArgumentRoles {
87 for (let offset = 1; offset < arg.length; offset += 1) {
88 const flag = `-${arg[offset]}`;
89 if (!values.has(flag)) {
90+ if (flag === "-r" || flag === "-R") recursive = true;
91 if (!"nrRiIilLhHqsvwxcobazZEFGPUSuN0123456789".includes(arg[offset])) return { paths, review: true };
92 continue;
93 }
94@@ -460,14 +432,18 @@ function searchPaths(program: string, args: string[]): ArgumentRoles {
95 }
96 }
97 }
98- paths.push(...(expression || (program === "rg" && args.includes("--files")) ? positional : positional.slice(1)));
99- return { paths };
100+ const roots = expression || (program === "rg" && files) ? positional : positional.slice(1);
101+ paths.push(...roots);
102+ return { paths, searchRoots: recursive ? (roots.length ? roots : ["."]) : undefined };
103 }
104
105 function argumentRoles(program: string, args: string[]): ArgumentRoles {
106- if (["echo", "printf", "cd", "dirname", "tr"].includes(program)) return { paths: [] };
107+ if (["echo", "printf", "dirname", "tr"].includes(program)) return { paths: [] };
108 if (program === "grep" || program === "rg") return searchPaths(program, args);
109- if (program === "find") return { paths: findRoots(args) };
110+ if (program === "find") {
111+ const roots = findRoots(args);
112+ return { paths: roots, searchRoots: roots.length ? roots : ["."] };
113+ }
114 if (program === "sed") return { paths: args.slice(2) };
115 if (program === "jq") {
116 const paths: string[] = [];
117@@ -617,38 +593,28 @@ function specialAllow(program: string, args: string[]): boolean {
118 return program === "systemctl" && args[0] === "--user" && ["is-active", "is-enabled"].includes(args[1]);
119diff --git a/test/core/deterministic.test.ts b/test/core/deterministic.test.ts
120index 5fbd63e7c77fb3265976c61bff01070f1825122a..82e7874cd5cfaa4a0d046379d6ff81c19305924a 100644
121--- a/test/core/deterministic.test.ts
122+++ b/test/core/deterministic.test.ts
123@@ -30,10 +30,14 @@ beforeAll(() => {
124 symlinkSync(join(cwd, "credentials/missing"), join(cwd, "future.swift"));
125 symlinkSync("/etc/policy-engine-missing", join(cwd, "system-output"));
126 symlinkSync("cycle", join(cwd, "cycle"));
127+ symlinkSync("/", join(cwd, "root-link"));
128+ symlinkSync(process.env.HOME!, join(cwd, "home-link"));
129 });
130 afterAll(() => rmSync(cwd, { recursive: true, force: true }));
131
132 const allowed = [
133+ "cd safe",
134+ "cd -- safe",
135 "bash -n script.sh",
136 "command -v bun node",
137 'grep -rn "namespace\\|Namespace" README.md',
138@@ -43,16 +47,36 @@ const allowed = [
139 "bun test",
140 "sed -n '10,20p' src/core/deterministic.ts",
141 "find src -type f -name '*.ts'",
142+ "find . -iname '*.md'",
143+ "find -iname '*.md'",
144+ "find ~/dev -name '*.md'",
145+ "rg --files safe",
146+ "rg needle",
147+ "grep -rn needle safe",
148+ "grep -r needle",
149+ "rg -e / safe",
150+ "rg --glob / needle safe",
151 "docker compose ps --all",
152 "systemctl --user is-active pi.service",
153 "echo value > output.txt",
154 "cat README.md 2>/dev/null",
155 "ls safe/Sources/*.swift",
156- "cd safe && wc -l Sources/*.swift",
157+ "cd safe && wc -l safe/Sources/*.swift",
158+ "cd safe; cat safe/Sources/*.swift",
159+ "cd safe || cat safe/Sources/*.swift",
160+ "touch safe/Sources/new.swift; cat safe/Sources/*.swift",
161+ "echo value > out | cat safe/Sources/*.swift",
162+ "printf -v LABEL example; ls output/",
163+ "cd /home/pavle/dev/kaiwari/pixel/comfyui && find user -type f | head -20; echo ---; ls output/",
164+ "cd /mnt/media/documents/mama_prevod && sed -n '85,100p' drugi_rad_raw.txt; echo \"=== doc3 60-130 ===\"; sed -n '60,130p' treci_rad_raw.txt | grep -n -i -E \"abstract|key word|©|Received|Accepted\"",
165 "echo text | wc -c",
166+ "(cd safe && ls Sources/); ls output/",
167 'rocm-smi --showmemuse 2>&1; echo ---; ps aux | grep -E "comfyui|main.py" | grep -v grep',
168 "ls -la safe && ls safe/Sources 2>/dev/null | head -30",
169 'ls -la /mnt/media/documents/mama_prevod/sr/ && echo "---FRONT---" && cat /mnt/media/documents/mama_prevod/sr/01_front.txt 2>/dev/null || echo "no front file"',
170+ 'rg -n "arecord|WAV|wav|Rate|Bits|bit" safe/Sources/*.swift 2>/dev/null || ls safe/Sources/; rg -rn "arecord" safe --type swift',
171+ "git -C /home/pavle/dev/kaiwari/server status --short && git -C /home/pavle/dev/kaiwari/server diff && git -C /home/pavle/dev/kaiwari/server branch --show-current",
172+ 'cd /home/pavle/dev/kaiwari/ios && find . -name "*.png" -not -path "./.build/*" 2>/dev/null; echo "---all pngs incl build---"; find . -name "*.png" 2>/dev/null | head; echo "---git tracked files---"; git ls-files | grep -iE "png|icon"',
173 ];
174
175 test.each(allowed)("deterministically allows %s", async (command) => {
176@@ -61,6 +85,12 @@ test.each(allowed)("deterministically allows %s", async (command) => {
177
178 const asks = [
179 "sudo id",
180+ "cd ~/.ssh",
181+ "cd ~/.ssh && cat config",
182+ "cd -- credentials",
183+ "cd shortcut",
184+ "cd cred*",
185+ "cd safe/../credentials",
186 "cat .env",
187 'cat .e"nv"',
188 "cat ~/.ssh/id_ed25519",
189@@ -81,7 +111,6 @@ const asks = [
190 "herdr server stop",
191 "echo value > .env",
192 "cat < .env",
193- "find ~ -name '*.ts'",
194 "docker volume rm app-data",
195 "docker compose down -v",
196 "kubectl --namespace default apply -f deploy.yaml",
197@@ -93,22 +122,60 @@ const asks = [
198 "aws s3api put-object --bucket production --key file --body file",
199 "aws configure set region us-east-1",
200 "wc -l mixed/*.swift",
201- "cd injected && grep -r needle --include=*.go .",
202 "cat safe/*.swift",
203 "cat shortcut/../data.txt",
204 "cat dangling/*.swift",
205 "cat future.swift",
206 "touch system-output",
207- "cd /etc && touch policy-engine",
208- "cd options && grep pattern *",
209+ "cd safe; touch /etc/policy-engine",
210 "command -v node; rm -rf /",
211+ "(echo ok; cat .env) | head -20",
212 ];
213
214 test.each(asks)("requires approval for %s", async (command) => {
215 expect(await checkDeterministic("bash", { command }, cwd)).toMatchObject({ decision: "ask" });
216 });
217
218+test.each([
219+ 'find / -iname "*.md" 2>/dev/null',
220+ "find ~ -name '*.ts'",
221+ 'find "$HOME" -name "*.md"',
222+ "find /tmp/.. -name '*.md'",