L'espace libre n'était pas vide
Le 24 septembre, Cloudflare et Oren Yomtov d'Accomplish ont publié une faille inter-locataires dans Cloudflare Containers et Sandboxes. Écrivez 4 Kio dans un sandbox neuf, lisez le disque brut, et les 60 Kio restants du bloc reviennent chargés des fichiers .env, des fichiers d'identifiants et des profils Chromium du locataire précédent. Rien ne s'est échappé de rien : toutes les frontières actives ont tenu, et la fuite était dans ce qu'il advenait d'un bloc une fois que son propriétaire en avait fini avec lui. Cloudflare vend Sandboxes pour exécuter du code généré par des modèles de langage. Bromure Agentic Coding exécute ce code depuis un fichier image sur votre propre Mac, et les blocs libres dans lesquels il puise sont les vôtres.
Un sandbox a deux missions. Contenir ce qui tourne dedans, et oublier ce qui y tournait avant. La seconde est une option de configuration.
Vous envoyez la suite de tests de l'agent dans un sandbox cloud, parce que faire
tourner du code non relu sur votre portable a cessé de paraître malin il y a un an
environ. Le sandbox démarre en une seconde avec un système de fichiers Linux
complet. L'agent écrit un petit fichier. En dessous, la plateforme lui remet un
bloc de disque de 64 kilo-octets, et 60 de ces kilo-octets contiennent encore le
.env de quelqu'un d'autre.
Vous n'avez eu à vous échapper de rien pour arriver là.
Écrivez quatre kilo-octets, lisez-en soixante
Le 24 septembre, Cloudflare a publié un compte rendu conjoint avec les chercheurs sur une faille inter-locataires dans Cloudflare Containers. Oren Yomtov d'Accomplish l'a signalée le 4 septembre via HackerOne et a publié sa propre version la même semaine. The Hacker News l'a couverte le lendemain.
Containers donne à chaque client un disque tiré d'un pool partagé, via le
provisionnement fin de device-mapper sous Linux, avec une taille de bloc de
64 Kio. Le pool portait l'option skip_block_zeroing, qui fait ce qu'elle
annonce : dm-thin n'effaçait pas un bloc fraîchement alloué avant de le remettre à
un conteneur. Quand un conteneur était supprimé, ses blocs retournaient au pool,
contenu intact, et le pool s'étendait à cheval sur les comptes clients.
À partir de là, la technique tient en quatre étapes et aucun exploit :
- Trouver les régions alignées sur 64 Kio que le système de fichiers ext4 du conteneur lui-même croit libres.
- Écrire un unique bloc de 4 Kio dans chacune. Cela suffit à faire allouer par dm-thin un bloc physique de 64 Kio pris dans le pool partagé.
- Lire le périphérique brut plutôt que le système de fichiers.
- Garder les 60 Kio restants.
La phrase de Cloudflare pour décrire la primitive est le bug tout entier : « une écriture couvrant tout le bloc remplaçait son contenu précédent, mais une écriture plus petite ne modifiait que la portion écrite. »
Les blocs récupérés contenaient des structures exploitables. Cloudflare rapporte
des « structures de répertoires, des pages de bases de données et des bases SQLite
structurellement complètes », et compte 2 700 inodes de répertoires étrangers
distincts sur six placements de production, avec du matériel résiduel sur 18 des
24 placements. The Hacker News rapporte des données récupérées sur 20 des
22 machines sous-jacentes, réparties sur quatre continents. Yomtov énumère ce
qu'il a lu dans ces blocs : « des listings de répertoires, des bases SQLite, des
profils Chromium, des fichiers .env et des fichiers d'identifiants. »
Cloudflare énonce deux limites qui bornent l'incident. Un attaquant « ne pouvait pas choisir une victime particulière ni accéder à un disque activement attaché » : vous tombiez donc sur qui avait été sur cette machine, pas sur une cible choisie. Et Cloudflare a construit des signatures de détection à partir de la preuve de concept des chercheurs, les a passées sur sa télémétrie historique d'activité disque, et n'a trouvé « aucune preuve que ce vecteur d'attaque précis ait été exploité par quelqu'un d'autre. »
Cloudflare a agi vite. Signalement à 15h26 UTC le 4 septembre ; mauvaise configuration de production confirmée à 18h45 ; correctif du runtime fusionné à 21h27 ; configuration du pool fusionnée à 22h03 ; déploiement lancé à 23h15. Tout cela en une seule journée. Le déploiement s'est achevé le 7 septembre, les chercheurs ont confirmé que leur preuve de concept était morte le 14 septembre, et Cloudflare a effacé les derniers instantanés disque antérieurs à la mitigation le 19 septembre. Les clients n'avaient aucune action à mener, et aucun moyen de vérifier depuis l'intérieur d'un sandbox si leurs propres blocs étaient arrivés propres.
Toutes les frontières actives ont tenu
Le titre du compte rendu de Yomtov, « Escaping the Cloudflare Sandbox », sous-vend à quel point ce bug est silencieux.
Il n'a trouvé aucun bug de sûreté mémoire, aucune élévation de privilèges et aucune évasion de conteneur. Toutes les frontières autour d'un conteneur en cours d'exécution ont tenu comme prévu pendant toute la durée de l'incident. Le client B ne pouvait pas lire le conteneur vivant du client A, et Cloudflare note qu'un sandbox ne pouvait pas non plus atteindre un disque activement attaché.
La fuite était dans le passage de relais. Cloudflare libérait un bloc d'un locataire et le remettait au suivant, et ce qui séparait ces deux clients était un booléen dans une configuration de pool.
Les chercheurs consacrent leur attention à ce que le code à l'intérieur d'un sandbox peut atteindre à l'instant présent, et bien moins à ce que le dernier occupant a laissé derrière lui. Cette seconde question a une réponse ennuyeuse la plupart du temps : la plateforme remet le bloc à zéro. La réponse tient jusqu'au jour où un ingénieur désactive l'effacement pour gagner en débit.
Accomplish compte celle-ci comme sa sixième évasion de sandbox publiée depuis juillet 2026. Les cinq précédentes : SharedRoot dans Claude Cowork, Beltdown dans Claude Code, Beltdown2 dans le CLI de Cursor, une dans le VMM de Docker, et une dans le sandbox Codex d'OpenAI. Cinq de ces six exécutent du code écrit par un agent. La conclusion de Yomtov tient en une ligne : « l'isolation peut échouer de bien des façons. »
C'est justement là que le code des agents devait aller
Cloudflare Sandboxes tourne sur Containers, et a donc hérité du bug. Sandboxes est aussi le produit que Cloudflare vend pour la chose dont ce blog n'arrête pas de parler. D'après la documentation de Cloudflare elle-même, le SDK existe pour « exécuter du code non fiable en toute sécurité dans des environnements isolés », il sert à « exécuter du code généré par de grands modèles de langage de façon sûre et fiable », et il convient aux « agents IA, assistants de code et systèmes autonomes qui ont besoin d'exécuter du code non fiable. » Chaque sandbox tourne « dans son propre conteneur isolé avec un environnement Linux complet, offrant de solides frontières de sécurité tout en maintenant les performances. »
Chaque proposition de cette phrase est une chose raisonnable à vouloir, et elle décrit à peu près aussi bien la VM de workspace Bromure qu'un Cloudflare Sandbox. La différence, c'est qui d'autre se trouve sur la machine.
Déplacer l'exécution des agents hors de votre portable relocalise une décision de confiance, elle ne la supprime pas. Vous faites désormais confiance à un allocateur de pool que vous ne voyez pas, dans un placement que vous n'avez pas choisi, à côté de locataires que personne ne vous nommera. Ce peut être le bon compromis. Faites-le exprès, et posez d'abord la question du résidu : d'où viennent mes blocs libres, et qu'y avait-il dedans.
Le disque est un fichier sur votre Mac
Bromure Agentic Coding répond à cette question en n'ayant pas de pool.
Chaque workspace correspond à exactement une VM. Deux workspaces ne partagent
jamais une VM, et un workspace n'en a jamais deux. La VM démarre depuis deux
disques virtuels qui sont des fichiers ordinaires dans le répertoire propre à ce
workspace, sur votre Mac. disk.img est le disque système, créé au premier
lancement comme un clone APFS en copie sur écriture de l'image de base Ubuntu
locale : instantané, et gratuit jusqu'à ce que l'invité écrive. home.img est une
image ext4 creuse et privée qui porte /home/ubuntu, attachée comme second
périphérique virtio-blk.
La technique en quatre étapes n'a nulle part où atterrir ici. Un agent dans l'invité peut lire le périphérique bloc brut au lieu du système de fichiers, puisqu'il y est root, et c'est très bien. Il atteint un fichier image appartenant à un workspace. Les blocs dans lesquels ce fichier grandit viennent du volume APFS de votre propre Mac, et l'occupant précédent d'un bloc libre sur votre Mac, c'était vous.
Un seul objet est partagé entre les workspaces, et « rien n'est partagé » serait
une affirmation fausse, alors nommons-le : chaque disque de workspace est cloné
depuis base.img. Ubuntu 24.04, 24 Go logiques et 6 à 8 Go physiques, récupéré
contre un catalogue d'images signé portant une signature ed25519 faite avec la même
clé qui signe les mises à jour de l'app, couvrant l'identité de l'image, son
SHA-256 et chaque commande de postinstall, avec protection contre le retour arrière
et refus des catalogues non signés. Il est identique au bit près sur chaque
installation et ne contient rien de vous. C'est le bon genre de chose à partager :
un artefact public dont le métier est d'être identique partout.
Et le résidu ne vaudrait rien
La disposition des blocs est la moitié de la réponse. L'autre moitié est ce qu'un
agent de code écrit sur le disque, et c'est ce qui rendait ce bug digne d'être
signalé : des fichiers .env, des fichiers d'identifiants, des profils Chromium.
À l'intérieur d'un workspace Bromure, ces fichiers existent, et les outils qui les
lisent fonctionnent comme d'habitude, mais les valeurs qu'ils contiennent sont
fausses. Aucun fichier, aucune variable d'environnement, aucun processus dans la VM
ne détient jamais une vraie clé d'API, un vrai jeton OAuth, un vrai secret AWS ou
une vraie clé privée SSH. L'invité reçoit à la place des bouche-trous qui
préservent la structure : sk-ant-api03-brm-…, un jeton GitHub à la forme
ghp_, brm_…, déterministes par installation, pour que les validateurs côté
client soient satisfaits et qu'aucun outil ne croie jamais qu'une clé a tourné
entre deux sessions. Les vraies valeurs vivent dans la table de substitution en
mémoire du proxy hôte, sur le Mac, chacune associée à l'hôte de destination auquel
elle appartient, et sont injectées à la sortie avec une portée exacte-ou-sous-domaine,
jamais par sous-chaîne. Les requêtes AWS sont re-signées côté hôte : l'invité signe
avec un faux secret, l'hôte retire cette signature et re-signe. SSH passe par un
pont vsock en signature seule, et les octets des clés privées ne franchissent jamais
la frontière vers l'invité.
Bromure traite le profil Chromium séparément. Le volet navigateur que l'agent pilote tourne dans sa propre VM Alpine éphémère, un clone en copie sur écriture dans le répertoire temporaire que l'app supprime quand vous fermez le volet. Conserver les sessions est en opt-in, et cela place le profil sur un disque chiffré propre au workspace.
Si une faille future exposait les blocs d'un invité Bromure, le lecteur en repartirait avec une arborescence de répertoires, du code source que vous avez déjà, et un fichier d'identifiants qui ne peut être dépensé nulle part.
La jetabilité comme verbe
Les workspaces sont persistants à dessein. Le code a besoin d'état durable : dépôts clonés, caches de paquets, chaînes d'outils, historique du shell. Bromure contrôle donc ce que la VM a le droit de contenir plutôt que de la détruire chaque nuit, et effacer un disque est quelque chose que vous demandez. L'app est explicite sur chaque façon de le demander :
Réinitialiser à la base
Supprime disk.img pour que le lancement suivant re-clone une copie fraîche de
l'image de base. La boîte de dialogue nomme ce qui disparaît : tout ce que vous
avez installé avec apt install, et vos modifications de /etc, /var,
/usr, /opt. Le home survit.
Effacer le home, supprimer le workspace
Effacer le home fait tomber /home/ubuntu et la paire de clés générée.
Supprimer le workspace retire tout le répertoire de profil, les deux images, le
fichier de secrets chiffré, chaque point de contrôle et le matériel SSH côté
hôte, puis libère son adresse MAC réservée.
Jetables et points de contrôle
bromure-cli vm run --rm vous donne un workspace à la manière de docker,
supprimé à l'arrêt de la VM. Chaque démarrage réussi laisse un point de contrôle
en copie sur écriture du disque et du home, pris seulement après un démarrage
ayant produit un terminal fonctionnel, donc chacun est un démarrage prouvé.
L'effacement forcé
Si le proxy attrape un bouche-trou d'identifiant partant vers un endroit où il n'a pas sa place, Bromure marque le workspace comme compromis, et il ne redémarrera pas avant que vous n'effaciez son disque et son home. Vous gardez vos réglages, vos jetons et vos clés SSH, et l'effacement ne touche jamais aux dossiers hôtes partagés.
Cet ensemble de verbes, c'est la différence entre un effacement que vous avez demandé et un effacement que vous avez supposé.
Demandez ce que le locataire précédent a laissé
Cloudflare a trouvé la mauvaise configuration, l'a corrigée en neuf heures, a purgé les pools et les instantanés, a construit des signatures de détection pour vérifier le passé, et a publié la taille de bloc et le nom du drapeau. Lisez cela comme un éditeur qui gère bien une mauvaise journée.
« Sandbox » nomme un espoir à propos d'une frontière, et cette frontière s'est avérée être un cycle de vie de ressource plutôt qu'un mur. La réponse de l'industrie aux agents qui exécutent du code non relu a été de déplacer l'exécution ailleurs et de faire confiance à cet ailleurs pour arriver propre. Il arrive propre, la plupart du temps. Vous ne pouvez pas le vérifier depuis l'intérieur du sandbox, ni cette semaine ni une autre.
Faites tourner le code sur une infrastructure que vous pouvez voir et vous pourrez répondre vous-même à la question du résidu : un fichier image par workspace, des blocs libres pris sur votre propre volume, un seul objet partagé et c'est une image de base publique signée, et rien sur ce disque qui vaille la peine d'être lu s'il fuyait un jour. L'effacer est une entrée de menu.