Skip to content

Reserve missing preserved paths in Linux sandbox policy#18446

Merged
viyatb-oai merged 1 commit intocodex/bugb15632-runtime-permissionsfrom
codex/bugb15632_missing_protected_paths_v2
Apr 27, 2026
Merged

Reserve missing preserved paths in Linux sandbox policy#18446
viyatb-oai merged 1 commit intocodex/bugb15632-runtime-permissionsfrom
codex/bugb15632_missing_protected_paths_v2

Conversation

@evawong-oai
Copy link
Copy Markdown
Contributor

@evawong-oai evawong-oai commented Apr 18, 2026

Summary

This PR protects preserved workspace metadata paths under workspace write sandboxes without breaking normal Git discovery. The preserved names are .git, .codex, and .agents.

BUGB 15632 tracks the Bugcrowd submission.

Root Cause

The previous policy only treated .git and .agents as protected after they already existed. That left a first run gap where sandboxed code in a new workspace could create .git/config.

The report used git init followed by a Git config value that could persist on the host. A later host side Git command could then load attacker controlled repo metadata.

The hard part is that missing .git cannot always be materialized as a read only placeholder. If Codex runs inside a subdirectory of an existing Git repo or worktree, materializing a child .git changes Git discovery and hides the valid parent repo. In that case the child .git must stay absent while creation is still denied.

Design

  1. The protocol layer now carries a real preserved path policy primitive on writable roots through preserved_path_names.
  2. Writable root policy rejects writes whose path components include .git, .codex, or .agents unless an explicit user write policy covers that preserved path.
  3. Existing .git, .codex, and .agents paths stay read only under default writable roots.
  4. Missing .codex and .agents are reserved under writable roots so first creation is blocked.
  5. Missing child .git under a valid parent repo or worktree stays absent so normal git status and git rev parse keep discovering the parent.
  6. Seatbelt enforces preserved names directly in the generated sandbox profile for writable roots.
  7. Linux bwrap setup masks representable missing preserved paths with /dev/null.
  8. Linux tracks missing preserved targets that cannot be safely materialized as protected_create_targets, runs bwrap with a parent helper only when cleanup or protected target checks are needed, and removes any forbidden preserved target that a sandboxed process managed to create before returning failure.
  9. The command preflight no longer contains a hard coded table for touch, mkdir, rm, rmdir, ln, mv, cp, install, or git init. It only catches literal shell redirection targets early for user feedback. The sandbox policy is the security boundary.
  10. The Linux launcher forwards termination signals to the bwrap child process group, clears the child pid after wait, and then runs cleanup.

Validation

Current PR head: 3ae2e24e72eb879d66de9e28b6f32226f52d5eae.

Current base main: bb83eec825b74aaf06f74650d2c004b0629dd19a.

  1. Local macOS build passed for the CLI crate and Linux sandbox crate.
  2. Formatting passed with just fmt.
  3. Diff whitespace check passed.
  4. Conflict marker scan passed across the checkout outside vendored code.
  5. GitHub Actions passed on the current head, including macOS, Ubuntu, Windows, Bazel local builds, Bazel clippy, release builds, argument comment lint, format, spellcheck, SDK checks, cargo shear, cargo deny, blob policy, CLA, and required CI results.
  6. Linux devbox validation passed all 46 preserved path smoke cases on the current head using literal just c for each case.
  7. The devbox smoke run reported all 46 cases passed on the current head.
  8. Case 46 covered Viyat's parent repo subdirectory scenario: parent Git discovery still works, git init and mkdir .codex are denied, normal coding can create the requested JSONL viewer, and no child .git, .codex, or .agents directory is left behind.
  9. The detailed devbox log is available in the validation workspace.

@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 6 times, most recently from 30deabb to e4249a5 Compare April 20, 2026 16:55
@evawong-oai evawong-oai changed the title [Linux] Reserve missing top level .git at runtime Reserve missing top level .git in sandbox policy Apr 20, 2026
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch from e4249a5 to f85a396 Compare April 20, 2026 17:11
@evawong-oai
Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 3 times, most recently from 30d621d to 042c0ba Compare April 20, 2026 21:01
@evawong-oai evawong-oai changed the title Reserve missing top level .git in sandbox policy Reserve missing project metadata in sandbox policy Apr 20, 2026
@evawong-oai
Copy link
Copy Markdown
Contributor Author

evawong-oai commented Apr 20, 2026

Reviewer evidence update.

I extended the same preserved path rule to missing .agents. The existing .agents directory was already protected when present. The missing case now follows .git and .codex for the active workspace root, while explicit user write rules still win.

I also reran linked worktree validation on a Linux devbox with system bwrap installed at /usr/bin/bwrap, version 0.9.0. The .git worktree pointer case passed. Normal writes inside the worktree succeeded. Overwriting .git, replacing .git, and writing through the resolved gitdir were blocked. The pointer file stayed unchanged and no .git directory was created.

I fixed the devbox validation gap where blocked missing .codex and .agents attempts left zero byte host files. The Linux bwrap path now tracks synthetic missing mount targets, runs bwrap in a child only when cleanup is needed, removes only zero byte synthetic targets after preflight and the real run, then preserves the bwrap exit status.

The rerun passed on the Linux devbox with system bwrap at /usr/bin/bwrap, version 0.9.0, against commit 87f8ccb2bc22abb0e2c403da30866ae014941ae7. Normal checkout and linked worktree cases passed for .codex, .git, and .agents. Missing .codex and .agents writes were blocked and left no host side file or directory immediately after the sandbox command returned. Normal writes still succeeded, the linked .git pointer stayed unchanged, and resolved gitdir writes stayed blocked. The validation transcript did not contain the previous proc mount setup error.

I also locally passed sandboxing crate tests, protocol crate tests, format check, and whitespace diff check.

The branch remains one commit, now 87f8ccb2bc22abb0e2c403da30866ae014941ae7.

@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch from 042c0ba to 18885a0 Compare April 20, 2026 21:10
@evawong-oai evawong-oai changed the title Reserve missing project metadata in sandbox policy Reserve missing preserved paths in sandbox policy Apr 20, 2026
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 3 times, most recently from 2daf148 to bd1520f Compare April 20, 2026 21:34
@evawong-oai evawong-oai changed the title Reserve missing preserved paths in sandbox policy Reserve missing preserved paths in Linux sandbox policy Apr 20, 2026
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 6 times, most recently from fb6fcdf to e76236e Compare April 20, 2026 23:40
@evawong-oai evawong-oai changed the title Reserve missing preserved paths in Linux sandbox policy Reserve missing preserved paths in Linux/Mac sandbox policy Apr 21, 2026
@evawong-oai evawong-oai changed the title Reserve missing preserved paths in Linux/Mac sandbox policy Reserve missing preserved paths in Linux sandbox policy Apr 21, 2026
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 2 times, most recently from 10fa02e to fa603bb Compare April 24, 2026 15:53
Comment thread codex-rs/cli/src/debug_sandbox.rs Outdated
handle_exit_status(status);
}

fn preserved_path_write_forbidden_reason(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something feels off: this the logic that powers codex sandbox. This file should be small, as it should be calling into existing library code. This seems off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I moved this out of the CLI sandbox entry point in the latest commit.

The CLI sandbox path now calls the shared preserved path write guard from the shell command crate, and the agent shell path calls the same helper. The debug sandbox file is back to being an entry point instead of owning the policy logic.

Comment thread codex-rs/cli/src/debug_sandbox.rs Outdated
Comment on lines +338 to +339
"git" => git_init_preserved_path_write(command, cwd, file_system_sandbox_policy),
"touch" | "mkdir" | "rm" | "rmdir" | "ln" | "mv" | "cp" | "install" => command
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we special-casing things?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for resolving parent/child .git folders security concern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have since then moved this list into a policy configuration rather than having policy defined in code for separation of concerns.

Evyatar108 pushed a commit to Evyatar108/codex-patched that referenced this pull request Apr 24, 2026
…factors

F-1: SANDBOX PATCH send_add_credits_nudge_email_inner -> early auth-required
error. Upstream openai#18446 added a new /backend-api/add_credits_nudge_email call;
copilot-api does not use it. backend_credit_type kept #[allow(dead_code)].

F-3: Reconcile type-level drift against new codex-model-provider crate:

- codex-api/src/auth.rs: add on_unauthorized(&self) default no-op method on
  AuthProvider trait so Copilot retry path in core/src/client.rs can trigger
  invalidation through dyn AuthProvider.
- codex-api/src/api_bridge.rs: switch CoreAuthProvider to impl
  crate::auth::AuthProvider (the upstream trait), and move on_unauthorized
  into the trait impl.
- codex-api/src/lib.rs: pub use CoreAuthProvider + AuthProvider as
  ApiAuthProvider (alias for pre-refactor call sites).
- core/src/client.rs:
  - ModelClientState.provider: ModelProviderInfo -> SharedModelProvider so
    .auth(), .api_provider(), .api_auth(), .auth_manager() trait methods
    resolve through create_model_provider(...).
  - ModelClient::new stores the wrapped provider (fixes stray 'provider,'
    shorthand).
  - is_copilot() uses .info().is_copilot().
  - current_client_setup: Copilot branch re-inlined — build CopilotHeaderSource
    from the session-scoped OnceLock<Arc<CopilotAuth>> and wrap in
    CoreAuthProvider.with_copilot. Non-Copilot branch unchanged.
- core/src/copilot_transport.rs: build_copilot_client now takes
  SharedAuthProvider and returns ResponsesClient<T> (upstream collapsed the
  auth generic on ResponsesClient).
- core/src/compact_remote.rs: turn_context.provider now SharedModelProvider;
  use .info().is_copilot().
- cli/src/responses_cmd.rs: wrap CoreAuthProvider in Arc<dyn AuthProvider> via
  SharedAuthProvider alias to match the new build_copilot_client signature.
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch 11 times, most recently from 3145058 to 3ae2e24 Compare April 27, 2026 15:46
@evawong-oai evawong-oai force-pushed the codex/bugb15632_missing_protected_paths_v2 branch from 3ae2e24 to 0293af6 Compare April 27, 2026 18:12
@evawong-oai evawong-oai changed the title Reserve missing preserved paths in Linux sandbox policy Enforce preserved path names in Linux sandbox Apr 27, 2026
@evawong-oai evawong-oai changed the base branch from main to codex/bugb15632-runtime-permissions April 27, 2026 18:13
@viyatb-oai viyatb-oai merged commit 05f4eb2 into codex/bugb15632-runtime-permissions Apr 27, 2026
8 of 25 checks passed
@viyatb-oai viyatb-oai deleted the codex/bugb15632_missing_protected_paths_v2 branch April 27, 2026 18:14
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 27, 2026
@evawong-oai
Copy link
Copy Markdown
Contributor Author

Split this into a manual stack so each review surface is smaller.

  1. [sandbox] Enforce protected workspace metadata paths #19846 policy primitive
  2. Enforce workspace metadata protections in Seatbelt #19847 macOS Seatbelt enforcement
  3. Add workspace metadata shell preflight #19848 shell preflight
  4. Propagate runtime permission profiles #19849 runtime permission propagation
  5. Reserve missing preserved paths in Linux sandbox policy #18446 Linux bubblewrap enforcement

PR 18446 is now the Linux child at the top of the stack. The final tree is unchanged from the rebased full branch, but the review surfaces are separated by policy, macOS, preflight, runtime propagation, and Linux.

@evawong-oai evawong-oai restored the codex/bugb15632_missing_protected_paths_v2 branch April 27, 2026 18:17
@evawong-oai
Copy link
Copy Markdown
Contributor Author

Follow up: the old auto merge setting carried over when I changed this PR base, so GitHub marked this old PR merged into the temporary parent branch.

I restored the parent branch to its scoped runtime permission commit and opened #19852 as the current Linux child for review. Use #19852 for the active top of the stack.

@evawong-oai evawong-oai changed the title Enforce preserved path names in Linux sandbox Superseded by split Linux PR Apr 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants