Skip to content

feat(sandbox): resolve task sandbox from config.toml + add task --read-only (port openai#508)#33

Merged
axisrow merged 2 commits into
mainfrom
port/cubicj-508-config-sandbox
Jul 21, 2026
Merged

feat(sandbox): resolve task sandbox from config.toml + add task --read-only (port openai#508)#33
axisrow merged 2 commits into
mainfrom
port/cubicj-508-config-sandbox

Conversation

@axisrow

@axisrow axisrow commented Jul 21, 2026

Copy link
Copy Markdown
Owner

What

Port of @cubicj's openai#508. The plugin hardcoded the sandbox (read-only/workspace-write), ignoring sandbox_mode in ~/.codex/config.toml. Now the task path sends sandbox: null when the caller does not pin one, letting the app-server resolve it from config — the same mechanism already used for model. An explicit --write still forces workspace-write; review paths keep their pinned read-only.

Adds a task --read-only flag for a per-run read-only pin, mutually exclusive with --write.

Why

Users who set sandbox_mode = "danger-full-access" (e.g. trusted delegation) had to hand-patch the installed plugin. Porting openai#508 lets config drive the default and gives an explicit per-run escalation/lockdown.

Merge notes

openai#508 was written against an older base; merged onto the fork resolving conflicts in codex.mjs, codex-companion.mjs, rescue.md, fake-codex-fixture.mjs, runtime.test.mjs. Kept our reasoning-validation, --turn-timeout-ms, model aliases (spark/sol/terra/luna), and --cwd/-C; layered cubicj's sandbox: null + --read-only on top. Our fake-codex fixture already recorded sandbox in lastThreadStart/lastThreadResume, so openai#508's assertions pass as-is.

Verification

  • npm run build: clean.
  • npm test: 174 tests, 171 pass / 3 pre-existing failures (was 4 — the task --background test that previously asserted a foreign model-luna value now passes). All 6 new sandbox tests green: --write→workspace-write, resuming --write, --background preserves --read-only, --read-only pins, --read-only pins resumed, --write --read-only rejected.
  • TODO: smoke-verify sandbox: null resolves from config.toml on real codex (as done for Capture resolved model/effort/sandbox in job records openai/codex-plugin-cc#506).

Hat-tip to @cubicj — original PR openai#508.

🤖 Generated with Claude Code

axisrow and others added 2 commits July 22, 2026 01:00
…d-only (port openai#508)

Port of @cubicj's openai#508. The plugin hardcoded the
sandbox ("read-only"/"workspace-write"), ignoring sandbox_mode in
~/.codex/config.toml. Now the task path sends sandbox: null when the caller
does not pin one, letting the app-server resolve it from config — the same
mechanism already used for model. Explicit --write still forces
workspace-write; review paths keep their pinned read-only.

- buildThreadParams/buildResumeParams: sandbox ?? null (config-driven default)
- task --read-only flag: per-run read-only pin, mutually exclusive with --write
- stop-review-gate hook's internal task passes --read-only explicitly so the
  gate stays read-only regardless of config
- rescue command/agent/skill docs updated for --read-only

Merged onto the fork (conflicts in codex.mjs, codex-companion.mjs, rescue.md,
fake-codex-fixture, runtime.test): kept our reasoning-validation +
turn-timeout-ms + model aliases + --cwd, layered cubicj's sandbox-null +
--read-only on top. Our fake-codex fixture already recorded sandbox in
lastThreadStart/lastThreadResume, so openai#508's assertions pass as-is.

Verified: npm run build clean; npm test 174/171 pass / 3 pre-existing (was
4 — the background test that previously asserted a foreign model-luna value
now passes). All 6 new sandbox tests green.

Co-Authored-By: Claude <noreply@anthropic.com>
…me (cycle-review critical)

Codex adversarial review of #33 flagged — and a real-codex smoke confirmed —
that Codex 0.144.6 keeps a loaded thread's sandbox and ignores thread/resume
overrides: `task --read-only --resume-last` on a write-capable thread ran
with workspace-write access, violating the --read-only contract.

Verified empirically (codex-cli 0.144.6): write-thread -> resume with
--read-only returned resolved.sandbox = workspaceWrite. The code never
compared the resolved sandbox to the explicit pin.

Fix: assertExplicitSandboxHonored() in runAppServerTurn compares an explicit
read-only pin to the app-server's resolved sandbox and throws on mismatch —
fail closed instead of running write-capable under a read-only flag. Only the
read-only pin is enforced (a write request on a read-only resolved sandbox is
a downgrade, not a safety leak). TDD: a resume-ignores-sandbox fake-codex
behavior reproduces the leak (RED), the assertion turns it green.

Also confirms the port's cubicj claim: a plain task with sandbox:null resolves
to read-only from config (smoke: resolved readOnly), so stock behavior is
unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
@axisrow

axisrow commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

📋 Review summary — all cycles

Cycle Reviewer Finding Verdict Resolution
1 codex --read-only --resume-last can run with the prior thread's write sandbox — Codex 0.144.x ignores thread/resume overrides; code never compared resolved vs the explicit pin UNVERIFIED→CONFIRMED Real-codex smoke confirmed (write-thread → resume --read-only → resolved workspaceWrite). Fixed in c5af938: assertExplicitSandboxHonored fails closed on read-only mismatch
1 claude (/review) config-driven sandbox:null stock-behavior unverified resolved Smoke confirmed: plain task → resolved readOnly (config default), stock behavior unchanged
2 codex (no findings — guard rejects missing/unknown/write resolved sandboxes before turn/start) approve
2 claude (/review) (clean — fail-closed scoped to read-only only, write-path intact) approve

Totals: 1 critical (smoke-confirmed, resolved), 0 SKIP, 0 UNVERIFIED.
Verdict: both reviewers approve at head c5af938. Ready to merge (local mode — merge is yours to trigger).

@axisrow
axisrow merged commit ec6e98b into main Jul 21, 2026
@axisrow
axisrow deleted the port/cubicj-508-config-sandbox branch July 21, 2026 17:50
axisrow added a commit that referenced this pull request Jul 21, 2026
Ships accumulated fixes since fork.4:
- #29 broker-busy hang (PR #30): bound broker handshake
- #31 spawned-app-server handshake (PR #32): bound request() primitive
- openai#508 config-driven sandbox + --read-only (PR #33) + resume-sandbox fail-closed
- #34 model/effort in /codex:rescue launch line (PR #35)
- #28 turn-timeout gaps (already in fork.4 base)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant