core-agent-ide/codex-rs/linux-sandbox
viyatb-oai b0cbc25a48
fix(protocol): preserve legacy workspace-write semantics (#13957)
## Summary
This is a fast follow to the initial `[permissions]` structure.

- keep the new split-policy carveout behavior for narrower non-write
entries under broader writable roots
- preserve legacy `WorkspaceWrite` semantics by using a cwd-aware bridge
that drops only redundant nested readable roots when projecting from
`SandboxPolicy`
- route the legacy macOS seatbelt adapter through that same legacy
bridge so redundant nested readable roots do not become read-only
carveouts on macOS
- derive the legacy bridge for `command_exec` using the sandbox root cwd
rather than the request cwd so policy derivation matches later sandbox
enforcement
- add regression coverage for the legacy macOS nested-readable-root case

## Examples
### Legacy `workspace-write` on macOS
A legacy `workspace-write` policy can redundantly list a nested readable
root under an already-writable workspace root.

For example, legacy config can effectively mean:
- workspace root (`.` / `cwd`) is writable
- `docs/` is also listed in `readable_roots`

The new shared split-policy helper intentionally treats a narrower
non-write entry under a broader writable root as a carveout for real
`[permissions]` configs. Without this fast follow, the unchanged macOS
seatbelt legacy adapter could project that legacy shape into a
`FileSystemSandboxPolicy` that treated `docs/` like a read-only carveout
under the writable workspace root. In practice, legacy callers on macOS
could unexpectedly lose write access inside `docs/`, even though that
path was writable before the `[permissions]` migration work.

This change fixes that by routing the legacy seatbelt path through the
cwd-aware legacy bridge, so:
- legacy `workspace-write` keeps `docs/` writable when `docs/` was only
a redundant readable root
- explicit `[permissions]` entries like `'.' = 'write'` and `'docs' =
'read'` still make `docs/` read-only, which is the new intended
split-policy behavior

### Legacy `command_exec` with a subdirectory cwd
`command_exec` can run a command from a request cwd that is narrower
than the sandbox root cwd.

For example:
- sandbox root cwd is `/repo`
- request cwd is `/repo/subdir`
- legacy policy is still `workspace-write` rooted at `/repo`

Before this fast follow, `command_exec` derived the legacy bridge using
the request cwd, but the sandbox was later built using the sandbox root
cwd. That mismatch could miss redundant legacy readable roots during
projection and accidentally reintroduce read-only carveouts for paths
that should still be writable under the legacy model.

This change fixes that by deriving the legacy bridge with the same
sandbox root cwd that sandbox enforcement later uses.

## Verification
- `just fmt`
- `cargo test -p codex-core
seatbelt_legacy_workspace_write_nested_readable_root_stays_writable`
- `cargo test -p codex-core test_sandbox_config_parsing`
- `cargo clippy -p codex-core -p codex-app-server --all-targets -- -D
warnings`
- `cargo clean`
2026-03-09 18:43:27 -07:00
..
src fix(protocol): preserve legacy workspace-write semantics (#13957) 2026-03-09 18:43:27 -07:00
tests linux-sandbox: honor split filesystem policies in bwrap (#13453) 2026-03-07 23:46:52 -08:00
BUILD.bazel build(linux-sandbox): always compile vendored bubblewrap on Linux; remove CODEX_BWRAP_ENABLE_FFI (#11498) 2026-02-11 21:30:41 -08:00
build.rs build(linux-sandbox): always compile vendored bubblewrap on Linux; remove CODEX_BWRAP_ENABLE_FFI (#11498) 2026-02-11 21:30:41 -08:00
Cargo.toml feat(linux-sandbox): implement proxy-only egress via TCP-UDS-TCP bridge (#11293) 2026-02-21 18:16:34 +00:00
config.h build(linux-sandbox): always compile vendored bubblewrap on Linux; remove CODEX_BWRAP_ENABLE_FFI (#11498) 2026-02-11 21:30:41 -08:00
README.md fix(linux-sandbox): always unshare bwrap userns (#13624) 2026-03-05 21:57:40 +00:00

codex-linux-sandbox

This crate is responsible for producing:

  • a codex-linux-sandbox standalone executable for Linux that is bundled with the Node.js version of the Codex CLI
  • a lib crate that exposes the business logic of the executable as run_main() so that
    • the codex-exec CLI can check if its arg0 is codex-linux-sandbox and, if so, execute as if it were codex-linux-sandbox
    • this should also be true of the codex multitool CLI

On Linux, the bubblewrap pipeline uses the vendored bubblewrap path compiled into this binary.

Current Behavior

  • Legacy Landlock + mount protections remain available as the legacy pipeline.
  • The bubblewrap pipeline is standardized on the vendored path.
  • During rollout, the bubblewrap pipeline is gated by the temporary feature flag use_linux_sandbox_bwrap (CLI -c alias for features.use_linux_sandbox_bwrap; legacy remains default when off).
  • When enabled, the bubblewrap pipeline applies PR_SET_NO_NEW_PRIVS and a seccomp network filter in-process.
  • When enabled, the filesystem is read-only by default via --ro-bind / /.
  • When enabled, writable roots are layered with --bind <root> <root>.
  • When enabled, protected subpaths under writable roots (for example .git, resolved gitdir:, and .codex) are re-applied as read-only via --ro-bind.
  • When enabled, symlink-in-path and non-existent protected paths inside writable roots are blocked by mounting /dev/null on the symlink or first missing component.
  • When enabled, the helper explicitly isolates the user namespace via --unshare-user and the PID namespace via --unshare-pid.
  • When enabled and network is restricted without proxy routing, the helper also isolates the network namespace via --unshare-net.
  • In managed proxy mode, the helper uses --unshare-net plus an internal TCP->UDS->TCP routing bridge so tool traffic reaches only configured proxy endpoints.
  • In managed proxy mode, after the bridge is live, seccomp blocks new AF_UNIX/socketpair creation for the user command.
  • When enabled, it mounts a fresh /proc via --proc /proc by default, but you can skip this in restrictive container environments with --no-proc.

Notes

  • The CLI surface still uses legacy names like codex debug landlock.