macOS Seatbelt Sandbox Profiling
Generate minimally-permissioned allowlist-based Seatbelt sandbox configurations for applications.
When to Use
- User asks to "sandbox", "isolate", or "restrict" an application on macOS
- Sandboxing any macOS process that needs restricted file/network access
- Creating defense-in-depth isolation if supply chain attacks are a concern
When NOT to Use
- Linux containers (use seccomp-bpf, AppArmor, or namespaces instead)
- Windows applications
- Applications that legitimately need broad system access
- Quick one-off scripts where sandboxing overhead isn't justified
Profiling Methodology
Step 1: Identify Application Requirements
Determine what the application needs across these resource categories:
| Category | Operations | Common Use Cases | |----------|------------|------------------| | **File Read** | `file-read-data`, `file-read-metadata`, `file-read-xattr`, `file-test-existence`, `file-map-executable` | Reading source files, configs, libraries | | **File Write** | `file-write-data`, `file-write-create`, `file-write-unlink`, `file-write-mode`, `file-write-xattr`, `file-clone`, `file-link` | Output files, caches, temp files | | **Network** | `network-bind`, `network-inbound`, `network-outbound` | Servers, API calls, package downloads | | **Process** | `process-fork`, `process-exec`, `process-exec-interpreter`, `process-info*`, `process-codesigning*` | Spawning child processes, scripts | | **Mach IPC** | `mach-lookup`, `mach-register`, `mach-bootstrap`, `mach-task-name` | System services, XPC, notifications | | **POSIX IPC** | `ipc-posix-shm*`, `ipc-posix-sem*` | Shared memory, semaphores | | **Sysctl** | `sysctl-read`, `sysctl-write` | Reading system info (CPU, memory) | | **IOKit** | `iokit-open`, `iokit-get-properties`, `iokit-set-properties` | Hardware access, device drivers | | **Signals** | `signal` | Signal handling between processes | | **Pseudo-TTY** | `pseudo-tty` | Terminal emulation | | **System** | `system-fsctl`, `system-socket`, `system-audit`, `system-info` | Low-level system calls | | **User Prefs** | `user-preference-read`, `user-preference-write` | Reading/writing user defaults | | **Notifications** | `darwin-notification-post`, `distributed-notification-post` | System notifications | | **AppleEvents** | `appleevent-send` | Inter-app communication (AppleScript) | | **Camera/Mic** | `device-camera`, `device-microphone` | Media capture | | **Dynamic Code** | `dynamic-code-generation` | JIT compilation | | **NVRAM** | `nvram-get`, `nvram-set`, `nvram-delete` | Firmware variables |
For each category, determine: **Needed?** and **Specific scope** (paths, services, etc.)
If the application has multiple subcommands that perform significantly different operations, such as `build` and `serve` commands for a Javascript bundler like Webpack, do the following:
- Profile the subcommands separately
- Create separate Sandbox configurations for each subcommand
- Create a helper script that acts as a drop-in replacement for the original binary, executing the sandboxed application with the appropriate Seatbelt profile according to the subcommand passed.
Step 2: Start with Minimal Profile
Begin with deny-all and essential process operations, saved in a suitably-named Seatbelt profile file with the `.sb` extension.
(version 1)
(deny default)
;; Essential for any process
(allow process-exec*)
(allow process-fork)
(allow sysctl-read)
;; Metadata access (stat, readdir) - doesn't expose file contents
(allow file-read-metadata)Step 3: Add File Read Access (Allowlist)
Use `file-read-data` (not `file-read*`) for allowlist-based reads:
(allow file-read-data
;; System paths (requ
<!-- truncated -->