permissions.<id>.network.unix_sockets config not propagated to macOS Seatbelt policy
Summary
The canonical permissions schema accepts permissions.<id>.network.unix_sockets (per #15120) and codex doctor reports the config as loaded with no warnings, but the allowlist is never injected into the generated Seatbelt SBPL. Connecting to a listed AF_UNIX socket from a sandboxed child still produces a network-outbound denial.
The equivalent CLI flag --allow-unix-socket (from #17654) works correctly, which confirms the Seatbelt-side machinery is fine — only the config → SBPL wiring is missing.
Version
codex-cli 0.135.0-alpha.1
Codex.app on macOS 26.5 (aarch64)
Repro
~/.codex/config.toml:
default_permissions = "uds-test"
[permissions.uds-test]
extends = ":workspace"
[permissions.uds-test.network.unix_sockets]
"/tmp/.X11-unix" = "allow"
$ codex doctor | grep config
✓ config loaded # no warnings, profile recognized
$ codex sandbox --permissions-profile uds-test --log-denials -- \
python3 -c 'import socket; s=socket.socket(socket.AF_UNIX); s.connect("/tmp/.X11-unix/X0")'
ERR PermissionError [Errno 1] Operation not permitted
=== Sandbox denials ===
(Python) network-outbound /tmp/.X11-unix/X0
The same socket via the CLI flag works:
$ codex sandbox --permissions-profile uds-test \
--allow-unix-socket /tmp/.X11-unix -- \
python3 -c 'import socket; s=socket.socket(socket.AF_UNIX); s.connect("/tmp/.X11-unix/X0")'
# OK
Other schema variants I tried (none work)
All accepted by the config loader, none reach Seatbelt:
permissions.<id>.network.unix_sockets = { "/path" = "allow" }— canonical per #15120permissions.<id>.network.allow_unix_sockets = ["/path"]— legacy array formpermissions.<id>.experimental_network.unix_sockets = {...}permissions.<id>.experimental_network.dangerously_allow_all_unix_sockets = truepermissions.<id>.network.network_access = true(also not wired)- Top-level
experimental_network.unix_sockets - Top-level
experimental_network.allow_unix_sockets
The only working config-side switch I found is the legacy, pre-profile path:
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
…but that opens the entire network namespace, which is exactly the over-reach #15120's unix_sockets map was meant to avoid.
Expected
A permissions.<id>.network.unix_sockets entry on the active profile should produce the same SBPL allow rules as --allow-unix-socket:
(allow network-bind (local unix-socket (subpath "...")))
(allow network-outbound (remote unix-socket (subpath "...")))
Notes
- On my install, only
:workspacewas a recognized built-in;extends = ":workspace-write"failed with "cannot extend unsupported built-in profile". Documenting which built-ins ship per build flavor would help. - Defining
[permissions.workspace-write]directly (withoutextends) triggers the warning "Permissions profileworkspace-writedoes not define any recognized filesystem entries" and silently strips the built-in filesystem defaults — that footgun is a separate UX concern. - Linux side has its own open issue (#16910); this one is specifically about the macOS Seatbelt path being half-wired.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I traced this on current
main; the report looks valid, and the gap appears to be the bridge from permission-profile config into the normal Seatbelt runtime invocation, not Seatbelt rule generation itself.What I found:
codex-rs/core/src/config/permissions.rsmapspermissions.<id>.network.unix_socketsinto the network permission config.codex-rs/core/src/config/network_proxy_spec.rsmapsrequirements.unix_sockets, andcodex-rs/core/src/network_proxy_loader.rsmaps configured sockets into the managed proxy constraints.codex-rs/sandboxing/src/seatbelt_tests.rshas tests covering explicit Unix-socket allowlists, andcodex-rs/sandboxing/src/seatbelt.rspassesextra_allow_unix_socketsintoproxy_policy_inputs(...)before building the dynamic network policy.codex-rs/cli/src/debug_sandbox.rspasses--allow-unix-socketintoCreateSeatbeltCommandArgsParams.extra_allow_unix_sockets, which matches the report that the explicit CLI flag works.The missing bridge seems to be regular permission-profile execution. In
codex-rs/core/src/config/mod.rs, the runtime permission conversion keeps the activeNetworkSandboxPolicybit, while a nearby comment saysPermissionProfile carries the active network sandbox bit, not the configured proxy/allowlist policy. That means a profile can round-trip the configured allowlist, but the normal Seatbelt invocation can still receive noextra_allow_unix_socketsunless the explicit CLI flag or managed-network/proxy path supplies it.Minimal fix shape: carry the active profile's concrete Unix-socket allowlist into the runtime sandbox args used for macOS Seatbelt, or extend the runtime network policy/profile value so it includes the concrete socket allowlist instead of only enabled/restricted state. Regression coverage should assert that a config profile containing
[permissions.<id>.network.unix_sockets]results in aUNIX_SOCKET_PATH_0parameter and the corresponding Seatbelt network rules. The lower-level explicit-flag path already appears covered.Per
docs/contributing.md, I am not opening an unsolicited PR; I can submit a focused patch if a maintainer says this direction is welcome.Still reproduces on codex-cli 0.150.1 (macOS 26.x, arm64), ~15 minor versions after the original report. Same asymmetry:
--allow-unix-socketworks, the profile config silently no-ops.Repro, using a host app's control socket directory as the target (
~/Library/Application Support/orca, whose CLI connects too-<pid>-<id>.sockinside it):(A profile selected with
-Pbut with nounix_socketsentry behaves identically to the two no-op cases, as expected — included as a control.)The working flag is unavailable where it's most needed
One impact point I don't think has been raised:
--allow-unix-socketand-Pexist only on thecodex sandboxsubcommand. The interactive TUI accepts neither:So for a long-running TUI session — an agent pane that needs to talk to a host application over its AF_UNIX control socket — the config path in this issue is the only available route, and it's the one that doesn't work. The practical consequences are the full-network fallback below, or disabling the sandbox entirely.
This also produces a confusing failure mode downstream: the denial surfaces as
connect EPERM, which client CLIs commonly translate into "the app isn't running." I spent a while chasing a phantom dead daemon before finding the actual seatbelt denial (network-outbound <path>.sock).--log-denialswas what made it obvious.Note on the
network_access = truefallbackConfirming it still works on 0.150.1, with one gotcha worth recording:
sandbox_modemust be set in the same invocation, otherwise thesandbox_workspace_writetable is ignored and the switch silently does nothing.Positive control that
-citself is honored:-c 'sandbox_mode="danger-full-access"'alone does open the sandbox, so the first form is genuinely a no-op rather than a dropped override. I initially concluded from the first form thatnetwork_accesswas broken too — it isn't; it just needs the mode alongside it. Worth knowing before anyone reads a false negative into it.Either way, as the original report notes, this opens the entire network namespace, which is exactly what the
unix_socketsmap was introduced to avoid.