Chapter 13: Sandboxes, Network Policy, and Platform Boundaries
Reading Contract: Use this chapter to follow an approved tool action into containment. Track three owners separately: the permission profile that describes allowed effects, the sandbox manager that turns that profile into an execution request, and the platform backend that enforces what its operating system can actually express. After reading, you should be able to explain why approval is not a sandbox bypass, why “network allowed” is not the same as “no network boundary”, and why macOS, Linux, and Windows are not interchangeable sandbox dialects.

Source boundary: this chapter explains the public OpenAI Codex snapshot pinned at commit 569ff6a1. File paths, function names, enum cases, and short code excerpts are verified source when linked to that commit. Terms such as “policy compiler”, “platform dialect”, and “containment ledger” are surrounding contract inference from visible source, not claims about private OpenAI service internals. Cross-platform comparisons are deliberately framed as differences in enforcement shape; this chapter does not claim that macOS Seatbelt, Linux bwrap/seccomp, and Windows identities/ACL/WFP provide identical guarantees.
Chapter 12 stopped at the last approval boundary. A tool call had been routed, hooks could observe or block it, permission-request hooks could answer approval, Guardian or the user could approve, and a sandbox denial could trigger a new retry decision. That still leaves the part of the runtime that actually touches the host. Approval answers “may Codex try this side effect?” Sandboxing answers “what can the approved attempt still reach?”
The source keeps those questions separate. A ReviewDecision::Approved does not make the process omnipotent. A permission profile does not itself execute a command. A platform sandbox is not selected by reading a shell string and guessing what it might do. The runtime passes an execution attempt through a chain:
- Resolve a permission profile.
- Merge any additional permissions granted for the command.
- Split the effective profile into filesystem and network policies.
- Select an initial sandbox type from tool preference, policy shape, managed-network requirements, and platform support.
- Transform the command or execution request for the selected backend.
- Let the executor run the transformed request and report evidence.
That chain is the chapter’s main thesis: Codex containment is a policy transform pipeline, not a single sandbox toggle.
1. Approval Ends Before Containment Begins
The easiest mistake is to treat approval as the thing that “turns off” the sandbox. That is not the normal path. Approval lets the tool attempt proceed. The subsequent SandboxAttempt still carries the manager, permission profile, selected sandbox type, managed network flag, sandbox cwd, Linux helper path, and Windows sandbox settings into the transform call.
The bridge is visible in SandboxAttempt::env_for:
self.manager
.transform(SandboxTransformRequest {
command,
permissions: self.permissions,
sandbox: self.sandbox,
enforce_managed_network: self.enforce_managed_network,
network,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.map(PathBuf::as_path),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
The excerpt is trimmed to the handoff. The important fact is not the exact struct syntax; it is the owner boundary. The tool attempt does not carry only argv. It carries the policy context that must be lowered into an execution request.

This gives the retry path a clean meaning. If the first sandboxed attempt is denied by the platform, the orchestrator can ask a new approval question for a no-sandbox retry. That retry is a new risk decision, not a secret escape hatch hidden inside approval. Readers who collapse these two stages will misread almost every later security branch.
2. Permission Profiles Are the Unit Codex Compiles
The old mental model is “sandbox mode”. The source in this snapshot is closer to “permission profile”. A profile can come from built-ins, custom configuration, or command-scoped additions. It is then compiled into two runtime policies: FileSystemSandboxPolicy and NetworkSandboxPolicy.
The built-in profile resolver in core/src/config/permissions.rs maps profile names to read_only, workspace_write, or Disabled. The custom profile compiler walks filesystem entries, warns for unsupported glob shapes on non-macOS platforms, validates glob scan depth, and computes the network policy (compile_permission_profile). The network side is intentionally small at the runtime-policy level:
pub enum NetworkSandboxPolicy {
Restricted,
Enabled,
}
Small does not mean trivial. Restricted can later mean “deny network”, “allow only proxy sockets”, “unshare a namespace”, or “prepare Windows firewall/WFP state”, depending on platform and managed-network settings. Enabled can still pass through proxy environment injection when a managed proxy is configured. The enum is a policy signal, not a complete packet-filter description.
2.1 Additional Permissions Stay Scoped
Command-scoped additional permissions are merged before enforcement. In policy_transforms.rs, additional filesystem paths are normalized, duplicate entries are removed, and glob write/read additions are rejected because only deny-read glob entries are supported in that additional-permission path. The effective profile is then recomputed:
let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
let effective_file_system_policy =
effective_file_system_sandbox_policy(&file_system_policy, additional_permissions);
let effective_network_policy =
effective_network_sandbox_policy(network_policy, additional_permissions);
PermissionProfile::from_runtime_permissions_with_enforcement(
permission_profile.enforcement(),
&effective_file_system_policy,
effective_network_policy,
)
That source shape protects a useful invariant: granting a tool one extra writable root or one network allowance does not rewrite the session’s base policy in place. The transform receives an effective profile for this command path, while the surrounding runtime can still reason about where the grant came from.
2.2 Profiles Split Policy Before Platform Code Runs
The split into filesystem and network policy happens before macOS, Linux, or Windows code gets to express it. That order is what keeps the article from saying “Linux is the sandbox” or “Seatbelt is the policy”. Platform helpers are enforcement dialects. The permission profile is the source-level contract the runtime is trying to lower.
| Profile pressure | Source mechanism | Invariant protected |
|---|---|---|
| Default execution needs a sane containment baseline | Built-in profile name and resolver in permissions.rs | A session starts from a named profile, not ad hoc command inspection. |
| Custom filesystem shape may include unsupported globs | Compiler warnings in compile_permission_profile | Platform differences are surfaced instead of silently widened. |
| One command may receive a narrower grant | Additional-permission normalization and merge in policy_transforms.rs | A local grant does not become global session authority. |
| Managed network may require platform forcing even when filesystem is broad | should_require_platform_sandbox | Network requirements can force sandbox selection independently of file writes. |
3. The Sandbox Manager Chooses a Dialect
The selected sandbox type is explicit in sandboxing/src/manager.rs:
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
}
The enum names are worth reading carefully. LinuxSeccomp is the manager’s sandbox type name, but the actual Linux path can include bwrap filesystem layout, namespace work, and then seccomp. WindowsRestrictedToken names a Windows sandbox branch, but the elevated Windows backend also prepares identities, ACLs, firewall rules, and WFP state. The enum is a runtime routing value, not a full security design document.
Platform selection itself is deliberately narrow:
pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType> {
if cfg!(target_os = "macos") {
Some(SandboxType::MacosSeatbelt)
} else if cfg!(target_os = "linux") {
Some(SandboxType::LinuxSeccomp)
} else if cfg!(target_os = "windows") {
if windows_sandbox_enabled {
Some(SandboxType::WindowsRestrictedToken)
} else {
None
}
} else {
None
}
}
Then SandboxManager::select_initial applies the tool’s preference:
| Preference | Runtime meaning |
|---|---|
Forbid | Select SandboxType::None. The tool path has opted out of platform sandboxing. |
Require | Ask for the platform sandbox when the current OS exposes one. |
Auto | Require a platform sandbox only when filesystem, network, or managed-network requirements need it. |
The transform step then takes the selected type and builds a platform-shaped SandboxExecRequest (manager.rs). Three details matter:
effective_permission_profileis computed inside the transform after taking command-scoped additions.- macOS and Linux rewrite
argvinto a wrapper/helper command. - Windows leaves the command vector unchanged at this transform layer and carries sandbox metadata to the Windows execution backend.
That is why “the command string looked harmless” is not a serious boundary. The runtime is not trying to infer authority from shell syntax after the fact. It compiles policy, chooses a dialect, and asks that dialect to express the boundary.
4. Platform Backends Are Not Equivalent

4.1 macOS Generates a Seatbelt Profile
On macOS, the transform constructs arguments for a fixed Seatbelt executable. MACOS_PATH_TO_SEATBELT_EXECUTABLE is /usr/bin/sandbox-exec, not a shell-resolved convenience binary. create_seatbelt_command_args builds read policy, write policy, deny-read glob policy, network policy, and directory parameters into one profile.
The macOS network generator shows the careful part. In dynamic_network_policy_for_network, proxy ports, proxy configuration, managed-network enforcement, and Unix-domain socket needs can force the restricted-network branch. In this pinned snapshot, proxy configuration and enforce_managed_network are already part of that branch condition, so they stay on the restricted path. The branch only adds outbound loopback allowances for explicit proxy ports, plus selected local-binding or Unix-socket allowances; without inferred loopback proxy ports, the generated profile remains restricted instead of silently opening broad outbound traffic.
The macOS boundary is therefore not merely “run sandbox-exec”. It is “generate a policy that represents the profile, including filesystem roots, protected metadata, denied read patterns, sockets, and network/proxy behavior, then execute through the fixed platform runner.”
4.2 Linux Builds the Filesystem View Before Seccomp
On Linux, the manager routes through the Codex Linux sandbox helper. The helper comments in linux_run_main.rs state the execution order:
/// 1. When needed, wrap the command with bubblewrap to construct the
/// filesystem view.
/// 2. Apply in-process restrictions (no_new_privs + seccomp).
/// 3. `execvp` into the final command.
That order explains the “Linux bwrap plus seccomp” phrasing. bwrap constructs the view and namespace conditions. The inner stage applies seccomp/no-new-privileges before the final execvp. The code path also has a legacy Landlock branch for compatible legacy policies, but the current bwrap path is not allowed to pretend success if the required platform behavior is unavailable. The manager checks for WSL1 and bubblewrap support before building the Linux command (manager.rs).
Network is folded into bwrap mode. bwrap_network_mode chooses ProxyOnly when managed proxy routing is active, FullAccess when the network policy is enabled, and Isolated otherwise. Managed proxy routing then prepares host proxy sockets and rewrites proxy environment inside the namespace (proxy_routing.rs).
4.3 Windows Moves Work Into Setup and Runner Backends
The Windows transform branch is easy to misread because the manager does not prepend a wrapper executable in the same way it does for macOS or Linux. In the transform, WindowsRestrictedToken returns the original argv while carrying sandbox type, Windows level, private desktop flag, permission profile, filesystem policy, and network policy in the SandboxExecRequest (manager.rs).
The enforcement work appears in the Windows sandbox crate. Setup refresh builds an elevation payload with read roots, write roots, deny-write paths, proxy ports, and local-binding behavior (setup.rs). The elevated setup path builds a similar payload and decides whether elevation is needed (setup.rs). The elevated spawn backend prepares context and sends a runner request with capability SIDs and sandbox credentials (elevated.rs).
The identity, ACL, firewall, and WFP labels are not decorative. spawn_prep.rs prepares tokens, sandbox credentials, capability SIDs, and per-policy ACL inputs, while acl.rs reads and updates DACL/ACE state. The setup helper’s firewall.rs configures offline outbound and loopback proxy block rules, and wfp_setup.rs calls install_wfp_filters_for_account for the offline account. The exact WFP filter shapes are visible in filter_specs.rs: user-scoped blocks for ICMP, DNS ports, and SMB ports.
The legacy backend has a clear limitation: it rejects restricted read-only access and says that path requires the elevated Windows sandbox backend (legacy.rs). That is the practical difference between “there is a Windows branch” and “every Windows backend can enforce every profile.”
4.4 The Correct Comparison Table
| Platform path | What the transform or backend changes | Network expression | Boundary to state honestly |
|---|---|---|---|
| macOS Seatbelt | Wraps command with /usr/bin/sandbox-exec and generated profile arguments. | Generated policy allows or denies network, proxy ports, and selected sockets. | Expressive policy generation, but still constrained by Seatbelt grammar and endpoint discovery. |
| Linux bwrap/seccomp | Runs Codex helper, builds filesystem/namespace view, then applies no-new-privileges and seccomp before execvp. | bwrap mode chooses isolated, full access, or proxy-only; proxy routes can be bridged into the namespace. | Host support matters: WSL1 and unsuitable bwrap behavior are real platform boundaries. |
| Windows identity/ACL/WFP | Carries metadata from transform into Windows setup and runner backends. | Offline proxy settings, firewall allow/block rules, and WFP setup participate in containment. | Backend level matters; legacy and elevated paths do not enforce the same policy shapes. |
SandboxType::None | No platform sandbox wrapper is applied. | The host process/network environment is not represented as equivalent containment. | Treat as an explicit unsandboxed path or unsupported platform result, not as a hidden sandbox. |
5. Managed Network Is Stronger Than Environment Variables, Weaker Than a Universal Firewall

The proxy code is where many descriptions become either too weak or too strong. Too weak: “Codex just sets HTTP_PROXY.” Too strong: “Codex has a complete network firewall.” The source lands between those claims.
The proxy builder can reserve HTTP and SOCKS listeners, apply proxy environment overrides, set NO_PROXY defaults for loopback and private IP literals, align websocket proxy environment variables, and set ALL_PROXY when SOCKS is enabled (proxy.rs). Domain rules are normalized as exact hosts or scoped wildcard host patterns such as *.example.com and **.example.com, while a denylist rejects the global * wildcard (policy.rs). The important distinction is scope: bounded host patterns are allowed, but a bare match-everything rule is not.
The limited method rule is visible in tests:
assert!(NetworkMode::Limited.allows_method("GET"));
assert!(NetworkMode::Limited.allows_method("HEAD"));
assert!(NetworkMode::Limited.allows_method("OPTIONS"));
assert!(!NetworkMode::Limited.allows_method("POST"));
assert!(!NetworkMode::Limited.allows_method("CONNECT"));
That test lives in network-proxy/src/policy.rs. It is one reason the network figure uses GET, HEAD, and OPTIONS as allowed methods and POST/CONNECT as blocked examples under limited mode.
The platform integration makes this more than ordinary environment-variable guidance:
- On macOS, the generated Seatbelt policy can restrict network access to loopback proxy ports and selected Unix sockets when managed network is required.
- On Linux, proxy-only bwrap mode can bridge proxy routes into the network namespace and then apply seccomp.
- On Windows, setup payloads carry proxy ports and local-binding behavior, while firewall and WFP setup participate in the offline identity boundary.
The honest boundary is therefore: managed network can route and police application traffic when clients and platform constraints cooperate. It is not proof that every possible packet, DNS behavior, or host-level escape is controlled by the proxy layer alone.
6. Failure Cases Are Part of the Design
The source has several failure edges that are easy to smooth over in prose. Smoothing them over would make the article nicer and less useful.
| Misreading | Source-correct reading | Why it matters |
|---|---|---|
| Approval means “run outside the sandbox”. | Approval lets the attempt proceed; SandboxAttempt::env_for still calls the transform with policy and sandbox metadata. | A reviewer can approve a command without granting unlimited host reach. |
| The permission profile is the sandbox. | The profile compiles to filesystem and network policies, then a platform dialect enforces what it can express. | Config review and platform review are different jobs. |
| Linux sandboxing is only seccomp. | The current helper can build a bwrap filesystem/namespace view before applying seccomp; legacy Landlock is a separate path. | File visibility and syscall filtering are separate layers. |
| Windows transform doing no argv wrapping means no Windows sandbox. | Windows carries sandbox metadata into setup and runner backends instead of using the same wrapper shape as Unix. | Equivalent-looking command vectors can still run under different identities and ACL/firewall state. |
| Managed network is a packet firewall. | It is a proxy boundary with platform forcing where available, domain/method policy where visible, and audit hooks. | Overclaiming makes gaps such as non-proxy-aware programs and host networking harder to reason about. |
SandboxType::None is harmless fallback. | It is an explicit no-platform-sandbox path or unsupported-platform result that must not be described as equivalent containment. | UI, logs, and prose should not hide when containment is absent. |
Apply This
The transferable rule is simple: keep the authority, profile, transform, and backend in different boxes.
- Separate approval from containment. Approval authorizes the attempt; the sandbox transform still decides the process boundary.
- Compile profile before execution. Review the effective filesystem and network policies before reasoning about platform behavior.
- Name the platform dialect. Say Seatbelt, bwrap/seccomp, Windows identity/ACL/WFP, or no sandbox instead of saying “the sandbox” generically.
- Describe network guarantees narrowly. Managed proxy plus platform forcing is meaningful, but it is not a universal packet firewall.
- Treat missing containment as a first-class state.
SandboxType::None, missing helpers, and unsupported host features should be visible in UI, logs, and prose.
| When you see… | Ask this question | Source owner to inspect |
|---|---|---|
| A user or hook approval | Did this authorize the attempt, or did it also request a no-sandbox retry? | Tool orchestrator and approval decision flow from Chapter 12. |
| A permission profile name | What filesystem and network policies does it compile into? | core/src/config/permissions.rs and protocol/src/permissions.rs. |
| A command with extra grants | Were the grants normalized and merged only for this effective profile? | policy_transforms.rs. |
| A selected sandbox type | Which platform dialect is being requested, and can the host provide it? | SandboxManager::select_initial. |
| A network claim | Is it proxy policy, platform forcing, both, or neither? | network-proxy plus platform sandbox sources. |
| A platform error | Is this a policy refusal, missing helper, unsupported OS capability, or backend limitation? | Platform-specific transform and setup sources. |
Part III ends here. The runtime has taken a model-proposed side effect through routing, governance, mutation protocols, hook and approval gates, retry semantics, permission compilation, and platform containment. Part IV moves from local execution to clients and external runtimes: app-server, cloud tasks, memory, and release machinery all depend on the same discipline of typed boundaries instead of implicit side effects.
Source Map
| Evidence class | Claim | Source anchor |
|---|---|---|
| Verified source | Built-in profile resolution maps profile names to read-only, workspace-write, or disabled profiles. | codex-rs/core/src/config/permissions.rs |
| Verified source | Custom profiles compile filesystem entries and network policy before platform transforms. | codex-rs/core/src/config/permissions.rs |
| Verified source | Network sandbox policy has the runtime-level Restricted and Enabled cases. | codex-rs/protocol/src/permissions.rs |
| Verified source | Additional permissions are normalized and merged into an effective permission profile. | codex-rs/sandboxing/src/policy_transforms.rs and policy_transforms.rs |
| Verified source | Sandbox type selection distinguishes None, macOS Seatbelt, Linux seccomp, and Windows restricted-token routes. | codex-rs/sandboxing/src/manager.rs |
| Verified source | Initial sandbox selection uses tool preference, runtime policies, Windows level, and managed-network requirements. | codex-rs/sandboxing/src/manager.rs |
| Verified source | A sandbox attempt hands command, permissions, selected sandbox, network proxy state, cwd, and platform options into the transform. | codex-rs/core/src/tools/sandboxing.rs |
| Verified source | macOS transform builds a Seatbelt command through /usr/bin/sandbox-exec and generated policy sections. | codex-rs/sandboxing/src/manager.rs and seatbelt.rs |
| Verified source | macOS proxy configuration and managed-network enforcement force the restricted-network branch; only explicit loopback proxy ports add outbound allowances. | codex-rs/sandboxing/src/seatbelt.rs |
| Verified source | Linux helper builds bwrap filesystem/namespace state, applies seccomp/no-new-privileges, and then execs the command. | codex-rs/linux-sandbox/src/linux_run_main.rs |
| Verified source | Linux proxy routing prepares loopback proxy routes on the host and rewrites proxy environment inside the namespace. | codex-rs/linux-sandbox/src/proxy_routing.rs |
| Verified source | Windows sandbox setup carries roots, deny-write paths, proxy ports, and local-binding behavior into elevated setup. | codex-rs/windows-sandbox-rs/src/setup.rs and setup.rs |
| Verified source | Windows spawn preparation selects sandbox credentials, capability SIDs, and ACL inputs, and ACL helpers read/update DACL/ACE state. | codex-rs/windows-sandbox-rs/src/spawn_prep.rs and acl.rs |
| Verified source | Windows firewall setup configures offline outbound and loopback proxy rules, while WFP setup installs account-scoped filters. | firewall.rs, wfp_setup.rs, wfp.rs, and filter_specs.rs |
| Verified source | Windows legacy backend refuses restricted read-only access and requires the elevated backend for that shape. | codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs |
| Verified source | Limited network mode allows GET, HEAD, and OPTIONS, but not POST or CONNECT. | codex-rs/network-proxy/src/policy.rs |
| Surrounding contract inference | The portable abstraction is a permission-profile-to-platform-transform pipeline; the OS backends are not equivalent. | Synthesized from the linked manager, policy transform, platform helper, and proxy sources above. |