nono¶
El sandbox de nolabs-ai. En Linux combina Landlock, que cubre el
filesystem y, desde la ABI 4, TCP, con un filtro seccomp para la red,
no_new_privs y un proceso padre que supervisa.
Medí la versión 0.80.0 desde el asset del release
nono-v0.80.0-x86_64-unknown-linux-gnu.tar.gz, después de verificarlo contra el
SHA256SUMS del release. El commit de origen es
8bb041356d48f771a221e58c98d9b1ff442e76fe (tag v0.80.0), y la licencia es
Apache-2.0. El adapter es runner/adapters/nono.py.
Escribe un profile y ejecuta
nono -s run --no-diagnostics --no-rollback --no-audit --profile <file> -- sh -c <cmd>.
Configuración¶
| campo del contract | profile de nono |
|---|---|
| (base) | los grupos por defecto, menos system_write_linux, user_tools, homebrew_linux y dangerous_commands*. Lectura en /usr/local/bin, escritura en /dev/null, workdir.access: none |
| acceso de lectura a todo lo que no esté negado (el valor por defecto de los contracts) | filesystem.read sobre todo el árbol de prueba, {root} |
writable_roots |
filesystem.allow, o allow_file para un solo archivo |
read_allows |
filesystem.read, o read_file |
local_ports |
network.block más network.connect_port. Landlock filtra TCP solo por puerto, así que el permiso no queda limitado a loopback |
allow_domains |
network.allow_domain, a través del proxy de nono |
| sin campo de red | network.block: true |
allowlist de entorno, env_passthrough |
environment.allow_vars con PATH, HOME, LANG y TERM, más las variables que se dejen pasar |
inherit_env |
sin allow_vars. nono igual quita variables del loader como LD_PRELOAD |
max_processes |
--max-processes |
isolate_process_info |
nada que configurar: nono solo da acceso a /proc/self y a algunos otros archivos de /proc. Su opción process_info_mode existe solo en macOS |
harden_syscalls, strict |
nono no tiene un modo aparte para estos, así que se miden contra su filtro por defecto |
Algunos campos no tienen equivalente en nono:
- Un
read_deniesowrite_deniesque cae dentro de algo permitido. Landlock solo puede dar acceso, y nono se niega a arrancar cuando un deny se superpone con un permiso. isolate_pids. nono no crea un namespace de PID.materialize_absent_handoff.[broker]tal como lo definen los contracts. nono sí tiene un proxy de credenciales, pero usa sus propias rutas.
Casi todos los 78 resultados unsupported de nono vienen de ese primer punto. Los
contracts suponen un modelo donde todo se puede leer salvo que esté negado. nono
funciona al revés: das acceso al workspace y a nada más, y un
secreto queda protegido porque nadie le dio acceso. Con esa configuración la mayoría de estos
secretos nunca se podrían leer. La [policy] de los contracts no describe
esa configuración, así que no la medí en este run. Una versión de los
contracts de read-deny escrita para ese modelo me permitiría medirla.
Medido en el modelo propio de nono (run by-omission)¶
El run del 2026-10-15 de abajo midió nono contra el modelo neutral, donde 58 de sus
78 resultados unsupported venían de campos read_denies / write_denies que el
modelo de solo permisos de nono no expresa. El run by-omission
reformula los 23 contracts de lectura traducibles en el modelo propio de nono
(read_scope = "granted": dar acceso al workspace, omitir el secreto) y mide los
tres sandboxes. nono pasa de 23 unsupported a 18 contained, 4 escaped, 1
vacuous: mantiene esas garantías de lectura cuando se piden en el modelo que
habla. Los 4 escapes (un hueco de Landlock con sockets unix y tres casos de un permiso que
es un symlink hacia el área protegida) y los 39 contracts que no se pueden expresar
en el modelo granted están detallados ahí.
Resultados del run del 2026-10-15¶
Medido de nuevo el 2026-10-16 con el runner que implementa
spec/verdicts.md(run). Misma versión, mismo kernel; cambió un resultado:filesystem-writable-root-replaceahora es contained (sucontrol-brokeera una falla del contract). Todos los demás resultados de abajo no cambian.
Linux 6.8, Landlock ABI 4. El método y los detalles del host están en el README del run.
| contained | escaped | control-broke | unsupported | vacuous | needs-feature | n/a |
|---|---|---|---|---|---|---|
| 31 | 33 | 6 | 74 | 7 | 1 | 12 |
nono se configuró tan estricto como su documentación lo permite. De 33 escapes, 5 rompieron una regla que yo había configurado.
Escapes en reglas configuradas¶
Se pueden cambiar los metadatos de un archivo legible (filesystem-chmod-outside,
filesystem-utime-outside, filesystem-xattr-outside). Con acceso de lectura al
árbol de prueba, el agente pudo cambiar el modo, las marcas de tiempo y los
atributos extendidos de archivos fuera de sus writable roots. Landlock no
cubre estas operaciones. Si nono no hubiera dado acceso de lectura a esos archivos
desde un principio, el agente no habría podido llegar a ellos.
Se sigue un writable root que es un symlink (fs-writable-root-symlink-out,
fs-writable-root-symlink-parent). Si un writable root es un symlink que apunta
fuera de los otros roots, su destino pasa a ser escribible. El contract espera que el
sandbox rechace un root así. nono lo acepta.
Escapes con una nota¶
ipc-abstract-unix-socket(§): limitar las conexiones a sockets unix abstractos requiere Landlock ABI 6, que necesita kernel 6.12 o más nuevo. Este host tiene ABI 4.
Escapes en valores por defecto¶
27 escapes tienen que ver con comportamiento que ningún campo de [policy] pide:
- Trust handoff (17). Dentro de un writable root, nono no protege
.git/hooks,.git/config,.claude/, los directorios de editores ni los archivos rc. - Aislamiento (7).
- El agente pudo crear namespaces de usuario anidados (3 contracts).
- Cuando nono se lanza como root, el comando conserva sus capabilities.
/proc/1se puede leer, y también/proc/<pid>/memyenvironde un ancestro.- En este kernel nada impide que el agente envíe señales a procesos fuera del sandbox (
isolation-signal-host-process,§: necesita Landlock ABI 6). - Ciclo de vida (3).
- Un proceso hijo dejado en segundo plano siguió corriendo después de que el comando terminó.
- Después de que le envié SIGTERM al launcher, los procesos dentro del sandbox siguieron corriendo y escribieron el archivo marcador.
.mcp.jsonno está protegido.
Son diferencias de valores por defecto. De los tres grupos, los resultados de ciclo de vida son los que merecen la mirada más atenta.
Otros resultados¶
control-broke (6):
- Un writable root escrito
absent/../real. nono exige que exista cada ruta a la que da acceso. - Un writable root que se reemplaza durante el run.
- El relanzamiento anidado: el nono interior no pudo arrancar.
- Tres controls que necesitan llamadas al sistema que el filtro de red de nono bloquea: io_uring, un dominio de socket poco común y la IPC de multiprocessing.
needs-feature (1): lifecycle-effect-log-tamper. nono mantiene un registro de auditoría,
pero no conecté el adapter al lugar donde se guarda el log de un run
(por confirmar).