## Why Managed-network commands within one Codex conversation share the same HTTP and SOCKS proxy ingress. When several exec calls run concurrently, the proxy sees the requested destination but cannot tell which exec opened the connection. For example: ```text exec A: curl https://example.com/a ─┐ ├─> conversation proxy ─> Guardian exec B: curl https://example.com/b ─┘ host: example.com trigger: unknown ```. Three parallel network execs reached Guardian without their triggering call IDs or commands. Guardian denied the requests, but Codex could not safely associate those outcomes with the individual tool calls. ## What changes Keep the shared proxy ingress and tag each connection at the existing trusted Linux bridge: ```text exec A ─> existing Linux bridge ─> [token A][proxy bytes] ─┐ ├─> shared HTTP/SOCKS ingress exec B ─> existing Linux bridge ─> [token B][proxy bytes] ─┘ │ token A ─> exec A ─────┤ token B ─> exec B ─────┘ ``` The complete path is: ```text active exec registration │ ├─ registers its UUID as a short-lived attribution token ├─ passes the token to the Linux sandbox helper ├─ helper removes the token before launching the user command ├─ existing host bridge prepends the token to each proxy connection ├─ shared proxy consumes the bounded attribution frame └─ proxy attaches the matching execution-scoped state ├─ Guardian receives the exact call ID and command └─ a denial finishes/cancels the matching tool call ``` Dropping the active or deferred exec registration removes the token. Connections that were already accepted retain their resolved attribution; new connections using an expired token fail closed. ## Before and after Before, Guardian could receive only the network destination: ```json { "tool": "network_access", "host": "www.17track.net", "port": 443, "protocol": "https" } ``` After, the same request includes the action that caused it: ```json { "tool": "network_access", "host": "www.17track.net", "port": 443, "protocol": "https", "trigger": { "callId": "exec-network-first", "command": ["/bin/sh", "-c", "curl https://www.17track.net"] } } ``` ## Listener accounting This PR does **not** create proxy listeners per exec. ```text Existing topology: one conversation -> one HTTP listener + optional one SOCKS listener Discarded per-exec approach: one conversation -> existing listener pair + up to one additional listener pair per active exec This PR: one conversation -> existing listener pair only + one small token-map entry per active exec ``` The Linux sandbox already creates a trusted routing bridge for each sandboxed command. This PR adds a short frame write to that bridge rather than introducing another listener, task, or proxy process. The existing conversation-scoped listener pair remains. Making a single proxy service shared across multiple conversations would be a separate multi-tenant architecture change involving per-conversation policy, configuration, audit, and Guardian routing. ## Keeping the implementation small The attribution is bound once, when the TCP connection enters the proxy. The ingress installs an execution-scoped clone of the existing `NetworkProxyState`, so the established HTTP, SOCKS, MITM, policy, audit, and blocked-request paths continue using their existing state lookup. This avoids plumbing a new request-context type through every protocol handler. Outside the two ingress wrappers, protocol-specific request handling is unchanged. ## Security behavior - Tokens are generated from the existing random execution registration IDs. - The trusted Linux helper consumes and removes the token before executing user code. - Attribution frames have a fixed magic prefix, bounded token length, and bounded read timeout. - Unknown or expired tokens close the connection. - A token presented to a proxy for another environment closes the connection. - Existing unframed callers preserve the current conservative attribution behavior. ## Platform scope Exact bridge attribution is enabled on Linux. macOS and Windows retain their current shared-proxy behavior. ## Test coverage The concurrent end-to-end test starts two managed-network execs together and synchronizes them so both are active before either connects. It then inspects the two Guardian requests and compares the complete attribution pairs: ```text (exec-network-first, exact first command) (exec-network-second, exact second command) ``` Focused proxy coverage verifies the bounded frame and that a registered framed connection receives the matching execution and environment state. ## Scope This fixes the Linux network-to-exec attribution path and records a denial against the exact matching tool call. It intentionally does not change: - delivery of an entirely unattributed denial to the parent turn; - how parallel denials count toward the Guardian circuit breaker; - how the UI displays the rejection reason or completed-turn state. Those remain separate concerns from attribution. ## Relationship to #29456 and #29668 #29456 made the proxy environment and sandbox policy come from the same prepared network context. This PR adds the execution token to that prepared launch and consumes it at the shared ingress. This follows #29668's shared-ingress framing direction, but completes the production registration, Linux bridge, core call mapping, denial mapping, and concurrent end-to-end path. It also keeps attribution in the existing per-connection proxy state instead of introducing request-context plumbing through every HTTP, SOCKS, and MITM handler. This PR is intended to supersede #29668 for the Linux attribution fix. --------- Co-authored-by: viyatb-oai <viyatb@openai.com> Co-authored-by: Codex <noreply@openai.com>
codex-core
This crate implements the business logic for Codex. It is designed to be used by the various Codex UIs written in Rust.
Wine-exec integration tests
On x86-64 Linux, run the shared suite against the Windows exec server with
bazel test //codex-rs/core:core-all-wine-exec-test.
Local execution targets the host OS, Docker targets Linux, and Wine exec targets Windows. Choose the skip macro by what the test depends on:
skip_if_target_windows!: Windows target behavior.skip_if_host_windows!: Windows host constraints.skip_if_remote!: Local-only test behavior.skip_if_no_remote_env!: Remote-only test behavior.skip_if_wine_exec!: Wine-specific runner debt.
Dependencies
Note that codex-core makes some assumptions about certain helper utilities being available in the environment. Currently, this support matrix is:
macOS
Expects /usr/bin/sandbox-exec to be present.
When using the workspace-write sandbox policy, the Seatbelt profile allows
writes under the configured writable roots while keeping .git (directory or
pointer file), the resolved gitdir: target, and .codex read-only.
Network access and filesystem read/write roots are controlled by
SandboxPolicy. Seatbelt consumes the resolved policy and enforces it.
Seatbelt also keeps the legacy default preferences read access
(user-preference-read) needed for cfprefs-backed macOS behavior.
Linux
Expects the binary containing codex-core to run the equivalent of codex sandbox when arg0 is codex-linux-sandbox. See the codex-arg0 crate for details.
Legacy SandboxPolicy / sandbox_mode configs are still supported on Linux.
They can continue to use the legacy Landlock path when the split filesystem
policy is sandbox-equivalent to the legacy model after cwd resolution.
Split filesystem policies that need direct FileSystemSandboxPolicy
enforcement, such as read-only or denied carveouts under a broader writable
root, automatically route through bubblewrap. The legacy Landlock path is used
only when the split filesystem policy round-trips through the legacy
SandboxPolicy model without changing semantics. That includes overlapping
cases like /repo = write, /repo/a = none, /repo/a/b = write, where the
more specific writable child must reopen under a denied parent.
The Linux sandbox helper 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, it falls back to the bundled codex-resources/bwrap
binary shipped with Codex and Codex surfaces a startup warning through its
normal notification path instead of printing directly from the sandbox helper.
Codex also surfaces a startup warning when bubblewrap cannot create user
namespaces. WSL2 uses 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 before invoking bwrap.
Windows
Legacy SandboxPolicy / sandbox_mode configs are still supported on
Windows. Legacy read-only and workspace-write policies imply full
filesystem read access; exact readable roots are represented by split
filesystem policies instead.
The elevated Windows sandbox also supports:
- legacy
ReadOnlyandWorkspaceWritebehavior - split filesystem policies that need exact readable roots, exact writable roots, or extra read-only carveouts under writable roots
- backend-managed system read roots required for basic execution, such as
C:\Windows,C:\Program Files,C:\Program Files (x86), andC:\ProgramData, when a split filesystem policy requests platform defaults
The unelevated restricted-token backend still supports the legacy full-read
Windows model for legacy ReadOnly and WorkspaceWrite behavior. It also
supports a narrow split-filesystem subset: full-read split policies whose
writable roots still match the legacy WorkspaceWrite root set, but add extra
read-only carveouts under those writable roots.
New [permissions] / split filesystem policies remain supported on Windows
only when they can be enforced directly by the selected Windows backend or
round-trip through the legacy SandboxPolicy model without changing semantics.
Policies that would require direct explicit unreadable carveouts (none) or
reopened writable descendants under read-only carveouts still fail closed
instead of running with weaker enforcement.
All Platforms
Expects the binary containing codex-core to simulate the virtual
apply_patch CLI when arg1 is --codex-run-as-apply-patch. See the
codex-arg0 crate for details.