697aced46e378c367c0d0e8e41de1f2c52425bc4

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

Message

Fix permissions for generic pi tool calls

Diff

  1diff --git a/src/config.ts b/src/config.ts
  2index ffa817d4842d19a7cd43e04f034b5ae52319b313..00f60d27ed8e1182b5d082ef9906ddcb895cb398 100644
  3--- a/src/config.ts
  4+++ b/src/config.ts
  5@@ -7,7 +7,12 @@ export type PermissionRules = PermissionAction | Record<string, PermissionAction
  6 
  7 export type PermissionConfig = {
  8   bash?: PermissionRules
  9+  read?: PermissionRules
 10+  write?: PermissionRules
 11   edit?: PermissionRules
 12+  find?: PermissionRules
 13+  grep?: PermissionRules
 14+  ls?: PermissionRules
 15   external_directory?: PermissionRules
 16   webfetch?: PermissionRules
 17 }
 18diff --git a/src/rules.ts b/src/rules.ts
 19index 4c1753235d47e3b8a9bad3c834ca8d21c1439c1b..f614ddadd4da95afb9523e89885ce39862bca39f 100644
 20--- a/src/rules.ts
 21+++ b/src/rules.ts
 22@@ -1,5 +1,5 @@
 23 // Bump to invalidate all cached decisions when rules change.
 24-export const POLICY_VERSION = 21;
 25+export const POLICY_VERSION = 23;
 26 
 27 // Trailing stderr redirections that are safe to strip before pattern matching.
 28 // `2>&1` and `2>/dev/null` have no security implication but would otherwise
 29diff --git a/src/static-permissions.ts b/src/static-permissions.ts
 30index 18a19007c7d37864aa252b80d20b0e8eccdd4431..4d8f616a613ba70b53b1cb932286bfadccdc5309 100644
 31--- a/src/static-permissions.ts
 32+++ b/src/static-permissions.ts
 33@@ -67,9 +67,18 @@ function toolRules(toolType: string, config: PermissionConfig): PermissionRules
 34   switch (toolType) {
 35     case "bash":
 36       return config.bash
 37-    case "edit":
 38+    case "read":
 39+      return config.read
 40     case "write":
 41+      return config.write
 42+    case "edit":
 43       return config.edit
 44+    case "find":
 45+      return config.find
 46+    case "grep":
 47+      return config.grep
 48+    case "ls":
 49+      return config.ls
 50     case "webfetch":
 51       return config.webfetch
 52     default:
 53diff --git a/test/static-permissions.test.ts b/test/static-permissions.test.ts
 54index 9f856baf81a0dbcde261700a3d464e5f1c9e8624..18a07ccd2f84f8bfec46956b6c415de39bae3103 100644
 55--- a/test/static-permissions.test.ts
 56+++ b/test/static-permissions.test.ts
 57@@ -18,7 +18,12 @@ beforeEach(() => {
 58   process.env.PI_POLICY_ENGINE_CONFIG = join(tempDir, "config.json")
 59   writeFileSync(process.env.PI_POLICY_ENGINE_CONFIG, JSON.stringify({
 60     permission: {
 61+      read: "allow",
 62+      write: "allow",
 63       edit: "allow",
 64+      find: "allow",
 65+      grep: "allow",
 66+      ls: "allow",
 67       bash: { "*sudo*": "deny", "*terraform apply*": "deny" },
 68       external_directory: { [`${externalDirectory}/*`]: "allow" },
 69       webfetch: "check",
 70@@ -46,13 +51,50 @@ describe("static permissions", () => {
 71       .resolves.toMatchObject({ decision: "deny" })
 72   })
 73 
 74-  test("allows edits in the workspace and actions in allowed external directories", async () => {
 75+  test("uses separate path-tool permissions", async () => {
 76+    writeFileSync(process.env.PI_POLICY_ENGINE_CONFIG!, JSON.stringify({
 77+      permission: { read: "allow", write: "deny", edit: "deny", find: "deny", grep: "deny", ls: "deny" },
 78+    }))
 79+
 80+    await expect(checkStaticPermission("read", { path: "file.ts" }, workspace))
 81+      .resolves.toMatchObject({ decision: "allow" })
 82+    await expect(checkStaticPermission("write", { path: "file.ts" }, workspace))
 83+      .resolves.toMatchObject({ decision: "deny" })
 84     await expect(checkStaticPermission("edit", { path: "file.ts" }, workspace))
 85+      .resolves.toMatchObject({ decision: "deny" })
 86+    await expect(checkStaticPermission("find", { path: "file.ts" }, workspace))
 87+      .resolves.toMatchObject({ decision: "deny" })
 88+    await expect(checkStaticPermission("grep", { path: "file.ts" }, workspace))
 89+      .resolves.toMatchObject({ decision: "deny" })
 90+    await expect(checkStaticPermission("ls", { path: "file.ts" }, workspace))
 91+      .resolves.toMatchObject({ decision: "deny" })
 92+  })
 93+
 94+  test("allows configured path tools in the workspace and allowed external directories", async () => {
 95+    await expect(checkStaticPermission("read", { path: "file.ts" }, workspace))
 96+      .resolves.toMatchObject({ decision: "allow" })
 97+    await expect(checkStaticPermission("write", { path: "file.ts" }, workspace))
 98+      .resolves.toMatchObject({ decision: "allow" })
 99+    await expect(checkStaticPermission("edit", { path: "file.ts" }, workspace))
100+      .resolves.toMatchObject({ decision: "allow" })
101+    await expect(checkStaticPermission("find", { path: "file.ts" }, workspace))
102+      .resolves.toMatchObject({ decision: "allow" })
103+    await expect(checkStaticPermission("grep", { path: "file.ts" }, workspace))
104+      .resolves.toMatchObject({ decision: "allow" })
105+    await expect(checkStaticPermission("ls", { path: "file.ts" }, workspace))
106       .resolves.toMatchObject({ decision: "allow" })
107     await expect(checkStaticPermission("write", { path: join(externalDirectory, "file.ts") }, workspace))
108       .resolves.toMatchObject({ decision: "allow" })
109+    await expect(checkStaticPermission("edit", { path: join(externalDirectory, "file.ts") }, workspace))
110+      .resolves.toMatchObject({ decision: "allow" })
111     await expect(checkStaticPermission("read", { path: join(externalDirectory, "file.ts") }, workspace))
112       .resolves.toMatchObject({ decision: "allow" })
113+    await expect(checkStaticPermission("find", { path: join(externalDirectory, "file.ts") }, workspace))
114+      .resolves.toMatchObject({ decision: "allow" })
115+    await expect(checkStaticPermission("grep", { path: join(externalDirectory, "file.ts") }, workspace))
116+      .resolves.toMatchObject({ decision: "allow" })
117+    await expect(checkStaticPermission("ls", { path: join(externalDirectory, "file.ts") }, workspace))
118+      .resolves.toMatchObject({ decision: "allow" })
119   })
120 
121   test("passes unlisted external paths to the policy pipeline", async () => {