Cross-Platform Sandbox
codex isolates command execution into platform-native sandboxes: macOS uses sandbox-exec (seatbelt), Linux uses bubblewrap + seccomp, Windows uses restricted tokens. The sandboxing crate provides the unified abstraction; linux-sandbox/, bwrap/, windows-sandbox-rs/, process-hardening/ are the per-platform implementations. core computes the SandboxType before each exec, then hands it to SandboxManager::transform to build the actual argv to spawn.
Responsibilities
- Decide whether to enable the sandbox and which backend by
PermissionProfile:codex-rs/sandboxing/src/manager.rs:273-319 - On macOS, build the
sandbox-execseatbelt policy and argv:codex-rs/sandboxing/src/seatbelt.rs:21-30 - On Linux, go through the
codex-linux-sandboxhelper: first no_new_privs + seccomp, then exec bubblewrap:codex-rs/linux-sandbox/src/lib.rs:1-30 - On Windows, isolate filesystem and network via restricted token / elevated backend:
codex-rs/windows-sandbox-rs/src/lib.rs:13-50 - Process-level hardening before main (disable core dumps, refuse ptrace, clear
LD_PRELOAD/DYLD_*):codex-rs/process-hardening/src/lib.rs:12-25
Design motivation
Why not just trust execpolicy's gating? Because the model can construct arbitrary shells and rule engines always have edge cases. Forcing every command into an OS-native sandbox is the "even if the model bypasses everything, the kernel layer still stops it" last line of defense. SandboxablePreference::Auto lets strategic profiles like workspace-write / read-only enable the sandbox, while the full-access profile degrades directly to SandboxType::None, avoiding needless process wrapping.
Each platform has a different backend because there is no unified cross-platform sandbox primitive: macOS's seatbelt is declarative .sbpl, Linux's bubblewrap is user namespace + bind mount, Windows's restricted token is token + ACL. The SandboxType enum unifies these three backends with None under the same label, so core / exec-server only see the label and don't care about platform details.
Key files
codex-rs/sandboxing/src/lib.rs:1-46— module entry, deciding which backend to compile bytarget_os.codex-rs/sandboxing/src/manager.rs:35-75—SandboxTypeenum andget_platform_sandbox.codex-rs/sandboxing/src/manager.rs:273-329—SandboxManagerandselect_initial/should_sandbox.codex-rs/sandboxing/src/manager.rs:321-413—transformproduces a wrapped argv from the original argv by sandbox type.codex-rs/sandboxing/src/seatbelt.rs:21-30— macOSsandbox-execpath is hardcoded to/usr/bin/sandbox-exec, preventing PATH injection.codex-rs/linux-sandbox/src/launcher.rs:36-67— Linux bubblewrap launcher: prefer system bwrap, fall back to bundled.codex-rs/process-hardening/src/lib.rs:43-61— Linux process hardening:prctl(PR_SET_DUMPABLE, 0)+ clearingLD_*.codex-rs/core/src/exec.rs:117-130— the core-side entry that callsselect_initial.
SandboxType unifies the three platform backends with "no sandbox" into one label; core only sees the label.
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
}In select_initial, first decide whether to sandbox via should_sandbox, then ask the platform what backend it can provide:
pub fn select_initial(
&self,
file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
has_managed_network_requirements: bool,
) -> SandboxType {
if self.should_sandbox(
file_system_policy,
network_policy,
pref,
has_managed_network_requirements,
) {
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
.unwrap_or(SandboxType::None)
} else {
SandboxType::None
}
}The Linux launcher picks between system bwrap and bundled bwrap, and panics if neither is found — when bubblewrap is unavailable the whole Linux sandbox chain is dead.
pub(crate) fn exec_bwrap(argv: Vec<String>, preserved_files: Vec<File>) -> ! {
match preferred_bwrap_launcher() {
BubblewrapLauncher::System(launcher) => {
exec_system_bwrap(&launcher.program, argv, preserved_files)
}
BubblewrapLauncher::Bundled(launcher) => launcher.exec(argv, preserved_files),
BubblewrapLauncher::Unavailable => {
panic!(
"bubblewrap is unavailable: no system bwrap was found on PATH and no bundled \
codex-resources/bwrap binary was found next to the Codex executable"
)
}
}
}Data flow
Boundaries and failures
SandboxablePreference::Forbidforces the sandbox off,Requireforces it on; onlyAutoconsults the policy:codex-rs/sandboxing/src/manager.rs:303-319- On macOS the
sandbox-execpath is hardcoded to/usr/bin/sandbox-exec, sidestepping PATH attacks:codex-rs/sandboxing/src/seatbelt.rs:26-30 - On Linux, missing bubblewrap panics rather than silently degrading — to avoid an "thought it had a sandbox but doesn't" insecure state:
codex-rs/linux-sandbox/src/launcher.rs:42-48 - The MITM CA trust bundle path is explicitly added to readable roots, otherwise sandboxed processes cannot verify TLS:
codex-rs/sandboxing/src/manager.rs:76-95 - In
core::exec,is_likely_sandbox_deniedinspects stderr after exec completes to judge whether the sandbox blocked it, giving the model a readable error:codex-rs/core/src/exec.rs:768-810
Summary
SandboxManager is codex's unified cross-platform sandbox entry: it translates PermissionProfile into SandboxType, then into a wrapped argv. Platform backends isolate separately but all derive from the same FileSystemSandboxPolicy / NetworkSandboxPolicy. To trace "why was this command rejected", see command classification execpolicy and command execution exec-server.