83ddb3749e31c14d5f4127f26cabeabeb86a446b

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Split policy rules by concern

Diff

This diff is truncated to protect this page.

  1diff --git a/src/core/audit.ts b/src/core/audit.ts
  2index b1b8aa4778ca5a575b4f7a4760552105bdb5553b..ac8ed0fd79caf14c528cfc3b794a38be1aa7f117 100644
  3--- a/src/core/audit.ts
  4+++ b/src/core/audit.ts
  5@@ -1,6 +1,6 @@
  6 import { appendFile, mkdir } from "node:fs/promises";
  7 import { dirname } from "node:path";
  8-import { POLICY_VERSION } from "./rules";
  9+import { POLICY_VERSION } from "./normalize";
 10 import type { DecisionAudit } from "./types";
 11 
 12 export function createJsonlDecisionAudit(file: string): DecisionAudit {
 13diff --git a/src/core/cache.ts b/src/core/cache.ts
 14index e85e9ed26d7d437de67e2b0b9644229ba540b473..1f937de0af4d5947dd34a463ca632032d2f30f26 100644
 15--- a/src/core/cache.ts
 16+++ b/src/core/cache.ts
 17@@ -1,6 +1,6 @@
 18 import { appendFile, mkdir, readFile } from "node:fs/promises";
 19 import { dirname } from "node:path";
 20-import { POLICY_VERSION } from "./rules";
 21+import { POLICY_VERSION } from "./normalize";
 22 import type { CacheEntry, Decision, DecisionCache } from "./types";
 23 
 24 type StoredCacheEntry = CacheEntry & {
 25diff --git a/src/core/deterministic.ts b/src/core/deterministic.ts
 26index c8615ed1c43ab76b6f803e5c595f28a214b091bd..f07a33a64e51fc51fbfa361a531b569054da6d7c 100644
 27--- a/src/core/deterministic.ts
 28+++ b/src/core/deterministic.ts
 29@@ -2,9 +2,99 @@ import { lstatSync, readlinkSync, statSync } from "node:fs";
 30 import { dirname, isAbsolute, resolve } from "node:path";
 31 import { parseBash, type BashCommand, type BashNode, type BashParseResult } from "./bash";
 32 import { resolveBashGlob } from "./bash-glob";
 33-import { ALLOW_COMMANDS } from "./rules";
 34 import type { Decision } from "./types";
 35 
 36+export const ALLOW_COMMANDS: ReadonlySet<string> = new Set([
 37+  "./gradlew",
 38+  "base64",
 39+  "break",
 40+  "bun",
 41+  "bunx",
 42+  "cargo",
 43+  "cat",
 44+  "cd",
 45+  "chmod",
 46+  "chown",
 47+  "composer",
 48+  "continue",
 49+  "cp",
 50+  "cut",
 51+  "date",
 52+  "deno",
 53+  "dirname",
 54+  "dotnet",
 55+  "echo",
 56+  "elixir",
 57+  "exit",
 58+  "file",
 59+  "gofmt",
 60+  "git",
 61+  "getent",
 62+  "go",
 63+  "golangci-lint",
 64+  "grep",
 65+  "head",
 66+  "id",
 67+  "jar",
 68+  "java",
 69+  "javac",
 70+  "jq",
 71+  "kotlinc",
 72+  "ln",
 73+  "lsof",
 74+  "ls",
 75+  "make",
 76+  "mix",
 77+  "mkdir",
 78+  "mvn",
 79+  "mv",
 80+  "netstat",
 81+  "nl",
 82+  "node",
 83+  "npm",
 84+  "npx",
 85+  "php",
 86+  "pip",
 87+  "pip3",
 88+  "pnpm",
 89+  "poetry",
 90+  "printf",
 91+  "ps",
 92+  "pwd",
 93+  "python",
 94+  "python3",
 95+  "qmd",
 96+  "readlink",
 97+  "rg",
 98+  "rmdir",
 99+  "rm",
100+  "ruby",
101+  "rustc",
102+  "sbt",
103+  "scala",
104+  "sleep",
105+  "sort",
106+  "ss",
107+  "stat",
108+  "strings",
109+  "swift",
110+  "tail",
111+  "tc",
112+  "tee",
113+  "touch",
114+  "tree",
115+  "tr",
116+  "true",
117+  "type",
118+  "uniq",
119+  "uv",
120+  "wc",
121+  "whereis",
122+  "which",
123+  "yarn",
124+  "yarnpkg",
125+]);
126+
127 const SYSTEM_DIRECTORIES = [
128   "/bin",
129diff --git a/src/core/index.ts b/src/core/index.ts
130index ce4318e3b8dee42f787a3c2e8c61b52d5f1174a5..d6053377b4c5b502e24da9a765279d266468ba35 100644
131--- a/src/core/index.ts
132+++ b/src/core/index.ts
133@@ -7,5 +7,4 @@ export * from "./normalize";
134 export * from "./pipeline";
135 export * from "./providers";
136 export * from "./review";
137-export * from "./rules";
138 export * from "./types";
139diff --git a/src/core/normalize.ts b/src/core/normalize.ts
140index 646ad24bda2c3f316d872bfea2912f004f9d2929..2376ca8131727661d503e059b79865721953d7ef 100644
141--- a/src/core/normalize.ts
142+++ b/src/core/normalize.ts
143@@ -1,7 +1,9 @@
144 import { createHash } from "node:crypto";
145-import { POLICY_VERSION } from "./rules";
146 import type { PolicyRequest } from "./types";
147 
148+// Bump to invalidate all cached decisions when rules change.
149+export const POLICY_VERSION = 39;
150+
151 const SENSITIVE_KEY_RE = /(?:api[-_]?key|auth(?:entication|orization)?|credential|cookie|password|secret|token)/i;
152 const SENSITIVE_ARGUMENT_RE =
153   /((?:^|\s)(?:-u|-H)(?:=|\s*)|(?:^|\s)--(?:api[-_]?key|authorization|cookie|password|secret|token|user|proxy-user|header)(?:=|\s+))(?:(?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*')|\S+)/gi;
154diff --git a/src/core/review.ts b/src/core/review.ts
155index f997b9676e7a177c01ea21df57779756934cf5d9..26726380db9964e724a89db599e3adabfcad19bb 100644
156--- a/src/core/review.ts
157+++ b/src/core/review.ts
158@@ -1,6 +1,6 @@
159 import { callLlamaReviewer } from "./providers";
160-import { llmPolicyPrompt } from "./rules";
161 import {
162+  DECISION_CATEGORIES,
163   isDecisionCategory,
164   type Decision,
165   type LLMEvaluationResult,
166@@ -22,6 +22,65 @@ export type PiReviewerCaller = (
167   context: PolicyContext,
168 ) => Promise<string>;
169 
170+const EXTERNAL_DIRECTORIES_PLACEHOLDER = "<<EXTERNAL_DIRECTORIES>>";
171+
172+export const LLM_POLICY_PROMPT = `Classify the serialized tool call as allow or ask.
173+
174+Decision procedure:
175+1. Return ask only when the actual operation directly does a listed risk below.
176+2. Otherwise return allow.
177+
178+Ask only for:
179+- Privilege escalation: sudo, su, or doas.
180+- Wide destruction, such as rm -rf of /, /etc, /usr, or ~/.
181+- Direct reading or exposure of an identified secret: a private SSH key, cloud credential file, .env file, or file explicitly named credentials or token.
182+- Editing system (non-user) files, outside of the current working directory tree, or not on the list of allowed directories.
183+- Remote infrastructure, data, access, or deployment mutation: create, update, delete, grant, revoke, apply, or execute a write on production databases, Kubernetes, or cloud providers.
184+- Irreversibly destructive Git operations, such as reset --hard or checking out files.
185+- When a command attempts to search the entire (and only *THE ENTIRE*) home directory for something, since that's very slow and rarely needed.
186+- Stopping the Herdr server with \`herdr server stop\`.
187+
188diff --git a/src/core/rules.ts b/src/core/rules.ts
189deleted file mode 100644
190index 5478a044bd65ab5cf4bd140d4343fba7b21e8eba..0000000000000000000000000000000000000000
191--- a/src/core/rules.ts
192+++ /dev/null
193@@ -1,156 +0,0 @@
194-import { DECISION_CATEGORIES } from "./types";
195-
196-// Bump to invalidate all cached decisions when rules change.
197-export const POLICY_VERSION = 39;
198-
199-// Local commands that can run without LLM review. Secret paths, destructive
200-// Git operations, system-file changes, and shell control syntax are checked first.
201-export const ALLOW_COMMANDS: ReadonlySet<string> = new Set([
202-  "./gradlew",
203-  "base64",
204-  "break",
205-  "bun",
206-  "bunx",
207-  "cargo",
208-  "cat",
209-  "cd",
210-  "chmod",
211-  "chown",
212-  "composer",
213-  "continue",
214-  "cp",
215-  "cut",
216-  "date",
217-  "deno",
218-  "dirname",
219-  "dotnet",
220-  "echo",
221-  "elixir",
222-  "exit",
223-  "file",
224-  "gofmt",
225-  "git",
226-  "getent",
227-  "go",
228-  "golangci-lint",
229-  "grep",
230-  "head",
231-  "id",
232-  "jar",
233-  "java",
234-  "javac",
235-  "jq",
236-  "kotlinc",
237-  "ln",
238-  "lsof",
239-  "ls",
240-  "make",
241-  "mix",
242-  "mkdir",
243-  "mvn",
244-  "mv",
245-  "netstat",
246-  "nl",
247-  "node",
248-  "npm",
249-  "npx",
250-  "php",
251-  "pip",
252-  "pip3",
253-  "pnpm",
254-  "poetry",
255-  "printf",
256-  "ps",
257-  "pwd",
258-  "python",
259-  "python3",
260-  "qmd",
261-  "readlink",
262-  "rg",
263-  "rmdir",
264-  "rm",
265-  "ruby",
266-  "rustc",
267-  "sbt",
268-  "scala",
269-  "sleep",
270-  "sort",
271-  "ss",
272-  "stat",
273-  "strings",
274-  "swift",
275-  "tail",
276-  "tc",
277-  "tee",
278-  "touch",
279-  "tree",
280-  "tr",
281-  "true",
282-  "type",
283-  "uniq",
284-  "uv",
285-  "wc",
286-  "whereis",
287-  "which",
288-  "yarn",
289-  "yarnpkg",
290-]);
291-
292-const EXTERNAL_DIRECTORIES_PLACEHOLDER = "<<EXTERNAL_DIRECTORIES>>";
293diff --git a/test/core/deterministic.test.ts b/test/core/deterministic.test.ts
294index 031ba91c4ffce51db02780de2743a7ecfd714804..efcc888f75f0329cad5ab5c6c7799bd4fe2791df 100644
295--- a/test/core/deterministic.test.ts
296+++ b/test/core/deterministic.test.ts
297@@ -41,7 +41,7 @@ const allowed = [
298   "git commit -m 'sudo rm -rf /'",
299   "cp README.md README.copy",
300   "bun test",
301-  "sed -n '10,20p' src/core/rules.ts",
302+  "sed -n '10,20p' src/core/deterministic.ts",
303   "find src -type f -name '*.ts'",
304   "docker compose ps --all",
305   "systemctl --user is-active pi.service",