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:
This approach:
- 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:
The
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:
To access a protected path, provide both a matching filesystem or Unix socket
grant and a
--bypass-protection path. A grant alone does not lift a deny rule.
The bypass reopens only the access modes and socket scope granted for that
path; a read-only grant does not permit writes, and an exact socket exception
does not reopen sibling sockets.
System Operations
nono narrowssystem* permissions to only what’s commonly needed:
Notably omitted:
system-audit, system-privilege, system-reboot, system-set-time
Network Control
Network access is allowed by default:Unix Socket Connections
When a process callsconnect(2) on a Unix socket (e.g., /var/run/docker.sock), Seatbelt classifies the operation as network-outbound, not a file operation. This means a file-read-data or file-write-data deny rule for the socket path will not block the connection.
To block Unix socket connections, nono emits an additional rule alongside the standard file deny rules. Exact socket or file targets use path; denied directories use subpath so sockets anywhere below the directory are also blocked:
connect(2) time, so they also block connections to sockets created after the sandbox initializes. Non-existent deny targets use the fail-secure recursive form because they may later become directories.
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
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.