Aller au contenu

Backends de bac à sable

Backend Valeur Plateforme Disponibilité
VM cloisonnée microvm macOS, Linux, Windows (hyperviseur microVM) Conditionnée à la sonde du runtime microVM ; s’applique aux terminaux interactifs — le shell s’exécute dans un rig, pas sur l’hôte
Natif native macOS (Seatbelt), Linux et WSL2 (bubblewrap) Sondé au démarrage ; Linux refuse une exécution qui exige des refus d’écriture fondés sur des globs de noms de fichiers, que bubblewrap ne sait pas appliquer
Aucun none Tous La valeur par défaut à l’analyse, celle du terminal et le repli lorsqu’aucun backend n’est disponible ou que --sandbox off est défini

SandboxBackend.fromName renvoie none pour une valeur stockée nulle ou non reconnue. Un "docker" stocké est associé à native.

microvm est le seul backend doté d’une frontière de noyau — la charge de travail ne s’exécute véritablement pas sur l’hôte. Il n’est jamais un repli silencieux, dans un sens comme dans l’autre : le demander explicitement sur un hôte incapable de démarrer une VM échoue bruyamment (« j’ai demandé une VM et j’ai eu mon ordinateur portable » est la seule dégradation que l’enceinte ne peut pas se permettre), et seul un défaut non spécifié retombe sur native, avec une ligne de journal qui le dit. Le backend réel de la session est renvoyé au client et affiché sous forme de badge, si bien qu’un terminal ne peut jamais prétendre être cloisonné tout en s’exécutant sur l’hôte. Le cycle de vie, les images et la sortie réseau de la VM elle-même sont traités dans la référence des rigs.

Windows n’a pas de bac à sable natif. NativeSandboxAdapter.probe renvoie available: false sur tout hôte qui n’est ni macOS ni Linux/WSL2. Le lancement d’agents sous Windows utilise donc none. Le backend microvm est distinct : il est conditionné par la sonde du runtime microVM et s’applique aux terminaux interactifs et aux rigs, pas à l’encapsulation de bash sur l’hôte.

C’est la sonde qui conditionne l’application. Elle s’exécute une fois, au démarrage du serveur.

Hôte available Remarque / indice
macOS true sandbox-exec (Seatbelt) — isolation par espaces de noms, sans frontière de noyau
Linux ou WSL2 avec bwrap et socat true bubblewrap — isolation par espaces de noms, sans frontière de noyau
Linux ou WSL2 auquel il manque l’un des deux outils false requiresInstall: true, avec un indice d’installation nommant les paquets manquants
Toute autre plateforme false Le bac à sable natif n’est pas proposé sur cette plateforme

Le serveur consigne le résultat sur une seule ligne de démarrage : agent sandbox ON suivi de la remarque, no OS-native agent sandbox on this host suivi de l’indice, ou agent sandbox DISABLED by --sandbox off. GET /healthz ne comporte aucun champ de bac à sable.

--sandbox / CC_SERVER_SANDBOX accepte on (par défaut) ou off. C’est un désengagement, pas une activation : on signifie « encapsuler lorsque l’hôte offre un backend », jamais « en exiger un ».

Utilise le mécanisme de bac à sable Seatbelt d’Apple (le même système que celui employé par l’App Sandbox de macOS).

Le profil généré est permissif par défaut ((allow default)) et y définit des refus ciblés, car un profil entièrement à refus par défaut rend un shell interactif inutilisable — il y a trop d’appels dyld, XPC et Mach à énumérer. La conséquence pratique est qu’un agent peut encore lire la majeure partie de l’hôte ; vos copies de travail enregistrées sont protégées par des refus explicites d’écriture et de renommage, non par l’invisibilité.

  • Lectures du système de fichiers : refusées pour les chemins de secrets (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains et le reste de l’ensemble de refus de lecture)
  • Écritures dans le système de fichiers : réinitialisées à un refus, puis des autorisations explicites de sous-chemins, les chemins à refus obligatoire (fichiers rc du shell, .git/hooks, configuration de Claude, copies de travail protégées) étant bloqués même à l’intérieur d’une racine inscriptible. file-write-unlink est refusé sur chaque chemin refusé et ses ancêtres, si bien qu’un renommage ne peut pas contourner un refus d’écriture
  • Processus : des refus process-exec pour les binaires toujours dangereux, plus un blocage de l’exécution de tout binaire depuis un répertoire inscriptible
  • Réseau : limité aux ports des proxys in-process plus le DNS — la boucle locale n’est pas autorisée en bloc et les sockets Unix ne sont pas autorisées. Les proxys appliquent la liste d’autorisation de domaines propre à la session (voir Sortie réseau)
  • Configuration : les profils sont générés à chaque lancement à partir de la SandboxSpec

Prérequis :

  • sandbox-exec (/usr/bin/sandbox-exec), présent dans toute installation macOS — aucun outil supplémentaire requis

Utilise bubblewrap (bwrap) pour créer des espaces de noms de montage et de PID.

  • Système de fichiers : crée un espace de noms de montage où seuls les chemins liés sont visibles
  • Réseau : s’exécute dans un espace de noms réseau non partagé ; le trafic atteint les proxys de l’hôte via des ponts de sockets Unix socat
  • Processus : isolation par espace de noms de PID

Prérequis :

  • bubblewrap et socat installés (sudo apt-get install bubblewrap socat sous Debian/Ubuntu)
  • la prise en charge des espaces de noms du noyau (standard sur Linux moderne)

Bubblewrap peut masquer un chemin existant, mais ne peut pas interdire un nouveau fichier .env, *.key ou *.pem à l’intérieur d’une liaison inscriptible. La politique de lancement standard exige ces refus fondés sur des globs de noms de fichiers, si bien que le lancement natif d’agents échoue actuellement en mode fermé sous Linux/WSL2, même lorsque la sonde détecte bubblewrap. Il ne s’exécute pas avec une politique silencieusement affaiblie. Un backend doté de règles de noms de fichiers au moment de la création est nécessaire pour rétablir le lancement natif sous Linux. Désactiver le bac à sable est un désengagement explicite et sans confinement, pas un contournement équivalent.

Aucun bac à sable. Le processus s’exécute directement sur l’hôte, avec l’accès de l’hôte au système de fichiers, au réseau et aux processus. ACP et l’outil bash du harnais bénéficient tout de même de l’assainissement de l’environnement et de la politique de commandes ; Claude Code, via NoSandboxAdapter, hérite de l’environnement de l’hôte. C’est sur cela que retombe le lancement des agents sur un hôte dépourvu de backend disponible ou avec --sandbox off, et c’est la valeur par défaut du terminal lorsqu’un client n’en nomme aucun.

Le terminal est isolé selon ses propres règles, et non par la sonde de lancement. Le defaultBackend de TerminalSessionService vaut none et cc_server ne le remplace pas, si bien qu’une session de terminal exécute le shell de l’hôte à moins que le client ne demande explicitement native — le panneau de terminal de l’application de bureau ne le fait pas.

Lorsque native est demandé, le shell est encapsulé via SandboxManager sous un profil de terminal fixe : $HOME et le répertoire de travail de la session sont inscriptibles, le réseau est ouvert et la lecture de ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh et ~/Library/Keychains est refusée. Le jeton de forge en lecture seule et la passerelle de push ne s’appliquent pas — ils relèvent du lancement. Si l’encapsulation n’est pas prise en charge, la session échoue au lieu d’afficher un badge natif sur un PTY de shell hôte.

Quand un bac à sable est lancé, l’accès réseau sortant est appliqué par des proxys in-process, et non par des règles de pare-feu du noyau. Sur un hôte dépourvu de backend disponible, rien de tout cela ne s’applique et un processus lancé atteint le réseau exactement comme le ferait l’utilisateur du serveur.

  • Le runtime du bac à sable exécute un proxy HTTP/HTTPS et un proxy SOCKS5 sur 127.0.0.1 à l’intérieur du processus cc_server, une paire par session restreinte (http_proxy.dart, socks_proxy.dart). Les exécutions concurrentes ne partagent pas de paire.
  • Les processus isolés reçoivent des variables d’environnement de proxy (HTTP_PROXY/HTTPS_PROXY → le proxy HTTP, ALL_PROXY → le proxy SOCKS ; GIT_SSH_COMMAND achemine git-over-SSH via le proxy SOCKS). Sous Linux, l’espace de noms réseau n’est pas partagé, si bien que les proxys sont la seule sortie ; sous macOS, le bac à sable partage la boucle locale de l’hôte et leur parle directement.
  • L’hôte cible de chaque connexion est comparé à la liste d’autorisation de domaines de la session ; tout le reste est refusé. Deux formes de joker, chacune avec exactement un * : un *.example.com en tête correspond à n’importe quel sous-domaine (y compris a.b.example.com) mais pas au domaine apex lui-même ; un bedrock.*.amazonaws.com en milieu de libellé correspond à exactement un libellé (bedrock.us-east-1.amazonaws.com, et non bedrock.evil.attacker.amazonaws.com).
  • La liste d’autorisation est résolue par session à partir de la base curatée (kBaselineAllowedDomains : fournisseurs de LLM, hébergement Git, registres de paquets), plus les domaines GitHub (kGithubDomains), plus SandboxSpec.egressAllowlist. Le chemin de lancement ne transmet jamais d’entrées egressAllowlist supplémentaires, si bien qu’en pratique chaque exécution obtient la base plus GitHub.
  • Une liste de refus fixe (kBaselineDeniedDomains : points de terminaison de métadonnées cloud, récepteurs de télémétrie) l’emporte sur toutes les autorisations.
  • networkEnabled: false démarre quand même les proxys de la session, mais la liste d’autorisation est vide, si bien que toute connexion sortante est refusée. Le lancement le règle au démarrage de l’exécution à partir de la règle Accéder au réseau : un refus désactive le réseau, autoriser et demander d’abord le laissent activé.

La spécification transmise à SandboxPort.launch :

Champ Type Description
sessionId String Identifiant de session stable (généralement l’identifiant de l’espace)
workspaceId String Identifiant de l’espace de travail propriétaire
agentId String? Agent actuellement lié au bac à sable, le cas échéant
bindMounts List<SandboxBindMount> Montages hôte→invité ; le bac à sable ne voit que les chemins d’hôte listés ici
networkEnabled bool (par défaut true) Réglé au lancement à partir de la règle de sortie réseau (désactivé seulement si elle aboutit à un refus). Lorsqu’il est désactivé, les proxys de la session démarrent quand même, mais la liste d’autorisation est vide, si bien que toute connexion sortante est refusée
egressAllowlist List<String> (par défaut []) Domaines de sortie supplémentaires autorisés ; jokers pris en charge (*.example.com, ainsi qu’une forme en milieu de libellé unique). Le lancement ne renseigne jamais ce champ, si bien qu’une exécution encapsulée obtient la base curatée plus les domaines GitHub
guestWorkdir String? Répertoire de travail par défaut à l’intérieur du bac à sable ; les appels exec individuels peuvent le remplacer
mode Mode (par défaut chat) Définit les règles d’écriture du système de fichiers : chat garde les montages par liaison inscriptibles ; review, plan et orchestrate sont en lecture seule
protectedPaths List<String> (par défaut []) Chemins d’hôte qui ne doivent jamais être inscriptibles à l’intérieur du bac à sable, quel que soit le mode
runnerStateDirs List<String> (par défaut []) Répertoires de l’hôte où l’exécuteur conserve son propre état, inscriptibles dans tous les modes (un CLAUDE_CONFIG_DIR géré par Control Center est le cas actuel)
execGrantRoots List<String> (par défaut []) Arborescences de répertoires approuvées par l’opérateur qui rouvrent le blocage d’exécution dans les répertoires inscriptibles sous macOS. Linux les ignore — bwrap n’a aucun blocage d’exécution $HOME à rouvrir

protectedPaths contient les copies de travail enregistrées d’origine des dépôts, résolues à chaque lancement à partir des dépôts de l’espace de travail. Les agents travaillent exclusivement dans des arbres de travail en copie sur écriture propres à chaque conversation, si bien que les originaux ne doivent jamais être inscriptibles depuis l’intérieur du bac à sable — sous macOS, l’autorisation d’écriture globale sur $HOME accordée par Seatbelt les laisserait sinon inscriptibles via bash. Ils rejoignent l’ensemble de refus d’écriture des secrets dans tous les modes, chat compris.

Chaque chemin de refus de lecture et d’écriture est émis à la fois dans son orthographe littérale et dans sa forme résolue par lien symbolique. Le noyau compare une règle de refus au chemin résolu, si bien qu’une règle écrite pour un chemin symbolique ne se déclenche jamais — et sous macOS, c’est le cas courant plutôt que l’exception, puisque /tmp se résout en /private/tmp et /var/folders/… en /private/var/folders/…. Les entrées contenant * ou ? sont traitées comme des globs et laissées telles quelles ; un chemin qui ne se résout pas est conservé tel quel.

Champ Type Description
hostPath String Chemin absolu sur l’hôte
guestPath String Chemin à l’intérieur du bac à sable (avec le bac à sable natif, il est égal à hostPath ; conservé pour la symétrie des adaptateurs)
readOnly bool (par défaut false) Monte en lecture seule lorsque vrai

Renvoyé par SandboxPort.launch :

Champ Type Description
sessionId String Identifiant de session
backend SandboxBackend Le backend en cours d’exécution
state SandboxState État du cycle de vie
error Object? Défini lorsque state vaut error
details Map<String, Object?> Comptabilité interne à l’adaptateur (répertoire de travail, PID, chemin du profil, etc.)
État Description
created Spécification créée, mais la VM n’a pas encore démarré
warm En cours d’exécution et prêt à exécuter des commandes
active Exécute actuellement une commande
suspended En pause / avec point de contrôle sur disque pour économiser la RAM
destroyed Démonté ; le descripteur n’est plus valide
error Le lancement a échoué — voir SandboxHandle.error
Type d’événement Description
stdout Sortie standard du bac à sable
stderr Erreur standard du bac à sable
exit Le processus du bac à sable s’est terminé (porte exitCode)
starting Le bac à sable démarre
ready Le bac à sable est prêt à accepter des appels exec
killed Processus tué par l’hôte (OOM, expiration, arrêt manuel)
violation Une politique du bac à sable a été violée (porte une SandboxViolation)
Champ Type Description
action String Le verbe refusé (par ex. file-read*, network-outbound)
target String Chemin ou hôte qui était la cible de l’opération refusée
raw String? Ligne de journal brute, utile pour le débogage