diff --git a/src/core/auto-approval/__tests__/commands.spec.ts b/src/core/auto-approval/__tests__/commands.spec.ts new file mode 100644 index 00000000000..6a238ec0eb3 --- /dev/null +++ b/src/core/auto-approval/__tests__/commands.spec.ts @@ -0,0 +1,39 @@ +import { containsDangerousSubstitution, getCommandDecision } from "../commands" + +describe("containsDangerousSubstitution", () => { + describe("zsh array assignments (should NOT be flagged)", () => { + it("should return false for files=(a b c)", () => { + expect(containsDangerousSubstitution("files=(a b c)")).toBe(false) + }) + + it("should return false for var=(item1 item2)", () => { + expect(containsDangerousSubstitution("var=(item1 item2)")).toBe(false) + }) + + it("should return false for x=(hello)", () => { + expect(containsDangerousSubstitution("x=(hello)")).toBe(false) + }) + }) + + describe("zsh process substitution (should be flagged)", () => { + it("should return true for standalone =(whoami)", () => { + expect(containsDangerousSubstitution("=(whoami)")).toBe(true) + }) + + it("should return true for =(ls) with leading space", () => { + expect(containsDangerousSubstitution(" =(ls)")).toBe(true) + }) + + it("should return true for echo =(cat /etc/passwd)", () => { + expect(containsDangerousSubstitution("echo =(cat /etc/passwd)")).toBe(true) + }) + }) +}) + +describe("getCommandDecision", () => { + it("should auto_approve array assignment command with wildcard allowlist", () => { + const command = 'files=(a.ts b.ts); for f in "${files[@]}"; do echo "$f"; done' + const result = getCommandDecision(command, ["*"]) + expect(result).toBe("auto_approve") + }) +}) diff --git a/src/core/auto-approval/commands.ts b/src/core/auto-approval/commands.ts index 83a80cab0ff..e4e9c62d157 100644 --- a/src/core/auto-approval/commands.ts +++ b/src/core/auto-approval/commands.ts @@ -13,7 +13,7 @@ import { parseCommand } from "../../shared/parse-command" * - ${var=value} with escape sequences - Can embed commands via \140 (backtick), \x60, or \u0060 * - ${!var} - Indirect variable references * - <<<$(...) or <<<`...` - Here-strings with command substitution - * - =(...) - Zsh process substitution that executes commands + * - =(...) - Zsh process substitution that executes commands (array assignments like `var=(...)` are excluded) * - *(e:...:) or similar - Zsh glob qualifiers with code execution * * @param source - The command string to analyze @@ -46,7 +46,7 @@ export function containsDangerousSubstitution(source: string): boolean { // Check for zsh process substitution =(...) which executes commands // =(...) creates a temporary file containing the output of the command, but executes it - const zshProcessSubstitution = /=\([^)]+\)/.test(source) + const zshProcessSubstitution = /(?