La dernière étape était un prompt
Le 18 septembre, Hacktron a publié la chaîne qui l'a menée jusqu'au monorepo privé d'OpenAI. Elle commence par une image déposée sur un forum public et un débordement de tas dans un décodeur d'images dont le correctif amont ne portait aucun CVE. Elle se termine par des chercheurs qui envoient un prompt au compte Codex d'un employé, et l'agent ouvre la pull request. Rien n'a été injecté, rien n'a dysfonctionné ; l'agent a fait ce que demandait celui qui tenait la session. Bromure Agentic Coding décide cette écriture sur votre Mac, en dehors de la session.
La dernière étape de cette chaîne n'a demandé aucun exploit. Les chercheurs tenaient l'agent de codage d'un employé ; ils lui ont donc demandé d'ouvrir une pull request dans le monorepo privé, et il en a ouvert une.
À six heures du matin UTC le 25 juillet, trois chercheurs de Hacktron avaient
l'exécution de code sur le forum communautaire public d'OpenAI. En début
d'après-midi, une pull request numérotée 1186742 se trouvait dans openai/openai,
le monorepo privé, et ils n'avaient écrit aucune ligne de code pour l'y mettre.
Ils ont demandé à un agent, et l'agent l'a déposée.
Hacktron a publié la chaîne le 18 septembre, et elle mérite d'être lue maillon par maillon, parce que chaque maillon est ordinaire. Les chercheurs sont Harsh Jaiswal, Mohan Pedhapati et Rahul Maini. The Hacker News en a rendu compte le lendemain.
Une image, une conversion et un parseur
La porte d'entrée était l'envoi d'images du forum. Discourse vérifie les images
déposées avec une bibliothèque Ruby nommée FastImage, qui lit assez de l'en-tête
d'un fichier pour en déduire les dimensions et le format. FastImage ne comprend
pas le HEIF, le conteneur dans lequel arrive une photo d'iPhone ; Discourse
passait donc ces fichiers à la commande magick d'ImageMagick pour conversion.
ImageMagick décode le HEIC avec libheif, qui est écrit en C.
Voilà toute la condition d'entrée. Un format que le vérificateur sûr ne reconnaissait pas est parti vers un convertisseur qui appelait un parseur écrit dans un langage où un champ de longueur malformé devient un problème de sûreté mémoire. Hacktron a trouvé un débordement de tampon de tas dans le décodage HEIC et en a tiré des primitives de lecture et d'écriture hors limites.
La paperasse est pire que le bug. L'amont avait corrigé ce débordement l'année
précédente sans jamais qualifier le commit de correctif de sécurité. Il n'a reçu
ni CVE, ni bulletin, ni ligne de changelog qu'un scanner de dépendances aurait pu
lire ; Debian ne l'a donc jamais rétroporté, et l'image Docker de Discourse
embarquait libheif 1.19.7 sur Debian 12 avec le bug intact. Aucun scanner au
monde n'avait matière à signaler cette version. Debian a publié DSA-6417-1 plus
tard et le problème porte désormais le CVE-2026-32882, attribué après que
quelqu'un l'a exploité.
On ne peut pas scanner un bug que personne n'a déclaré. Ce que l'on peut décider à l'avance, c'est ce dans quoi se tient le parseur au moment où il tombe.
L'exploit a pris une soirée
Hacktron a commencé à regarder la chaîne d'envoi le 23 juillet. Le 24 juillet, ils sont partis à l'assaut du débordement avec Claude Opus 4.8 qui, selon eux, a peiné sur plusieurs sessions à produire quoi que ce soit de fonctionnel contre la randomisation de l'espace d'adressage, la défense qui brasse la carte mémoire d'un processus pour qu'un attaquant ne puisse pas prédire où se trouve quoi que ce soit. Transformer un débordement de tas en exécution fiable face à l'ASLR est la partie difficile, et c'est là que ce genre de recherche cale une semaine.
Anthropic a sorti Opus 5 ce soir-là. Hacktron a ouvert une session neuve et,
trois heures plus tard, disposait d'un exploit ARM64 fonctionnel tournant contre
un Mac local. Ils ont demandé au modèle de le porter vers la configuration x86-64
et jemalloc sur laquelle tourne Discourse, et ont confirmé l'exécution de code en
local via un envoi d'image à six heures le lendemain matin. Puis ils ont lancé
Claude en boucle autonome contre leur propre instance Discourse Cloud. À dix
heures, il y avait aussi l'exécution de code, et lisait /etc/hosts pour le
prouver.
Ils sont prudents sur ce que cela signifie, et la réserve a sa place dans toute lecture de l'affaire :
Il ne s'agissait pas de piratage entièrement autonome. L'expertise humaine est restée nécessaire pour diriger les sessions, valider les résultats et gérer la divulgation.
Juste. Trois chercheurs compétents ont pointé la chose, et leur compétence n'a pas changé du jour au lendemain. Le bug n'a pas changé non plus. Entre ces deux sessions, la seule variable nouvelle était le modèle, et il a fait passer un travail d'exploitation de plusieurs tentatives infructueuses à trois heures. La campagne dont cela faisait partie a coûté à Hacktron moins de 3 000 dollars de jetons et quelques heures de temps humain.
Ce chiffre est la donnée de planification. Un bug qui n'a jamais reçu de CVE, niché dans une dépendance transitive d'un convertisseur de fichiers, se convertit désormais en primitive fiable pour le prix d'un ordinateur portable. Attendre le bulletin est un espoir, et personne ne devance cette horloge en lisant les changelogs plus vite.
La dernière étape était un prompt
L'exécution de code sur le forum n'a rien apporté à Hacktron dont OpenAI se souciait. C'est un second problème qui l'a fait. OpenAI avait câblé son authentification unique de telle sorte qu'une session de forum compromise devenait une prise de contrôle sans interaction du compte ChatGPT et Codex de tout employé utilisant le forum. Selon les termes de Hacktron, c'est le problème SSO qui a transformé une compromission de forum en accès à ChatGPT et Codex.
Le Codex d'un employé était connecté à l'organisation GitHub de l'entreprise ;
les chercheurs ont donc fait la chose évidente. Ils ont envoyé à ce compte un
prompt lui demandant d'ouvrir une pull request dans le monorepo interne, et la PR
1186742 est apparue dans openai/openai. Puis ils se sont arrêtés, sans lire le
moindre code source, et ont déposé leur rapport.
Relisez cette étape. Quelqu'un détenant une session valide a demandé à l'agent de faire une chose ordinaire, et l'agent l'a faite avec l'accès en écriture qu'il possédait déjà. Pas d'injection, pas de jailbreak, pas d'entrée malformée. Tous les contrôles que quiconque avait construits se tenaient derrière cette session : la connexion, l'appartenance à l'organisation, les permissions du dépôt. Dès lors qu'un attaquant tenait la session, il les tenait tous.
Un agent de codage est une concession permanente d'accès en écriture à vos
dépôts, attachée à une conversation. Quiconque atteint la conversation hérite de
la concession. Cela vaut pour Codex dans un navigateur, et cela vaut pour Claude
Code, Codex CLI ou Gemini CLI sur un portable, où l'agent porte un vrai jeton
ghp_ dans ~/.git-credentials et un vrai GH_TOKEN dans son environnement.
Sur un portable, les voies d'accès se multiplient : un fichier empoisonné dans un
dépôt, un commentaire d'issue, un script postinstall de paquet, un terminal
détourné.
OpenAI a corrigé le forum en quatorze heures environ. Le rapport est parti ce matin-là et OpenAI a confirmé le correctif à 22h49 UTC le jour même. Discourse a livré son propre correctif pendant le week-end et publié le bulletin GHSA-vhm9-85gw-x335 le 28 juillet, et la prime s'est élevée à 6 500 dollars. La part de cette forme qui se trouve sur votre propre bureau, c'est l'agent.
Où se décide l'écriture
Bromure Agentic Coding garde la réponse à « qui a le droit de pousser » sur votre Mac, en dehors de la session de l'agent, parce que la session est précisément ce qu'un attaquant cherche à atteindre. Deux choses s'y produisent.
D'abord, l'environnement de l'agent ne contient aucun jeton dont hériter. La VM
de l'espace de travail reçoit un leurre : ghp_ suivi de 36 caractères, de forme
telle que gh et git l'acceptent sans broncher, dérivé de votre vrai jeton par
HKDF-SHA256 avec un sel propre à l'installation, de sorte qu'il ne semble jamais
tourner d'une session à l'autre. Votre vrai jeton vit chiffré sur l'hôte, et le
proxy hôte le substitue sur le fil après que la requête a quitté la VM, uniquement
lorsque la destination correspond à l'hôte pour lequel ce jeton a été frappé.
Comme le dit le manuel : aucun fichier, variable d'environnement ou processus à
l'intérieur de la VM ne contient jamais une vraie clé d'API, un jeton OAuth, un
secret AWS ou une clé SSH privée. Emportez ce leurre là où il n'a rien à faire et
le proxy répond HTTP 451 sans transmettre un octet, met la VM en pause et inscrit
une ligne rouge dans la Security Timeline.
La seconde est celle que cette histoire appelle. Chaque identifiant de forge git
porte une politique d'écriture, réglée par espace de travail dans le volet
Guardrails, et les nouveaux espaces de travail démarrent par défaut sur
Demander avant écriture. Les lectures passent sans commentaire. Un git push
arrive sur le fil sous la forme git-receive-pack et compte comme une écriture,
tout comme un POST vers l'API des pull requests. Lorsqu'il en arrive un, le
proxy retient la requête et ouvre une fenêtre sur votre Mac qui porte l'opération
littérale plutôt qu'un résumé : la méthode et le chemin. Vous obtenez
Autoriser 15 minutes, Autoriser une fois, Autoriser pour le reste de la
session ou Ne pas autoriser. Un refus parvient à l'agent comme une erreur
d'API ordinaire qu'il peut rapporter, non comme une connexion suspendue.
La description que le volet donne lui-même de l'endroit où cela s'exécute est la phrase qui compte : appliqué dans le proxy, de sorte qu'un agent compromis dans la VM ne peut pas le contourner.
Qui que ce soit qui pilote l'agent — vous, une injection de prompt enfouie dans un fichier de fixtures, ou quelqu'un qui tient une session qu'il vous a prise — l'écriture sort par le même proxy et se voit classée de la même manière. La fenêtre sur votre Mac est l'autorisation, et un attaquant qui tient votre session ne tient pas votre Mac.
Le problème du parseur arrive aussi sur votre bureau
L'autre moitié de la chaîne se généralise elle aussi, et peu de gens l'ont intégrée à leurs calculs.
Un agent de codage passe sa journée à lire des fichiers qu'il n'a pas écrits :
des fixtures de test livrées avec le dépôt, une image jointe à un rapport de bug,
une capture d'écran que vous avez collée dans la conversation, le PDF d'une
spécification, une archive tirée d'une page de release, une police, une vidéo que
le front end est censé lire. Les traiter suppose d'appeler magick, ffmpeg,
pdftotext ou unzip, ou une bibliothèque d'images liée à Python, et chacun de
ces outils est un parseur C ou C++ qui avale des octets choisis par quelqu'un
d'autre. C'est exactement la condition qui a permis à un envoi de forum
d'atteindre libheif. Un agent la rencontre plus souvent que vous, parce qu'il
n'hésite jamais avant d'ouvrir quelque chose.
La détection n'a pas de réponse ici. Le bug de libheif ne portait aucun CVE au moment où cela comptait ; aucun scanner, aucune liste d'autorisation, aucun épinglage de version n'aurait dit un mot. Vous pouvez choisir le rayon de l'explosion, et c'est tout.
Dans un espace de travail Bromure, ce décodeur tourne à l'intérieur d'un invité
Ubuntu sous Virtualization.framework d'Apple, une frontière d'hyperviseur plutôt
qu'un bac à sable de processus, dans un invité que Bromure traite comme non fiable
par construction. Un débordement de tas dans un parseur d'images achète à un
attaquant un shell dans une VM Linux dont les identifiants sont des leurres et
dont la route vers Internet est un jeu de règles ordonné sur l'hôte. Le
commutateur virtuel applique ce jeu de règles sur l'IP de destination et le nom
d'hôte relevé par espionnage DNS, en TCP comme en UDP, et le proxy l'applique de
nouveau sur le nom de serveur TLS et la méthode HTTP. Réglez le trafic non
apparié sur Refuser et le shell dispose d'une liste d'autorisation et de rien
d'autre. Le commutateur dévie :80 et :443 vers le proxy lui-même, de sorte
qu'il n'y a aucune variable de proxy que le shell puisse désactiver. Les
modifications de règles atteignent les sessions en cours sans redémarrage, et
chaque verdict atterrit en ligne Firewall dans la Security Timeline.
Partez du principe que le parseur tombe
Ce bug ne portait aucun CVE au moment où cela comptait. Prévoyez que le décodeur perde, et décidez à l'avance dans quoi il se tient à ce moment-là.
Une session n'est pas une autorisation
Quiconque atteint la conversation hérite de l'accès en écriture de l'agent. Placez la décision d'un push là où la conversation ne peut pas aller.
Ne laissez rien à voler dans la VM
Un leurre que les outils acceptent ne vaut rien en amont, et vaut moins que rien pour un attaquant qui l'emporte ailleurs.
Chiffrez le prix de l'exploit
Trois heures et quelques milliers de dollars de jetons. Lire les bulletins plus vite ne répond pas à ce chiffre.
L'analyse de Hacktron est de la bonne recherche, et honnête sur la part qu'un humain a encore dû piloter. La fin est ce qui sera encore vrai l'an prochain. Chaque défense de la chaîne se tenait derrière une identité, et dès qu'une session portant cette identité a changé de mains, plus aucune n'avait d'avis. Quelqu'un a demandé à l'agent, et l'agent s'est exécuté.
Si votre agent peut pousser, demandez-vous où vit la permission de pousser, et si un attaquant qui prendrait tout ce que possède l'agent prendrait cela aussi. Sur un portable, oui. Déplacez cette seule décision hors de la machine où tourne l'agent, et la dernière étape de cette chaîne a un endroit où s'arrêter.