What is Seatbelt?
Seatbelt is macOS’s mandatory access control (MAC) framework. It’s the same technology that sandboxes App Store applications and Safari. Seatbelt policies are enforced by the XNU kernel - they cannot be bypassed by userspace code.Note: nono uses Apple’s privatesandbox_init()API rather than the newersandbox_apply_container(). While technically undocumented, this API has been stable for over a decade and is widely used by third-party tools. Apple’s publicsandbox-execcommand uses the same underlying mechanism.
How nono Uses Seatbelt
nono generates a Seatbelt profile (a Scheme-like DSL) based on your capability flags, then callssandbox_init() to apply it before executing the target command.
Profile Structure
A nono-generated Seatbelt profile follows this structure:Security Model: “Allow Discovery, Deny Content”
nono uses a nuanced approach to sensitive path protection:| Operation | Seatbelt Rule | Result |
|---|---|---|
stat ~/.ssh | file-read-metadata | Allowed |
test -d ~/.ssh | file-read-metadata | Allowed |
ls ~/.ssh | file-read-data (readdir) | Blocked |
cat ~/.ssh/id_rsa | file-read-data | Blocked |
- Prevents data exfiltration - actual file contents cannot be read
- Allows graceful error handling - programs can check if files exist without crashing
- Mirrors TCC behavior - feels native to macOS users
System Paths
nono allows read access to system paths required for running executables. These are loaded fromsecurity-lists.toml:
| Category | Paths | Purpose |
|---|---|---|
| Executables | /System/Library, /Library, /usr/lib | System binaries and libraries |
| Frameworks | /System/Library/Frameworks, /Library/Frameworks | macOS frameworks |
| dyld | /private/var/db/dyld, /var/db | Dynamic linker cache |
| SSL | /etc/ssl, /private/etc/ssl | Certificate stores |
| Locale | /usr/share/zoneinfo, /usr/share/locale | Timezone and locale data |
| System | /var, /private/var, /System/Volumes | System paths and APFS volumes |
file-map-executable permission is also granted globally, which is required for dyld to map executables and shared libraries into memory.
Sensitive Paths
nono explicitly denies data access to credential storage:| Category | Paths |
|---|---|
| Cloud Credentials | ~/.aws, ~/.azure, ~/.gcloud, ~/.kube |
| SSH/GPG | ~/.ssh, ~/.gnupg |
| Password Managers | ~/Library/Keychains, ~/.password-store, ~/.1password |
| Browser Data | ~/Library/Application Support/Google/Chrome, Firefox, Safari, etc. |
| macOS Private | ~/Library/Messages, ~/Library/Mail, ~/Library/Cookies |
| Shell Configs | ~/.zshrc, ~/.bashrc, ~/.profile, etc. (read-blocked; may contain API keys) |
| History Files | ~/.zsh_history, ~/.bash_history (read-blocked; may contain secrets) |
| Credential Files | ~/.git-credentials, ~/.netrc, ~/.npmrc |
--allow or --read flags.
System Operations
nono narrowssystem* permissions to only what’s commonly needed:
| Permission | Purpose |
|---|---|
system-socket | Network socket operations |
system-fsctl | Filesystem control operations |
system-info | Reading system information (uname, etc.) |
system-audit, system-privilege, system-reboot, system-set-time
Network Control
Network access is allowed by default:Granular Filtering Limitations
Seatbelt supports filtering by protocol (TCP/UDP), direction (inbound/outbound), and even IP address viaremote ip filters. However, it does not provide per-hostname or per-domain filtering. Since DNS resolution happens before the connection, filtering by domain would require:
- IP allowlists - Fragile due to CDNs, load balancers, and changing IP addresses
- Application-layer proxy - Adds complexity, requires elevated permissions
- Packet filtering (pf) - Requires root, conflicts with nono’s design
Irreversibility
Oncesandbox_init() is called, restrictions are permanent:
- There is no
sandbox_remove()orsandbox_expand()API - The process cannot modify its own sandbox
- All child processes inherit the restrictions
- The only way to escape is to exploit a kernel vulnerability
Debugging
If a command fails with permission errors:- Run with
--dry-runto see what capabilities would be granted - Run with
-vvvfor verbose logging (shows generated profile) - Check Console.app for sandbox violation logs:
- Filter by “sandbox” or your process name
- Violations show the exact path and operation blocked
Common Issues
| Error | Likely Cause | Solution |
|---|---|---|
| Abort (exit 134) | Missing system path | Check if a custom tool needs additional paths |
| Trace/BPT trap: 5 | Sandbox profile syntax error | Run with -vvv to see the generated profile |
| ”Operation not permitted” on sensitive path | Working as intended | Use --read ~/.path to explicitly allow |
| Python/Node fails | Interpreter outside allowed paths | Ensure /usr/bin, /usr/local/bin are accessible |
| Missing env vars in subshell | Shell configs are read-blocked | Use --read-file ~/.zshrc if shell init is needed |
Limitations
macOS Version Support
Seatbelt is available on macOS 10.5+, but nono is tested on macOS 10.15 (Catalina) and later.APFS Firmlinks
macOS 10.15+ uses APFS with firmlinks - bidirectional hard links that make/System/Volumes/Data/Users appear as /Users. nono’s security lists include /System/Volumes to handle path resolution across firmlink boundaries. Without this, sandbox rules written for /Users/luke might not match the kernel’s resolved path /System/Volumes/Data/Users/luke.