Laisser un Agent exécuter du code sans laisser votre machine se faire détruire
Il y a quelques mois, j'ai regardé un agent décider que la façon la plus propre de « réinitialiser la base de données de test » était un rm -rf sur un chemin qu'il avait construit à partir d'une variable qui s'est avérée vide. La commande qu'il a proposée était rm -rf /$DIR, et $DIR était vide. Je l'ai remarqué parce que je fonctionnais en mode par défaut et qu'il a demandé d'abord. J'ai cliqué sur Non, j'ai corrigé le script, et je suis allé me faire un café avec les mains qui tremblaient légèrement.
C'était sur mon vrai ordinateur portable. La même machine qui contient mes clés SSH, mon ~/.aws/credentials, ma clé Stripe restreinte dans un .env deux répertoires plus haut, et le seul exemplaire d'un projet parallèle que je n'avais pas encore poussé. L'agent n'était pas malveillant. Il avait simplement tort, comme un junior rapide et confiant a tort, sauf qu'il tape à 200 commandes par minute et ne se fatigue jamais assez pour ralentir.
Ce que j'ai mis trop de temps à intérioriser : le danger n'est pas que l'agent devienne incontrôlable. C'est l'ennuyeuse intersection d'une commande fausse et confiante, d'une information d'identification dans une variable d'environnement, et d'une connexion réseau qui peut atteindre l'internet ouvert. Vous n'avez pas besoin d'un méchant. Vous avez besoin d'un accident et d'un chemin d'exfiltration, et la plupart des configurations livrent les deux par défaut.
La version courte : l'isolation est un spectre, pas un interrupteur. La couche la moins chère est celle des propres invites de permission de l'agent. La plus robuste est une microVM avec son propre kernel. Pour le travail quotidien sur du code que vous avez écrit, vous voulez des protections au niveau de l'OS plus des portes de permissions. Pour tout ce que vous n'avez pas écrit — un dépôt aléatoire, un script récupéré, du code généré par un autre agent — vous voulez une vraie frontière que le système d'exploitation ne peut pas être convaincu de contourner. Voici toute l'échelle et où je me situe réellement à chaque échelon.
Le modèle de l'accident, pas le modèle du méchant
Faites une modélisation des menaces honnête et l'image se simplifie. Il y a trois choses qui peuvent mal tourner quand un agent exécute du code :
- Il détruit l'état local. Supprime des fichiers, écrase la mauvaise chose, force-push par-dessus votre branche.
- Il lit un secret et l'envoie quelque part. Fait un grep dans
~/.ssh, trouve un token dans une variable d'environnement, l'envoie viacurlvers un domaine. C'est celui que les gens sous-estiment. - Il exécute du code auquel vous n'avez jamais fait confiance. Le script post-install d'une dépendance, un payload généré, un dépôt que vous avez cloné pour « jeter un coup d'œil ».
Les invites de permission traitent (1) et en partie (3). Elles ne font presque rien pour (2), parce qu'au moment où une commande s'exécute, le secret est déjà dans l'environnement du processus. Le mouvement n'est donc pas « trouver l'outil qui résout tout ». C'est : empiler une couche bon marché qui arrête la destruction stupide, puis ajouter une vraie frontière en dessous pour les cas où être convaincu par une invite ne suffit pas.
Échelon un : les propres portes de permission de l'agent
Les agents de programmation livrent déjà un modèle de permissions, et c'est le confinement le moins cher que vous ayez. Claude Code, par exemple, dispose de modes de permissions distincts : default (les lectures s'exécutent librement, tout le reste fait une invite), acceptEdits (les éditions de fichiers et une poignée de commandes de système de fichiers comme mkdir, mv, cp s'auto-approuvent dans votre répertoire de travail), plan (recherche uniquement, pas d'éditions), auto (un modèle classificateur séparé examine chaque action et bloque les escalades), dontAsk (seuls les outils pré-approuvés s'exécutent, pour un CI verrouillé), et bypassPermissions — le flag --dangerously-skip-permissions, sur lequel la documentation est directe : « N'utilisez ce mode que dans des environnements isolés comme des conteneurs, des VMs ou des dev containers sans accès à internet. »
Ces modes sont réels et utiles, mais comprenez ce qu'ils sont : une décision prise avant l'exécution de la commande, basée sur la chaîne de commande et, en mode auto, le jugement d'un modèle. C'est une couche de politique, pas un mur. Elle peut se tromper, et une commande créative peut faire plus que son nom ne le suggère. Je m'appuie sur le mode plan pour l'exploration et default pour tout ce qui touche à l'infrastructure, mais je ne considère aucun d'entre eux comme une frontière de sécurité pour du code auquel je ne fais pas confiance. (J'ai écrit sur les hooks Claude Code comme garde-fous ailleurs — même mise en garde : un hook est une politique appliquée dans votre processus, pas de l'isolation.)
Échelon deux : des protections au niveau de l'OS avec lesquelles le modèle ne peut pas argumenter
C'est l'échelon que la plupart des builders en solo sautent, et c'est celui qui a le meilleur rapport effort/sécurité. L'idée : laisser le système d'exploitation appliquer une frontière de système de fichiers et de réseau sur chaque commande shell et ses enfants, peu importe ce que le modèle a décidé d'exécuter.
Claude Code livre cela sous forme d'un outil Bash sandboxé. Sur macOS, il utilise le framework intégré Seatbelt — rien à installer. Sur Linux et WSL2, il utilise bubblewrap pour l'isolation du système de fichiers plus socat pour acheminer le trafic via un proxy. Par défaut, les commandes sandboxées ne peuvent écrire que dans votre répertoire de travail et le répertoire temporaire de session, et l'accès réseau est refusé par défaut : « aucun domaine n'est pré-autorisé. La première fois qu'une commande a besoin d'un nouveau domaine, Claude Code demande l'approbation. »
La raison pour laquelle cela surpasse une invite de permission : la documentation le dit clairement. « Le système d'exploitation applique la frontière du sandbox sur le processus en cours d'exécution, donc elle tient peu importe ce que le modèle a choisi d'exécuter et même si une commande autorisée fait plus que ce que son nom suggère. » C'est la différence entre politique et application.
Deux mises en garde valant la peine d'être lues deux fois, toutes deux directement issues de la documentation. Premièrement, la politique de lecture par défaut permet encore la lecture des fichiers d'informations d'identification — « ce défaut permet encore la lecture des fichiers d'informations d'identification tels que ~/.aws/credentials et ~/.ssh/. » Vous devez les ajouter vous-même à denyRead. Deuxièmement, le proxy réseau applique la liste d'autorisation par nom d'hôte et « ne termine pas et n'effectue pas d'inspection TLS sur le trafic sortant », donc une autorisation large comme github.com peut devenir un chemin d'exfiltration via le domain fronting. Un sandboxing efficace, selon leurs termes, « nécessite à la fois l'isolation du système de fichiers et du réseau » — élargissez un côté et vous pouvez silencieusement annuler l'autre.
Mon avis : activez-le, refusez la lecture de vos répertoires d'informations d'identification et maintenez la liste des domaines autorisés restreinte. C'est gratuit et cela arrête les accidents les plus courants. Mais c'est une couche de durcissement sur votre vraie machine, pas une salle blanche.
Échelon trois : les conteneurs
Un conteneur est la prochaine étape évidente, et pour un travail digne de confiance mais désordonné, c'est bien. Vous faites tourner l'agent dans Docker, vous montez uniquement le répertoire du projet, vous passez uniquement les variables d'environnement dont ce projet a besoin, et le rayon d'explosion est le conteneur. Supprimez le conteneur, le dommage disparaît.
La limitation honnête est celle que les conteneurs ont toujours eue : ils partagent le kernel hôte. Un conteneur, ce sont des namespaces et des cgroups, pas une machine séparée. Pour du code que vous avez écrit, c'est amplement suffisant — le risque réaliste est un accident, et la frontière de namespace contient bien les accidents. Pour du code véritablement non fiable, un exploit de kernel dans le conteneur atteint l'hôte : une classe d'attaque réelle, même si peu commune. Il y a aussi une option intermédiaire — gVisor de Google intercepte les syscalls d'un conteneur dans un kernel en espace utilisateur avant qu'elles n'atteignent l'hôte, réduisant la surface d'attaque sans une VM complète. Plus robuste qu'un conteneur simple, moins que la virtualisation matérielle, avec un coût d'E/S mesurable.
La configuration pratique de conteneur pour un agent : un dev container qui fait tourner l'agent en tant qu'utilisateur non-root, avec le projet monté et rien d'autre. Ce détail de non-root est important — Claude Code refuse de démarrer en mode bypass-permissions en tant que root, et la configuration du dev container existe en partie pour vous donner un utilisateur non-root afin que les exécutions autonomes puissent être laissées sans surveillance en toute sécurité.
Échelon quatre : les microVMs, le vrai mur
Quand vous devez exécuter du code auquel vous ne faites pas confiance, la référence en 2026 est une microVM : une vraie machine virtuelle avec son propre kernel, démarrée en millisecondes, avec le modèle de périphériques réduit à presque rien. Firecracker, le VMM open-source qu'AWS a construit pour Lambda et Fargate, est celui que tout le monde utilise. Toute sa philosophie de conception est une petite surface d'attaque — il « exclut les périphériques inutiles et les fonctionnalités orientées vers le guest pour réduire l'empreinte mémoire et la surface d'attaque de chaque microVM », démarre un kernel minimal sans BIOS et sans modèle de périphériques complet, et fait tourner chaque guest sur la virtualisation matérielle KVM.
L'argument de sécurité est structurel : chaque charge de travail obtient son propre kernel sur une frontière de virtualisation matérielle, de sorte qu'un exploit de kernel à l'intérieur d'une microVM ne peut pas atteindre l'hôte ou une VM voisine. C'est la frontière qu'un conteneur ne peut pas vous donner, parce que le conteneur n'a pas son propre kernel. Le coût était autrefois le temps de démarrage ; les démarrages en ~125ms de Firecracker ont effacé la majeure partie de cela.
Vous ne voudrez presque certainement pas exploiter Firecracker vous-même. C'est à cela que servent les services hébergés.
Échelon cinq : les services de sandbox hébergés
Si « donner à l'agent un ordinateur qui n'est pas le mien » vous semble correct, c'est un sandbox hébergé, et l'espace a beaucoup mûri l'année dernière.
Vercel Sandbox est devenu généralement disponible le 30 janvier 2026 (annonce). Il fait tourner chaque sandbox dans une microVM Firecracker avec son propre système de fichiers et réseau, sur Amazon Linux 2023, avec les runtimes node26/node24/node22/python3.13 et l'accès sudo. L'argument de vente pour le problème de fuite de secrets : le code dans un sandbox est isolé de votre infrastructure, donc il ne peut pas accéder aux variables d'environnement, aux connexions de base de données ou aux ressources cloud de votre projet — cet isolement est le produit, pas un paramètre que vous devez vous rappeler d'activer. Vous le pilotez avec un SDK JS ou Python, ou une CLI. La persistance (sauvegarde automatique et reprise) est activée par défaut depuis la version GA.
E2B est l'autre option évidente, ciblée directement sur l'exécution de code généré par IA. Il est open-source sous Apache-2.0, dispose de SDKs Python et JavaScript, utilise un Dockerfile pour construire un modèle de microVM (pas pour exécuter un conteneur), et — la partie que j'apprécie — est auto-hébergeable via Terraform sur AWS, GCP, Azure ou vos propres machines Linux. Vous pouvez donc commencer sur leur cloud et le rapatrier en interne plus tard sans réécrire.
Le compromis est le habituel : latence, coût par exécution, et un aller-retour réseau entre votre agent et le code. Pour un outil qui exécute des extraits soumis par des utilisateurs ou générés par des agents, c'est un prix juste pour une frontière de kernel propre et un isolement automatique des secrets. Pour éditer votre propre dépôt toute la journée, c'est excessif et la friction vous énervera.
Ce que je fais tourner réellement
Voici la configuration concrète, correspondant aux deux cas qui comptent.
Pour mon propre code, au quotidien : Claude Code sur ma vraie machine, en mode default ou plan, avec le sandbox de l'OS activé. La configuration non négociable est de refuser les lectures sur les répertoires d'informations d'identification et de garder la liste des domaines autorisés courte :
{
"sandbox": {
"enabled": true,
"filesystem": { "denyRead": ["~/.aws", "~/.ssh", "~/.config/gh"] },
"allowedDomains": ["registry.npmjs.org", "github.com"]
}
}
Les secrets vivent dans un .env que le répertoire de travail de l'agent ne peut pas lire, et je purge les informations d'identification du fournisseur des sous-processus (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB) pour qu'un curl errant dans un script de construction ne puisse pas récupérer ma clé API depuis l'environnement. C'est du durcissement, pas un mur, et je le traite comme tel.
Pour tout ce que je n'ai pas écrit — un dépôt que j'audite, du code généré, une dépendance à laquelle je ne fais pas confiance, un agent qui s'exécute sans surveillance pendant la nuit — ça va dans une microVM. En pratique, cela signifie un sandbox hébergé (Vercel Sandbox ou E2B) pour que la frontière du kernel et l'isolement des secrets viennent gratuitement, ou un dev container faisant tourner l'agent en non-root avec bypassPermissions à l'intérieur du conteneur où il n'y a rien de précieux à abîmer. La règle que je suis : la solidité de la frontière doit correspondre à la confiance que j'accorde au code, pas à la commodité de la frontière.
L'erreur que j'ai faite tôt était de traiter cela comme une seule décision — « le sandboxing est-il activé ou désactivé ? » C'en est deux. Le code de confiance sur ma machine veut des protections bon marché qui arrêtent les accidents. Le code non fiable veut un mur que le modèle ne peut pas être convaincu de traverser. Choisissez l'échelon qui correspond à la confiance, refusez la lecture de vos secrets dans tous les cas, et vous pouvez laisser la chose tourner sans surveiller chaque commande — ce qui était tout l'intérêt d'engager un agent.
