## What changed
- Add `codex-mxc-sandbox` with native MXC availability detection and a launcher that inherits standard I/O and waits for the sandboxed process.
- Reject unsupported learning-mode and fallback policies, and verify deny-path support before launch.
- Route the existing Windows MXC availability metric through the new crate and move its Windows linking dependencies alongside the adapter.
GitOrigin-RevId: e2a220b85718e00b0e710c30bbe66b6c56958176
## Why
`ShellExecuteExW` needs `SystemRoot` to elevate the Windows sandbox setup helper.
## What changed
Add `SYSTEMROOT` to the environment variables copied from the host for wrapper
setup. Keep the serialized inner command environment unchanged so the host value
is available only to the wrapper.
## Testing
Extend Windows sandbox manager tests to cover case-insensitive replacement of
`SystemRoot` and verify that it is excluded from the inner command environment.
GitOrigin-RevId: c69f9f92665e97a178e2de488fbb9fd6b7ff7ac6
## What changed
- Add the macOS-only `allow_symlinked_codex_home` user setting. When enabled in the execution host's `$CODEX_HOME/config.toml`, writable roots at or beneath that home may traverse symlinks.
- Keep the exception disabled by default and prevent project config, command-line overrides, and ignored user config from enabling it. Other writable roots remain subject to symlink checks.
- Propagate the resolved setting through local, interactive, and exec-server sandbox paths, including `CODEX_HOME` aliases.
- Explain the opt-out in symlink rejection errors, including that it trusts targets outside `CODEX_HOME` and targets that change between commands.
## Testing
- Add macOS coverage for shell commands, patches, filesystem helpers, process execution, and interactive startup with enabled, disabled, aliased, and out-of-scope homes.
GitOrigin-RevId: 99fcdf611200c9e1b7713cf06f7fdea5bfa7f089
## What changed
Increment the `codex.windows_sandbox.private_desktop` counter when preparing a
Windows restricted-token sandbox execution, tagging it with whether private
desktop isolation is enabled.
GitOrigin-RevId: 1eb5e183b57e19753dc133dbe152eabe23dd2a23
## What changed
- Pass the selected executor's `PathUri` through user command approval requests instead of converting the working directory to a host-native path.
- Make native-path permission helpers explicitly local and require executor-aware permission transforms to receive a `FileSystemSandboxPolicyContext`.
- Cover approved remote commands to verify that the approval request reports the selected remote working directory and execution resumes after approval.
GitOrigin-RevId: 0aa3042f2d7594af6c7d0abf31d7f99cf67d5556
## Why
`request_permissions` paths and grants need to be evaluated against the selected executor environment, including its path convention, home directory, workspace roots, and temporary directories.
## What changed
- Resolve relative and home-relative permission paths using the executor context, reject mismatched path conventions and lossy paths, and support legacy `read` and `write` path lists.
- Move grant intersection into core so requested and granted permissions use the originating environment's sandbox context. Preserve deny entries conservatively when a special path cannot be resolved.
- Keep the full originating environment with pending permission requests so delayed responses are normalized against the same context.
## Testing
- Cover POSIX, Windows, UNC, relative, and home-relative path resolution and invalid path contexts.
- Verify end-to-end app-server grants are limited to the requested workspace scope and unresolved temporary-directory denies are preserved.
GitOrigin-RevId: 730a2aacd391262e92a6314f3a5b6c262e3dca10
## What changed
- Probe whether the native Windows MXC process security environment is usable
when selecting a sandbox.
- Emit `codex.windows_mxc.available` at most once per process, labeled with the
probe result.
- Add the MXC workspace dependencies for Cargo and Bazel, and pin
`tracelogging` to a version compatible with the GNU Windows toolchain.
GitOrigin-RevId: d7cfeed6f0333feda5764e1ee44fe5bbd50f9a58
## Why
Permission grants can belong to an executor whose path convention differs from
the local host, such as a Windows remote environment. Matching those grants
against a host-native working directory can reject valid permissions.
## What changed
- Normalize, materialize, and compare additional filesystem permissions using
the selected executor's URI-based sandbox context.
- Apply the same context-aware preapproval flow to `exec_command`,
`apply_patch`, and extension tools.
- Fail closed when symbolic temporary-directory or project-root permissions
cannot be resolved from executor metadata, while accepting opaque working
directory URIs when requested paths use the executor's convention.
## Testing
- Added coverage for reusing a Windows permission grant from a non-Windows
host, resolving symbolic temporary-directory grants, and normalizing paths
with an opaque Windows working directory.
GitOrigin-RevId: aee54350a25430a0443f8319e2bb0bbdc4f44282
## What changed
- Add context-aware normalization, materialization, and intersection helpers for additional filesystem permissions.
- Resolve project roots, home-relative deny globs, temporary directories, and filesystem roots from the executor's `FileSystemSandboxPolicyContext` while preserving URI path conventions and symlinked paths.
- Keep deny constraints when intersecting grants across POSIX, Windows, and UNC paths, and reject incompatible path conventions when executor context is available.
## Testing
- Cover cross-convention validation, empty temporary-directory contexts, home-relative deny globs, and rooted deny globs for POSIX, Windows, and UNC paths.
GitOrigin-RevId: 14fd866a17a0ac4a595aa4f0e2fb82bfae1a8838
## Why
Programs running in a PTY can block while waiting for replies to terminal
queries.
## What changed
- Intercept device-status, window-size, cursor-position, and DEC private-mode
queries from TTY subprocess output and write bounded responses to the
subprocess input.
- Remove handled queries from the output stream while preserving other escape
sequences and output, including queries split across chunks.
- Apply the responder to both direct and driver-backed TTY process spawning.
## Testing
Added tests for driver-backed query handling and for a PTY subprocess that
waits for a cursor-position response before exiting.
GitOrigin-RevId: de92e06d183647271ef49024da9c30f01699f886
## Why
Filesystem policies can describe paths using a convention that differs from the
host running Codex. Native path comparisons can therefore mis-handle cases such
as case-variant Windows paths or ambiguous encoded components.
## What changed
- Resolve policy entries and special roots as `PathUri` values using the
executor's path convention.
- Use validated URI components for containment, overlap, and precedence, and
fail closed when component boundaries are ambiguous.
- Restrict relative joins to descendants and apply the same matching rules to
protected metadata paths and permission-profile intersections.
## Testing
Added coverage for Windows case variants, encoded and opaque paths, repeated
separators, descendant joins, special roots, metadata protection, and preserved
deny entries.
GitOrigin-RevId: fb09d44d11df25faaac806fe00457e6f6b0d8596
## Why
Filesystem helpers should not inherit access to system scratch directories beyond the paths granted by their filesystem policy, while ordinary processes still need scratch access for compatibility.
## What changed
- Move the `/tmp` and `/var/tmp` read/write grants, including their `/private` aliases, from the shared platform defaults into the process-only Seatbelt defaults.
- Keep process access to `/Applications` alongside the scratch-directory grants.
## Testing
Add a macOS Seatbelt test that verifies the generated policies and, when nested Seatbelt execution is available, confirms process scratch access, helper denial, and helper access to an approved project path.
GitOrigin-RevId: 26d83f09c131c1f1d000163b2f8929f601749719
## What changed
- Add Windows sandbox mode, private-desktop behavior, and legacy Landlock selection to each `EnvironmentConfig`.
- Centralize sandbox context construction on `TurnEnvironment`, preserving environment-owned permissions, paths, and backend settings while applying additional permission grants.
- Use the environment-derived context for project instructions, capability discovery, file uploads, and filesystem-oriented tools.
## Testing
- Verify capability discovery uses the selected environment's permission profile and sandbox backends when they differ from the thread defaults.
GitOrigin-RevId: 7ae2fed90c8ada950f2e8431e571e9b2c793c291
## Why
Python's `ProcessPoolExecutor` queries `SEM_NSEMS_MAX` through `sysconf`, which reads the `kern.sysv.semmns` sysctl on macOS.
## What changed
- Allow reads of `kern.sysv.semmns` in the base Seatbelt policy.
- Verify `/usr/bin/getconf SEM_NSEMS_MAX` succeeds with both read-only and workspace-write sandbox policies.
GitOrigin-RevId: ab3ae92dc8991729f90a47c1994eedb6c7a2b9cd
## Why
The macOS preferences service can expose data outside a sandbox's allowed
filesystem read roots.
## What changed
- Move the Seatbelt preference and `cfprefsd` grants into a separate policy
section that is included only when filesystem reads are unrestricted.
- Remove the equivalent grants from the restricted platform defaults.
## Testing
- Verify both Seatbelt profiles include preference grants only for full-disk
read policies, including policies with denied paths or globs.
- Verify a restricted sandbox cannot retrieve a preference whose plist is
denied, while an unrestricted read policy can.
GitOrigin-RevId: 90388366a7302bca1830ad0439544be8babc7321
## Why
Resolving attacker-mutable path components while preparing a Seatbelt profile can let a writable root be rebound to a different location before the sandbox is applied. File roots also need to remain confined to the file itself rather than granting access to descendants after replacement.
## What changed
- Preserve mutable components of writable-root paths until Seatbelt binds them, while still normalizing trusted top-level aliases such as `/tmp`.
- Use literal grants for existing file and device roots, and subpath grants for directories and missing roots.
- Exclude both logical and resolved forms of protected subpaths so symlinked metadata directories remain read-only.
## Testing
Add coverage for ancestor rebinding, file-root symlink and directory replacement, missing directory roots, top-level aliases, and symlinked metadata carveouts.
GitOrigin-RevId: 63c00e44dd30766e873b8acdad09d6657657fad9
## Why
Renaming a writable directory could move protected descendants outside the
paths covered by their read-only or unreadable policy entries.
## What changed
- Deny unlink-style writes to directory ancestors of protected paths, placing
these rules after broader write allowances in the generated Seatbelt policy.
- Apply full write denies to unreadable glob matches and protect their directory
ancestors against renames.
- Support brace alternation and backslash escapes when translating filesystem
globs into Seatbelt regular expressions.
## Testing
Add coverage for ancestor rename attempts across explicit read-only and deny
entries, multiple glob forms, canonicalized glob paths, and the new glob syntax.
GitOrigin-RevId: edfda9d4c2bc7ba2ecd6b67744446871e526084f
## Why
A sandboxed process could replace a writable directory root, changing the
authority boundary used to construct a later sandbox policy.
## What changed
- Deny unlinking or renaming directory roots granted write access by Seatbelt,
while preserving replacement and deletion behavior for writable files.
- Reject writable roots with nested symlink components and report these as
Seatbelt preparation errors instead of network proxy failures.
- Normalize writable roots before adding them to the generated Seatbelt policy.
## Testing
Add coverage for symlink rejection, error classification, directory-root
replacement and rename protection, newly created roots, and writable file
replacement.
GitOrigin-RevId: ab1ed4e55f20034bc43e028e6529d3d1f0d8181c
## Why
A stored permission grant may allow access to a parent while explicitly denying
a child path. A later request for that child must not be treated as already
approved by the broader parent grant.
## What changed
- Compare materialized permission profiles without intersecting away denied or
reopened paths before deciding that a request is preapproved.
- Execute preapproved commands with the stored grant itself so its denied paths
remain enforced.
- Fail closed when permission profiles cannot be materialized.
## Testing
Added unit and integration coverage for turn and session grants across
`exec_command`, `shell_command`, and `apply_patch`, including approval-disabled
and `Never` approval modes.
GitOrigin-RevId: 5455880328a89c7958f859c7ce87805dff9704fb
## Why
Filesystem permission paths can use a convention that differs from the host
running Codex. Converting them immediately to native absolute paths can change
the meaning of ambiguous paths such as `/C:/secret` or Windows UNC paths.
## What changed
- Store literal filesystem permission paths as `PathUri` values through the
runtime policy and execution protocol.
- Keep legacy string-based serialization at explicit protocol boundaries and
reject conversions that cannot be represented losslessly.
- Encode native paths as opaque URIs when a normal file URI would imply the
wrong path convention.
## Testing
Added coverage for cross-platform and ambiguous path round trips, UNC path
variants, permission-profile serialization, and deny-policy enforcement.
GitOrigin-RevId: 5247713796d1f2bb4e02f94eb9fc82d4698060f0
## Why
Filesystem helpers only need access to their own executables. Granting access to
the containing directories also makes unrelated sibling files readable.
## What changed
- Allow filesystem helpers to read the Codex and Linux sandbox executables
directly instead of their parent directories.
- Give macOS filesystem helpers a narrower Seatbelt profile that omits the
normal process sandbox's `/Applications` read access.
- Preserve the existing platform defaults for normal sandboxed processes.
## Testing
Add unit and integration coverage for direct and symlinked sibling-file access,
sandboxed copies, allowed workspace reads, and the macOS-specific Seatbelt
profiles.
GitOrigin-RevId: 9380c6868a695bdf2275baa74bd5e8a30a64ba1b
## What changed
Implement `From<AbsolutePathBuf>` for `FileSystemPath` and use the conversion
when constructing concrete filesystem permission paths.
GitOrigin-RevId: 244bcbd0c3f76cd87cc1240458fcac6c9ef0a8e3
## Why
Cross-platform remote `apply_patch` calls were rejected when filesystem writes
were restricted because patch verification and writes could not be safely
performed against executor files.
## What changed
- Route intercepted and direct remote patches through the executor-managed
filesystem sandbox, including the configured workspace roots.
- Select the restricted-token sandbox for Windows executor paths when no
Windows sandbox level was configured.
- Fail closed when an executor cannot enforce the requested sandbox, and treat
executor-managed access failures as sandbox denials so approval can retry the
patch without sandboxing.
## Testing
- Cover sandboxed remote patches, denied writes, approval retries, Windows
sandbox selection, and executor filesystem enforcement.
GitOrigin-RevId: caddeed0b266c456a689080a14a3a58e2bd7887c
## Why
Managed networking implicitly selected the elevated Windows sandbox backend,
even when the sandbox was configured to use a restricted token.
## What changed
- Select the Windows sandbox backend solely from `WindowsSandboxLevel`.
- Reject managed networking with a restricted-token sandbox before spawning a
process, since managed networking requires the elevated backend.
- Cover the rejection through sandbox preparation, unified exec, and Windows
sandbox session tests.
GitOrigin-RevId: 308858652d7b629af623d22896dafde3a23d3758
## Why
The `:slash_tmp` filesystem entry represents the Unix `/tmp` directory and
should not affect Windows sandbox policy decisions.
## What changed
- Ignore symbolic `SlashTmp` entries when resolving Windows filesystem access,
converting permission profiles, and intersecting requested permissions.
- Continue treating a literal `/tmp` entry as a regular filesystem path.
## Testing
Add Windows-specific coverage for policy conversion, access checks, and
permission-profile intersection.
GitOrigin-RevId: f4cdd37ea41abc9debfe8a0d3a456b70da5e534c
## Why
Filesystem denials and managed-network blocks did not share a structured event
shape, requiring downstream consumers to rediscover enforcement paths and parse
backend-specific output. See https://github.com/openai/codex/pull/17573.
## What changed
- Add normalized filesystem and network violation types in `codex-sandboxing`
and emit them through a shared tracing seam.
- Classify filesystem denials by backend and reason, retaining an optional path
and bounded output snippet, and preserve managed-network block context.
- Report the sandbox type through exec-server responses so unified exec can
classify remote denials without guessing; omitted values remain compatible
with older peers.
- Record violations from exec, apply-patch, shell-escalation, unified-exec, and
managed-network enforcement paths without changing denial behavior.
## Testing
- Cover filesystem classification, path extraction, `SIGSYS`, network event
conversion, protocol compatibility, and remote sandbox-type propagation.
GitOrigin-RevId: d673173b4fa6bdf0a24421194a8a61c81fab9c96
## What changed
- Pass the canonical `PermissionProfile` through execution requests, sandbox
selection, escalation, and the exec server instead of carrying separate
derived filesystem and network policies.
- Derive runtime sandbox policies from the effective profile at their points of
use, including after applying additional permissions.
- Update sandbox and escalation tests to inspect policies through the profile.
GitOrigin-RevId: 695b82ea94f5289516b0933bb23074cb2dcce8ac
## Why
Elevated Windows sandboxes need stable managed-proxy ports while preserving the network policy and environment attribution of each sandboxed process.
## What changed
- Keep shared HTTP and SOCKS5 loopback ingress listeners alive across managed-proxy instances.
- Add a per-route restricting SID to elevated sandbox tokens and dispatch incoming connections to the matching proxy policy after attributing the client process.
- Reject connections without exactly one registered route, remove routes when their proxy handle is dropped, and keep unsandboxed Windows launches off the managed ingress.
- Provision the elevated sandbox with the configured proxy ports and local-binding setting, honoring the selected profile and CLI overrides.
## Testing
- Add Windows unit tests for TCP ownership attribution, route selection, restricting-token propagation, and setup settings.
- Add an end-to-end Windows test covering stable ports, isolated environment policies, HTTP and SOCKS5 routing, and route teardown.
GitOrigin-RevId: 783fac6e0f904dc9bb1955b75d4a5895e8bb9690
## Why
Default read-only protections for project metadata should apply when paths such
as `.git`, `.agents`, and `.codex` exist, without causing sandbox setup to
materialize missing paths as ACL targets.
## What changed
- Add an optional `missing_path_behavior` to filesystem sandbox entries and
mark default project-metadata protections with `skip`.
- Preserve the behavior through permission transforms and exec/MCP protocol
serialization while keeping existing path wire variants stable.
- Ignore skip-missing entries when projecting configuration or Windows sandbox
overrides, while retaining explicit metadata carveouts.
## Testing
- Cover protocol round trips for path and special-path entries.
- Verify default metadata protections and Windows explicit carveout handling.
GitOrigin-RevId: 6df13dadacdd131c44aab9f15a967c81051355c1
## Why
Sandboxed process launch through the exec server was unsupported on Windows.
## What changed
- Add a shared native process launcher that selects the Windows sandbox session backend when required and otherwise launches PTY or pipe-based processes.
- Pass Windows permission, workspace-root, proxy, filesystem-override, and desktop settings through exec-server process preparation.
- Preserve piped stdin and inherited file descriptors across the shared launch path.
## Testing
- Verify that a remote Windows sandbox process accepts stdin while blocking a write under a read-only policy.
- Enable workspace-root sandbox tests on native Windows while continuing to skip environments that cannot emulate Windows sandbox semantics.
GitOrigin-RevId: 6719ccbe87ad0d5a57af56503e3f23f894c60a71
## What changed
- Represent built-in filesystem policy entries with the same `Path` and
`Special` variants used by other entries.
- Remove generated-default filtering from Windows sandbox policy resolution and
simplify elevated deny-write handling to rely on direct-runtime enforcement
and the legacy policy projection.
- Update protocol conversions, policy transforms, and profile expectations for
the reduced filesystem path model.
GitOrigin-RevId: a65793e6855a03d52a575e82cb3d4b9b2e32db5c
## Why
Windows firewall enforcement is tied to the logon-user sandbox identities. Proxy-enforced commands therefore need the elevated backend even when the configured sandbox mode is the default restricted-token mode.
## What changed
- Select the elevated Windows sandbox backend whenever a command uses the managed network proxy.
- Remove configuration and startup checks that required users or managed requirements to select the elevated sandbox explicitly.
- Start and expose managed network proxy state consistently on Windows.
## Testing
- Verify that proxy enforcement selects the elevated backend for the restricted-token sandbox mode.
GitOrigin-RevId: e01ac052587721c468a8fbcf4e2c7c5a185a3e05
## Why
Elevated Windows sandbox setup could turn the built-in read-only protections
for workspace metadata into deny-write paths, creating metadata directories
that did not already exist.
## What changed
- Track whether filesystem permission entries come from a built-in profile or
were explicitly configured.
- Exclude generated metadata defaults from direct Windows deny-write overrides
while continuing to enforce explicit read-only carveouts for the same paths.
- Preserve the distinction while materializing and normalizing permission
paths, without exposing generated defaults as user configuration.
## Testing
Added Windows coverage confirming that elevated sandbox setup leaves missing
workspace metadata absent and still produces overrides for explicit metadata
carveouts.
GitOrigin-RevId: 701bcb229f631b18ab5ced22f3edf6f303d932f3
## Why
Network proxy enforcement on Windows requires the elevated sandbox backend. Silently selecting that backend for an unelevated configuration makes the effective sandbox differ from the configured mode.
## What changed
- Reject enabled network proxy configurations on Windows unless managed requirements allow only the elevated sandbox and `windows.sandbox` resolves to `"elevated"`.
- Preserve the configured sandbox backend during execution and validate proxy compatibility at config loading, sandbox setup, and process launch boundaries.
- Defer this validation during cloud-config bootstrap so authoritative managed requirements can be loaded first.
- Do not start or expose disabled Windows proxies, and render transitions to disabled networking as `<network enabled="false"></network>`.
## Testing
- Cover Windows requirement and sandbox compatibility matrices, bootstrap behavior, backend selection, disabled proxy handling, and environment-context updates.
GitOrigin-RevId: bfa1dda98594e0db61883f8e7f65bd560e3453e6
## Why
PR #31767 flattened `NetworkProxyConfig`, leaving the [seatbelt test
setup](d72d669ca7/codex-rs/sandboxing/src/seatbelt_tests.rs (L660-L662))
to assign fields directly after `NetworkProxyConfig::default()`. Rust
1.95 flags that pattern as `clippy::field_reassign_with_default`, and
the macOS Bazel Clippy job treats the warning as an error, blocking
`main` and PRs based on it.
## What Changed
Initialize `enabled` and `mode` in the `NetworkProxyConfig` struct
literal, then apply the Unix socket allowlist through the existing
setter. This preserves the test behavior while satisfying Clippy.
## How to Test
This is a test-only initialization change, so there is no manual product
flow.
Targeted tests:
- `just test -p codex-sandboxing` (65 passed)
- `just clippy -p codex-sandboxing`
The local argument-comment lint could not complete because Bazel's LLVM
repository is missing the `compiler-rt` BUILD package. The touched Rust
diff was manually audited and adds no positional literal calls.
## Why
`NetworkProxyConfig` only wrapped `NetworkProxySettings` in a single
`network` field. That extra level made runtime callers repeat `.network`
everywhere without representing a real boundary.
## What changed
- move the managed-network fields directly onto `NetworkProxyConfig`
- collapse the matching partial-config wrapper
- update runtime callers and tests to use the direct fields
- keep the user-facing permissions/profile TOML layout unchanged
The internal serialized shape now matches the runtime type itself. This
does not change managed-network behavior or the `config.toml` shape.
## Why
Filesystem helpers intentionally run with a minimal environment that
excludes proxy variables. After filesystem operations started using the
Windows sandbox wrapper, the wrapper derived an empty proxy
configuration from that helper environment and compared it with the
persistent sandbox setup marker. When the marker contained proxy ports,
every filesystem operation appeared to require a firewall update, which
could launch elevated setup, show a UAC or loader dialog, and fail
operations such as `apply_patch` with error 1223.
Filesystem helpers do not use network access, so they should preserve
the proxy/firewall state established by normal sandboxed process
launches.
## What changed
- Add an explicit Windows sandbox proxy-settings mode for reconciling or
preserving persistent proxy state.
- Use preserve mode for filesystem helpers while normal process launches
continue to reconcile proxy settings from their environment.
- Carry the selected proxy state consistently through setup validation,
elevated setup, and non-elevated ACL refreshes.
- Cover wrapper argument propagation and marker-derived proxy
preservation.
## Validation
- `cargo build -p codex-cli --bin codex`
- `just test -p codex-windows-sandbox
preserving_proxy_settings_uses_the_existing_marker`
- `just test -p codex-windows-sandbox windows_wrapper_args_round_trip`
- `just test -p codex-windows-sandbox
setup_request_prefers_explicit_proxy_settings`
- `just test -p codex-sandboxing transform_for_direct_spawn_windows`
- `just test -p codex-exec-server fs_sandbox::tests`
- Ran the same sandboxed `fs/writeFile` reproduction against published
`0.142.0-alpha.6` and the new CLI. The published CLI launched elevated
setup and failed with `ShellExecuteExW ... 1223`; the new CLI completed
without elevation.
Related to #28359.
## Why
Managed network configures commands to use local HTTP and SOCKS proxies.
For commands delegated to the exec server, the proxy environment and the
sandbox policy were prepared separately. On macOS, that meant a command
could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still
denied access to port `43123`.
## What changed
`NetworkProxy` now prepares the command environment and sandbox context
together from the same runtime snapshot:
```text
Prepared managed network
├── command environment: HTTPS_PROXY=http://127.0.0.1:43123
└── sandbox context: allow outbound to 127.0.0.1:43123
```
That context travels with remote exec requests. The exec server
preserves the managed proxy and CA environment, and macOS Seatbelt
allows only the prepared loopback proxy ports without enabling broad
network access or local binding.
The protocol field is optional and the existing enforcement flag remains
in place, preserving compatibility with callers that do not send the new
context.
## Why
#29113 moved remote sandbox setup and enforcement to the exec server.
That gives the executor ownership of the platform-specific work: a Linux
executor chooses and runs a Linux sandbox even when the Codex
orchestrator is running on macOS or Windows.
It also means the orchestrator no longer knows which concrete sandbox
the executor selected. When that sandbox blocks a remote command, the
orchestrator currently sees only a failed process and can treat the
denial as an ordinary command failure. The existing sandbox approval and
retry path is then skipped.
This PR lets the executor report one portable fact:
> This command probably failed because the executor sandbox blocked it.
The executor keeps its concrete sandbox type private. The protocol sends
only the semantic result.
## Example
Suppose a local macOS Codex session asks a Linux devbox to write outside
the allowed workspace.
Before this PR:
```text
Linux sandbox blocks the write
-> remote process exits with "Permission denied"
-> local orchestrator sees an ordinary command failure
-> the normal sandbox approval and retry path can be skipped
```
With this PR:
```text
Linux sandbox blocks the write
-> executor reports sandboxDenied: true
-> unified exec returns UnifiedExecError::SandboxDenied
-> the existing approval prompt is shown
-> an approved retry runs through the existing unsandboxed retry path
```
## What changes
### The executor remembers its selected sandbox
The prepared remote process now retains the executor-selected
`SandboxType`. This value never crosses the executor boundary.
Commands started without a sandbox retain `SandboxType::None` and are
never reported as sandbox denials.
### The executor uses the existing denial heuristic
The existing local denial heuristic moves from `codex-core` into the
shared `codex-sandboxing` crate.
When a sandboxed remote process exits, the executor:
1. waits the same short output grace period used by local unified exec;
2. reads the output currently available in the existing retained output
buffer;
3. runs the existing heuristic using the exit code and common denial
messages;
4. stores the yes/no result before publishing the process exit.
This deliberately matches the old local unified-exec behavior. It does
not add a new streaming classifier, another output buffer, or stronger
output-retention guarantees.
### The protocol reports a portable boolean
`process/read` gains `sandboxDenied`:
```json
{
"exited": true,
"exitCode": 1,
"closed": false,
"sandboxDenied": true
}
```
The field defaults to `false` when an older executor omits it. The
response does not expose the executor sandbox implementation or
executor-native paths.
### Unified exec uses the existing error path
The exec-server client carries `sandboxDenied` into the unified process
state. If it is true, unified exec returns the existing `SandboxDenied`
error instead of trying to classify remote output using an
orchestrator-side sandbox type.
Remote process exit remains visible as soon as the process exits. This
PR does not wait for stdout or stderr to close and does not change the
existing process lifecycle.
## Scope
This PR is intentionally limited to matching the existing local
unified-exec behavior for the initial command execution path.
It does not add:
- incremental denial tracking across the full output stream;
- new denial handling for commands completed later through
`write_stdin`;
- new guarantees for preserving the semantic flag during the narrow
reconnect-recovery race.
Those can be considered separately if the same behavior is added for
local execution.
## Test coverage
One remote end-to-end integration test covers the complete intended
flow:
```text
remote read-only sandbox
-> denied write
-> executor reports the denial
-> Codex requests approval
-> user approves
-> retry succeeds on the remote executor
```
Existing lifecycle coverage continues to verify that remote process exit
is reported before late output streams close.
## What changed
PR #29099 stopped sending the orchestrator's concrete sandbox wrapper to
a remote exec-server. Remote commands now arrive as plain native argv.
This PR adds the next piece: Codex also sends portable sandbox intent
next to that plain argv.
For a remote unified-exec command, the request can now include:
- the canonical permission profile before local workspace-root
materialization
- the sandbox cwd and workspace roots as `PathUri` values
- Windows sandbox settings
- the legacy Landlock setting
- whether managed networking must be enforced
The important part is that symbolic entries such as `:workspace_roots`
stay symbolic while crossing the boundary. The executor can then bind
them to its own workspace-root paths instead of receiving
orchestrator-local absolute paths.
The data travels through `ExecRequest` into `ExecParams`. Older
exec-servers can still deserialize requests because the new fields have
defaults.
## Why
The orchestrator should not decide how another machine implements
sandboxing.
For example:
- a local macOS Codex would normally build a Seatbelt command
- a remote Linux executor needs a Linux sandbox command instead
The orchestrator now sends the plain command plus the policy it intended
to enforce. A later PR can let the exec-server choose and build the
correct sandbox for its own operating system.
## Important detail
This keeps the portable intent separate from the local `SandboxType`.
`SandboxType::None` is ambiguous:
- it can mean the command was explicitly approved to run without a
sandbox
- it can also mean the orchestrator host has no concrete sandbox
implementation available
Those cases are different for remote execution. This PR adds
`sandbox_requested` so an executor can still receive sandbox intent when
the orchestrator cannot build a local wrapper. Explicit unsandboxed
retries still send no sandbox context.
## Behavior today
This PR only transports the intent. The exec-server accepts the new
fields but does not apply them yet.
Remote commands therefore remain unsandboxed after this PR, just as they
are after PR #29099.
## Follow-up
The next PR will make exec-server read this portable intent, bind
symbolic workspace permissions to executor-native roots, choose the
sandbox for its own operating system, build the wrapper locally, and
then spawn the command.
Stacked on #28766.
## Why
Network approvals are environment-scoped: allowing a host in one
execution environment should not allow the same host in another
environment.
#28766 adds the inert IDs and constructor plumbing. This PR applies the
behavior on top.
## What changed
- Route managed network traffic through per-environment HTTP and SOCKS
proxy listeners.
- Stamp HTTP, HTTPS CONNECT, SOCKS TCP, and SOCKS UDP policy requests
with the source environment at the proxy boundary.
- Carry the selected execution environment through shell, unified exec,
zsh-fork, and sandbox transform paths.
- Include the environment in pending, approved-for-session, and
denied-for-session network approval cache keys.
- Include the environment in approval IDs and approval prompts.
- Preserve legacy fallback for unattributed requests, but deny when
active-call attribution is ambiguous.
- Fail closed if an environment-specific proxy endpoint cannot be
prepared.
## Validation
- just fmt
- CI will run tests and clippy
## Why
Prepare network approval scoping to distinguish execution environments
without changing behavior yet.
## What changed
- Add optional environment IDs to network policy requests.
- Add optional network environment IDs to exec and sandbox request
structs.
- Thread default None values through existing construction points.
- Fix stale constructor call sites that caused the CI compile failures.
## Not included
- Per-environment proxy listeners.
- Network approval cache or prompt behavior changes.
- Ambiguous request attribution handling.
Those behavior changes moved to stacked follow-up #28899.
## Validation
- just fmt
- CI will run tests and clippy
## Why
It should be possible for app-server to handle "foreign" OS paths in
unified_exec working directories, allowing e.g. a Linux app-server to
run processes on e.g. a Windows exec-server.
## What
Convert the core unified_exec cwd values to use `PathUri`.
Adds fallible path conversion in several places to try to minimize the
scope of this change. The only time this change suppresses errors from
converting `PathUri` to an `AbsolutePathBuf` is when the turn is
configured with no sandboxing at all to allow us to make progress
testing without sandboxing.
Future changes to apply_patch and sandboxing will clean up these error
paths.
A tool's cwd is resolved from joining a model-provided workdir to the
environment's cwd. When using `AbsolutePathBuf::join()`, an
absolute-path workdir would overwrite the environment's cwd and we would
resolve permissions/sandboxing against the model-provided path. This
change extends `PathUri::join()` to also treat an absolute rhs as an
override of the base/lhs.
This also removes some coverage from the remove_env_windows tests until
a follow-up converts foreign paths in command exec events correctly.
## Breaking Changes
When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
`~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
`PathUri` joining, so after offline discussion this PR no longer
implements it.
A quick check of some power users' rollouts suggests that models don't
actually generate home-prefixed absolute working directories for their
spawns, so this shouldn't have any real blast radius.
## Why
This is the final PR in the Windows fs-helper sandbox stack and contains
the actual bug fix.
The exec-server filesystem helper is a direct-spawn path: it asks
`SandboxManager` for a `SandboxExecRequest`, then launches the returned
argv itself. That works on macOS and Linux because the transformed argv
is already a self-contained sandbox wrapper. On Windows, the transformed
request carried `WindowsRestrictedToken` metadata, but the direct-spawn
fs-helper runner still launched the helper argv directly.
That means Windows filesystem built-ins backed by the fs-helper could
run with the parent Codex process permissions instead of the configured
Windows sandbox. This PR makes the direct-spawn transform produce a
self-contained Windows wrapper argv before fs-helper launches it.
## What Changed
- Added `SandboxManager::transform_for_direct_spawn()` for callers that
launch the returned argv themselves.
- Wrapped Windows restricted-token direct-spawn requests with `codex.exe
--run-as-windows-sandbox` and then marked the outer request as
unsandboxed, matching the macOS/Linux wrapper argv shape.
- Updated `exec-server/src/fs_sandbox.rs` to use the direct-spawn
transform for fs-helper launches.
- Materialized the inner `codex.exe --codex-run-as-fs-helper` executable
into `.sandbox-bin` so the sandboxed user can run it.
- Carried runtime workspace roots through `FileSystemSandboxContext` as
`PathUri` values so `:workspace_roots` policies resolve correctly
without sending native client paths over exec-server JSON.
- Preserved wrapper setup identity environment needed by Windows sandbox
setup without changing the serialized inner helper environment.
## Verification
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just test -p codex-sandboxing transform_for_direct_spawn_windows`
- `just test -p codex-exec-server fs_sandbox::tests`
- `just fix -p codex-windows-sandbox -p codex-sandboxing -p
codex-exec-server -p codex-core -p codex-file-system`
Local note: `just fmt` completed Rust formatting, but this workstation
still fails the non-Rust formatter phases because uv cannot open its
cache and the local buildifier/dotslash path is missing.
## Intent
Keep Bazel and Starlark files consistently formatted without requiring
contributors to install or version buildifier themselves.
## Implementation
- Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
v8.5.1.
- Run buildifier from the shared `just fmt` and `just fmt-check` driver,
with Windows-safe explicit DotSlash invocation.
- Provision DotSlash in formatting CI and contributor devcontainers, and
document the source-build prerequisite.
- Apply the initial mechanical buildifier formatting baseline.
## Why
`PathUri::from_abs_path` can fail for absolute paths that do not have a
normal `file:` URI representation, forcing filesystem call sites to
handle a conversion error even though the original path can be preserved
losslessly.
## What
Make `from_abs_path` infallible and migrate its callers. Unrepresentable
paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
leading encoded null reserves a namespace that cannot collide with a
real Unix or Windows path, and fallback URIs remain opaque to lexical
path operations.
## Validation
Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
device/verbatim and non-Unicode paths, serialization, malformed
fallbacks, opaque lexical operations, invalid native payloads, and
literal `/bad/path` collision resistance.
## Why
Sandbox cwd values can cross app-server and exec-server host boundaries.
They should retain URI semantics until the receiving host validates them
instead of being interpreted early as native paths.
## What
- Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
and transform inputs.
- Convert command and policy cwd once in `SandboxManager::transform`,
then keep launch requests native.
- Preserve sandbox cwd over remote filesystem transport and reject
non-native URIs without fallback.
- Cache paired native/URI turn-environment cwd values during migration,
with immutable access to keep them synchronized.
- Extend existing protocol, forwarding, transform, and core runtime
tests.
## Why
First-party async traits should expose their `Send` contracts explicitly
without requiring `async_trait`. This completes the migration pattern
established in #27303 and #27304.
## What changed
- Replaced the remaining first-party `async_trait` traits with native
return-position `impl Future + Send` where statically dispatched and
explicit boxed `Send` futures where object safety is required.
- Kept implementations behavior-preserving, outlining existing async
bodies into inherent methods where that keeps the diff reviewable.
- Removed all direct first-party `async-trait` dependencies and the
workspace dependency declaration.
- Added a cargo-deny policy that permits `async-trait` only through the
remaining transitive wrapper crates.
- Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
keep the full cargo-deny check passing.
## Validation
- `just test -p codex-exec-server`: 216 passed, 2 skipped.
- `just test -p codex-model-provider`: 39 passed.
- `just test -p codex-core` and `just test`: changed tests passed;
remaining failures are environment-sensitive suites unrelated to this
migration.
- `cargo deny check`
- `just fix`
- `just fmt`
- `cargo shear`
- `just bazel-lock-check`
## Why
`codex sandbox` can start a network proxy from a configured permission
profile. Previously, sandbox-level containment was tied to managed
network requirements rather than whether a proxy was actually active.
This meant config-driven proxy policies were not consistently enforced
as the sandbox's only network path.
## What changed
- Enable proxy-only network containment whenever `codex sandbox` starts
a network proxy.
- Apply the same active-proxy check to the macOS and Linux sandbox
paths.
- Add a Linux regression test that verifies a sandboxed command cannot
establish a direct connection while the configured proxy is active.
## Test plan
- `just test -p codex-cli debug_sandbox::tests`
- `sandbox_with_network_proxy_blocks_direct_loopback_access` runs on
Linux to cover the config-driven proxy path end to end.