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 de shell et appelle des API externes pour votre compte. La défaillance qui vous préoccupe est rarement de la 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 d’examiner. La question à laquelle le produit doit donc répondre n’est pas « l’agent est-il digne de confiance », mais « quel est le rayon d’impact quand il se trompe ».
Control Center y répond par couches, et ces couches sont indépendantes à dessein. Si l’une manque, les autres tiennent quand même. C’est important, parce que la couche la plus externe — le bac à sable du système d’exploitation — est la seule conditionnelle : elle dépend de l’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 éléments bornent l’exécution d’un agent sur tous les hôtes, avec ou sans bac à sable. Aucun d’eux n’est une frontière du noyau; tous sont réels. Là où l’hôte offre un backend, le bac à sable du système d’exploitation se place au-dessus d’eux, et la section « Ce que le bac à sable ajoute et où il s’arrête » ci-dessous explique 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 par l’implémentation native rift fournie plutôt que copiée octet par
octet, de sorte que l’isolation est assez peu coûteuse pour être la valeur 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 peuvent pas voir les modifications de l’autre, et ni l’une ni
l’autre ne modifie votre copie de travail.
Le grain mérite d’être précisé : l’arbre de travail est par conversation, pas
par 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 précisément le but, puisqu’ils collaborent à 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 plutôt que de se rabattre sur autre
chose. Windows est l’exception — il n’y a aucun backend de copie sur écriture
là-bas, donc git worktree est le backend, et non une dégradation silencieuse.
Le confinement des 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 lorsqu’il existe — et refusent tout ce qui aboutit à
l’extérieur. Les chemins absolus ne sont acceptés que s’ils se trouvaient déjà à
l’intérieur. Un lien symbolique situé dans l’arbre de travail et pointant hors de
celui-ci est résolu puis refusé lui aussi. C’est pourquoi un agent ne peut pas
faire un write dans ~/.ssh, même sans bac à sable en jeu.
La surface d’outils du mode. Le mode d’une
conversation décide quels outils sont matérialisés pour l’exécution, point. 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 de shell de ces modes refuse carrément toute commande de mutation —
git commit, git push, npm install, docker et le reste — plutôt que de
demander une approbation. Un outil absent ne peut pas être appelé, ce qui est une
garantie plus forte qu’un outil présent mais contrôlé.
Les garde-fous d’actions unifiés. Chaque outil de mutation déclare les
classes d’effets qu’il peut produire, et le moteur de politiques résout chacune
d’elles en autorisation, en demande d’approbation ou en refus avant que l’appel
s’exécute. Une décision prompt sans approbateur connecté se résout en refus —
une question que personne ne peut répondre n’est jamais un oui. Voir
Garde-fous pour le modèle de résolution.
Des identifiants délivrés par le courtier et un 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 poussées passent par Control Center » plus bas. ACP, le bash
du harnais et Claude Code sous l’enveloppe native reçoivent un environnement
dépourvu 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 par l’intermédiaire de NoSandboxAdapter hérite de l’environnement de
l’hôte.
Là où ces limites s’amincissent
Section intitulée “Là où ces limites s’amincissent”Les outils de fichiers du harnais s’exécutent dans le processus du serveur, et
non dans un processus enfant, de sorte qu’aucun profil de bac à sable ne pourrait
les contraindre même si on en attachait un — seul bash passe par le lanceur de
commandes isolé. Leur confinement repose sur la vérification de chemin décrite
ci-dessus, et cette vérification est donc porteuse plutôt que d’être une deuxième
ligne de défense.
L’outil bash est l’autre endroit où il faut être précis. Ses commandes sont
évaluées en fonction 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 enveloppé, de sorte que le bac à sable est ce qui
empêche une commande de shell d’écrire à l’extérieur de l’arbre de travail; là où
il n’y en a pas — Windows, ou Linux sans bwrap et 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 approbation pour les mutations de l’état distant
(poussées 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 sert d’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 bloquerait autrement indéfiniment sur sa propre
invite d’approbation. Ses appels de lecture, d’écriture et de modification se produisent à l’intérieur de 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 consulte la règle correspondante, et les outils mcp__* que Control Center sert passent le point de contrôle comme d’habitude. Là où un
backend existe, ces appels invisibles se produisent quand même à l’intérieur de
l’enveloppe du bac à sable, de sorte que le système d’exploitation est la
frontière autour d’eux. Là où aucun n’existe, rien ne l’est. Chaque adaptateur
l’énonce dans sa matrice d’application — voir la matrice d’honnêteté des
adaptateurs.
Les poussées passent par Control Center
Section intitulée “Les poussées 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 comporte trois valeurs — native (Seatbelt sur
macOS, bubblewrap sur Linux et WSL2), none et microvm. L’enveloppement des
commandes d’agent n’utilise que les deux premières : microvm est le backend de
machine virtuelle cloisonnée pour les terminaux interactifs et les rigs, et non
une mise à niveau silencieuse d’une enveloppe bash. Comprendre la forme native
explique ce que vous gagnez là où une enveloppe est attachée, et ce qu’elle
n’allait jamais vous donner.
cc_server sonde l’hôte une fois au démarrage et, là où un backend répond, il
alimente les deux jointures du chemin de lancement, parce que les transports n’en
partagent pas une seule. La première (SandboxPort) enveloppe l’interface de
ligne de commande de Claude Code. La seconde (SandboxManager) enveloppe les
adaptateurs ACP et l’outil bash du harnais intégré. N’en alimenter qu’une
laisserait les autres se lancer sans enveloppe, ce qui se produisait auparavant.
Sur un hôte sans backend, ou sous --sandbox off, ils se lancent tous sans
enveloppe et le serveur indique dans quelle situation vous êtes sur sa ligne de
démarrage. ACP et le bash du harnais assainissent quand même l’environnement;
Claude Code par l’intermédiaire de NoSandboxAdapter hérite de l’environnement
de l’hôte.
Une surface s’y soustrait délibérément : le terminal hébergé sur le serveur
accepte un argument de backend par session, et cc_server le construit en
prenant par défaut le shell de l’hôte, de sorte qu’un terminal que vous ouvrez
depuis l’application s’exécute sans bac à sable, à moins qu’un client ne nomme
native explicitement. Il s’agit d’un vrai shell sur l’hôte du serveur, et non
d’une exécution d’agent.
Sous Linux, bubblewrap construit un espace de noms de montage, de sorte que
l’agent ne voit réellement que ce qui y a été lié. Sous macOS, sandbox-exec
applique un profil Seatbelt qui est permissif par défaut — un profil
entièrement fermé par défaut rend un shell interactif inutilisable, parce que le
nombre d’appels dyld, XPC et Mach à énumérer est impraticable. Le profil macOS
part donc de (allow default) et y découpe des refus : lectures de chemins
secrets, politique d’écriture réinitialisée en refus avec des autorisations
explicites par-dessus, blocage de la suppression (unlink) sur chaque chemin
refusé et 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 limité aux
ports du proxy de bouclage plus DNS.
La conséquence pratique : sous Linux, la vue qu’a l’agent 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 sont une isolation au niveau de l’espace de noms, pas une frontière de sécurité du noyau — les backends le disent dans leurs propres notes de capacités.
La série de montages par liaison est l’endroit où la frontière entre agents est
tracée. 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’enveloppe
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 la vérification de chemin
intraprocessus du harnais applique en logiciel, de sorte que les deux concordent —
mais seule la vérification de chemin couvre les outils de fichiers du harnais
lui-même, qui n’atteignent jamais un espace de noms de montage.
Vos copies de travail enregistrées rejoignent l’ensemble de refus d’écriture
par-dessus, dans tous les modes et sur les trois chemins enveloppés (Claude Code,
ACP, bash du harnais), et chaque règle est émise à la fois sous sa forme
littérale et sous sa forme résolue par lien symbolique. La seconde forme n’est
pas un simple raffinement : 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/… — de sorte qu’une copie de travail atteinte par l’une ou
l’autre forme 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 bouclage intraprocessus. Le trafic sortant
est refusé par défaut : lorsque le réseau est désactivé, les proxys démarrent
quand même, mais leur liste d’autorisation est vide, de sorte que toute
connexion sortante est refusée. Lorsqu’il est activé, le résolveur construit la
liste d’autorisation à partir d’une base de référence établie avec soin —
fournisseurs d’API de LLM, hébergement Git, registres de paquets, catalogue de
modèles — plus les domaines de GitHub, et il compare l’hôte cible de chaque
connexion à cette liste. Les caractères génériques fonctionnent
(*.example.com correspond aux sous-domaines, pas au domaine racine). Une liste
distincte de refus permanents l’emporte sur la liste d’autorisation et couvre
les points de terminaison de métadonnées infonuagiques et les collecteurs 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 remplit, de sorte
qu’en pratique chaque exécution obtient la base de référence plus GitHub, et
rien de plus.
Les refus, sous macOS, sont observables : un moniteur écoute le journal système à
la recherche des refus de Sandbox, filtre le bruit connu, déduplique les tempêtes
de nouvelles tentatives et expose chacun d’eux comme un SandboxViolation qui nomme ce qui a été tenté et ce qui a été bloqué. Sous Linux, le chemin
d’enveloppement 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
aucune écoute du journal système. Ces éléments arrivent sur le propre flux
d’événements de l’exécution, de sorte 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
pendant qu’un bac à sable natif enveloppe réellement le processus — sur un hôte
sans backend, il n’y a rien à observer. Elle est aussi sans rapport avec les garde-fous : une violation provient d’un appel système refusé, jamais d’une consultation 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 démarre, exécute, émet des événements et se démonte; il n’y a nulle part en lui où un humain peut répondre à une question. Quand vous voyez une confirmation dans un espace — approuver cette poussée, approuver cette suppression — elle a été soulevée par le moteur de garde-fous (ou par la politique de commandes du harnais) avant que l’action n’atteigne le moteur d’exécution.
Cet ordre est délibéré. Une approbation qui surviendrait après l’appel système serait une trace d’audit, pas un point de contrôle. C’est aussi pourquoi la règle du refus par défaut vit dans les garde-fous : sans approbateur connecté, la décision est prise avant que quoi que ce soit s’exécute, et c’est un refus.
Diagnostic
Section intitulée “Diagnostic”Exécuter les diagnostics d’un agent est le seul endroit qui énonce ce qui peut être vérifié à la main.
Concepts connexes
Section intitulée “Concepts connexes”- Rigs et enceintes : l’échelon au-dessus du bac à sable — quand le travail exige une machine jetable entière avec une frontière de noyau, pas un processus enveloppé
- 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’insère dans le flux de lancement
- Modes : comment un mode rétrécit la surface d’outils et la politique de commandes
- Référence des backends de bac à sable : les backends et leur configuration
Guides connexes
Section intitulée “Guides connexes”- Configurer les politiques de bac à sable : règles d’action, application des poussées et posture du système de fichiers
- Exécuter les diagnostics d’un agent : ce que couvrent les vérifications de diagnostic