Le jeton a fui du processus d'à côté
Des chercheurs ont extrait un JWT d'un Worker Cloudflare voisin à 12 bits par seconde, sans évasion de sandbox et sans bug de sûreté mémoire. La frontière de l'isolate a tenu. Le secret l'a traversée quand même, par le matériel en dessous. Les plateformes cloud proposent aujourd'hui cette même frontière pour exécuter du code écrit par des agents IA. Bromure Agentic Coding descend une couche plus bas, et garde vos identifiants entièrement hors de la boîte.
Le JavaScript d'un locataire a posé au processeur quelques millions de questions bien formées et a lu, dans les réponses, le jeton d'authentification d'un autre locataire. Il n'a jamais quitté la frontière que la plateforme avait tracée pour lui, il tournait en JavaScript ordinaire, et cette frontière a fait son travail du début à la fin.
Le 17 août, une équipe de chercheurs a publié Remote-Timer-as-a-Service, et le 19 Cloudflare a publié son propre récit de ce travail. Ils ont extrait un JSON Web Token, la chaîne signée qu'un service accepte comme preuve de votre identité, d'un Worker Cloudflare co-localisé en production, jusqu'à 12 bits par seconde avec 99,16 % de précision. Le précédent résultat public contre la même plateforme, en 2021, atteignait 2 bits par minute. À 360 fois ce débit, on passe de la curiosité à l'identifiant volé.
Cloudflare a corrigé la faille, n'a trouvé aucune trace d'exploitation par un tiers en trois ans, et affirme qu'aucune donnée client n'a été consultée. Lisez au-delà et regardez ce que l'attaque n'a jamais eu besoin de faire.
Rien n'a eu besoin de casser
Cloudflare Workers fait tourner beaucoup de locataires sur peu de matériel, et pour cela il se passe de ce qui les sépare d'habitude. Dans les mots de Cloudflare, Workers « exécute le code de plusieurs locataires dans des isolates V8 séparés à l'intérieur du même processus système ». Un isolate est un conteneur au niveau du JavaScript : son propre tas, ses propres globales, pas de mémoire partagée, pas de threads. Il démarre en quelques millisecondes, et c'est tout l'intérêt, parce que l'isolation par processus vous coûte un démarrage à froid à chaque requête.
L'attaquant a besoin de code qui tourne dans un isolate. Amener la victime dans le
même processus est la partie élégante : le Worker de l'attaquant appelle la victime.
Comme l'écrivent les chercheurs, invoquer la victime depuis le script de l'attaquant
avec fetch("https://victim.example") « fera dans la plupart des cas démarrer au
planificateur une instance du worker victime dans exactement le même processus ». On
n'attend pas la co-localisation, on la convoque.
À partir de là, l'attaque est Spectre. Entraîner de travers un prédicteur de branche
pour que le CPU exécute spéculativement une lecture qu'il n'aurait jamais dû faire,
puis mesurer le cache pour apprendre ce qu'il a lu. Cloudflare avait déjà passé des
années à rendre cela difficile : pas de SharedArrayBuffer, pas de threads, des
timers gelés et grossis pour que vous ne puissiez rien mesurer d'aussi petit qu'un
accès au cache, et Dynamic Process Isolation (DyPrIs), qui surveille les compteurs de
performance matériels et déplace tout ce qui sent le Spectre dans son propre
processus.
Les chercheurs ont contourné chacune de ces défenses.
L'horloge est venue du dehors. Un WebSocket vers un Durable Object, ou vers un simple serveur d'horodatage sur Internet, vous donne une résolution sous la milliseconde depuis une plateforme qui refuse de vous donner une horloge. Ensuite ils ont amplifié le signal au lieu de le résoudre : la politique de remplacement tree-PLRU du cache L1 suit des pointeurs d'arbre binaire, si bien qu'un seul événement de cache peut être transformé en une longue série de hits ou une longue série de misses. « Le timing d'un unique événement de cache peut être amplifié arbitrairement », écrivent-ils. Une horloge floue suffit si vous rendez énorme la chose que vous chronométrez.
DyPrIs est passé à côté deux fois. Il isole un script après la fin de l'invocation, et un Durable Object maintenu en vie par des messages de keep-alive tourne pendant des heures, jusqu'à une journée, si bien que l'exploit s'achève à l'intérieur d'une seule invocation qui ne finit jamais. Il normalise aussi les erreurs de prédiction de branche par les accès iTLB alors que, dans les mots de Cloudflare, « notre timer distant est une grosse boucle d'E/S, et ce trafic WebSocket gonfle l'activité iTLB ». Les chercheurs ont fait grossir leur propre dénominateur jusqu'à ce que l'alarme affiche normal.
Le reste est de la plomberie : un gadget de confusion de types spéculative pour fuiter l'adresse racine de l'isolate, une confusion de pointeurs 64 bits sur le backing store d'un typed array pour transformer cela en lecture arbitraire, puis le JWT de la victime, un bit à la fois.
Les correctifs en disent plus que la découverte
Cloudflare a livré trois mitigations. Lisez-les ensemble.
Ils ont intégré le V8 Sandbox, qui retire les pointeurs 64 bits bruts de la région du tas JavaScript et tue le gadget des typed arrays. Ils ont appris à DyPrIs à traiter les Durable Objects de longue durée et les charges lourdes en E/S comme des cas de sécurité à part entière plutôt que comme du bruit. Et ils ont déployé les Memory Protection Keys, qui placent le tas de chaque locataire derrière une frontière d'accès imposée par le matériel, à l'intérieur du processus partagé.
Les trois descendent sous JavaScript chercher quelque chose de plus solide que la sémantique de JavaScript, et la troisième atterrit sur le CPU lui-même. Cloudflare dit franchement que le V8 Sandbox « n'est pas une mitigation Spectre complète ». Des ingénieurs qui réparent une frontière au niveau du langage en ajoutant une frontière au niveau du matériel vous ont dit où ils pensent que passe la vraie ligne.
Un blog sur l'agentic coding qui parle d'un CDN
L'isolate a quitté le CDN. En mars, Cloudflare a livré les Dynamic Workers sous le
titre
Sandboxing AI agents, 100x faster,
une façon de démarrer un Worker à l'exécution avec du code qui n'existait pas une
seconde plus tôt. Si vous avez fait tourner un agent qui écrit et exécute ses propres
scripts, vous connaissez la motivation : « si un agent (ou un serveur MCP) doit
exécuter du code généré à la volée par une IA pour accomplir des tâches, ce code doit
tourner quelque part », et « vous ne pouvez pas simplement faire eval() sur du code
généré par IA directement dans votre application ».
Cloudflare nomme le mécanisme dans le même billet : « Les Dynamic Workers utilisent le même mécanisme de sandboxing sous-jacent sur lequel toute la plateforme Cloudflare Workers est bâtie depuis son lancement, il y a huit ans : les isolates. » La vitesse en est la raison. Un isolate démarre en quelques millisecondes sur quelques mégaoctets, environ 100 fois plus vite et 10 à 100 fois plus efficace en mémoire qu'un conteneur, si bien qu'un sandbox neuf par requête devient abordable au lieu d'un pool tiède que vous réutilisez et que chaque tour affaiblit.
Cloudflare est transparent sur le prix. Leur propre billet de bêta le dit en huit mots : « Les bugs de sécurité de V8 sont plus courants que les vulnérabilités d'hyperviseur. » Ils ont écrit cela des mois avant que quiconque ne fasse fuiter un jeton.
L'histoire ici est donc un échange, écrit noir sur blanc par ceux qui le font, et désormais chiffré par des chercheurs à 12 bits par seconde. La question retombe sur tous les autres. Quand le code mis en sandbox a été écrit par un modèle, façonné par ce qu'un paquet ou une page web lui a raconté, et laissé à tourner à côté d'un jeton porteur, est-ce l'échange que vous voulez ?
Bromure trace la ligne à l'hyperviseur
Bromure Agentic Coding ne fait pas tourner votre agent dans un conteneur au niveau du langage à côté du code d'autres gens. Chaque espace de travail est une machine virtuelle Linux sur Virtualization.framework d'Apple, Apple Silicon uniquement : son propre noyau, sa propre mémoire invitée, son propre disque cloné depuis une image de base en lecture seule. Pas de processus partagé signifie pas de co-locataire à convoquer, et la frontière derrière laquelle tourne votre agent est l'hyperviseur, c'est-à-dire le côté de la comparaison de Cloudflare où les bugs sont les plus rares.
Voilà l'architecture. Ce qui aurait rendu cette attaque précise sans objet est plus étroit, et se situe un cran plus loin.
Le jeton n'a jamais été dans la boîte
Ce que les chercheurs ont emporté, c'est un JWT posé dans l'espace d'adressage de la victime, là où un service en cours d'exécution garde l'identifiant qu'il utilise. Ils n'ont obtenu ni shell ni root.
Un espace de travail Bromure ne garde pas l'identifiant là.
Chaque secret d'un profil reste sur votre Mac. Cela couvre la ANTHROPIC_API_KEY, le
jeton d'accès personnel GitHub, la clé d'accès AWS, le jeton porteur du kubeconfig,
le blob Basic-auth du registre de conteneurs et la clé Linear. Ce que Bromure injecte
dans la VM est un faux : un espace réservé brm_…, un certificat client jetable, une
chaîne Basic dérivée. L'agent le lit, le SDK le lit, git, kubectl et doctl le
lisent, et le travail passe, parce que le proxy MITM sur l'hôte substitue la vraie
valeur sur la requête sortante. AWS reçoit un tour de vis supplémentaire. L'hôte
resigne la requête SigV4 avec le vrai matériel, si bien que tout ce qui contourne le
proxy reçoit un InvalidSignatureException d'Amazon au lieu d'un appel qui marche.
Ainsi un attaquant qui détient une lecture arbitraire parfaite dans cette VM, la primitive que les chercheurs ont passé un article à construire, lit un leurre. Les octets sont là, ils se parsent, ils ressemblent à une clé, et ils n'achètent rien hors de la machine. Vous obtenez cela que la frontière d'isolation soit parfaite ou non, parce que l'espace de travail est vide de la chose qui vaut la peine d'être volée.
Douze bits par seconde ont encore besoin d'une sortie
Un canal auxiliaire n'est que la moitié d'une exfiltration. L'autre moitié est une route vers l'extérieur, et 12 bits par seconde est une route qui doit rester ouverte un moment. Un jeton de 400 caractères, cela fait des minutes de trafic vers une destination que l'attaquant contrôle.
Un profil Bromure porte ses propres règles de sortie, écrites comme une courte
politique de style pf et éditées sous forme de table. Votre Mac les applique, dans le
switch virtuel et dans le proxy, et rien à l'intérieur de l'invité n'a voix au
chapitre : une connexion refusée reçoit un reset, et la décision atterrit comme une
ligne egress.firewall dans Window → Security Timeline avec l'hôte, l'IP, le port
et le verdict. Le code qui tourne dans l'espace de travail se trouve du mauvais côté
des règles qu'il lui faudrait changer.
Vous pouvez aussi poser la main sur chaque dépense d'un vrai identifiant. Activez Require approval to use par secret : les clés d'API des modèles, la signature SSH, les jetons GitHub, GitLab et Bitbucket, chaque contexte kubeconfig, DigitalOcean, chaque signature AWS SigV4, chaque registre de conteneurs, et toute règle de jeton manuelle que vous ajoutez. La substitution s'interrompt alors pour un dialogue sur macOS, et vous l'accordez pour cinq minutes, une heure ou la session. L'invité n'a jamais le droit de répondre à cette question.
L'échange est l'histoire
Les isolates sont une belle pièce d'ingénierie, et Cloudflare a géré cela comme on l'espère : divulgation coordonnée, trois mitigations réelles, aucun enfumage sur ce que le V8 Sandbox couvre et ne couvre pas. Ce n'est pas pour rien que des millions de requêtes par seconde ne tournent pas sur des conteneurs.
Les démarrages à froid sont ce qui a gagné l'argument sur un CDN, et votre agent de code ne répond pas à une requête HTTP en quatre millisecondes. Il tourne pendant une heure, installe des paquets que personne n'a audités, exécute des suites de tests écrites par un modèle et lit des pages web qui aimeraient bien lui glisser un mot. Ce qui fait tourner cette charge de travail détient vos jetons pendant toute la durée, et la seule chose que vous sachiez du code qu'il exécute, c'est que ce n'est pas vous qui l'avez écrit.
Pour ce travail-là, quelques centaines de millisecondes de démarrage de VM ne sont pas un coût qu'il vaut la peine d'optimiser. Cloudflare a déjà publié le taux de change : les bugs de V8 sont plus courants que les bugs d'hyperviseur. Bromure prend l'autre côté de cet échange, puis sort aussi le jeton de la pièce.
Sources : Schwarzl, Xiao, Pedersen, Ainsworth, Topham, « Remote-Timer-as-a-Service: Efficient Microarchitectural Leakage in the Cloud with Remote Timers » (arXiv:2608.17043, 17 août 2026) · Cloudflare, « A revisit of remote Spectre attacks on Cloudflare Workers » (19 août 2026) · Cloudflare, « Sandboxing AI agents, 100x faster » (24 mars 2026) · The Hacker News, « Cloudflare Workers Spectre Attack Leaks JWT From Co-Located Worker at 12 Bits/Second » (19 août 2026)