nono¶
The sandbox from nolabs-ai. On Linux it combines Landlock, which covers the
filesystem and, from ABI 4, TCP, with a seccomp filter for networking,
no_new_privs, and a supervising parent process.
I measured version 0.80.0 from the release asset
nono-v0.80.0-x86_64-unknown-linux-gnu.tar.gz, after checking it against the
release's SHA256SUMS. The source commit is
8bb041356d48f771a221e58c98d9b1ff442e76fe (tag v0.80.0), and the license is
Apache-2.0. The adapter is runner/adapters/nono.py.
It writes a profile and runs
nono -s run --no-diagnostics --no-rollback --no-audit --profile <file> -- sh -c <cmd>.
Configuration¶
| contract field | nono profile |
|---|---|
| (base) | the default groups, minus system_write_linux, user_tools, homebrew_linux and dangerous_commands*. Read on /usr/local/bin, write on /dev/null, workdir.access: none |
| read access to everything not denied (the contracts' default) | filesystem.read on the whole test tree, {root} |
writable_roots |
filesystem.allow, or allow_file for a single file |
read_allows |
filesystem.read, or read_file |
local_ports |
network.block plus network.connect_port. Landlock filters TCP by port only, so the grant is not limited to loopback |
allow_domains |
network.allow_domain, through nono's proxy |
| no network field | network.block: true |
environment allowlist, env_passthrough |
environment.allow_vars with PATH, HOME, LANG and TERM, plus any passed-through variables |
inherit_env |
no allow_vars. nono still removes loader variables such as LD_PRELOAD |
max_processes |
--max-processes |
isolate_process_info |
nothing to configure: nono only grants /proc/self and a few other /proc files. Its process_info_mode option exists on macOS only |
harden_syscalls, strict |
nono has no separate mode for these, so they are measured against its default filter |
Some fields have no equivalent in nono:
- A
read_deniesorwrite_deniesthat falls inside something granted. Landlock can only grant access, and nono refuses to start when a deny overlaps a grant. isolate_pids. nono does not create a PID namespace.materialize_absent_handoff.[broker]as the contracts define it. nono does have a credential proxy, but it uses its own routes.
Nearly all of nono's 78 unsupported results come from that first point. The
contracts assume a model where everything is readable unless denied. nono
works the other way round: you grant the workspace and nothing else, and a
secret stays protected because nobody granted it. In that setup most of these
secrets would never be readable. The contracts' [policy] does not describe
that setup, so I did not measure it in this run. A version of the read-deny
contracts written for that model would let me measure it.
Measured in nono's own model (by-omission run)¶
The 2026-10-15 run below measured nono against the neutral model, where 58 of its
78 unsupported results came from read_denies / write_denies fields nono's
allow-only model does not express. The by-omission run
re-states the 23 translatable read contracts in nono's own model
(read_scope = "granted": grant the workspace, omit the secret) and measures all
three sandboxes. nono goes from 23 unsupported to 18 contained, 4 escaped, 1
vacuous: it holds those read guarantees once they are asked in the model it
speaks. The 4 escapes (a Landlock unix-socket gap and three cases of a grant that
is a symlink into the protected area) and the 39 contracts that cannot be stated
in the granted model are detailed there.
Results of the 2026-10-15 run¶
Re-measured on 2026-10-16 with the runner that implements
spec/verdicts.md(run). Same version, same kernel; one result changed:filesystem-writable-root-replaceis now contained (itscontrol-brokewas a fault of the contract). Every other result below is unchanged.
Linux 6.8, Landlock ABI 4. Method and host details are in the run's README.
| contained | escaped | control-broke | unsupported | vacuous | needs-feature | n/a |
|---|---|---|---|---|---|---|
| 31 | 33 | 6 | 74 | 7 | 1 | 12 |
nono was set up as strictly as its documentation allows. Out of 33 escapes, 5 broke a rule I had configured.
Escapes on configured rules¶
Metadata of a readable file can be changed (filesystem-chmod-outside,
filesystem-utime-outside, filesystem-xattr-outside). With read access to
the test tree, the agent could change the mode, the timestamps and the
extended attributes of files outside its writable roots. Landlock does not
cover these operations. If nono had not granted read access to those files in
the first place, the agent could not have reached them.
A writable root that is a symlink is followed (fs-writable-root-symlink-out,
fs-writable-root-symlink-parent). If a writable root is a symlink pointing
outside the other roots, its target becomes writable. The contract expects the
sandbox to refuse such a root. nono accepts it.
Escapes with a note¶
ipc-abstract-unix-socket(§): limiting connections to abstract unix sockets requires Landlock ABI 6, which needs kernel 6.12 or newer. This host has ABI 4.
Escapes on defaults¶
27 escapes concern behaviour no [policy] field asks for:
- Trust handoff (17). Inside a writable root, nono does not protect
.git/hooks,.git/config,.claude/, editor directories or rc files. - Isolation (7).
- The agent could create nested user namespaces (3 contracts).
- When nono is launched as root, the command keeps its capabilities.
/proc/1is readable, and so are an ancestor's/proc/<pid>/memandenviron.- On this kernel nothing stops the agent from signalling processes outside the sandbox (
isolation-signal-host-process,§: needs Landlock ABI 6). - Lifecycle (3).
- A child process left in the background kept running after the command exited.
- After I sent SIGTERM to the launcher, the processes inside the sandbox kept running and wrote the marker file.
.mcp.jsonis not protected.
These are differences of defaults. Of the three groups, the lifecycle results deserve the closest look.
Other results¶
control-broke (6):
- A writable root spelled
absent/../real. nono requires every granted path to exist. - A writable root that is replaced during the run.
- The nested relaunch: the inner nono could not start.
- Three controls that need system calls nono's network filter blocks: io_uring, an unusual socket domain, and multiprocessing IPC.
needs-feature (1): lifecycle-effect-log-tamper. nono keeps an audit trail,
but I did not connect the adapter to the place where a run's log is stored
(por confirmar).