Aller au contenu

Backends de bac à sable

Backend Valeur Plateforme Disponibilité
VM cloisonnée microvm macOS, Linux, Windows (hyperviseur microVM) Conditionné par la sonde de l’environnement d’exécution microVM; s’applique aux terminaux interactifs — le shell s’exécute dans un rig, et non 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 par glob de nom de fichier, que bubblewrap ne peut pas appliquer
Aucun none Tous La valeur par défaut de l’analyse, la valeur par défaut du terminal et la solution de 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. Une valeur "docker" stockée correspond à 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 une solution de repli silencieuse, 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 obtenu mon ordinateur portable » est la seule dégradation que l’enceinte ne peut pas se permettre), et seul un défaut non spécifié se rabat sur native, avec une ligne de journal qui le dit. Le backend réel de la session est signalé au client et accompagné d’une puce, de sorte qu’un terminal ne peut jamais prétendre être cloisonné alors qu’il s’exécute 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 sur les rigs.

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

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

Hôte available Note / indice
macOS true sandbox-exec (Seatbelt) — isolation par espace de noms, sans frontière de noyau
Linux ou WSL2 avec bwrap et socat true bubblewrap — isolation par espace 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 offert sur cette plateforme

Le serveur consigne le résultat sur une seule ligne de démarrage : agent sandbox ON suivi de la note, 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. Il s’agit d’un retrait, et non d’une activation : on signifie « envelopper 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 macOS App Sandbox).

Le profil généré est permissif par défaut ((allow default)) et y découpe des refus, parce qu’un profil entièrement fermé par défaut rend un shell interactif inutilisable — les appels dyld, XPC et Mach à énumérer sont trop nombreux. 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, et non par l’invisibilité.

  • Lectures du système de fichiers : refusées pour les chemins secrets (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains et le reste de l’ensemble de refus de lecture)
  • Écritures du système de fichiers : réinitialisées en refus, puis autorisations explicites par sous-chemin, 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 accessible en écriture. file-write-unlink est refusé sur chaque chemin refusé et sur ses ancêtres, de sorte qu’un renommage ne peut pas contourner un refus d’écriture
  • Processus : refus de process-exec pour les binaires toujours dangereux, plus un blocage de l’exécution de quoi que ce soit depuis un répertoire accessible en écriture
  • Réseau : limité aux ports du proxy intraprocessus plus le DNS — le bouclage n’est pas autorisé de façon générale et les sockets Unix ne sont pas autorisés. Les proxys appliquent la liste d’autorisation de domaines par session (voir Sortie réseau)
  • Configuration : les profils sont générés à chaque lancement à partir du SandboxSpec

Exigences :

  • sandbox-exec (/usr/bin/sandbox-exec), présent dans toute installation de 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 par des ponts de sockets Unix socat
  • Processus : isolation par espace de noms de PID

Exigences :

  • bubblewrap et socat installés (sudo apt-get install bubblewrap socat sur Debian/Ubuntu)
  • Prise en charge des espaces de noms du noyau (standard sous 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 accessible en écriture. La politique de lancement standard exige ces refus par glob de nom de fichier, de sorte que le lancement natif d’agents échoue en mode fermé sous Linux/WSL2 même lorsque la sonde trouve bubblewrap. Il ne s’exécute pas avec une politique silencieusement affaiblie. Un backend doté de règles de nom de fichier au moment de la création est nécessaire pour rétablir le lancement natif sous Linux. La désactivation de l’isolation est un retrait explicite sans confinement, et non un contournement équivalent.

Aucune isolation. Le processus s’exécute directement sur l’hôte, avec l’accès au système de fichiers, au réseau et aux processus de celui-ci. ACP et l’outil bash du harnais reçoivent tout de même l’assainissement de l’environnement et la politique de commandes; Claude Code, par l’intermédiaire de NoSandboxAdapter, hérite de l’environnement de l’hôte. C’est sur ce backend que se rabat le lancement d’agents sur un hôte sans 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 est none et cc_server ne le remplace pas, de sorte qu’une session de terminal exécute le shell de l’hôte à moins qu’un 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 enveloppé par SandboxManager sous un profil de terminal fixe : $HOME et le répertoire de travail de la session sont accessibles en écriture, 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 poussée ne s’appliquent pas — ils relèvent du lancement. Si l’enveloppement n’est pas pris en charge, la session échoue plutôt que d’afficher une puce « natif » sur un PTY de shell hôte.

Lorsqu’un bac à sable est lancé, l’accès réseau sortant est appliqué par des proxys intraprocessus, et non par des règles de pare-feu du noyau. Sur un hôte sans 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.

  • L’environnement d’exécution 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 simultanées ne partagent pas une paire.
  • Les processus isolés reçoivent les variables d’environnement de proxy (HTTP_PROXY/HTTPS_PROXY → le proxy HTTP, ALL_PROXY → le proxy SOCKS; GIT_SSH_COMMAND achemine git par SSH à travers le proxy SOCKS). Sous Linux, l’espace de noms réseau n’est pas partagé, de sorte que les proxys sont la seule sortie possible; sous macOS, le bac à sable partage le bouclage 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 caractères génériques, 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 racine lui-même; un bedrock.*.amazonaws.com à étiquette intermédiaire correspond à exactement une étiquette (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 de référence établie avec soin (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, de sorte qu’en pratique chaque exécution obtient la base de référence plus GitHub.
  • Une liste de refus fixe (kBaselineDeniedDomains : points de terminaison de métadonnées infonuagiques, collecteurs de télémétrie) l’emporte sur toutes les autorisations.
  • networkEnabled: false démarre tout de même les proxys par session, mais la liste d’autorisation est vide, de sorte 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’id de l’espace)
workspaceId String Id de l’espace de travail propriétaire
agentId String? Agent actuellement lié au bac à sable, s’il y en a un
bindMounts List<SandboxBindMount> Montages hôte→invité; le bac à sable ne voit que les chemins de l’hôte énumérés ici
networkEnabled bool (valeur 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 par session démarrent quand même, mais la liste d’autorisation est vide, de sorte que toute connexion sortante est refusée
egressAllowlist List<String> (valeur par défaut []) Domaines de sortie supplémentaires autorisés; les caractères génériques sont pris en charge (*.example.com, plus une forme intermédiaire d’une seule étiquette). Le lancement ne remplit jamais ce champ, de sorte qu’une exécution enveloppée obtient la base de référence établie avec soin plus les domaines GitHub
guestWorkdir String? Répertoire de travail par défaut à l’intérieur du bac à sable; chaque appel exec peut le remplacer
mode Mode (valeur par défaut chat) Découpe les règles d’écriture du système de fichiers : chat garde les montages par liaison accessibles en écriture; review, plan et orchestrate sont en lecture seule
protectedPaths List<String> (valeur par défaut []) Chemins de l’hôte qui ne doivent jamais être accessibles en écriture à l’intérieur du bac à sable, dans quelque mode que ce soit
runnerStateDirs List<String> (valeur par défaut []) Répertoires de l’hôte où l’exécuteur conserve son propre état, accessibles en écriture dans tous les modes (un CLAUDE_CONFIG_DIR géré par Control Center est le cas actuel)
execGrantRoots List<String> (valeur par défaut []) Arborescences approuvées par l’opérateur qui rouvrent le blocage d’exécution dans les répertoires accessibles en écriture sous macOS. Linux les ignore — bwrap n’a aucun blocage d’exécution de $HOME à rouvrir

protectedPaths contient les copies de travail d’origine des dépôts enregistrés, 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, de sorte que les originaux ne doivent jamais être accessibles en écriture depuis l’intérieur du bac à sable — sous macOS, l’autorisation générale d’écriture dans $HOME accordée par Seatbelt les laisserait autrement modifiables par bash. Ils rejoignent l’ensemble de refus d’écriture des secrets dans tous les modes, chat compris.

Chaque chemin de refus de lecture et de refus d’écriture est émis à la fois sous sa forme littérale et sous sa forme résolue par lien symbolique. Le noyau compare une règle de refus au chemin résolu, de sorte 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 qu’écrit.

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 (valeur 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?> Données de suivi internes à 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 En train d’exécuter une commande
suspended En pause ou avec point de contrôle écrit sur disque pour économiser la RAM
destroyed Démoli; la référence n’est plus valide
error Échec du lancement — 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 (manque de mémoire, délai dépassé, arrêt manuel)
violation Une politique du bac à sable a été violée (porte une SandboxViolation)
Champ Type Description
action String Le verbe refusé (p. ex. file-read*, network-outbound)
target String Chemin ou hôte visé par l’opération refusée
raw String? Ligne de journal brute, utile pour le débogage