Skip to content

fix: use read-only sandbox for Codex with unified diff file fallback#620

Open
wesm wants to merge 8 commits intomainfrom
fix/codex-sandboxing
Open

fix: use read-only sandbox for Codex with unified diff file fallback#620
wesm wants to merge 8 commits intomainfrom
fix/codex-sandboxing

Conversation

@wesm
Copy link
Copy Markdown
Collaborator

@wesm wesm commented Apr 3, 2026

Summary

  • Revert Codex sandbox from --sandbox danger-full-access back to --sandbox read-only. With full access, Codex was scanning /home, /nix/store, and /root when investigating large diffs.
  • For all agents, when a diff is too large to inline in the prompt, the worker writes the full diff to a snapshot file in the repo's git dir and passes the path to the prompt builder. The builder references the file in the prompt. No agent-specific branching in the large-diff path.
  • CI prebuilt prompts embed a placeholder path at enqueue time (since the snapshot file doesn't exist yet). The worker replaces it with the real file path at execution time.
  • Exclude patterns applied consistently to diff snapshots.
  • Codex review prompt instructs the agent not to search or read files outside the repository checkout.
  • ResolveGitDir exported from internal/git with MSYS path normalization.

🤖 Generated with Claude Code

@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 3, 2026

roborev: Combined Review (5b233a6)

Verdict: Changes are not ready to merge; there are 2 medium-severity correctness issues in the new sandboxed Codex diff handling.

Medium

  1. Sandboxed prebuilt Codex reviews can fail outright in unsafe/full-access mode
    Location: internal/daemon/worker.go around preparePrebuiltCodexPrompt and prepareDiffFileForCodex
    preparePrebuiltCodexPrompt now treats a missing generated diff file as fatal, but prepareDiffFileForCodex intentionally returns no file when agent.AllowUnsafeAgents() is enabled. That makes prebuilt Codex reviews fail instead of falling back to the original git-based prompt in unsafe/full-access mode.

  2. Diff snapshot creation failures are silently downgraded into unusable sandboxed reviews
    Location: internal/daemon/worker.go around the new prepareDiffFileForCodex call in processJob
    On the normal review path, errors creating the diff snapshot are converted into diffFile == "" and the code falls back to pb.Build(...). For oversized Codex diffs, that fallback still tells the agent to inspect git state, but the review now runs with --sandbox read-only, so those instructions are not usable. The result can be a broken or no-op review instead of a clean retry/failure path.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 3, 2026

roborev: Combined Review (5b233a6)

Summary verdict: 2 medium-severity issues need attention before merge.

Medium

  1. Sandboxed Codex reviews can fail outright in unsafe/full-access mode

    • Location: internal/daemon/worker.go around preparePrebuiltCodexPrompt and prepareDiffFileForCodex
    • Issue: Prebuilt Codex prompts now always include the diff-file placeholder, but preparePrebuiltCodexPrompt treats a missing generated diff file as fatal. prepareDiffFileForCodex intentionally returns no file when agent.AllowUnsafeAgents() is enabled, so prebuilt Codex reviews fail instead of falling back to the original git-based prompt.
    • Suggested fix: Only inject the placeholder when the review is actually sandboxed, or allow preparePrebuiltCodexPrompt to preserve the stored prompt when unsafe/full-access mode is active.
  2. Diff snapshot failures are silently downgraded into a broken sandboxed fallback

    • Location: internal/daemon/worker.go around the new prepareDiffFileForCodex call in processJob
    • Issue: On the normal review path, failures creating the diff snapshot are converted into diffFile == "", and execution falls back to pb.Build(...). For oversized Codex diffs, that fallback still tells the agent to inspect git state, but the agent is launched with --sandbox read-only, so those instructions are unusable and the review can degrade into a no-op or incomplete analysis instead of failing cleanly.
    • Suggested fix: Propagate diff snapshot creation failures for sandboxed Codex reviews so the job retries/fails explicitly, or retain a full-access fallback when no readable diff snapshot can be prepared.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

Revert Codex sandbox from --sandbox danger-full-access back to
--sandbox read-only. With full access, Codex was scanning /home,
/nix/store, and /root when investigating large diffs.

For large diffs that don't fit inline in the prompt, the worker writes
the full diff to a file in the repo's git dir (resolved via git
rev-parse --git-dir) and references the absolute path in the prompt so
sandboxed Codex can read it directly.

- Diff file is only captured when the prompt builder detects truncation,
  avoiding extra git calls for small diffs
- CI prebuilt prompts with a diff file placeholder are resolved at job
  time; legacy prebuilt prompts get a diff file reference appended
- Exclude patterns applied consistently to both inline and file-based
  diffs
- Codex review prompt instructs the agent not to search or read files
  outside the repository checkout
- ResolveGitDir exported from internal/git with MSYS path normalization
- Config-aware agent resolution for diff file requirement checks
@wesm wesm force-pushed the fix/codex-sandboxing branch from 5b233a6 to 7b40338 Compare April 3, 2026 18:19
@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 3, 2026

roborev: Combined Review (7b40338)

Verdict: Changes are directionally correct, but there are 3 medium-severity issues that should be fixed before merge.

Medium

  • Stale diff snapshot path can be persisted into retried Codex jobs
    Location: internal/daemon/worker.go:427
    For oversized non-prebuilt Codex reviews, processJob rebuilds the prompt with a concrete snapshot path via BuildWithDiffFile(...) and then deletes that file afterward. If the prompt is persisted for retry/failover, later retries can reuse a prompt that points at a deleted file because preparePrebuiltCodexPrompt only rewrites the placeholder sentinel, not an already-materialized path.
    Fix: Persist a placeholder-based prompt and substitute the real snapshot path only in memory right before invoking Codex, or regenerate the prompt on each retry.

  • Dirty-review path does not get the new diff-file fallback
    Location: internal/daemon/worker.go:406
    The new oversized-diff fallback only applies to the git-backed Build(...) path. Jobs using job.DiffContent != nil still go through BuildDirty(...) without snapshot support, so large dirty Codex reviews can still fall back to truncated inline instructions even though review mode now forces --sandbox read-only.
    Fix: Add the same snapshot-file flow for dirty reviews by writing job.DiffContent to a temporary diff file and teaching the dirty prompt builder to reference it when inline diff content overflows.

  • git rev-parse --git-dir output may include a trailing newline and break os.Stat
    Location: internal/git/git.go:278
    cmd.Output() preserves the trailing newline from git rev-parse --git-dir. Unless normalizeMSYSPath already strips whitespace, passing that through filepath.Clean can leave the newline intact and cause later os.Stat(gitDir) calls to fail with ENOENT.
    Fix: Trim the command output before normalization/cleaning, e.g. strings.TrimSpace(...).


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@wesm wesm force-pushed the fix/codex-sandboxing branch from 7b40338 to e3a75ee Compare April 3, 2026 19:33
@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 3, 2026

roborev: Combined Review (e3a75ee)

Verdict: Changes are not ready to merge due to one high-severity build break and one medium-severity review regression.

High

  • Compilation failure at internal/daemon/worker.go:1167
    prepareDiffFileForCodex calls gitpkg.GetDiff / gitpkg.GetRangeDiff with excludes..., but the current function signatures in internal/git/git.go only accept two string arguments. This will not compile (too many arguments in call).
    Fix: Either update internal/git/git.go so those helpers accept excludes ...string, or remove the extra arguments if they are not needed.

Medium

  • Dirty-review fallback is incomplete at internal/daemon/worker.go:406
    The new diff-snapshot flow only covers stored prompts and the normal job.GitRef path. Dirty reviews still use BuildDirty(...) unchanged, so large uncommitted diffs can still be truncated in the prompt without a file-based fallback. With non-agentic Codex now running under --sandbox read-only, Codex can no longer recover omitted diff content itself, which is a review-quality regression.
    Fix: Add the same snapshot/file-reference mechanism for dirty reviews, or keep dirty Codex reviews on the previous sandbox mode until oversized dirty diffs are supplied out-of-band.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 3, 2026

roborev: Combined Review (209273e)

Verdict: One medium-severity regression needs to be addressed; no higher-severity issues were identified.

Medium

  • Retry path persists stale oversized-diff instructions for some Codex reviews
    Location: internal/daemon/worker.go, internal/daemon/worker.go
    Large non-agentic Codex reviews built through the normal worker path still save the original pb.Build(...) prompt before the diff-file rewrite happens. On retry, that leaves the stored prompt with the old git-based oversized-diff fallback instead of CodexDiffFilePathPlaceholder, so preparePrebuiltCodexPrompt can only append a cat <diff> note rather than replacing the stale instructions. In read-only sandbox mode, retries can therefore contain contradictory guidance and point the model back toward commands expected to fail.
    Suggested fix: When truncation is detected, persist a placeholder-based prompt for Codex reviews by building with prompt.CodexDiffFilePathPlaceholder, then replace that placeholder at execution time.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

Replace the Codex-specific large-diff handling with a single path
that works the same for every agent:

1. Worker writes the full diff to a snapshot file before building
   the prompt
2. Builder inlines the diff if it fits; references the file if it
   doesn't
3. No agent-specific branching, no prompt parsing, no placeholders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wesm wesm force-pushed the fix/codex-sandboxing branch from 209273e to 389f1ef Compare April 4, 2026 13:07
@wesm wesm changed the title fix: use read-only sandbox for Codex reviews with diff file snapshots fix: use read-only sandbox for Codex with unified diff file fallback Apr 4, 2026
@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 4, 2026

roborev: Combined Review (389f1ef)

Summary verdict: Changes are not ready to merge; there is one high-severity correctness issue and one medium-severity path-handling bug.

High

  • internal/daemon/worker.go:422, internal/daemon/worker.go:1078, internal/prompt/prompt.go:382
    Oversized-diff review jobs can continue even when the diff snapshot file fails to be created. In that case prepareDiffFile returns "", BuildWithDiffFile falls back to a prompt that only states the diff was too large, and the agent loses access to the actual changes. This can produce false “clean” reviews for exactly the large diffs this feature is meant to handle.
    Suggested fix: Make snapshot creation mandatory for oversized-diff reviews and fail/retry the job if it cannot be written, or keep a fallback that still exposes the real diff to the agent.

Medium

  • internal/git/git.go:278
    cmd.Output() includes a trailing newline. If gitDir is used without trimming, the resulting path can be invalid, causing diff snapshot creation to fail.
    Suggested fix: Apply strings.TrimSpace(string(out)) before passing the value to normalizeMSYSPath().

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

wesm and others added 5 commits April 4, 2026 08:12
CodexDiffFilePathPlaceholder → DiffFilePathPlaceholder
preparePrebuiltCodexPrompt → preparePrebuiltPrompt

The placeholder and replacement logic are agent-agnostic — the CI
poller already passes the placeholder for all agents, not just Codex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace `cat '/path'` with tool-neutral "Read the diff from:
  `/path`" so oversized-diff prompts work on Windows
- Make prepareDiffFile return an error so the worker retries when the
  snapshot cannot be written, instead of running a useless review
  with no diff access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Return ErrDiffTruncatedNoFile from the builder when the diff is too
large to inline and no file path was provided. The worker calls Build
first; on ErrDiffTruncatedNoFile it writes the snapshot and retries
with BuildWithDiffFile. Small diffs never touch the snapshot path.

For prebuilt CI prompts, degrade gracefully when the snapshot cannot
be created (strip placeholder) instead of hard-failing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…radation

- Build() returns a plain truncation note for oversized diffs (no
  error), preserving backward compat for roborev review --local,
  batch reviews, and other non-worker callers
- Only BuildWithDiffFile() with empty path returns
  ErrDiffTruncatedNoFile, which the worker uses to trigger snapshot
  creation
- On prebuilt prompt snapshot failure, strip the entire file-reference
  block (not just the placeholder) so the agent sees a clean
  truncation note without misleading "written to a file" instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match on the common prefix "(Diff too large to include inline" and
truncate at the closing paren, handling both the verbose and compact
forms from diffFileFallbackVariants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 4, 2026

roborev: Combined Review (0ec9708)

Verdict: Changes are not ready to merge; there are 2 High and 1 Medium findings that should be addressed.

High

  • internal/daemon/worker.go:1036
    preparePrebuiltPrompt can silently continue after diff snapshot creation fails for an oversized prebuilt prompt, degrading the prompt to a truncation note with no inline diff and no readable diff file. In the read-only Codex flow, that means a large-diff review may run without access to the actual changes. This should fail/retry the job instead of proceeding with the diff removed.

  • internal/daemon/worker.go:1060 (stripDiffFileBlock)
    The diff-fallback stripping logic is too broad and brittle. It currently matches the first occurrence of the marker text, which could appear in user-supplied PR discussion or prior review text and cause unrelated prompt content to be truncated. It also does not robustly handle both generated fallback variants, so broken diff instructions may be left behind. The replacement should target the specific generated diff-file fallback block using a precise anchor near the actual diff section.

Medium

  • internal/git/git.go:279 (ResolveGitDir)
    The result of git rev-parse --git-dir may include a trailing newline. If that output is passed through without trimming, the returned path can be corrupted and later file operations such as os.WriteFile can fail. Trim whitespace from the command output before normalizing/cleaning the path.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 4, 2026

roborev: Combined Review (099e55f)

Verdict: Changes are not ready to merge due to 1 High and 1 Medium issue in the diff-file fallback path.

High

  • internal/daemon/worker.go (~1036-1060, preparePrebuiltPrompt)
    If a stored or prebuilt prompt contains the diff-file placeholder and prepareDiffFile fails, the code logs a warning, removes the file-reference block, and still returns success. That allows the worker to proceed with a prompt that has neither the inline diff nor a valid external diff reference, which can silently produce incorrect reviews in retries, failover, or CI-prebuilt flows.
    Recommended fix: treat diff snapshot preparation failure as a hard failure for placeholder-based prompts, or rebuild the prompt with a real inline fallback before continuing.

Medium

  • internal/daemon/worker.go (~1071-1085, stripDiffFileBlock)
    stripDiffFileBlock does not fully remove the compact fallback form "(Diff too large to include inline; read from \`)". It leaves behind the read from ...` instruction, so after snapshot preparation fails the prompt can still direct the agent to a nonexistent placeholder path.
    Recommended fix: strip the entire fallback block, including the compact variant, and replace it with a plain truncation note if needed.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

…FileBlock

Prebuilt prompts with the diff file placeholder need the snapshot to
be useful — if it can't be created, fail the job so it retries
instead of running a review with no diff access.

Delete stripDiffFileBlock since the degradation path is no longer
used.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@roborev-ci
Copy link
Copy Markdown

roborev-ci bot commented Apr 4, 2026

roborev: Combined Review (bd5593e)

Verdict: No medium, high, or critical issues identified.

All reported findings were below the requested severity threshold, so there are no actionable PR comments to surface at Medium or above.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

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