
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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
Prérequis
Section intitulée « Prérequis »- 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,
bubblewrapetsocat. Sur macOS, rien à installer.
Pourquoi une frontière plutôt qu'une liste
Section intitulée « Pourquoi une frontière plutôt qu'une liste »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.
Ce que le bac à sable change
Section intitulée « Ce que le bac à sable change »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 permissions | Bac à sable | |
|---|---|---|
| Objet contrôlé | la commande | la capacité |
| Modèle | énumération de ce qui est permis | frontière imposée à tout |
| Une entrée oubliée | crée un trou | sans objet, il n'y a rien à énumérer |
| Croissance | inévitable, les règles fusionnent | nulle, la politique tient en une dizaine de lignes |
| Appliqué par | Claude Code, en espace utilisateur | le noyau |
| Contournable par une commande imprévue | oui | non |
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.
Quelle mise en œuvre choisir
Section intitulée « Quelle mise en œuvre choisir »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 session | npx @anthropic-ai/sandbox-runtime claude |
| Ce qui est confiné | les commandes Bash et leurs processus enfants | tout le processus Claude Code |
Outils Read, Edit, Write | hors frontière, régis par les permissions | dans la frontière |
| Serveurs MCP et hooks | hors frontière, sur l'hôte | dans la frontière |
| Configuration | clés sandbox.* de settings.json | fichier ~/.srt-settings.json |
| Maturité | intégré et stable | aperç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é.
Mettre en œuvre sur un projet
Section intitulée « Mettre en œuvre sur un projet »Voici la marche à suivre complète sur un projet réel, du premier lancement à la configuration durable.
-
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 socatbubblewrapassure l'isolation des fichiers,socatrelaie 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. -
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_userns1Une valeur de
1impose le profil ci-dessous. Une valeur de0, 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>}EOFsudo systemctl reload apparmor -
Activer le bac à sable depuis une session, dans le projet concerné.
/sandboxLe 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. -
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,terraformounpm. -
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.
Envelopper le processus entier
Section intitulée « Envelopper le processus entier »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.
npx @anthropic-ai/sandbox-runtime claudeLe 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.jsonet/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.
Comment il isole, selon la plateforme
Section intitulée « Comment il isole, selon la plateforme »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.
| Plateforme | Mécanisme |
|---|---|
| Linux | bubblewrap pour les namespaces, plus un filtre seccomp BPF qui bloque la création de sockets Unix au niveau des appels système |
| macOS | sandbox-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.
Installer le paquet autonome
Section intitulée « Installer le paquet autonome »-
Installer le paquet npm.
Fenêtre de terminal npm install -g @anthropic-ai/sandbox-runtime -
Installer bubblewrap, le moteur d'isolation. Sans lui,
srtrefuse de démarrer avec un message explicite.Fenêtre de terminal sudo apt install bubblewrapError: Sandbox dependencies not available: bubblewrap (bwrap) not installedCe 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,
bwrapmanipule des namespaces noyau et relève du système, pas de l'outillage de développement. -
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>}EOFsudo apparmor_parser -r /etc/apparmor.d/bwrap -
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
Ce que le bac à sable bloque réellement
Section intitulée « Ce que le bac à sable bloque réellement »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ère | Résultat | Détail |
|---|---|---|
Lecture de /etc/passwd | passe | comportement attendu |
| Lecture d'une clé SSH privée | passe | aucune protection par défaut |
Lecture de ~/.claude/.credentials.json | passe | aucune protection par défaut |
Écriture dans /tmp, $HOME, le répertoire courant | bloqué | Read-only file system |
| Résolution DNS, HTTPS, ICMP | bloqué | aucune sortie réseau |
Requête vers localhost | bloqué | y compris les services locaux |
Fichier docker.sock | visible | mais inutilisable, voir ci-dessous |
| Création d'un socket Unix | bloqué | refusé par le filtre seccomp |
| Liste des processus de l'hôte | bloqué | namespace PID isolé |
| Variables d'environnement | héritées | non 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ère | Paquet autonome | Bac à sable intégré |
|---|---|---|
| Écriture dans le répertoire de travail | refusée | autorisée, ainsi que le répertoire temporaire de session |
| Réseau | refusé en silence | demande une approbation au premier domaine, puis l'autorise pour la session |
| Sockets Unix | bloquées par seccomp | bloqué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.
Le piège : la lecture reste ouverte
Section intitulée « Le piège : la lecture reste ouverte »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à.
Écrire une configuration pour le paquet autonome
Section intitulée « Écrire une configuration pour le paquet autonome »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.
{ "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 :
srt -s ~/.srt-settings.json -c 'head -c 20 ~/.ssh/id_ed25519'head: cannot open '/home/dev/.ssh/id_ed25519' for readingLe réseau se rouvre domaine par domaine, et seulement pour ceux qui sont listés.
srt -s ~/.srt-settings.json -c 'curl -s -o /dev/null -w "%{http_code}" https://example.com'200Le 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 :
srt -s ~/.srt-settings.json -c 'echo "TOKEN=[$GITHUB_TOKEN]"'TOKEN=[]Ce qui reste exposé
Section intitulée « Ce qui reste exposé »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.
Dépannage
Section intitulée « Dépannage »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ôme | Cause probable | Solution |
|---|---|---|
bubblewrap (bwrap) not installed | Moteur d'isolation absent | sudo apt install bubblewrap |
loopback: Failed RTM_NEWADDR | AppArmor refuse CAP_NET_ADMIN | Créer /etc/apparmor.d/bwrap |
Invalid configuration: network: Required | Sections obligatoires manquantes | Déclarer filesystem et network |
Une commande échoue en Read-only file system | Chemin absent d'allowWrite | Ajouter le répertoire de travail |
Un curl rend le code 000 | Domaine absent d'allowedDomains | Lister le domaine, jokers acceptés |
| Le serveur local est injoignable | localhost est traité comme toute destination | Lancer la vérification hors du bac à sable, aucune clé documentée ne rouvre la boucle locale |
| Un secret est lisible dans le bac à sable | La lecture est ouverte par défaut | Renseigner denyRead, ou masquer |
À retenir
Section intitulée « À retenir »- Le bac à sable contraint ce que toute commande peut atteindre, là où une liste de permissions énumère les commandes acceptables.
- Écriture et réseau sont refusés par défaut : modèle allow-only, on ouvre ensuite.
- La lecture est autorisée par défaut, clés privées comprises : modèle deny-then-allow, on ferme ensuite.
- Le réseau bloqué n'empêche pas la fuite par le contexte : ce que l'agent lit, le modèle le voit.
localhostest bloqué comme n'importe quelle destination réseau.- Sous Ubuntu 24.04, un profil AppArmor est indispensable, et l'outil ne le documente pas.
filesystemetnetworksont obligatoires dans le fichier de configuration, sans repli par défaut.- Le confinement coûte environ 195 ms par démarrage.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »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.
Le bac à sable est intégré
/sandbox
Cette commande ouvre le panneau de configuration. Sur macOS il n'y a rien à installer, Seatbelt fait partie du système. Sur Linux et WSL2, deux paquets sont nécessaires :sudo apt install bubblewrap socat
À quoi sert le paquet npm alors
| Usage | Détail |
|---|---|
| Filtre seccomp | Optionnel, ajoute le blocage des sockets Unix au bac à sable intégré |
| Enveloppe du processus | npx @anthropic-ai/sandbox-runtime claude confine aussi les serveurs MCP et les hooks |
Ce que le bac à sable intégré ne couvre pas
Il confine les commandes Bash et leurs processus enfants. Les outilsRead, Edit et Write, les serveurs MCP et les hooks tournent hors de cette frontière. Pour les inclure, il faut envelopper le processus entier.Vérifié sur Claude Code 2.1.208.Non, pas en configuration par défaut
Vérification directe :srt -c 'head -c 20 ~/.ssh/id_ed25519'
La lecture passe. Les trois frontières ne suivent pas le même modèle :| Frontière | Modèle | Par défaut |
|---|---|---|
| Écriture | allow-only | tout refusé |
| Réseau | allow-only | tout refusé |
| Lecture | deny-then-allow | tout autorisé |
La correction
{
"filesystem": {
"denyRead": ["~/.ssh", "~/.claude/.credentials.json", "~/.aws"],
"allowWrite": ["/chemin/projet"],
"denyWrite": []
},
"network": { "allowedDomains": [], "deniedDomains": [] }
}
Vérifié sur srt 1.0.0 et bubblewrap 0.9.0.La cause réelle
Le message est trompeur. Le journal du noyau donne la vraie raison :sudo dmesg | grep -i "apparmor.*bwrap"
apparmor="DENIED" operation="capable" profile="unprivileged_userns"
comm="bwrap" capability=12 capname="net_admin"
Depuis Ubuntu 24.04, tout binaire créant un namespace utilisateur non privilégié transite vers le profil unprivileged_userns, qui refuse CAP_NET_ADMIN.La solution
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
C'est la forme recommandée par Ubuntu pour les applications qui utilisent légitimement les namespaces utilisateur.Non, et c'est structurel
Les releases du projet ne contiennent que :bubblewrap-0.11.2.tar.xz
bubblewrap-0.11.2.tar.xz.sha256sum
Aucun binaire. Les backends github, ubi et aqua savent télécharger et extraire, aucun ne sait compiler. Un mise use -g github:containers/bubblewrap extrait donc bubblewrap.c et meson.build, et bwrap reste introuvable.La bonne voie
sudo apt install bubblewrap
C'est cohérent avec sa nature : bwrap crée des namespaces noyau et relève du système, au même titre que util-linux. Installé hors gestionnaire de paquets, il ne recevrait aucun correctif de sécurité.Oui, et c'est souvent une surprise
srt -c 'curl -s -m 3 -o /dev/null http://127.0.0.1:4321'
La requête échoue, comme n'importe quelle destination externe. Le modèle réseau est allow-only : rien ne sort tant que rien n'est déclaré.La conséquence pratique
Un agent confiné ne peut pas vérifier le rendu d'un serveur de développement local, ni interroger une base de données surlocalhost. Il faut déclarer explicitement ces accès dans la section network.Vérifié sur srt 1.0.0.Ce que montre le test
srt -c 'test -S /var/run/docker.sock && echo present'
present
Le fichier existe bien dans l'arborescence. Mais :srt -c 'python3 -c "import socket;socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)"'
L'appel échoue. Un filtre seccomp BPF bloque la création de sockets Unix au niveau syscall, appliqué par un binaire C statique embarqué dans l'outil.La nuance
La documentation cite malgré tout l'accès aux sockets Unix parmi les risques, dans les configurations qui les réautorisent explicitement viaallowUnixSockets.Deux approches, deux coûts
| Critère | sandbox-runtime | Conteneur Docker |
|---|---|---|
| Mise en route | enveloppe un processus existant | construire une image |
| Accès au projet | direct, système de fichiers de l'hôte | volume à monter |
| Isolation | namespaces + seccomp | idem, plus complète |
| Coût au lancement | environ 195 ms | démarrage du conteneur |