Diff
1diff --git a/src/core/rules.ts b/src/core/rules.ts
2index d52252ebc1307a47cdd4e7b566c0d51b26abe1ea..050896f9812f5ec5903a6dcb60b14ca622ab74d7 100644
3--- a/src/core/rules.ts
4+++ b/src/core/rules.ts
5@@ -1,7 +1,7 @@
6 import { DECISION_CATEGORIES } from "./types";
7
8 // Bump to invalidate all cached decisions when rules change.
9-export const POLICY_VERSION = 29;
10+export const POLICY_VERSION = 30;
11
12 // Trailing stderr redirections that are safe to strip before pattern matching.
13 // `2>&1` and `2>/dev/null` have no security implication but would otherwise
14@@ -52,6 +52,8 @@ export const HARD_ALLOW_PATTERNS: RegExp[] = [
15 /^ls(?:\s+-[A-Za-z]+)*(?:\s+(?!.*\/\.)(?!.*\.\.)\/?[A-Za-z0-9][A-Za-z0-9._/-]*)*\s*$/,
16 // cat with relative paths only
17 /^cat\s+(?!.*\/\.)(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*(?:\s+(?!.*\/\.)(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*)*\s*$/,
18+ // base64 with relative paths only
19+ /^base64\s+(?!.*\/\.)(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*(?:\s+(?!.*\/\.)(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*)*\s*$/,
20 // head/tail with relative paths and line limits
21 /^(head|tail)(?:\s+-(?:n\s*)?\d+)?(?:\s+(?!.*\/\.)(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*)*\s*$/,
22 // find read-only on relative paths
23diff --git a/test/pi/extension.test.ts b/test/pi/extension.test.ts
24index 3c0e1bfefd6aa7143c3dad3e6c1db45b0c9bf505..ab482509567c6a3c5d1ddf903b47520d87dc37dd 100644
25--- a/test/pi/extension.test.ts
26+++ b/test/pi/extension.test.ts
27@@ -84,6 +84,7 @@ describe("Pi policy extension", () => {
28 await expect(extension.toolCall({ toolName: "custom_tool", input: {} }, context())).resolves.toBeUndefined()
29 await expect(extension.toolCall({ toolName: "blocked_tool", input: {} }, context())).resolves.toMatchObject({ block: true })
30 await expect(extension.toolCall({ toolName: "bash", input: { command: "git status" } }, context())).resolves.toBeUndefined()
31+ await expect(extension.toolCall({ toolName: "bash", input: { command: "base64 file.txt" } }, context())).resolves.toBeUndefined()
32 await expect(extension.toolCall({ toolName: "bash", input: { command: "sudo id" } }, context())).resolves.toMatchObject({ block: true })
33 await expect(extension.toolCall({ toolName: "mcp", input: {} }, context())).resolves.toBeUndefined()
34 await expect(extension.toolCall({ toolName: "read", input: { path: join(externalDirectory, "file") } }, context())).resolves.toBeUndefined()