EN / FR
nono / guide visuel d'architecture

Tout interdire.
Puis ouvrir des portes.

nono exécute des agents IA non fiables dans un sandbox imposé par le noyau. Rien n'est accessible sans une capability qui l'accorde explicitement ; et une fois les murs dressés, aucun processus à l'intérieur ne peut les abattre.

Imposé par l'OS · Landlock + Seatbelt Irréversible une fois appliqué Bibliothèque cœur sans politique
✕ ~/.ssh ✕ trousseau ✕ données navigateur ✕ historique shell ✕ réseau ouvert Agent IA pid 4242 · sandboxé ./workspace rw runtime node ro 127.0.0.1:8888 → proxy MUR IMPOSÉ PAR LE NOYAU
le modèle mental : des murs par défaut, les capabilities comme seules portes
Langage de couleurs, identique dans tous les diagrammes refusé / bloqué capability accordée agent non fiable machinerie nono noyau OS
01 · le problème

Un agent IA est un inconnu très rapide avec votre shell

Les agents de coding lisent des fichiers, en écrivent, exécutent des commandes et appellent le réseau ; des milliers de fois par session, plus vite que vous ne pouvez relire. Une prompt injection, une dépendance empoisonnée ou un simple bug peut rendre chacune de ces actions hostile.

Les garde-fous applicatifs (« ne touche pas à ~/.ssh, s'il te plaît ») vivent à l'intérieur du processus qu'ils surveillent ; quiconque contrôle l'agent contrôle le garde-fou. nono descend la frontière au niveau du système d'exploitation : le noyau lui-même refuse le syscall. L'agent ne se tient pas bien parce qu'on le lui a demandé gentiment ; il se tient bien parce que refusé est l'état physique par défaut de son monde, et que chaque chemin ou port accordé est une exception délibérée et auditable.

02 · sandboxing 101

Ce que le noyau offre déjà

Les deux grands noyaux desktop offrent nativement à un processus le moyen de se verrouiller lui-même avant de faire quoi que ce soit de risqué. nono est un orchestrateur de ces deux primitives ; il n'invente aucune enforcement qui lui soit propre.

Landlock

Linux 5.13+ · LSM

Un processus construit un ruleset de permissions sur le système de fichiers (et, sur les noyaux récents, sur le réseau), puis demande au noyau de s'y restreindre. À partir de ce syscall, les règles s'appliquent au processus et à tous les enfants qu'il créera jamais.

  • Strictement une allow-list. Ce que le ruleset ne mentionne pas est inaccessible. Impossible d'exprimer « autorise ce répertoire sauf ce fichier ».
  • ABI versionnée (v1-v6). Les noyaux récents ajoutent le filtrage TCP connect/bind par port (v4) et le cloisonnement signaux/IPC (v6). nono détecte l'ABI à l'exécution.
  • Lacunes sur les vieux noyaux. Là où Landlock ne peut pas filtrer le réseau, nono se rabat sur l'interception de syscalls seccomp ; et si même cela est impossible, il refuse de démarrer plutôt que de tourner ouvert.

Seatbelt

macOS · sandbox_init()

Un processus remet au noyau un profil, un petit programme de type Scheme qui part de (deny default) et énumère les exceptions. nono génère ce profil à partir de vos capabilities et échappe chaque valeur qui y entre.

  • Le deny-dans-allow fonctionne. Un profil peut autoriser un répertoire tout en refusant un fichier à l'intérieur ; c'est pourquoi certaines règles deny n'existent que sur macOS.
  • Extensions. Un superviseur peut remettre à un processus sandboxé en cours d'exécution un jeton qui élargit l'accès à un chemin précis ; c'est le canal médié que nono utilise pour l'élévation interactive.
  • Surprises de symlinks. /etc est en réalité /private/etc ; les profils doivent couvrir les deux visages de chaque alias, et la canonicalisation des chemins de nono s'en charge.

La propriété sur laquelle tout repose : l'irréversibilité

Sur les deux plateformes, appliquer le sandbox est une porte à sens unique. Aucune API ne permet d'élargir un sandbox vivant depuis l'intérieur ; ni pour nono, ni pour l'agent, ni pour du code que l'agent aurait piégé. Chaque règle est héritée par tous les descendants. Cette unique garantie du noyau est ce qui permet à nono de tenir des promesses qui survivent à un agent totalement compromis.

03 · la vue d'ensemble

Quatre couches, une seule direction de confiance

L'intention descend et se concrétise à chaque étape : les flags deviennent des capabilities, les capabilities deviennent des règles noyau. Rien ne remonte ; une couche basse ne demande jamais la permission à une couche haute.

Vous Déclarer l'intention nono run --profile node-dev -- claude : un nom de profil, quelques flags, une commande à confiner.
flags + profil → politique résolue
nono-cli Décider la politique Détient toutes les opinions : profils, groupes de politique (listes deny, runtimes, chemins système), packs, hooks, secrets, le proxy réseau, l'UX du rollback et le choix de la stratégie d'exécution. Tout cela est de la donnée et du code que vous pouvez auditer.
tout se réduit à un seul CapabilitySet
nono (bibliothèque) Construire le mécanisme Un primitif de sandbox pur, avec zéro politique intégrée. Il canonicalise et valide chaque chemin accordé, traduit le CapabilitySet en ruleset Landlock ou profil Seatbelt, et l'applique. Il accorde exactement ce qu'on lui a dit ; rien de plus, rien d'implicite.
ruleset / profil → un syscall irréversible
Noyau Imposer, pour toujours Landlock ou Seatbelt vérifie chaque ouverture de fichier, chaque connect, chaque signal, pour le processus et tous ses descendants, jusqu'à leur sortie. Aucun code userspace sur ce chemin.

Lisez la page de bas en haut si vous évaluez les garanties (que peut réellement promettre le noyau ?), de haut en bas si vous découvrez le code (où irait ma modification ?).

04 · la frontière porteuse

Le mécanisme ne porte jamais d'opinion

La décision la plus structurante de nono : la bibliothèque sait comment sandboxer, seule la CLI sait quoi sandboxer. Si la bibliothèque embarquait ne serait-ce qu'un seul « défaut raisonnable », chaque intégrateur hériterait d'une politique invisible qu'il n'a jamais relue.

la bibliothèque livre le mécanisme

nono

Déterministe, embarquable, sans politique. Tout ici répond à « comment imposer exactement ceci ? »

  • CapabilitySet · un builder de grants : chemins (lecture / écriture, fichier ou sous-arbre), mode réseau, ports, sockets Unix, cloisonnement signaux & IPC, limites de ressources.
  • Sandbox · un point d'entrée unique qui transforme un CapabilitySet en état noyau ; le point de non-retour.
  • SandboxState · un instantané sérialisé des grants, revalidé à chaque chargement pour qu'un état stocké ne puisse pas faire passer des chemins en douce devant la canonicalisation.
  • QueryContext · répond à « ce chemin serait-il autorisé ? » sans rien appliquer ; alimente nono why.
  • keystore · résout des références de credentials en valeurs mémoire zéroïsées.
  • undo · instantanés adressés par contenu pour le rollback : object store, arbre de Merkle, filtres d'exclusion.
la cli livre la politique

nono-cli

Opinionnée, tournée vers l'utilisateur, remplaçable. Tout ici répond à « que faut-il autoriser, et comment le dire à l'humain ? »

  • Groupes de politique · ~40 lots nommés (deny credentials, runtime node, config git, commandes dangereuses…) résolus par plateforme.
  • Profils · intégrés, écrits par l'utilisateur, ou tirés comme packs signés ; ils composent groupes, grants, règles réseau, hooks.
  • Stratégies d'exécution · Direct vs Supervised ; fork, supervision, reap.
  • Câblage du proxy · filtrage d'hôtes et injection de credentials autour de l'enfant sandboxé.
  • Cycle de vie du rollback · quand snapshotter, quoi exclure, comment restaurer.
  • Diagnostics & UX · le footer « pourquoi ça a échoué », les flags suggérés, l'apprentissage depuis les refus.
CapabilitySet est le contrat

Chaque profil, flag, pack et groupe de politique ; quelle que soit sa forme d'expression ; doit se réduire à cette unique valeur explicite et inspectable avant toute application. La FFI C expose exactement ce contrat aux autres langages, et aucune opinion de la CLI ne l'accompagne.

05 · flux d'exécution

La vie de nono run

Du clavier à l'agent sandboxé en deux phases : une phase décider, hors sandbox, qui transforme l'intention en CapabilitySet, puis une scission : un processus reste dehors comme superviseur, l'autre entre pour toujours.

décider · 1

Parser et charger le profil

Les flags sont parsés ; le profil nommé (intégré, fichier utilisateur ou pack installé) est chargé et fusionné avec la ligne de commande.

décider · 2

Résoudre la politique en un CapabilitySet

Les groupes de politique s'étendent en grants concrets. Chaque chemin est canonicalisé et doit exister. Un grant qui chevauche une règle deny, ou qui touche le répertoire d'état de nono, est une erreur fatale ; pas un avertissement.

décider · 3

Préparer les services d'exécution

Les secrets sont résolus depuis le keystore vers la mémoire. Si la politique réseau filtre le trafic, le proxy démarre maintenant et le CapabilitySet est réduit à « loopback, un port ». Si le rollback est actif, un instantané de référence du workspace est pris.

décider · 4

Choisir la stratégie

Tout ce qui exige un parent vivant ; proxy, rollback, élévation interactive, diagnostics ; sélectionne Supervised. Sinon Direct applique le sandbox sur place et exec : nono disparaît et seuls les murs restent.

fork()

deux processus existent désormais, aux destins très différents

Parent · le superviseur

reste hors sandbox
  1. Se durcit instantanément. L'attachement d'un débogueur est bloqué dès le retour du fork ; si le durcissement échoue, l'enfant est tué et le run avorte. Un processus non sandboxé ne doit pas être un processus attachable.
  2. Supervise. Récolte l'enfant, relaie les signaux et le terminal, sert le proxy, et répond aux notifications du noyau quand l'enfant touche quelque chose hors de ses grants ; ce qui peut devenir une invite interactive « autoriser ? ».
  3. Boucle la boucle. À la sortie de l'enfant : les after-hooks s'exécutent, un instantané incrémental de rollback enregistre ce qui a changé, et chaque refus observé devient un footer de diagnostic avec le flag exact qui l'aurait autorisé.
  4. Sort avec le code de l'enfant. Pour votre shell, ce n'était que la commande.

Enfant · devient l'agent

sandboxé à vie
  1. Se reconnecte au socket du superviseur ; le seul socket Unix qu'il a le droit d'atteindre.
  2. point de non-retourApplique le sandbox. Le CapabilitySet devient un ruleset Landlock ou un profil Seatbelt, et le noyau l'impose. À partir de cette ligne, même le propre code de ce processus ne peut plus élargir son monde.
  3. Devient l'agent. execve() remplace l'enfant par la commande réelle. L'agent naît déjà entre les murs, les transmet à chacun de ses sous-processus, et ne peut pas leur ordonner de bouger.
Pourquoi sandboxer dans l'enfant, et pas avant le fork ? Le parent a besoin de pouvoirs que le sandbox détruirait : écrire des instantanés, faire tourner le proxy, vous solliciter. La scission vient donc d'abord, et seul le processus qui deviendra l'agent franchit la porte à sens unique.
06 · deux façons d'exécuter

Direct ou Supervised

Une seule question décide de tout : quelque chose doit-il continuer à vivre hors du sandbox après le démarrage de l'agent ?

$ nono wrap -- make build

Direct appliquer, puis exec

Le sandbox est appliqué au processus courant, puis exec le remplace par votre commande. nono cesse d'exister ; pas de superviseur, pas de processus supplémentaire, aucune surface d'attaque au-delà des règles noyau elles-mêmes.

Le compromis : sans personne hors des murs, rien de ce qui exige un dehors ne peut fonctionner.

empreinte minimale zéro surcoût à l'exécution proxy rollback invites d'élévation footer de diagnostic
$ nono run --profile node-dev -- claude

Supervised fork, puis sandbox de l'enfant

Le défaut pour le travail d'agent. Le parent reste dehors comme superviseur durci ; seul l'enfant est confiné. Tout l'interactif et le forensique vit ici : le proxy réseau, les instantanés de rollback, les invites « autoriser ce fichier ? », et l'explication post-run de chaque refus.

Le compromis : un processus privilégié demeure, donc il se défend (attachement de débogueur bloqué, échec de durcissement fatal).

proxy + injection de credentials rollback invites d'élévation diagnostics
07 · le workspace

Quatre crates, un noyau de vérité

Le workspace Cargo reflète l'architecture : tout dépend de la bibliothèque nono ; la bibliothèque ne dépend d'aucun des autres.

nono-cli le produit : politique, profils, packs, stratégies, UX nono-proxy filtre réseau, injection de credentials, interception TLS nono-ffi ABI C + header généré : embarquez le primitif partout nono la bibliothèque : capabilities, sandbox, état, query, keystore, undo SANS POLITIQUE · NE DÉPEND DE RIEN ICI tourne dans le superviseur construit & applique ré-exporte le contrat
les flèches se lisent « dépend de » ; confiance et réutilisation convergent vers la bibliothèque

Cette forme est une garantie, pas une commodité : la bibliothèque ne pouvant pas voir la CLI, aucune politique ne peut fuir vers le primitif, et quiconque embarque nono (via Rust ou la FFI C) part d'un mécanisme réellement neutre.

08 · les seconds rôles

Les sous-systèmes autour du sandbox

Le sandbox dit non. Ces sous-systèmes rendent cela vivable : réparer les dégâts, atteindre le réseau sans danger, partager la politique, garder les secrets secrets, et comprendre chaque refus.

undo / rollback

Une machine à remonter le temps pour le workspace

Avant que l'agent ne tourne, le contenu de chaque fichier suivi est haché dans un object store adressé par contenu, et un arbre de Merkle scelle tout le workspace sous un unique hash racine. Après le run, une seconde passe montre exactement ce qui a changé ; et peut tout remettre en place, atomiquement.

aller plus loin

L'adressage par contenu signifie qu'un contenu identique n'est stocké qu'une fois, que les instantanés sont bon marché, et que la corruption saute aux yeux : re-hacher un objet et comparer. La racine de Merkle réduit « quelque chose a-t-il changé ? » à une seule comparaison.

Les exclusions tiennent les répertoires de VCS (.git et consorts) et les globs configurés par l'utilisateur hors du suivi, pour qu'une restauration ne se batte jamais contre votre gestion de versions. Les fichiers temporaires de sauvegarde atomique des éditeurs sont suivis à part pour qu'une restauration ne laisse pas d'orphelins.

Les primitives (object store, arbre de Merkle, gestionnaire d'instantanés, filtre d'exclusion) vivent dans la bibliothèque ; la CLI possède le cycle de vie : quand snapshotter, comment parcourir les sessions, et la restauration interactive.

proxy réseau

Une seule porte vers internet, avec un gardien

Sous politique réseau, le sandbox n'autorise qu'une seule destination : loopback, un port ; là où le proxy écoute, dans le superviseur. Chaque requête de l'agent doit passer devant lui.

aller plus loin

Filtrage d'hôtes : les tunnels HTTPS sont vérifiés contre votre allowlist plus une denylist intégrée (endpoints de métadonnées cloud et similaires). Les proxys d'entreprise peuvent être chaînés derrière ; la denylist de nono s'applique toujours comme plancher.

Injection de credentials : l'agent appelle une route locale sans vrai secret ; le proxy attache la véritable clé d'API (ou signe la requête, p. ex. AWS SigV4) au passage. Un environnement d'agent qui fuite ne fuite rien d'utilisable.

L'interception TLS, quand des règles au niveau domaine exigent de lire dans le HTTPS, utilise une CA de session générée à chaque run ; reconnue par ce seul sandbox, jetée ensuite.

packs & confiance

De la politique installable, sans se faire berner

Les profils voyagent en packs tirés d'un registre. Parce qu'un pack décide littéralement de ce qu'un agent peut toucher, les packs sont signés et vérifiés avant usage.

keylessle défaut : transparence de certificats Sigstore, vérifiée contre la racine de confiance publique.
keyedvotre propre clé ECDSA P-256, pour les registres internes ou isolés ; aucune infrastructure publique contactée.
unsignedchecksum seulement ; exige un flag --insecure explicite et avertit bruyamment.
aller plus loin

Pas de dégradation silencieuse : une fois une clé de confiance configurée, rien (ni fichier de flag, ni variable d'environnement) ne peut discrètement retirer la vérification ; seul le flag insecure explicite le peut, et il s'annonce.

La confiance est graduée par origine : les échappatoires comme les règles Seatbelt brutes ne sont honorées que dans les profils que vous avez écrits ; tout pack ou profil de registre en est purgé avant usage, car une telle règle peut tout accorder.

secrets

Des credentials comme références, jamais comme valeurs

Un profil nomme les secrets par référence ; op://, keyring://, apple-password://, env:, file:, cmd: ; résolus hors du sandbox au lancement et injectés comme variables d'environnement. Aucun secret n'apparaît jamais sur une ligne de commande ou dans un log.

aller plus loin

En mémoire, les valeurs vivent dans des wrappers zéroïsants : effacées à la destruction, pas laissées à un core dump. En sortie, chaque schéma a une forme caviardée, pour que les diagnostics puissent dire quel credential sans jamais le montrer.

Combiné au mode injection du proxy, l'agent peut recevoir un simple placeholder pendant que le vrai credential n'existe que dans le superviseur ; la posture la plus forte pour du code non fiable, détaillée ci-dessous dans le motif du jeton fantôme.

secrets × proxy · la posture la plus forte

Le motif du jeton fantôme

Injecter un secret en variable d'environnement est une bonne hygiène, mais le secret vit toujours à l'intérieur du sandbox ; un agent qui peut lire son propre environnement peut le faire fuiter. Le motif du jeton fantôme retire complètement le secret du sandbox : l'agent détient un leurre limité à la session, et l'échange contre le vrai credential se fait dans le superviseur, au passage vers l'extérieur.

SANDBOX Agent IA BASE_URL = http://127.0.0.1:8888 API_KEY  = nono-tok-4f2a91… ↑ fantôme · sans valeur hors de ce run :8888 SUPERVISEUR · HORS DES MURS op://vault/openai → sk-live-9xk2… 🔑 proxy valide le fantôme · substitue · filtre Authorization: nono-tok… Authorization: sk-live-9xk2… (réelle) api.openai.com evil.exfil.example connexion directe → bloquée par le noyau hôte hors allowlist → refusé par le proxy
la vraie clé n'existe qu'à droite du mur ; les deux chemins d'exfiltration meurent, chacun à une couche différente
1 · résoudre dehors

Au lancement, le superviseur résout les références de credentials (op://, keyring://…) en mémoire. La vraie clé naît hors des murs et ne les franchit jamais.

2 · distribuer un leurre

L'environnement de l'agent reçoit un jeton fantôme et une URL de base en loopback. Pour le SDK de l'agent, c'est une clé d'API parfaitement normale ; elle n'authentifie qu'auprès du proxy de cette session.

3 · substituer à la porte

À chaque requête, le proxy valide le fantôme, le remplace par le vrai credential, et transmet. Il peut même signer les requêtes sortantes (refresh OAuth2, AWS SigV4) pour qu'aucune clé longue durée n'existe sur le réseau.

4 · la compromission, un haussement d'épaules

Supposez le pire : la prompt injection gagne, l'agent déverse tout son environnement à un attaquant. Celui-ci obtient un jeton invalide hors du loopback de cette session, qui meurt à la fin du run. Il n'y avait rien à voler.

diagnostics

Chaque « non » vient avec une explication

Les refus ne sont pas des échecs silencieux. Le superviseur enregistre chacun d'eux, et à la fin du run imprime un footer : ce qui a été bloqué, pourquoi, et le flag ou grant exact qui l'autoriserait la prochaine fois. Itérer sur un profil est une boucle de feedback, pas de l'archéologie.

aller plus loin

nono why répond aux questions de permission sans rien exécuter, en rejouant un état de capabilities sauvegardé à travers la même logique de résolution que celle du sandbox ; le moment du grant et le moment de la question ne peuvent jamais diverger.

L'apprentissage depuis les refus boucle la boucle : les grants qui manquaient à un run peuvent être sauvegardés directement dans le profil utilisé.

ffi c

Le primitif, embarquable partout

Une ABI C avec header généré expose le contrat de la bibliothèque ; construire un capability set, appliquer le sandbox, interroger les permissions, sérialiser l'état, lire les diagnostics ; depuis tout langage capable d'appeler du C.

aller plus loin

La surface est délibérément la moitié mécanisme seulement : aucun des profils, packs, proxy ou groupes de politique de la CLI ne franchit la frontière. Un intégrateur reçoit le même primitif neutre sur lequel la CLI elle-même est construite, et apporte sa propre politique.

09 · invariants de sécurité

Les règles que le code refuse d'enfreindre

Ce ne sont pas des lignes directrices ; ce sont des propriétés contre lesquelles chaque changement est vérifié. Si une fonctionnalité ne peut pas être construite sans en plier une, la fonctionnalité perd.

Irréversible par conception

Aucune API n'élargit un sandbox vivant. L'expansion n'existe qu'à travers des canaux médiés : une invite d'élévation interactive traitée par le superviseur, ou un jeton d'extension macOS pour un chemin précis. Jamais en silence.

Fail secure, toujours

Si une règle ne peut pas être imposée exactement comme demandé ; noyau ancien, fonctionnalité manquante, config impossible à charger ; nono s'arrête en erreur plutôt que de tourner affaibli. Il n'y a pas de mode « best effort » pour la sécurité.

Canonicaliser à la frontière

Les chemins sont résolus et vérifiés à l'existence au moment du grant, puis revalidés à chaque rechargement d'état stocké. Les échanges de symlinks, les visages d'alias comme /etc vs /private/etc et les astuces de préfixes de chaînes meurent ici.

Le refus est l'état par défaut

Une capability non accordée n'existe pas. Les groupes accordent au minimum, lecture et écriture sont des décisions séparées, et le répertoire d'état de nono ne peut jamais être accordé ; l'agent ne peut réécrire ni sa propre histoire ni la mémoire de son geôlier.

Le mécanisme ne porte aucune politique

La bibliothèque applique exactement le CapabilitySet qu'on lui donne. Chaque opinion ; listes deny, commandes dangereuses, profils ; vit dans la CLI, sous forme de données que vous pouvez lire et auditer.

Le superviseur se protège lui-même

Le seul processus resté hors des murs bloque l'attachement d'un débogueur à l'instant du fork, et traite l'échec du durcissement comme fatal : tuer l'enfant, avorter le run.

Les secrets sont des références

Les credentials se résolvent en mémoire, se zéroïsent à la destruction et n'apparaissent dans les logs que caviardés. Avec l'injection par proxy, la vraie valeur n'entre jamais dans le sandbox.

Les échappatoires sont personnelles

Les règles plateforme brutes peuvent tout accorder, elles ne sont donc honorées que depuis les profils que vous avez écrits vous-même. Tout ce qui vient d'un pack ou d'un registre en est purgé avant usage.

10 · tout le modèle en une ligne
La CLI décide.
La bibliothèque construit.
Le noyau impose, irréversiblement.

Retenez cela, plus une image : un agent dans une pièce scellée où chaque porte a été posée à dessein. Tout le reste dans nono ; profils, packs, proxy, rollback, diagnostics ; existe soit pour bien choisir ces portes, soit pour vivre élégamment avec les murs.