Bac à sable et sécurité
Pourquoi l’isolation compte ici
Section intitulée « Pourquoi l’isolation compte ici »Un agent lit des fichiers, exécute des commandes shell et appelle des API externes en votre nom. La défaillance qui vous importe est rarement une malveillance — c’est un agent qui se trompe de chemin et réécrit votre copie de travail, ou qui pousse une branche que vous n’aviez pas fini de relire. La question à laquelle le produit doit répondre n’est donc pas « l’agent est-il digne de confiance », mais « quel est le rayon d’impact lorsqu’il se trompe ».
Control Center y répond par couches, et ces couches sont indépendantes à dessein. Si l’une manque, les autres tiennent. Cela compte, car la couche la plus externe — le bac à sable du système d’exploitation — est la seule conditionnelle : elle dépend d’un hôte qui offre un backend, et sous Windows aucun hôte n’en offre.
Ce qui limite réellement un agent
Section intitulée « Ce qui limite réellement un agent »Cinq choses bornent une exécution d’agent sur tout hôte, avec ou sans bac à sable. Aucune n’est une frontière du noyau ; toutes sont réelles. Là où l’hôte offre un backend, le bac à sable du système d’exploitation se place au-dessus d’elles, et la section « Ce que le bac à sable ajoute et où il s’arrête » ci-dessous décrit ce que cela change.
Son propre arbre de travail en copie sur écriture. Chaque conversation
provisionne sa propre copie de travail de chaque dépôt lié, clonée en copie sur
écriture via le natif rift embarqué plutôt que copiée octet par octet, si
bien que l’isolation est assez bon marché pour être le comportement par défaut.
Le dépôt enregistré que vous avez ajouté à l’espace de travail n’est pas le
répertoire dans lequel travaille l’agent — deux conversations qui touchent le
même dépôt ne voient pas les modifications de l’autre, et aucune n’édite votre
copie de travail.
Le grain mérite d’être précisé : l’arbre de travail est propre à la
conversation, pas à l’agent. Deux agents d’une même conversation obtiennent
des répertoires de travail superposés distincts qui partagent le repos/ de
cette conversation — c’est justement le but, puisqu’ils collaborent sur une
même modification — tandis que leurs répertoires d’agent privés restent
séparés. La copie sur écriture est le seul backend partout où rift est
livré : un système de fichiers incapable de reflink, ou un répertoire de données
situé sur un autre volume que le dépôt, fait échouer le provisionnement au lieu
de retomber sur un repli. Windows fait exception — aucun backend de copie sur
écriture n’y existe, donc git worktree est le backend, et non une dégradation
silencieuse.
Confinement de chemins dans les outils de fichiers du harnais intégré. Les
outils read, write, edit, apply_patch, search, find et
search_files du harnais résolvent chaque chemin par rapport au répertoire de
travail de l’exécution, plus des racines partagées — le répertoire repos/ de
la conversation, où vivent les arbres de travail, et le répertoire des pièces
jointes de l’espace quand il existe — et refusent tout ce qui atterrit en
dehors. Les chemins absolus ne sont acceptés que s’ils étaient déjà à
l’intérieur. Un lien symbolique situé dans l’arbre de travail et pointant en
dehors est résolu puis refusé, lui aussi. C’est pourquoi un agent ne peut pas
faire de write dans ~/.ssh, même sans bac à sable dans l’équation.
La surface d’outils du mode. Le
mode d’une conversation décide quels outils
sont matérialisés pour l’exécution. Dans les modes en lecture seule (plan,
review, orchestrate), les outils de mutation ne figurent tout simplement
pas dans le registre, et la politique de commandes shell de ces modes refuse
d’emblée toute commande de mutation — git commit, git push, npm install,
docker et les autres — au lieu de demander une confirmation. Un outil absent
ne peut pas être appelé, ce qui est une garantie plus forte qu’un outil présent
mais filtré.
Les garde-fous d’action unifiés. Chaque outil de mutation déclare les
classes d’effet qu’il peut produire, et le moteur de politiques résout chacune
d’elles en autorisation, demande de confirmation ou refus avant l’exécution de
l’appel. Un prompt sans approbateur connecté se résout en refus — une
question à laquelle personne ne peut répondre n’est jamais un oui. Voir
Garde-fous pour le modèle de résolution.
Identifiants délivrés par le courtier et environnement assaini. Les identifiants sont émis à chaque lancement et révoqués à la fin de l’exécution, et le jeton GitHub d’une exécution peut lire mais pas pousser — voir « Les push passent par Control Center » plus bas. ACP, le bash du harnais et
Claude Code sous l’encapsulation native reçoivent un environnement dépouillé des
vecteurs d’injection de l’éditeur de liens dynamique et de l’interpréteur
(LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND et
leurs semblables) avant le démarrage du processus. Claude Code via
NoSandboxAdapter hérite de l’environnement de l’hôte.
Là où ces limites s’estompent
Section intitulée « Là où ces limites s’estompent »Les outils de fichiers du harnais s’exécutent dans le processus du serveur, et
non dans un processus enfant : aucun profil de bac à sable ne pourrait donc les
contraindre, même s’il était attaché — seul bash passe par l’exécuteur de
commandes encapsulé. Leur confinement est la vérification de chemin décrite
plus haut, laquelle est donc porteuse plutôt qu’une seconde ligne de défense.
L’outil bash est l’autre endroit où il faut être précis. Ses commandes sont
évaluées au regard de la politique de commandes du mode, puis lancées avec un
environnement assaini et l’arbre de travail comme répertoire de travail. Là où
un backend existe, ce lancement est encapsulé : c’est donc le bac à sable qui
empêche une commande shell d’écrire en dehors de l’arbre de travail ; là où il
n’y en a pas — Windows, ou Linux sans bwrap ni socat — la politique de
commandes est la seule chose entre la commande et le reste du disque. En mode
chat, la politique refuse catégoriquement une liste fixe (élévation de
privilèges, destruction du système et du disque, évasion de conteneur et
d’espace de noms) et demande une confirmation pour les mutations d’état distant
(git push, gh pr create, publications et installations de paquets). Tout ce
qui ne figure sur aucune des deux listes s’exécute. Les pipelines, les chaînes
et les sous-shells sont analysés, et chaque sous-commande est évaluée
séparément, la décision la plus restrictive l’emportant.
Claude Code, lorsqu’il est utilisé comme adaptateur, est le cas où le bac à
sable pèse le plus lourd. Control Center le lance avec
--dangerously-skip-permissions, parce qu’un claude -p non interactif se
bloquerait sinon indéfiniment sur sa propre invite d’approbation. Ses appels de lecture, d’écriture et d’édition ont lieu dans son propre processus, et Control Center ne les voit jamais. Ses appels Bash sont envoyés, avant leur exécution, à un hook sur le serveur qui classe la ligne de commande et interroge la règle correspondante, et les outils mcp__* que Control Center sert passent la barrière comme d’habitude. Là où un backend existe, ces appels invisibles se déroulent tout de
même à l’intérieur de l’encapsulation du bac à sable : c’est donc le système
d’exploitation qui est la frontière autour d’eux. Là où il n’y en a pas, il n’y
a rien. Chaque adaptateur l’énonce dans sa matrice d’application — voir la
matrice d’honnêteté des
adaptateurs.
Les push passent par Control Center
Section intitulée « Les push passent par Control Center »Il n’existe aucun indicateur d’autorisation par agent. Le droit pour un agent de créer un commit, de pousser, d’ouvrir une pull request, d’écrire dans un outil de suivi ou d’atteindre le réseau relève des garde-fous — autoriser, demander d’abord ou refuser — et les mêmes règles s’appliquent quel que soit l’adaptateur qui exécute l’agent.
Un push est l’action qui doit tenir même face à un agent qui cherche à
contourner une règle : il est donc appliqué par ce que l’agent détient plutôt
que par ce qu’il exécute. Avec une GitHub App configurée, une exécution dans un
arbre de travail GitHub reçoit un jeton d’installation limité au dépôt, qui peut
lire le contenu et travailler avec les pull requests mais pas pousser. La
configuration git de l’exécution réécrit chaque URL de push GitHub
(https://github.com/, git@github.com:, ssh://git@github.com/) vers la
passerelle d’exécution d’agent, un point de terminaison en boucle locale sur
cc_server. La passerelle lit les refs que le push met à jour, interroge la
règle Pousser vers un dépôt distant — une règle limitée à des branches,
comme « refuser les push vers main », s’applique donc — et ne transmet le
push à GitHub qu’ensuite, avec un jeton d’écriture qu’elle émet à cette fin et
ne remet jamais à l’agent. Un push refusé apparaît dans la sortie de git
elle-même :
! [remote rejected] main -> main (Control Center policy: …)La passerelle ne transmet les push qu’au dépôt propre à l’arbre de travail ou à un dépôt GitHub lié à l’espace de travail. Les fetch vont directement à GitHub avec le jeton de lecture.
Les autres commandes shell rencontrent une barrière plus souple. L’outil bash
du harnais et, via un hook, l’outil Bash de Claude Code classent chaque ligne
de commande et interrogent la règle dont elle relève : git commit, merge,
rebase, cherry-pick, revert et am relèvent de Créer un commit ;
gh pr create de Ouvrir une pull request ; gh pr merge, review,
comment et close de Publier une revue ou fusionner ; gh issue create,
edit, comment et close de Écrire dans un outil de suivi externe ; les
installations de paquets de Installer un paquet ; curl et wget de
Accéder au réseau. Cela reconnaît l’écriture habituelle d’une action, et un
agent déterminé à en cacher une (eval, un fichier de script, un appel d’API
direct) y parvient. Les push n’en dépendent pas.
Aucune clé d’API de tickets n’est placée dans un bac à sable. Les agents écrivent les tickets via les outils MCP de Control Center, qui interrogent Écrire dans un outil de suivi externe.
La règle Accéder au réseau est lue une seule fois, au démarrage de
l’exécution. Le réseau d’un bac à sable est soit activé, soit désactivé : un
refus le désactive, tandis qu’autoriser et demander d’abord le laissent
activé ; les outils web_fetch et web_search du harnais continuent de
demander à chaque appel. Les identifiants sont révoqués au démontage de
l’exécution, avec tout jeton d’écriture émis par la passerelle, si bien
qu’aucune référence ne survit à l’exécution.
Ce que le bac à sable ajoute et où il s’arrête
Section intitulée « Ce que le bac à sable ajoute et où il s’arrête »L’énumération SandboxBackend compte trois valeurs — native (Seatbelt sous
macOS, bubblewrap sous Linux et WSL2), none et microvm. L’encapsulation des
commandes d’agent n’utilise que les deux premières : microvm est le backend de
VM cloisonnée destiné aux terminaux interactifs et aux rigs, et non une
amélioration silencieuse d’une encapsulation bash. Comprendre la forme native
explique ce que vous y gagnez lorsqu’elle est attachée, et ce qu’elle n’allait
jamais vous offrir.
cc_server sonde l’hôte une fois au démarrage et, lorsqu’un backend répond, il
alimente les deux jointures que comporte le chemin de lancement, car les
transports n’en partagent pas une seule. La première (SandboxPort) encapsule
la CLI Claude Code. La seconde (SandboxManager) encapsule les adaptateurs ACP
et l’outil bash du harnais intégré. N’en alimenter qu’une seule laisserait les
autres se lancer sans encapsulation, ce qui se produisait auparavant. Sur un
hôte dépourvu de backend, ou avec --sandbox off, ils se lancent tous sans
encapsulation et le serveur indique votre cas sur sa ligne de démarrage. ACP et
le bash du harnais assainissent toujours l’environnement ; Claude Code via
NoSandboxAdapter hérite de l’environnement de l’hôte.
Une surface s’y soustrait délibérément : le terminal hébergé par le serveur
accepte un argument de backend par session, et cc_server le construit en
retombant par défaut sur le shell de l’hôte ; un terminal que vous ouvrez depuis
l’application s’exécute donc sans bac à sable, à moins qu’un client ne nomme
explicitement native. Il s’agit d’un vrai shell sur l’hôte du serveur, pas
d’une exécution d’agent.
Sous Linux, bubblewrap construit un espace de noms de montage : l’agent ne
voit donc réellement que ce qui y a été lié. Sous macOS, sandbox-exec applique
un profil Seatbelt permissif par défaut — un profil entièrement fermé par
défaut rend un shell interactif inutilisable, car le nombre d’appels dyld, XPC
et Mach à énumérer n’est pas praticable. Le profil macOS part donc de
(allow default) et y taille des refus : lectures de chemins secrets, politique
d’écriture réinitialisée à un refus avec des autorisations explicites par-dessus,
blocage de la suppression sur chaque chemin refusé et sur ses ancêtres pour
qu’un renommage ne puisse pas contourner un refus d’écriture, refus d’exécution
pour les binaires toujours dangereux et pour tout ce qui est copié dans un
répertoire accessible en écriture, et réseau restreint aux ports proxy de la
boucle locale plus le DNS.
La conséquence pratique : sous Linux, la vue que l’agent a du système de fichiers est rétrécie ; sous macOS, l’agent peut encore lire la majeure partie de l’hôte, et vos copies de travail enregistrées sont protégées par des refus explicites d’écriture et de renommage plutôt que par l’invisibilité. Les deux relèvent de l’isolation au niveau des espaces de noms, et non d’une frontière de sécurité du noyau — les backends le disent eux-mêmes dans leurs propres notes de capacités.
L’ensemble des montages par liaison est l’endroit où se dessine la frontière
entre agents. Le lancement en superposition (Claude Code et ACP) monte jusqu’à
quatre chemins, résolus par exécution : le répertoire de travail superposé de
l’agent en lecture-écriture, le répertoire de configuration de l’agent en
lecture seule, le répertoire repos/ partagé de la conversation en
lecture-écriture lorsqu’il existe, et le répertoire des pièces jointes de
l’espace en lecture seule lorsqu’il existe. Le répertoire d’un agent voisin
n’est jamais monté. L’encapsulation bash du harnais est plus étroite : elle ne
monte par liaison que le répertoire de travail de l’exécution. C’est la même
forme que celle que la vérification de chemin in-process du harnais applique en
logiciel, si bien que les deux concordent — mais seule la vérification de chemin
couvre les outils de fichiers propres au harnais, qui n’atteignent jamais un
espace de noms de montage.
Vos copies de travail enregistrées rejoignent par-dessus l’ensemble des refus
d’écriture, dans tous les modes et sur les trois chemins encapsulés (Claude Code,
ACP, bash du harnais), et chaque règle est émise à la fois dans son orthographe
littérale et dans sa forme résolue par lien symbolique. La seconde n’est pas un
détail :
le noyau compare une règle de refus
au chemin résolu, et macOS fait de la résolution le cas courant plutôt que
l’exception — /tmp est en réalité /private/tmp, /var/folders/… est en
réalité /private/var/folders/… — si bien qu’une copie de travail atteinte par
l’un ou l’autre aurait été accessible en écriture alors que le profil semblait
l’interdire.
Le réseau, sous un bac à sable, est une liste d’autorisation de domaines par
session, appliquée par des proxys de boucle locale in-process. La sortie est
refusée par défaut : réseau désactivé, les proxys démarrent quand même mais leur
liste d’autorisation est vide, si bien que toute connexion sortante est refusée.
Réseau activé, le résolveur construit la liste d’autorisation à partir d’une
base sélectionnée — fournisseurs d’API de LLM, hébergement git, registres de
paquets, catalogue de modèles — à laquelle s’ajoutent les domaines de GitHub,
puis compare l’hôte cible de chaque connexion à cette liste. Les jokers
fonctionnent (*.example.com correspond aux sous-domaines, pas à l’apex). Une
liste distincte, toujours refusée, l’emporte sur la liste d’autorisation et
couvre les points de terminaison de métadonnées cloud ainsi que les récepteurs
de télémétrie. Ce qui n’est pas configurable, c’est la liste elle-même : la
spécification comporte un champ egressAllowlist pour des domaines
supplémentaires, mais rien dans le chemin de lancement ne le renseigne ; en
pratique, chaque exécution obtient donc la base plus GitHub, et rien de plus.
Les refus, sur macOS, sont observables : un moniteur puise dans le journal
système les refus du bac à sable, filtre le bruit connu, déduplique les tempêtes
de réessai et expose chacun d’eux comme un SandboxViolation nommant ce qui a été tenté et ce qui a été bloqué. Sous Linux, le chemin
d’encapsulation analyse stderr à la recherche de EPERM,
Operation not permitted et Permission denied et émet le même type
d’événement — il n’y a pas de lecture du journal système. Ces éléments arrivent
sur le flux d’événements propre à l’exécution, si bien qu’un appel système
refusé apparaît dans la transcription plutôt que seulement dans un journal. La
télémétrie n’existe que tant qu’un bac à sable natif encapsule réellement le
processus — sur un hôte dépourvu de backend, il n’y a rien à observer. Elle est également sans rapport avec les garde-fous : une violation provient d’un appel système refusé, jamais d’une résolution de règle.
Les confirmations viennent des garde-fous, pas du bac à sable
Section intitulée « Les confirmations viennent des garde-fous, pas du bac à sable »Le bac à sable n’a aucun chemin d’approbation. Il se lance, exécute, émet des événements et se démonte ; il n’y a nulle part, en lui, où un humain puisse répondre à une question. Quand vous voyez une confirmation dans un espace — approuver ce push, approuver cette suppression — elle a été soulevée par le moteur de garde-fous (ou par la politique de commandes du harnais) avant même que l’action n’atteigne le moteur d’exécution.
Cet ordre est délibéré. Une approbation qui interviendrait après l’appel système serait un enregistrement d’audit, pas une barrière. C’est aussi pourquoi la règle d’échec fermé réside dans les garde-fous : faute d’approbateur connecté, la décision est prise avant que quoi que ce soit ne s’exécute, et c’est un refus.
Diagnostics
Section intitulée « Diagnostics »Exécuter les diagnostics d’un agent est le seul endroit qui énonce ce qui peut être vérifié à la main.
Concepts liés
Section intitulée « Concepts liés »- Rigs et enceintes : l’étape au-dessus du bac à sable — quand le travail exige une machine jetable entière avec une frontière de noyau, et non un processus encapsulé
- Garde-fous : la couche de politiques au-dessus du plancher du système d’exploitation, et l’origine réelle des approbations
- Cycle de vie du lancement d’un agent : où l’isolation s’inscrit dans le flux de lancement
- Modes : comment un mode resserre la surface d’outils et la politique de commandes
- Référence des backends de bac à sable : les backends et leur configuration
Guides liés
Section intitulée « Guides liés »- Configurer les politiques de bac à sable : les règles d’action, l’application des push et la posture du système de fichiers
- Exécuter les diagnostics d’un agent : ce que couvrent les vérifications de diagnostic