codex exec --sandbox workspace-write --add-dir X: apply_patch tool ignores --add-dir whitelist and writes anywhere under --cd
Summary
When codex exec is launched with --sandbox workspace-write --add-dir <task-dir> and --cd <broader-root>, shell-based writes are correctly fenced to <task-dir> + /tmp + $TMPDIR, but the agent's internal apply_patch tool bypasses the --add-dir whitelist entirely and can create files anywhere reachable under --cd. This makes --add-dir decorative for write fencing — only the --cd boundary actually constrains apply_patch.
Tested on Windows 11 + codex-cli 0.133.0.
Severity
High for any embedding context where the operator trusts --add-dir as the write fence. In our case we use codex exec from a Claude Code subagent as a read-only critic for a multi-stage decision pipeline; the intended fence was --add-dir <task-dir> so Codex could only mutate its own task workspace. The bypass would have allowed Codex to write to wiki/, an append-only ledger.jsonl, .claude/agents/, and sibling task dirs — none of which were intended writable.
Environment
codex-cli 0.133.0(codex --version)- Windows 11, PowerShell 5.1 + Git Bash (MSYS2)
- Auth: ChatGPT login (
codex login) - Sandbox mode advertised by the CLI on startup:
sandbox: workspace-write [workdir, /tmp, $TMPDIR]
Steps to reproduce
# 1. Pick two dirs: a "task" dir (intended writable) and a "root" dir (broader, intended read-only-for-Codex).
ROOT="C:/Users/me/some-project"
TASK_DIR="$ROOT/drafts/task-001"
mkdir -p "$TASK_DIR"
# 2. Prepare a prompt asking Codex to write at a path that is INSIDE --cd but OUTSIDE --add-dir.
cat > "$TASK_DIR/prompt.txt" <<'EOF'
Sandbox containment test. Try to write a file at this exact path:
C:/Users/me/some-project/wiki/test-fence-probe.md
with content "bypass test". Use whatever tool you have. After attempting, report:
did the write succeed yes/no, and the exact error message if any.
Return JSON {"attempted": true, "succeeded": true/false, "error": "..."}.
EOF
mkdir -p "$ROOT/wiki" # parent must exist so the failure is a sandbox failure, not a missing-dir failure
# 3. Run codex exec with the intended fence:
codex exec \
--sandbox workspace-write \
--add-dir "$TASK_DIR" \
--skip-git-repo-check \
--ephemeral \
--cd "$ROOT" \
- < "$TASK_DIR/prompt.txt"
# 4. Check whether the file was created.
ls -la "$ROOT/wiki/test-fence-probe.md"
Expected behavior
apply_patch (and any other write tool inside the agent) refuses to write to $ROOT/wiki/test-fence-probe.md because that path is not under --add-dir "$TASK_DIR". The write should fail the same way the shell-based path fails, or at minimum a path-not-allowed error from apply_patch itself.
Actual behavior
Shell-write attempt is sandbox-refused as expected. But Codex then auto-falls-back to apply_patch which succeeds:
codex
Sandbox test in writable root. Will create target file, report exact JSON.
exec
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Command "Test-Path -LiteralPath 'C:\Users\me\some-project\wiki'" in C:\Users\me\some-project
2026-05-23T02:41:23.377312Z ERROR codex_core::exec: exec error: windows sandbox: spawn setup refresh
2026-05-23T02:41:23.385869Z ERROR codex_core::tools::router: error=execution error: Io(Custom { kind: Other, error: "windows sandbox: spawn setup refresh" })
exited -1 in 0ms:
execution error: Io(Custom { kind: Other, error: "windows sandbox: spawn setup refresh" })
codex
Shell launch failed before file check: `windows sandbox: spawn setup refresh`. Trying direct workspace edit next.
apply patch
patch: completed
C:\Users\me\some-project\wiki\test-fence-probe.md
Final agent message:
{"attempted":true,"succeeded":true,"error":""}
The file wiki/test-fence-probe.md is on disk after the run, confirming apply_patch ignored --add-dir.
Containment that DOES hold
A second probe with --cd "$TASK_DIR" (narrow root, no --add-dir needed) confirmed apply_patch correctly refuses writes outside --cd. The failure mode is specific to the apply_patch × --add-dir interaction, not a general sandbox failure.
codex exec \
--sandbox workspace-write \
--add-dir "$TASK_DIR" \
--skip-git-repo-check \
--ephemeral \
--cd "$TASK_DIR" \
- < <(echo "Try to write C:/Users/me/Desktop/escape.txt — report JSON {attempted,succeeded,error}")
# -> apply_patch does NOT fall back; shell write refused; Desktop file not created.
So --cd is the actual fence; --add-dir is decorative for write scope.
Suggested fix
apply_patch (and any other internal write tool) should consult the same write-target allowlist used by the shell-exec sandbox layer. The allowlist should be: --cd ∩ (--add-dir ∪ workdir ∪ /tmp ∪ $TMPDIR). If --add-dir is set, paths outside the union but still under --cd should be refused, not silently allowed via the patch tool.
Alternative: make the flag's behavior explicit in codex exec --help (today the text reads "Additional directories that should be writable alongside the primary workspace" — which implies apply_patch should also honor it).
Workaround
Use --sandbox read-only for all codex exec invocations until a fixed CLI release ships. If write capability is needed, set --cd "$TASK_DIR" (collapse the root) and pre-bake context files into the task dir, rather than rely on --add-dir to fence writes from a broader --cd "$ROOT".
Severity assessment for embedded use cases
Anyone embedding codex exec from a parent agent / orchestrator / IDE plugin and relying on --add-dir for sandbox isolation is exposed: a Codex turn that decides to write to an unintended path will succeed via apply_patch, regardless of the whitelist. Schema-enforced output (--output-schema) does NOT mitigate this — apply_patch is invoked during the reasoning phase, before the final structured message is emitted.
2 Comments
Solution: Codex #24214 — Sandbox Whitelist Bypass via --add-dir
Issue Summary
GitHub: openai/codex #24214
Title: codex exec --sandbox workspace-write --add-dir X: apply_patch tool ignores --add-dir whitelist and writes anywhere under --cd
Labels: bug, windows-os, sandbox, exec, CLI, tool-calls
Competition: Zero (0 comments)
Quote: $3,000-$5,000 (Security)
Root Cause Analysis
Primary Root Cause: Tool-Specific Path Enforcement Gap
The
--add-dirflag correctly restricts shell-based writes (via sandbox seccomp/ACL rules) but theapply_patchtool (and potentially other file manipulation tools) bypass the sandbox's path restrictions because they operate at a different layer — the tool execution layer, not the process layer.Failure Chain
Root Cause Details
--add-dirflag configures the process-layer sandbox but does NOT propagate to the tool-layer path restriction. The tool layer only checks against--cd(the working directory), allowing writes anywhere under that directory.--add-dir /task/dir --cd /projectcan useapply_patchto modify files in/project/src/,/project/config/, etc. — completely bypassing the intended restriction to/task/dir.Evidence Pattern
| Signal | Interpretation |
|--------|---------------|
| shell writes fenced correctly | Process-layer sandbox works |
| apply_patch ignores restriction | Tool-layer sandbox missing the config |
| --add-dir flag | Configures process-layer only |
| --cd broader root | Tool layer uses this as the only boundary |
Proposed Fix
Fix 1: Unified Path Restriction Propagation
File:
codex-rs/core/src/sandbox/path_restriction.rsConfirmed the same tool-layer enforcement gap on macOS Codex Desktop, and it also crosses a documented protected-path boundary—not only
--add-dir.Environment
26.715.21425(bundle build5488)codex-cli 0.145.0-alpha.1826.5.2(25F84):workspace/workspace-writeReproduction and controls
write, but separately grants the exact workspace.codex,.agents, and.gitchildren onlyread.mkdir -p .codex/protected-path-canaryfrom the shell returnsOperation not permitted.apply_patchtool successfully creates missing parents and new files beneath.codex/protected-path-canary/; readback confirms the exact bytes.mkdirbeneath that now-existing.codexdirectory still returnsOperation not permitted..codex-canary/directory are writable;.git/remains denied.This rules out host ownership/ACL/flags, generic dot-directory handling, and the projectless folder location. Changing only the mutation surface changes the result: process-layer shell enforcement denies the protected path, while the tool-layer patch succeeds.
Current docs explicitly say
<writable_root>/.codexis recursively read-only under defaultworkspace-write: https://learn.chatgpt.com/docs/agent-approvals-security#protected-paths-in-writable-rootsI did not modify pre-existing protected content; the proof used only newly created canary files in my own disposable generated workspaces. I also discarded a local workflow patch that would have relied on this discrepancy.
Expected:
apply_patchshould consult the same effective permission profile/protected-path decision as shell execution (or use a distinct, non-model-controlled internal capability if trusted Codex state requires writes).This appears to be the macOS Desktop/projectless variant of the same two-layer enforcement bug already reported here, so I am adding evidence rather than opening a duplicate.