diff --git a/codex-cli/src/approvals.ts b/codex-cli/src/approvals.ts index f4a35402..ff37a890 100644 --- a/codex-cli/src/approvals.ts +++ b/codex-cli/src/approvals.ts @@ -329,11 +329,20 @@ export function isSafeCommand( reason: "Ripgrep search", group: "Searching", }; - case "find": - return { - reason: "Find files or directories", - group: "Searching", - }; + case "find": { + // Certain options to `find` allow executing arbitrary processes, so we + // cannot auto-approve them. + if ( + command.some((arg: string) => UNSAFE_OPTIONS_FOR_FIND_COMMAND.has(arg)) + ) { + break; + } else { + return { + reason: "Find files or directories", + group: "Searching", + }; + } + } case "grep": return { reason: "Text search (grep)", @@ -421,6 +430,21 @@ function isValidSedNArg(arg: string | undefined): boolean { return arg != null && /^(\d+,)?\d+p$/.test(arg); } +const UNSAFE_OPTIONS_FOR_FIND_COMMAND: ReadonlySet = new Set([ + // Options that can execute arbitrary commands. + "-exec", + "-execdir", + "-ok", + "-okdir", + // Option that deletes matching files. + "-delete", + // Options that write pathnames to a file. + "-fls", + "-fprint", + "-fprint0", + "-fprintf", +]); + // ---------------- Helper utilities for complex shell expressions ----------------- // A conservative allow-list of bash operators that do not, on their own, cause diff --git a/codex-cli/tests/approvals.test.ts b/codex-cli/tests/approvals.test.ts index a39adff4..a90abad6 100644 --- a/codex-cli/tests/approvals.test.ts +++ b/codex-cli/tests/approvals.test.ts @@ -89,4 +89,56 @@ describe("canAutoApprove()", () => { expect(check(["cargo", "build"])).toEqual({ type: "ask-user" }); }); + + test("find", () => { + expect(check(["find", ".", "-name", "file.txt"])).toEqual({ + type: "auto-approve", + reason: "Find files or directories", + group: "Searching", + runInSandbox: false, + }); + + // Options that can execute arbitrary commands. + expect( + check(["find", ".", "-name", "file.txt", "-exec", "rm", "{}", ";"]), + ).toEqual({ + type: "ask-user", + }); + expect( + check(["find", ".", "-name", "*.py", "-execdir", "python3", "{}", ";"]), + ).toEqual({ + type: "ask-user", + }); + expect( + check(["find", ".", "-name", "file.txt", "-ok", "rm", "{}", ";"]), + ).toEqual({ + type: "ask-user", + }); + expect( + check(["find", ".", "-name", "*.py", "-okdir", "python3", "{}", ";"]), + ).toEqual({ + type: "ask-user", + }); + + // Option that deletes matching files. + expect(check(["find", ".", "-delete", "-name", "file.txt"])).toEqual({ + type: "ask-user", + }); + + // Options that write pathnames to a file. + expect(check(["find", ".", "-fls", "/etc/passwd"])).toEqual({ + type: "ask-user", + }); + expect(check(["find", ".", "-fprint", "/etc/passwd"])).toEqual({ + type: "ask-user", + }); + expect(check(["find", ".", "-fprint0", "/etc/passwd"])).toEqual({ + type: "ask-user", + }); + expect( + check(["find", ".", "-fprintf", "/root/suid.txt", "%#m %u %p\n"]), + ).toEqual({ + type: "ask-user", + }); + }); });