Linux already has the exact primitive we need — no special entitlements required.
seccomp user notification allows a supervisor process to intercept syscalls from a sandboxed child and make allow/deny decisions — the same model as macOS Endpoint Security, but available to any unprivileged process.
Child (sandboxed) Kernel Supervisor (nono)
│ │ │
├── openat("/home/.aws/config") ►│ │
│ ├── SECCOMP_RET_USER_NOTIF ───►│
│ │ (syscall blocked) │
│ │ ├── Check policy
│ │ ├── Prompt user?
│ │ ├── openat() on behalf of child
│ │◄── NOTIF_ADDFD + NOTIF_SEND ─┤
│◄── returns fd ─────────────────│ │
- Install filter: The child installs a seccomp-bpf filter with
SECCOMP_FILTER_FLAG_NEW_LISTENER, which returns a notification fd to the supervisor - Intercept: When the child hits a filtered syscall (e.g.,
openat), the kernel blocks it and delivers a notification to the supervisor viaSECCOMP_IOCTL_NOTIF_RECV - Decide: The supervisor inspects the syscall args (path, flags, etc.), checks policy, optionally prompts the user
- Respond: Three options:
- Allow: Supervisor calls
openat()itself and injects the resulting fd into the child's fd table viaSECCOMP_IOCTL_NOTIF_ADDFD(Linux 5.9+) withSECCOMP_ADDFD_FLAG_SENDfor atomic fd injection + response - Deny: Supervisor responds with an error code (e.g.,
-EPERM) - Pass through: Supervisor responds with
SECCOMP_USER_NOTIF_FLAG_CONTINUEto let the syscall proceed normally (Linux 5.5+)
- Allow: Supervisor calls
nono currently uses Landlock on Linux, which (like Seatbelt on macOS) is static and allow-list-only:
| Landlock (current) | seccomp user notification | |
|---|---|---|
| Permission model | Static allow-list, set once | Dynamic per-syscall decisions |
| Deny within allow | Not possible (no deny semantics) | Supervisor can deny any individual operation |
| Interactive retry | Not possible (no notification) | Supervisor can prompt user, then allow/deny |
| Dynamic expansion | Not possible after apply | Supervisor makes real-time decisions |
| exec() survival | Permissions survive exec | Notification fd is inherited; filter survives exec |
| Entitlements | None needed | None needed |
| Kernel version | 5.13+ | 5.0+ (NOTIF_ADDFD: 5.9+) |
nono (supervisor)
│
├── fork()
│ │
│ Child:
│ ├── seccomp(SET_MODE_FILTER, FLAG_NEW_LISTENER, bpf_filter)
│ │ → returns notify_fd (inherited by parent via fork ordering,
│ │ or passed back via pre-arranged pipe)
│ ├── Landlock (for the static allow-list baseline)
│ └── exec(claude)
│
└── Supervisor loop:
├── SECCOMP_IOCTL_NOTIF_RECV(notify_fd)
├── Read path from /proc/<pid>/mem (seccomp provides pointer + pid)
├── Check against policy (deny_credentials, never_grant, etc.)
├── If denied → prompt user → allow/deny
└── SECCOMP_IOCTL_NOTIF_SEND (with ADDFD if emulating open)
The ideal architecture layers both:
- Landlock provides the static baseline sandbox (allow project dir, system libs, etc.) — fast, no per-operation overhead for allowed paths
- seccomp user notification intercepts operations on sensitive paths (credentials, SSH keys, etc.) — dynamic, interactive, per-operation decisions
This is analogous to what we want on macOS: Seatbelt for the baseline + Endpoint Security for dynamic decisions. The difference is that on Linux, both primitives are available without entitlements.
No entitlement, no Apple approval, no provisioning profile, no app bundle requirement. SECCOMP_RET_USER_NOTIF is available to any unprivileged process on any Linux 5.0+ kernel. This makes it immediately usable for nono without any distribution gatekeeping.