## Problem Ubuntu/AppArmor hosts started failing in the default Linux sandbox path after the switch to vendored/default bubblewrap in `0.115.0`. The clearest report is in [#14919](https://github.com/openai/codex/issues/14919), especially [this investigation comment](https://github.com/openai/codex/issues/14919#issuecomment-4076504751): on affected Ubuntu systems, `/usr/bin/bwrap` works, but a copied or vendored `bwrap` binary fails with errors like `bwrap: setting up uid map: Permission denied` or `bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted`. The root cause is Ubuntu's `/etc/apparmor.d/bwrap-userns-restrict` profile, which grants `userns` access specifically to `/usr/bin/bwrap`. Once Codex started using a vendored/internal bubblewrap path, that path was no longer covered by the distro AppArmor exception, so sandbox namespace setup could fail even when user namespaces were otherwise enabled and `uidmap` was installed. ## What this PR changes - prefer system `/usr/bin/bwrap` whenever it is available - keep vendored bubblewrap as the fallback when `/usr/bin/bwrap` is missing - when `/usr/bin/bwrap` is missing, surface a Codex startup warning through the app-server/TUI warning path instead of printing directly from the sandbox helper with `eprintln!` - use the same launcher decision for both the main sandbox execution path and the `/proc` preflight path - document the updated Linux bubblewrap behavior in the Linux sandbox and core READMEs ## Why this fix This still fixes the Ubuntu/AppArmor regression from [#14919](https://github.com/openai/codex/issues/14919), but it keeps the runtime rule simple and platform-agnostic: if the standard system bubblewrap is installed, use it; otherwise fall back to the vendored helper. The warning now follows that same simple rule. If Codex cannot find `/usr/bin/bwrap`, it tells the user that it is falling back to the vendored helper, and it does so through the existing startup warning plumbing that reaches the TUI and app-server instead of low-level sandbox stderr. ## Testing - `cargo test -p codex-linux-sandbox` - `cargo test -p codex-app-server --lib` - `cargo test -p codex-tui-app-server tests::embedded_app_server_start_failure_is_returned` - `cargo clippy -p codex-linux-sandbox --all-targets` - `cargo clippy -p codex-app-server --all-targets` - `cargo clippy -p codex-tui-app-server --all-targets`
62 lines
3.7 KiB
Markdown
62 lines
3.7 KiB
Markdown
# 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 prefers the system `/usr/bin/bwrap` whenever
|
|
it is available. If `/usr/bin/bwrap` is missing, the helper still falls back to
|
|
the vendored bubblewrap path compiled into this binary.
|
|
Codex also surfaces a startup warning when `/usr/bin/bwrap` is missing so users
|
|
know it is falling back to the vendored helper.
|
|
|
|
**Current Behavior**
|
|
- Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported.
|
|
- Bubblewrap is the default filesystem sandbox pipeline.
|
|
- If `/usr/bin/bwrap` is present, the helper uses it.
|
|
- If `/usr/bin/bwrap` is missing, the helper falls back to the vendored
|
|
bubblewrap path.
|
|
- If `/usr/bin/bwrap` is missing, Codex also surfaces a startup warning instead
|
|
of printing directly from the sandbox helper.
|
|
- Legacy Landlock + mount protections remain available as an explicit legacy
|
|
fallback path.
|
|
- Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`)
|
|
to force the legacy Landlock fallback.
|
|
- The legacy Landlock fallback is used only when the split filesystem policy is
|
|
sandbox-equivalent to the legacy model after `cwd` resolution.
|
|
- Split-only filesystem policies that do not round-trip through the legacy
|
|
`SandboxPolicy` model stay on bubblewrap so nested read-only or denied
|
|
carveouts are preserved.
|
|
- When the default bubblewrap pipeline is active, the helper applies `PR_SET_NO_NEW_PRIVS` and a
|
|
seccomp network filter in-process.
|
|
- When the default bubblewrap pipeline is active, the filesystem is read-only by default via `--ro-bind / /`.
|
|
- When the default bubblewrap pipeline is active, writable roots are layered with `--bind <root> <root>`.
|
|
- When the default bubblewrap pipeline is active, protected subpaths under writable roots (for
|
|
example `.git`,
|
|
resolved `gitdir:`, and `.codex`) are re-applied as read-only via `--ro-bind`.
|
|
- When the default bubblewrap pipeline is active, overlapping split-policy
|
|
entries are applied in path-specificity order so narrower writable children
|
|
can reopen broader read-only or denied parents while narrower denied subpaths
|
|
still win. For example, `/repo = write`, `/repo/a = none`, `/repo/a/b = write`
|
|
keeps `/repo` writable, denies `/repo/a`, and reopens `/repo/a/b` as
|
|
writable again.
|
|
- When the default bubblewrap pipeline is active, 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 the default bubblewrap pipeline is active, the helper explicitly isolates the user namespace via
|
|
`--unshare-user` and the PID namespace via `--unshare-pid`.
|
|
- When the default bubblewrap pipeline is active 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 the default bubblewrap pipeline is active, 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`.
|