## Why Native TCP clients need to reach managed network destinations without implementing proxy support themselves. ## What changed - Add `--transparent-tcp` to the Linux sandbox command, using a bundled TCP stack, private TUN interface, and DNS stub to route native IPv4 and IPv6 connections through the attributed managed SOCKS proxy. - Preserve original hostnames for proxy policy checks and cancel pending approval connections when their native sockets close. - Expose `--linux-sandbox-transparent-tcp` and `--tcp-upstream-proxy` on `codex exec-server` for transparent routing and a mandatory HTTP CONNECT next hop without direct fallback. - Require private network and PID namespaces, private procfs, system `bwrap`, and access to `/dev/net/tun`; reject incompatible launcher options and untrusted proxy ports. ## Testing Add integration coverage for native hostname and IPv6 connections, SOCKS attribution, pending connection cancellation, policy denials, and filesystem and host-loopback isolation. Extend launcher and executor tests for option validation and managed-policy propagation. GitOrigin-RevId: 96bd7726e5369efceb65320face56aa85ae3deb8
codex-linux-sandbox
This crate is responsible for producing:
- a
codex-linux-sandboxstandalone 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-execCLI can check if its arg0 iscodex-linux-sandboxand, if so, execute as if it werecodex-linux-sandbox - this should also be true of the
codexmultitool CLI
- the
The helper uses bubblewrap for filesystem and namespace isolation, and seccomp
for syscall filtering. src/seccomp.rs installs PR_SET_NO_NEW_PRIVS and the
seccomp filters; it also retains the legacy Landlock filesystem implementation.
src/linux_run_main.rs coordinates these stages through LinuxSandboxCommand.
End-to-end coverage lives in tests/suite/sandbox.rs and the other suite modules.
Transparent TCP
codex sandbox --transparent-tcp --permission-profile db -- psql -h pg.example
The Rust sandbox helper includes the TCP stack. Codex starts it automatically.
The Linux host needs distro bwrap on PATH, user and network namespaces, IPv6,
and read/write access to /dev/net/tun.
Use full managed SOCKS5 mode for native database ports and an isolated PID namespace.
For executor-wide capture and HTTP CONNECT routing, see the exec-server setup.
The private DNS stub maps up to 8,192 names to stable addresses in 198.18.0.0/15
and fdc0:de00::/96. The relay sends the original name and port to the attributed proxy;
literal addresses, including entries from a hosts file, are checked as IPs. Unknown
synthetic addresses are rejected. Loopback stays local. Each bridge permits 128 concurrent
connections. When the TCP stack is full, clients retry their opening SYN. The relay identifies each
native socket by both endpoints and its inode in the private namespace TCP tables. Closing that
socket cancels its pending approval connection, including when other clients use the same
destination. A client that shuts down only its write side can still receive a reply. General UDP,
QUIC, ICMP, legacy Landlock, host PID inheritance, and procfs fallback are outside this mode.
With transparent TCP off, Codex prefers the first bwrap found on PATH
outside the current working directory whenever it is available. If bwrap is
present but too old to support
--argv0, the helper keeps using system bubblewrap and switches to a
no---argv0 compatibility path for the inner re-exec. If bwrap is missing,
the helper falls back to the bundled codex-resources/bwrap binary shipped
with Codex.
Codex also surfaces a startup warning when bwrap is missing so users know it
is falling back to the bundled helper. Codex surfaces the same startup warning
path when bubblewrap cannot create user namespaces. WSL2 follows the normal
Linux bubblewrap path. WSL1 is not supported for bubblewrap sandboxing because
it cannot create the required user namespaces, so Codex rejects sandboxed shell
commands that would enter the bubblewrap path.
Current Behavior
-
Legacy
sandbox_modeconfigs remain supported and are converted toPermissionProfilebefore sandbox execution. -
Bubblewrap is the default filesystem sandbox.
-
If
bwrapis present onPATHoutside the current working directory, the helper uses it. -
If
bwrapis present but too old to support--argv0, the helper uses a no---argv0compatibility path for the inner re-exec. -
If
bwrapis missing, the helper falls back to the bundledcodex-resources/bwrappath. -
If
bwrapis missing, Codex also surfaces a startup warning instead of printing directly from the sandbox helper. -
If bubblewrap cannot create user namespaces, Codex surfaces a startup warning instead of waiting for a runtime sandbox failure.
-
WSL2 uses the normal Linux bubblewrap path.
-
WSL1 is not supported for bubblewrap sandboxing; Codex rejects sandboxed shell commands that would require the bubblewrap path before invoking
bwrap. -
Filesystem-restricted execution requires bubblewrap. The legacy Landlock option is rejected for these policies because it cannot isolate app-server Unix sockets. Disable
features.use_legacy_landlockwhen upgrading. -
Bubblewrap preserves nested read-only and denied filesystem rules from
PermissionProfile. -
When bubblewrap is active, the helper applies
PR_SET_NO_NEW_PRIVSand a seccomp filter in-process after establishing the filesystem view. Restricted network policies block network syscalls; filesystem-restricted policies with otherwise unrestricted networking still blockAF_VSOCKsockets. All filter modes blockio_uringto prevent bypassing socket-family restrictions. -
When bubblewrap is active, the filesystem is read-only by default via
--ro-bind / /. -
When bubblewrap is active, writable roots are layered with
--bind <root> <root>. -
When bubblewrap is active, protected subpaths under writable roots (for example
.git, resolvedgitdir:, and.codex) are re-applied as read-only via--ro-bind. -
When bubblewrap 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 = writekeeps/repowritable, denies/repo/a, and reopens/repo/a/bas writable again. -
When bubblewrap is active, unreadable glob entries are expanded before launching the sandbox and matching files are masked in bubblewrap:
Prefer: rg --files --hidden --no-ignore --glob <pattern> -- <search-root> Fallback: internal globset walker when rg is not installed Failure: any other rg failure aborts sandbox constructionUsers can cap the scan depth per permissions profile:
[permissions.workspace.filesystem] glob_scan_max_depth = 2 [permissions.workspace.filesystem.":workspace_roots"] "**/*.env" = "none" -
When bubblewrap is active, symlink-in-path and non-existent protected paths inside writable roots are blocked by mounting
/dev/nullon the symlink or first missing component. -
When bubblewrap is active, the helper explicitly isolates the user namespace via
--unshare-user. By default it also creates a PID namespace via--unshare-pid. -
When bubblewrap 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-netplus 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 allows IP sockets inside the isolated network namespace and blocks new standalone
AF_UNIXsockets unlessdangerously_allow_all_unix_socketsis granted. Unix socket pairs remain allowed for communication between related processes. -
When bubblewrap is active, it mounts a fresh
/procvia--proc /procby default. Transparent TCP requires this mount and aborts the launch if it fails. In other modes, if that mount is denied, it retains the inherited/procand still creates a PID namespace, preserving the existing fallback.--no-procalso retains the inherited/procwithout disabling PID isolation. In these cases, process IDs inside the sandbox can differ from those exposed by/proc. Default invocations send no new helper flags and remain compatible with older helpers. -
Trusted provisioning of a dedicated environment can start
codex exec-server --linux-sandbox-pid-namespace=inherit. This startup-only setting applies to both process and filesystem helpers; repository config and command environment variables cannot enable it. The helper receives the new--inherit-pid-namespaceoption, which requires an updated helper; deploy the helper and startup flag together. Inheritance reuses the caller's PID namespace and/proctogether, omitting--unshare-pidand--as-pid-1. This preserves process lookups but allows sandboxed commands to signal other same-UID processes, including the executor. With:minimal, the existing/procis bound read-only, preserving its container masks; explicit filesystem denials are applied afterward. Filesystem, user, IPC, network, seccomp, and existing container/procmasks remain in force. The defaultisolatemode retains PID isolation.
Notes
- The CLI surface is
codex sandbox; the host OS selects the sandbox backend.