From c7cf763b08b3ec1ce8ff4a6bb9b4d5a7c623f9d3 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 10 Sep 2026 09:16:51 -0700 Subject: [PATCH] Fix auto-mode allow of bash combined file redirects Bash combined redirects write a file the same way a plain redirect does. The matcher skipped every form that started with >& so it would not flag fd dups, and those writes auto-allowed. Classify >& to a non-fd, non-safe-sink path as a file mutation and leave fd dups unmatched. --- src/permission/auto-shell-policy.ts | 3 +++ src/permission/classify-security.test.ts | 26 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/permission/auto-shell-policy.ts b/src/permission/auto-shell-policy.ts index d22150610..cf190bd2a 100644 --- a/src/permission/auto-shell-policy.ts +++ b/src/permission/auto-shell-policy.ts @@ -121,6 +121,9 @@ export const AUTO_SHELL_RULES: AutoShellRule[] = [ // trailing `|` as in `>|` / `>>|`) to a target that is not an fd dup // (`2>&1`) or a safe pseudo-device (`> /dev/null`, a TTY). /[0-9]?>>?\|?\s*(?!&|\/dev\/(?:null|stdout|stderr|stdin|tty|pts\/|fd\/))[^\s|;&)]/, + // bash `>& word` is `>word 2>&1` when word is not an fd number or `-`. + // `2>&1` and `n>&-` stay unmatched; `>& /dev/null` stays a safe sink. + /[0-9]?>&\s*(?!(?:[0-9]+|-|\/dev\/(?:null|stdout|stderr|stdin|tty|pts\/|fd\/)\S*)(?:\s|$|[|;&)]))[^\s|;&)]/, // tee writes its stdin to one or more files. /(?:^|[\n;&|({]\s*)tee\b/, // In-place stream editors: sed -i, perl -pi -e, ruby -i. diff --git a/src/permission/classify-security.test.ts b/src/permission/classify-security.test.ts index d4725f44d..5d137142c 100644 --- a/src/permission/classify-security.test.ts +++ b/src/permission/classify-security.test.ts @@ -962,6 +962,32 @@ describe("CL-6702 — bash clobber redirects match file-mutation", () => { }); }); +describe("bash >& file redirects match file-mutation", () => { + test("echo hi >& out.txt denies", () => { + expect(autoShellRuleForCall(shellCall("echo hi >& out.txt"))?.name).toBe( + "file-mutation", + ); + }); + + test("echo hi >&file denies", () => { + expect(autoShellRuleForCall(shellCall("echo hi >&file"))?.name).toBe( + "file-mutation", + ); + }); + + test("echo hi > out.txt still denies", () => { + expect(autoShellRuleForCall(shellCall("echo hi > out.txt"))?.name).toBe( + "file-mutation", + ); + }); + + test("echo hi 2>&1 is not a file-mutation deny", () => { + expect(autoShellRuleForCall(shellCall("echo hi 2>&1"))?.name).not.toBe( + "file-mutation", + ); + }); +}); + describe("CL-6697 — quoted dangerous flags and program names still deny/ask", () => { test("a quoted -c interpreter one-liner denies", () => { expect(