Skip to content

[sandboxing] Prevent macOS fs-helper startup hangs#24956

Draft
JaviSoto wants to merge 1 commit into
mainfrom
dev/javi/fix-fs-helper-macos-startup-ipc
Draft

[sandboxing] Prevent macOS fs-helper startup hangs#24956
JaviSoto wants to merge 1 commit into
mainfrom
dev/javi/fix-fs-helper-macos-startup-ipc

Conversation

@JaviSoto

@JaviSoto JaviSoto commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Split the existing macOS :minimal platform policy into native runtime permissions and filesystem defaults.
  • Ensure fs-helper processes retain the native runtime permissions even when their policy already grants full disk read access.
  • Keep file/temp grants scoped out of full-read helpers and add regressions for that distinction.

Root Cause

The fs helper used by apply_patch is launched with a split filesystem sandbox policy. For full-read/workspace-write profiles, Codex correctly did not need the additional readable filesystem roots from restricted_read_only_platform_defaults.sbpl, but it incorrectly suppressed that entire policy unit. That file also contained native macOS startup permissions required before the helper reaches Rust main, including notification and logging IPC.

The exact semantic bug is that current Codex accepts :minimal, but FileSystemSandboxPolicy::include_platform_defaults() returns false whenever the same policy has :root = read. In other words, full disk read correctly subsumes :minimal's extra filesystem roots, but Codex also erases the unrelated native runtime capability that :minimal carries. The legacy full-read bridge has the same collapse because ReadOnlyAccess::FullAccess cannot represent the runtime half of :minimal.

As a result, a full-read fs helper can launch without the native runtime permissions implied by its :minimal requirement. The policy composition bug was introduced across the split-policy work in #13440 and #13448: filesystem access and native runtime capability were treated as the same semantic bit.

This change models those concerns independently. :minimal now always enables its native runtime policy; it only enables the additional filesystem defaults when full disk read has not already made them redundant.

Fixes #19020.

Observed Failure

In affected Studio threads, the model emits apply_patch, but no tool result or patch_apply_end follows. Corresponding --codex-run-as-fs-helper children never reach Rust execution; samples stop in macOS initialization:

CoreFoundation -> _CFStringGetUserDefaultEncoding -> getpwuid_r
  -> notify_register_check -> bootstrap_look_up -> mach_msg2_trap

A standalone C executable that only calls getpwuid_r(getuid(), ...), run through sandbox-exec with the notification-center lookup denied, reproduces the same hang on Studio. The same denied probe completes 5,000 launches locally, while allowing the lookup on Studio completes 5,000 launches. Cross-running the differing packaged Codex helpers also rules out the helper binary or Codex Remote transport as a prerequisite.

The shipped current Nightly codex sandbox command reproduces the same host split without the exec-server or fs-helper wrapper. On MBP, codex sandbox -c 'sandbox_mode="workspace-write"' -- getpwuid_probe passed 2,000 guarded launches. On Studio, the same exact Codex sandbox command timed out on guarded launch 1; its probe child sampled in getpwuid_r -> notify_register_check -> bootstrap_look_up2 -> mach_msg2_trap after launchd logged the denied com.apple.system.notification_center lookup.

The reduction is mechanical: on Studio, the exact current Codex sandbox path with a restricted permission profile containing ":minimal" = "read" and ":project_roots" = "write" passed 1,000 guarded launches. The same current binary with ":root" = "read", ":minimal" = "read", and ":project_roots" = "write" timed out on guarded launch 239 in the same startup stack. That proves the failure is not "missing :minimal in config"; it is current Codex semantically dropping :minimal when full read is present.

Host-Specific Boundary

This PR fixes the Codex policy defect; it does not claim to identify the underlying Studio-only macOS failure mode.

Both Macs run macOS 26.5 (25F71). On Studio, direct denied bootstrap_look_up, direct denied notify_register_check, and a one-process sequence of the four DirectoryService cache-notification names all return their expected failure values immediately. Pre-initializing Libnotify before getpwuid_r does not prevent the intermittent failure: a guarded loop still timed out under the denied policy.

Therefore the prior theory that Studio generically fails to complete sandbox-denied bootstrap calls is disproved. The remaining platform-level observation is narrower: Codex's missing-runtime policy exposes an intermittent Studio-specific failure inside the getpwuid_r / Libinfo startup path, after the sandbox denial is logged and before the caller completes. Apple Libinfo documents those cache-registration failures as ignorable, so a blocked user lookup is unexpected platform behavior, but its Studio-specific trigger remains unknown.

Validation

  • Added failing-first regressions for a full-read fs-helper profile requesting :minimal native runtime permissions and for keeping those permissions out of the base policy.
  • just fmt
  • just fix -p codex-protocol -p codex-sandboxing -p codex-exec-server
  • just test -p codex-protocol passed (226 tests plus bench smoke).
  • just test -p codex-exec-server passed (186 tests plus bench smoke).
  • bazelisk build //codex-rs/sandboxing:sandboxing passed, covering the new include_str! policy resource under Bazel.
  • The new codex-sandboxing regression passes. The full just test -p codex-sandboxing remains nonzero only for two existing shell-stderr spelling assertions that failed before this correction as well (bash: path versus bash: line 1: path).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS 0.122.0: apply_patch hangs under workspace-write but succeeds under danger-full-access

1 participant