permissions.<id>.network.unix_sockets config not propagated to macOS Seatbelt policy

Open 💬 3 comments Opened May 31, 2026 by naoto256
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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 #15120
  • permissions.<id>.network.allow_unix_sockets = ["/path"] — legacy array form
  • permissions.<id>.experimental_network.unix_sockets = {...}
  • permissions.<id>.experimental_network.dangerously_allow_all_unix_sockets = true
  • permissions.<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 :workspace was 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 (without extends) triggers the warning "Permissions profile workspace-write does 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.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #25076

Powered by Codex Action

wgu9 · 2 months ago

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:

  • Config parsing accepts profile network Unix-socket policy. codex-rs/core/src/config/permissions.rs maps permissions.<id>.network.unix_sockets into the network permission config.
  • Network-proxy configuration can also preserve this data. codex-rs/core/src/config/network_proxy_spec.rs maps requirements.unix_sockets, and codex-rs/core/src/network_proxy_loader.rs maps configured sockets into the managed proxy constraints.
  • The Seatbelt layer can already emit the right SBPL for explicit Unix sockets. codex-rs/sandboxing/src/seatbelt_tests.rs has tests covering explicit Unix-socket allowlists, and codex-rs/sandboxing/src/seatbelt.rs passes extra_allow_unix_sockets into proxy_policy_inputs(...) before building the dynamic network policy.
  • The debug sandbox CLI wires the command-line flag directly: codex-rs/cli/src/debug_sandbox.rs passes --allow-unix-socket into CreateSeatbeltCommandArgsParams.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 active NetworkSandboxPolicy bit, while a nearby comment says PermissionProfile 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 no extra_allow_unix_sockets unless 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 a UNIX_SOCKET_PATH_0 parameter 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.

LiuYu0521 · 1 day ago

Still reproduces on codex-cli 0.150.1 (macOS 26.x, arm64), ~15 minor versions after the original report. Same asymmetry: --allow-unix-socket works, 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 to o-<pid>-<id>.sock inside it):

SOCKDIR="$HOME/Library/Application Support/orca"

# canonical map schema (#15120) -> no-op
codex sandbox \
  -c 'permissions.uds.extends=":workspace"' \
  -c "permissions.uds.network.unix_sockets={\"$SOCKDIR\"=\"allow\"}" \
  -P uds -- orca status --json
# exit 0, runtime "reachable": false   <- AF_UNIX connect denied

# legacy array form -> also no-op
codex sandbox \
  -c 'permissions.uds2.extends=":workspace"' \
  -c "permissions.uds2.network.allow_unix_sockets=[\"$SOCKDIR\"]" \
  -P uds2 -- orca status --json
# exit 0, "reachable": false

# same profile + the CLI flag -> works
codex sandbox \
  -c 'permissions.uds.extends=":workspace"' \
  -c "permissions.uds.network.unix_sockets={\"$SOCKDIR\"=\"allow\"}" \
  -P uds --allow-unix-socket "$SOCKDIR" -- orca status --json
# exit 0, "reachable": true, real runtimeId

(A profile selected with -P but with no unix_sockets entry 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-socket and -P exist only on the codex sandbox subcommand. The interactive TUI accepts neither:

$ codex -P uds
error: unexpected argument '-P' found
$ codex --allow-unix-socket "$SOCKDIR"
error: unexpected argument '--allow-unix-socket' found

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-denials was what made it obvious.

Note on the network_access = true fallback

Confirming it still works on 0.150.1, with one gotcha worth recording: sandbox_mode must be set in the same invocation, otherwise the sandbox_workspace_write table is ignored and the switch silently does nothing.

# no effect - IP and AF_UNIX both still denied
codex sandbox -c 'sandbox_workspace_write.network_access=true' -- <cmd>

# works
codex sandbox -c 'sandbox_mode="workspace-write"' \
              -c 'sandbox_workspace_write.network_access=true' -- <cmd>

Positive control that -c itself 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 that network_access was 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_sockets map was introduced to avoid.