Skip to content

Instantly share code, notes, and snippets.

@josephgimenez
Last active March 1, 2026 20:23
Show Gist options
  • Select an option

  • Save josephgimenez/7c30d7c2299b8429f4444d03beeb21d4 to your computer and use it in GitHub Desktop.

Select an option

Save josephgimenez/7c30d7c2299b8429f4444d03beeb21d4 to your computer and use it in GitHub Desktop.
issues leveraging seatbelt extension tokens

Sandbox Extension Tokens Don't Survive exec()

The Intended Flow

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

The Problem: nono's Process Model

nono uses forksandbox_initexec (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.

Empirical Results

Sequence Result
forksandbox_initconsume(token)read(file) Works
forksandbox_initconsume(token)execread(file) EPERM
forksandbox_initexecconsume(token)read(file) consume() returns handle=1 (success) but read()EPERM

Why

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 at sandbox_init() time; post-exec token consumption appears to succeed at the API level but has no effect on the enforcement engine.

Why This Blocks nono

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.

Current Workaround

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment