f3fa5b1e8a6786c33dde5271211afc6791da9d1f

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

Message

Allow safe OR fallbacks

Diff

 1diff --git a/src/core/deterministic.ts b/src/core/deterministic.ts
 2index 9ba9b64a244be231a8a531e31e9c7637c84edfee..11cf1ecff320c7ee680544ffe4bab64aab96a03b 100644
 3--- a/src/core/deterministic.ts
 4+++ b/src/core/deterministic.ts
 5@@ -727,7 +727,14 @@ function checkCommand(command: BashCommand, context: ShellContext): CommandResul
 6       return { ...unknown, decision: ask("System file changes require confirmation") };
 7     writes ||= writing && paths[0] !== "/dev/null";
 8   }
 9-  if (!context.certain || optionLikeGlob || roles.review || inlineCode(program, args)) return unknown;
10+  const readOnly = READ_ONLY.has(program) && !(program === "printf" && args[0] === "-v");
11+  if (
12+    optionLikeGlob ||
13+    roles.review ||
14+    inlineCode(program, args) ||
15+    (!context.certain && (!readOnly || writes || roles.paths.length))
16+  )
17+    return unknown;
18   if (
19     command.assignments.some(
20       (word) =>
21@@ -759,7 +766,6 @@ function checkCommand(command: BashCommand, context: ShellContext): CommandResul
22       return { decision, context: { ...context, certain: false }, changesDirectory: true };
23     }
24   }
25-  const readOnly = READ_ONLY.has(program) && !(program === "printf" && args[0] === "-v");
26   return { decision, context: { ...context, certain: !writes && readOnly } };
27 }
28 
29@@ -788,9 +794,8 @@ function evaluateNode(node: BashNode, context: ShellContext): CommandResult {
30   const left = evaluateNode(node.left, { ...context });
31   if (node.operator === "||") {
32     const right = evaluateNode(node.right, { ...context, certain: false });
33-    const decision = combine(left.decision, right.decision);
34     return {
35-      decision: decision?.decision === "allow" ? undefined : decision,
36+      decision: combine(left.decision, right.decision),
37       context: { ...context, certain: false },
38       reviewCommands: combinedReviewCommands(left, right),
39     };
40@@ -814,7 +819,8 @@ function evaluateNode(node: BashNode, context: ShellContext): CommandResult {
41 }
42 
43 export function evaluateParsedBash(parsed: BashParseResult, cwd = process.cwd()): BashDeterministicResult {
44-  if (parsed.parserUnavailable) return { decision: { decision: "deny", reason: "Bash parser is unavailable", category: "bash" } };
45+  if (parsed.parserUnavailable)
46+    return { decision: { decision: "deny", reason: "Bash parser is unavailable", category: "bash" } };
47   if (!parsed.parsed) return {};
48   if (!parsed.root) return { decision: { decision: "allow", reason: "No operations to evaluate", category: "empty" } };
49   try {
50diff --git a/src/core/normalize.ts b/src/core/normalize.ts
51index 2376ca8131727661d503e059b79865721953d7ef..674782059ca20b1627410f03275739d2b68c7bab 100644
52--- a/src/core/normalize.ts
53+++ b/src/core/normalize.ts
54@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
55 import type { PolicyRequest } from "./types";
56 
57 // Bump to invalidate all cached decisions when rules change.
58-export const POLICY_VERSION = 39;
59+export const POLICY_VERSION = 40;
60 
61 const SENSITIVE_KEY_RE = /(?:api[-_]?key|auth(?:entication|orization)?|credential|cookie|password|secret|token)/i;
62 const SENSITIVE_ARGUMENT_RE =
63diff --git a/test/core/deterministic.test.ts b/test/core/deterministic.test.ts
64index 29cd0b8006b23ba5b091c9ebede1bc1864e708a5..5fbd63e7c77fb3265976c61bff01070f1825122a 100644
65--- a/test/core/deterministic.test.ts
66+++ b/test/core/deterministic.test.ts
67@@ -52,6 +52,7 @@ const allowed = [
68   "echo text | wc -c",
69   'rocm-smi --showmemuse 2>&1; echo ---; ps aux | grep -E "comfyui|main.py" | grep -v grep',
70   "ls -la safe && ls safe/Sources 2>/dev/null | head -30",
71+  '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"',
72 ];
73 
74 test.each(allowed)("deterministically allows %s", async (command) => {