codex exec --sandbox workspace-write --add-dir X: apply_patch tool ignores --add-dir whitelist and writes anywhere under --cd

Open 💬 2 comments Opened May 23, 2026 by samuny2329

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-dirworkdir/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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗