Supervisor (unsandboxed)
│
├── sandbox_extension_issue_file("/path/to/file", ...)
│ → returns token string
│
└── Pass token to sandboxed child (env var, pipe, socket, etc.)
│
Child (sandboxed)
├── sandbox_extension_consume(token)
│ → activates capability
└── open("/path/to/file") ← should succeed
nono uses fork → sandbox_init → exec (apply sandbox in the child, then exec the target program). Extension tokens consumed before exec() are reset by the kernel when exec() replaces the process image.
| Sequence | Result |
|---|---|
fork → sandbox_init → consume(token) → read(file) |
Works |
fork → sandbox_init → consume(token) → exec → read(file) |
EPERM |
fork → sandbox_init → exec → consume(token) → read(file) |
consume() returns handle=1 (success) but read() → EPERM |
The kernel resets extension grant state on exec(). This is undocumented but consistent:
- Pre-exec consume: Grants are active but wiped by
exec(). The new process image starts with a clean grant table. - Post-exec consume:
sandbox_extension_consume()returns a valid handle (non-negative), but the grant is not honored by the kernel. The sandbox profile was compiled and sealed atsandbox_init()time; post-exec token consumption appears to succeed at the API level but has no effect on the enforcement engine.
nono can't avoid exec() — the whole point is to sandbox an arbitrary target program (Claude Code, shell commands, etc.). Without exec(), the child would need to be the same binary as nono, which defeats the purpose.
The only scenario where extension tokens work (fork → sandbox → consume → use directly without exec) requires the sandboxed code to run in-process, which isn't viable for sandboxing third-party programs.
For the supervisor IPC path (SDK clients that link against libnono), we use SCM_RIGHTS fd passing: the unsandboxed supervisor opens the file and passes the fd over a Unix socket. This works because Seatbelt enforces at open() time — an already-opened fd is usable regardless of sandbox restrictions. But this only works for programs that integrate the nono SDK and explicitly request fds over the supervisor socket. It doesn't help for unmodified programs like cat, git, etc.