Skip to content

fix(sandbox): allow the FSEvents Mach service on macOS - #7

Open
paveq wants to merge 1 commit into
mainfrom
fix/macos-fsevents-sandbox
Open

fix(sandbox): allow the FSEvents Mach service on macOS#7
paveq wants to merge 1 commit into
mainfrom
fix/macos-fsevents-sandbox

Conversation

@paveq

@paveq paveq commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem

File watchers do not work inside the macOS sandbox, and the errors do not look like sandbox denials:

  • node --watch (and nodemon, vite): Error: EMFILE: too many open files, watcherrno: -24, even with ulimit -n at 1048576.
  • Bun: error: Error starting FSEvents stream.

Both are the same failure. libuv's uv__fsevents_init returns UV_EMFILE when FSEventStreamStart fails, so the descriptor-exhaustion message is a red herring; the stream never started because the process could not reach fseventsd.

Cause

com.apple.FSEvents was missing from the mach-lookup allowlist in emit_common_rules. Confirmed from inside a sandboxed session with bootstrap_look_up:

com.apple.FSEvents                       kr=1100 (Permission denied)
com.apple.trustd.agent                   kr=0 (Success)
com.apple.system.notification_center     kr=0 (Success)

Fix

Add com.apple.FSEvents to the shared allowlist (tool and agent profiles both), with tests asserting it is present in each, and a note in SECURITY.md.

Security trade-off

The grant is notification-only — reading a changed file still goes through the filesystem rules. It does widen metadata disclosure: an event stream rooted outside the sandbox reports the paths of files the process cannot open. SECURITY.md records this as the accepted cost of working dev servers.

Not covered here

Phoenix live-reload's Can't find executable mac_listener / {:error, :fs_mac_bootstrap_error} is a separate issue — the fs dependency's mac_listener helper binary is not on the path it expects. Once it does run, this change is what lets it talk to fseventsd.

Testing

cargo test --lib could not be run: this sandbox has neither read nor write access to ~/.cargo, so cargo cannot resolve the registry. The two new assertions are pure string checks over the generated SBPL and need a run on a host with cargo access before merge.

Every file watcher on macOS reaches fseventsd through the
`com.apple.FSEvents` Mach service, which was missing from the
`mach-lookup` allowlist in [emit_common_rules](src/sandbox.rs#L484).
The resulting failures are unrecognisable as sandbox denials: libuv
reports a failed `FSEventStreamStart` as `EMFILE: too many open files,
watch` (so `node --watch`, nodemon and vite look like they hit a
descriptor limit, even with `ulimit -n` at 1048576), and Bun reports
`error: Error starting FSEvents stream`.

Confirmed with `bootstrap_look_up` from inside a sandboxed session:
`com.apple.FSEvents` returns 1100 (permission denied) while allowlisted
services such as `com.apple.trustd.agent` return success.

The grant is notification-only — reading a changed file still goes
through the filesystem rules — but an event stream rooted outside the
sandbox does report paths of files the process cannot open. That
metadata channel is noted in [SECURITY.md](SECURITY.md) as the cost of
working dev servers.
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.

1 participant