Aller au contenu
Développement medium

Isoler Claude Code : bac à sable Bash intégré et sandbox-runtime

23 min de lecture

Logo Claude Code - rules ciblées par dossier

Gérer les permissions d'un agent revient à énumérer ce qu'il a le droit de faire, commande par commande. La liste grossit, personne ne la relit, et une seule règle oubliée rouvre le passage. Le bac à sable prend le problème par l'autre bout : au lieu de lister les commandes acceptables, il contraint ce que n'importe quelle commande peut atteindre, au niveau du noyau.

Anthropic propose deux mises en œuvre de cette idée, et les confondre fait perdre du temps. Le bac à sable Bash est déjà dans Claude Code, il s'active avec /sandbox et confine les commandes shell. Le paquet @anthropic-ai/sandbox-runtime est un outil séparé qui enveloppe tout le processus Claude Code, serveurs MCP et hooks compris.

Les deux reposent sur les mêmes primitives du système : Seatbelt sur macOS, bubblewrap sur Linux. Ce guide traite les deux, en commençant par celle qu'il faut essayer en premier.

Les mesures ont été relevées avec Claude Code 2.1.208, bubblewrap 0.9.0 et le paquet npm 0.0.67, sous Ubuntu 24.04, noyau 6.8. Une précision utile d'emblée, valable pour les deux mises en œuvre : la lecture reste ouverte par défaut, y compris sur vos clés privées. C'est le réglage le plus important de ce guide, et le seul qu'aucune des deux ne pose pour vous.

  • Pourquoi une liste de permissions dérive et ce que le bac à sable change
  • Choisir entre le bac à sable intégré et l'enveloppe du processus entier
  • Mettre en œuvre sur un projet, de /sandbox à la protection des identifiants
  • Ce que le bac à sable bloque vraiment, frontière par frontière, mesuré
  • Installer l'outil et débloquer AppArmor sur Ubuntu 24.04
  • Écrire une configuration valide, avec le schéma réel
  • Refermer la lecture sur les secrets, qui reste ouverte par défaut
  • Ce qui reste exposé, et ce que ça coûte en temps
  • Claude Code installé, en version 2.1.187 ou plus récente pour la protection des identifiants.
  • Un système macOS, Linux ou WSL2. Windows natif n'est pas pris en charge : sous Windows, il faut lancer Claude Code dans une distribution WSL2.
  • Les droits d'installer deux paquets système sous Linux, bubblewrap et socat. Sur macOS, rien à installer.

Une liste de permissions énumère les commandes acceptables. Le modèle a trois défauts qui se cumulent : les règles fusionnent entre portées au lieu de s'écraser, une seule approbation sur une commande enchaînée en écrit jusqu'à cinq, et chaque règle enregistre la ligne complète, mot de passe compris. Sur le poste ayant servi à ce guide, cela donnait 1917 règles pour 219 Ko, dont deux secrets en clair. Le détail de cette dérive et sa remise à plat sont traités dans sécuriser son settings.json.

Le défaut de fond n'est pas la longueur de la liste, c'est son principe. Une énumération raisonne sur des commandes, alors que le risque porte sur des capacités : lire un secret, écrire hors du projet, joindre un serveur distant. Une même capacité s'atteint par des dizaines de commandes, dont celles que personne n'a anticipées.

Le bac à sable inverse la question. Au lieu de demander quelles commandes sont acceptables, il fixe ce que n'importe quelle commande peut atteindre. La contrainte ne porte plus sur le nom du programme lancé, mais sur les appels système qu'il peut faire aboutir.

Liste de permissionsBac à sable
Objet contrôléla commandela capacité
Modèleénumération de ce qui est permisfrontière imposée à tout
Une entrée oubliéecrée un trousans objet, il n'y a rien à énumérer
Croissanceinévitable, les règles fusionnentnulle, la politique tient en une dizaine de lignes
Appliqué parClaude Code, en espace utilisateurle noyau
Contournable par une commande imprévueouinon

La différence pratique est immédiate. Sur un poste où le fichier de permissions compte deux mille lignes, la politique du bac à sable équivalente tient en une quinzaine : trois chemins interdits en lecture, un répertoire autorisé en écriture, deux domaines joignables. Un fichier de quinze lignes se relit, donc il est relu, donc il est correct.

Le tableau ci-dessous résume ce que chacune couvre. La différence tient à un seul critère : ce qui se trouve à l'intérieur de la frontière.

Bac à sable Bash intégréPaquet sandbox-runtime
Activation/sandbox dans une sessionnpx @anthropic-ai/sandbox-runtime claude
Ce qui est confinéles commandes Bash et leurs processus enfantstout le processus Claude Code
Outils Read, Edit, Writehors frontière, régis par les permissionsdans la frontière
Serveurs MCP et hookshors frontière, sur l'hôtedans la frontière
Configurationclés sandbox.* de settings.jsonfichier ~/.srt-settings.json
Maturitéintégré et stableaperçu de recherche, format de configuration susceptible de changer

Commencez par le bac à sable intégré. Il couvre le cas le plus courant, réduire les demandes de confirmation sur les commandes shell pendant le travail quotidien, sans rien installer sur macOS.

Passez au paquet quand vous voulez confiner aussi les serveurs MCP et les hooks, qui sont des processus séparés tournant sans contrainte sur l'hôte. La documentation est explicite sur ce point : une session lancée avec --dangerously-skip-permissions doit tourner dans un conteneur, une machine virtuelle ou ce paquet, précisément parce que le bac à sable Bash seul ne suffit pas pour du travail non surveillé.

Voici la marche à suivre complète sur un projet réel, du premier lancement à la configuration durable.

  1. Installer les dépendances Linux. Sur macOS il n'y a rien à faire, Seatbelt est intégré au système.

    Fenêtre de terminal
    sudo apt install bubblewrap socat

    bubblewrap assure l'isolation des fichiers, socat relaie le trafic réseau vers le mandataire du bac à sable. Le filtre seccomp, qui bloque en plus les sockets Unix, est optionnel et s'obtient en installant le paquet @anthropic-ai/sandbox-runtime.

  2. Débloquer AppArmor sur Ubuntu 24.04. Vérifiez d'abord si la restriction s'applique chez vous.

    Fenêtre de terminal
    sysctl kernel.apparmor_restrict_unprivileged_userns
    1

    Une valeur de 1 impose le profil ci-dessous. Une valeur de 0, ou une erreur indiquant que la clé n'existe pas, dispense de cette étape.

    Fenêtre de terminal
    sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
    abi <abi/4.0>,
    include <tunables/global>
    profile bwrap /usr/bin/bwrap flags=(unconfined) {
    userns,
    include if exists <local/bwrap>
    }
    EOF
    sudo systemctl reload apparmor
  3. Activer le bac à sable depuis une session, dans le projet concerné.

    /sandbox

    Le panneau affiche trois onglets : le mode d'approbation, les dérogations, et la configuration résolue. Si un onglet « Dependencies » apparaît seul, c'est qu'un paquet manque : installez-le et redémarrez Claude Code, la vérification n'a lieu qu'au démarrage.

    Choisir un mode depuis ce panneau écrit dans .claude/settings.local.json, donc pour le projet courant seulement.

  4. Poser la configuration du projet. Par défaut, les commandes n'écrivent que dans le répertoire de travail et le répertoire temporaire de session. Les outils qui écrivent ailleurs ont besoin d'une autorisation explicite.

    .claude/settings.json
    {
    "sandbox": {
    "enabled": true,
    "filesystem": {
    "allowWrite": ["~/.kube", "~/.terraform.d"]
    },
    "network": {
    "allowedDomains": ["registry.npmjs.org", "*.github.com"]
    }
    }
    }

    Ces chemins sont appliqués par le système, donc ils valent aussi pour les sous-processus lancés par kubectl, terraform ou npm.

  5. Protéger les identifiants, ce que la configuration par défaut ne fait pas. C'est l'étape que l'on oublie le plus souvent.

    ~/.claude/settings.json
    {
    "sandbox": {
    "enabled": true,
    "credentials": {
    "files": [
    { "path": "~/.aws/credentials", "mode": "deny" },
    { "path": "~/.ssh", "mode": "deny" }
    ],
    "envVars": [
    { "name": "GITHUB_TOKEN", "mode": "deny" },
    { "name": "NPM_TOKEN", "mode": "deny" }
    ]
    }
    }
    }

    La documentation le formule sans ambiguïté : il n'existe aucune liste de refus intégrée, seuls les fichiers et variables que vous nommez sont protégés.

Quand les serveurs MCP et les hooks doivent aussi être confinés, on lance Claude Code à l'intérieur du bac à sable plutôt que l'inverse.

Fenêtre de terminal
npx @anthropic-ai/sandbox-runtime claude

Le paquet refuse toute écriture et tout réseau par défaut. Il faut donc le configurer avant ce premier lancement, faute de quoi Claude Code ne démarre pas. Trois autorisations sont indispensables :

  • l'écriture dans votre répertoire de projet ;
  • l'écriture dans ~/.claude, ~/.claude.json et /tmp, où Claude Code écrit ses fichiers d'exécution ;
  • l'accès réseau à api.anthropic.com, ou au point d'entrée de votre fournisseur.

L'outil ne réimplémente pas l'isolation, il pilote les mécanismes natifs du système. C'est ce qui explique sa légèreté et ses limites.

PlateformeMécanisme
Linuxbubblewrap pour les namespaces, plus un filtre seccomp BPF qui bloque la création de sockets Unix au niveau des appels système
macOSsandbox-exec avec des profils Seatbelt générés à la volée

L'architecture tient en deux étages. Une CLI TypeScript calcule la politique, et un petit binaire C statique par architecture applique le filtre seccomp. Le fichier vendor/seccomp/x64/apply-seccomp est un ELF 64 bits statiquement lié compilé avec GCC 13.3 : aucune dépendance à l'exécution, ce qui compte pour un composant qui doit s'exécuter avant le processus qu'il confine.

  1. Installer le paquet npm.

    Fenêtre de terminal
    npm install -g @anthropic-ai/sandbox-runtime
  2. Installer bubblewrap, le moteur d'isolation. Sans lui, srt refuse de démarrer avec un message explicite.

    Fenêtre de terminal
    sudo apt install bubblewrap
    Error: Sandbox dependencies not available: bubblewrap (bwrap) not installed

    Ce paquet ne s'installe pas par un gestionnaire de versions comme mise : le projet ne publie que des sources, aucun binaire précompilé. C'est cohérent avec sa nature, bwrap manipule des namespaces noyau et relève du système, pas de l'outillage de développement.

  3. Créer un profil AppArmor si vous êtes sous Ubuntu 24.04 ou plus récent. Sans lui, l'outil échoue au démarrage.

    Fenêtre de terminal
    sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
    abi <abi/4.0>,
    include <tunables/global>
    profile bwrap /usr/bin/bwrap flags=(unconfined) {
    userns,
    include if exists <local/bwrap>
    }
    EOF
    sudo apparmor_parser -r /etc/apparmor.d/bwrap
  4. Vérifier que le bac à sable démarre.

    Fenêtre de terminal
    srt -c 'echo bonjour depuis le bac a sable'
    bonjour depuis le bac a sable

Voici le comportement du paquet autonome en configuration par défaut, chaque ligne vérifiée par exécution. Cette précision n'est pas cosmétique : le bac à sable intégré part de réglages plus permissifs, détaillés juste après le tableau.

FrontièreRésultatDétail
Lecture de /etc/passwdpassecomportement attendu
Lecture d'une clé SSH privéepasseaucune protection par défaut
Lecture de ~/.claude/.credentials.jsonpasseaucune protection par défaut
Écriture dans /tmp, $HOME, le répertoire courantbloquéRead-only file system
Résolution DNS, HTTPS, ICMPbloquéaucune sortie réseau
Requête vers localhostbloquéy compris les services locaux
Fichier docker.sockvisiblemais inutilisable, voir ci-dessous
Création d'un socket Unixbloquérefusé par le filtre seccomp
Liste des processus de l'hôtebloquénamespace PID isolé
Variables d'environnementhéritéesnon filtrées par défaut

Deux résultats méritent d'être soulignés. Le socket Docker est visible dans l'arborescence, ce qui pourrait faire croire à une évasion possible, mais toute tentative de s'y connecter échoue : le filtre seccomp refuse l'appel système de création de socket Unix. Et le blocage réseau inclut localhost, ce qui surprend en développement : un agent confiné ne peut pas interroger le serveur de développement qui tourne sur votre machine.

Le bac à sable intégré part de réglages plus ouverts

Section intitulée « Le bac à sable intégré part de réglages plus ouverts »

Trois différences importantes, qui font que ce tableau ne se transpose pas tel quel :

FrontièrePaquet autonomeBac à sable intégré
Écriture dans le répertoire de travailrefuséeautorisée, ainsi que le répertoire temporaire de session
Réseaurefusé en silencedemande une approbation au premier domaine, puis l'autorise pour la session
Sockets Unixbloquées par seccompbloquées seulement si le filtre seccomp est installé, il est optionnel

La dernière ligne mérite attention : sans le paquet @anthropic-ai/sandbox-runtime installé, le bac à sable intégré ne bloque pas les sockets Unix, et /var/run/docker.sock redevient le vecteur d'évasion que la documentation signale explicitement.

C'est le point le plus important, et il découle d'un choix de conception assumé. Les trois frontières ne suivent pas le même modèle :

  • L'écriture suit un modèle allow-only : tout est refusé, vous ouvrez ensuite les chemins nécessaires.
  • Le réseau suit le même modèle : tout est refusé, vous listez les domaines.
  • La lecture suit un modèle deny-then-allow : tout est autorisé, vous bloquez ensuite ce qui doit l'être.

Autrement dit, un agent confiné ne peut rien écrire ni rien envoyer, mais il peut tout lire, y compris ~/.ssh/id_ed25519.

Le réseau étant coupé, un secret lu ne peut pas être exfiltré par une requête. Mais il existe un autre chemin, et il est moins visible : l'agent voit la sortie de la commande. Si le modèle lit votre clé privée dans le bac à sable, le contenu remonte dans son contexte et part au fournisseur. Le confinement ne protège pas de ce trajet-là.

Attention, le paquet n'utilise pas les clés sandbox.* de settings.json : il lit son propre fichier, dont le format diffère. Ce dernier attend deux sections obligatoires au premier niveau, filesystem et network. Toute autre structure est refusée, et l'outil ne se rabat pas sur une configuration par défaut.

~/.srt-settings.json
{
"filesystem": {
"denyRead": ["~/.ssh", "~/.claude/.credentials.json", "~/.aws"],
"allowWrite": ["/home/dev/projets/mon-app"],
"denyWrite": []
},
"network": {
"allowedDomains": ["registry.npmjs.org", "*.github.com"],
"deniedDomains": []
}
}

Le résultat, mesuré avec cette configuration :

Fenêtre de terminal
srt -s ~/.srt-settings.json -c 'head -c 20 ~/.ssh/id_ed25519'
head: cannot open '/home/dev/.ssh/id_ed25519' for reading

Le réseau se rouvre domaine par domaine, et seulement pour ceux qui sont listés.

Fenêtre de terminal
srt -s ~/.srt-settings.json -c 'curl -s -o /dev/null -w "%{http_code}" https://example.com'
200

Le paquet accepte une troisième section, credentials, avec la même forme que celle du bac à sable intégré. C'est elle qui traite le problème des secrets, en refusant la lecture d'un fichier et en retirant une variable d'environnement avant l'exécution :

{
"filesystem": { "denyRead": [], "allowWrite": ["/chemin/projet"], "denyWrite": [] },
"network": { "allowedDomains": ["api.anthropic.com"], "deniedDomains": [] },
"credentials": {
"files": [{ "path": "~/.ssh", "mode": "deny" }],
"envVars": [{ "name": "GITHUB_TOKEN", "mode": "deny" }]
}
}

La variable disparaît réellement de l'environnement du sous-processus :

Fenêtre de terminal
srt -s ~/.srt-settings.json -c 'echo "TOKEN=[$GITHUB_TOKEN]"'
TOKEN=[]

La documentation du projet est honnête sur ses limites, et elles méritent d'être connues avant de considérer le problème réglé.

Un domaine autorisé trop large rouvre la porte à l'exfiltration. Autoriser github.com permet de pousser des données dans une issue ou une gist. Le domain fronting reste envisageable pour contourner le filtrage par nom. Une configuration de fichiers trop permissive peut mener à une escalade de privilèges, par exemple en autorisant l'écriture dans un répertoire dont le contenu est exécuté par un service.

Sous Linux, l'option enableWeakerNestedSandbox, prévue pour faire fonctionner Docker à l'intérieur du bac à sable, réduit significativement le niveau d'isolation. Elle ne s'active pas par confort.

Enfin, le confinement a un coût mesurable : 195 ms par démarrage du bac à sable sur la machine de test. Sur une commande unique c'est indolore, sur une boucle de plusieurs centaines d'appels cela devient perceptible.

Les pannes se répartissent en deux familles, et les confondre fait perdre du temps. Soit l'isolation ne démarre pas, et c'est une question de dépendance système ou de politique de sécurité de la distribution. Soit elle démarre et bloque quelque chose de légitime, et c'est la configuration qu'il faut ajuster.

SymptômeCause probableSolution
bubblewrap (bwrap) not installedMoteur d'isolation absentsudo apt install bubblewrap
loopback: Failed RTM_NEWADDRAppArmor refuse CAP_NET_ADMINCréer /etc/apparmor.d/bwrap
Invalid configuration: network: RequiredSections obligatoires manquantesDéclarer filesystem et network
Une commande échoue en Read-only file systemChemin absent d'allowWriteAjouter le répertoire de travail
Un curl rend le code 000Domaine absent d'allowedDomainsLister le domaine, jokers acceptés
Le serveur local est injoignablelocalhost est traité comme toute destinationLancer la vérification hors du bac à sable, aucune clé documentée ne rouvre la boucle locale
Un secret est lisible dans le bac à sableLa lecture est ouverte par défautRenseigner denyRead, ou masquer
  1. Le bac à sable contraint ce que toute commande peut atteindre, là où une liste de permissions énumère les commandes acceptables.
  2. Écriture et réseau sont refusés par défaut : modèle allow-only, on ouvre ensuite.
  3. La lecture est autorisée par défaut, clés privées comprises : modèle deny-then-allow, on ferme ensuite.
  4. Le réseau bloqué n'empêche pas la fuite par le contexte : ce que l'agent lit, le modèle le voit.
  5. localhost est bloqué comme n'importe quelle destination réseau.
  6. Sous Ubuntu 24.04, un profil AppArmor est indispensable, et l'outil ne le documente pas.
  7. filesystem et network sont obligatoires dans le fichier de configuration, sans repli par défaut.
  8. Le confinement coûte environ 195 ms par démarrage.

Les questions ci-dessous portent sur les écarts entre ce que le bac à sable laisse attendre et ce qu'il fait, en particulier sur la lecture des secrets et sur les blocages d'installation. Chaque réponse donne la commande qui permet de vérifier sur votre poste.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn