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(