macOS permission profiles cannot re-allow child paths under a denied parent path

Open 💬 0 comments Opened May 4, 2026 by samal-rasmussen

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.429.30905 (2345)

What subscription do you have?

Pro

What platform is your computer?

Darwin 24.6.0 arm64 arm

What issue are you seeing?

On macOS, permission profiles appear unable to preserve most-specific filesystem precedence when a parent path is denied and a child path is explicitly allowed.

Example desired policy:

[permissions.locked.filesystem]
":project_roots" = { "." = "write" }
"/tmp" = "write"
"/private/tmp" = "write"
"/private/var/folders" = "write"

"/Users/me" = "none"
"/Users/me/.gitconfig" = "read"
"/Users/me/.cache/uv" = "write"

Desired behavior:

ls ~                                      fails
ls ~/.gitconfig >/dev/null                succeeds
ls ~/.cache/uv >/dev/null                 succeeds
wc -c < ~/.config/my-app/.env >/dev/null  fails

Observed behavior: the broad home deny also blocks the nested allow entries.

This looks like a macOS Seatbelt projection issue. Codex’s permission resolver appears to support most-specific path precedence, but the generated Seatbelt policy seems to turn exact deny roots into broad carveouts that shadow more-specific child allow rules.

What steps can reproduce the bug?

Use a profile like this, replacing /Users/me as needed:

approval_policy = "never"
sandbox_mode = "workspace-write"
default_permissions = "locked"

[permissions.locked.filesystem]
":project_roots" = { "." = "write" }
"/tmp" = "write"
"/private/tmp" = "write"
"/private/var/folders" = "write"
"/etc/ssl" = "read"
"/System/Library/OpenSSL" = "read"
"/Library/Developer/CommandLineTools" = "read"

"/Users/me" = "none"
"/Users/me/.gitconfig" = "read"
"/Users/me/.cache/uv" = "write"

[permissions.locked.network]
enabled = true
mode = "full"

Then run:

codex sandbox macos --permissions-profile locked -- /bin/zsh -lc 'ls ~ >/dev/null'
codex sandbox macos --permissions-profile locked -- /bin/zsh -lc 'ls ~/.gitconfig >/dev/null'
codex sandbox macos --permissions-profile locked -- /bin/zsh -lc 'ls ~/.cache/uv >/dev/null'
codex sandbox macos --permissions-profile locked -- /bin/zsh -lc 'wc -c < ~/.config/my-app/.env >/dev/null 2>&1'

What is the expected behavior?

Most-specific path rules should win consistently in the macOS runtime sandbox.

Expected result:

ls ~                                      fails
ls ~/.gitconfig >/dev/null                succeeds
ls ~/.cache/uv >/dev/null                 succeeds
wc -c < ~/.config/my-app/.env >/dev/null  fails

Additional information

Possible fix without new config syntax:

  1. In codex-rs/protocol/src/permissions.rs, expose readable roots that are more-specific descendants of unreadable roots, for example get_readable_roots_under_unreadable_roots_with_cwd(...).
  1. In codex-rs/sandboxing/src/seatbelt.rs, when generating the read policy:
  • keep the existing broad allow with denied roots carved out;
  • add a second explicit allow file-read* block for more-specific child exception roots;
  • add metadata-only traversal for denied ancestors so the child path can be reached without enabling ls ~.
  1. Update ReadDenyMatcher so exact path denies use the same most-specific precedence as resolve_access_with_cwd, instead of treating deny roots as unconditional prefix masks. Deny globs can stay hard-deny unless glob exception semantics are added separately.

Related: #2847, but this issue is narrower: it is specifically about parent-deny / child-allow behavior in macOS permission-profile enforcement.

View original on GitHub ↗