Retour à tous les articles
Publié le · par Renaud Deraison

Trois agents, une seule chose à voler

À Black Hat USA 2026, Novee a cassé la configuration par défaut de Claude Code, Gemini CLI et Codex de trois façons différentes, chacune partant d’une issue GitHub qu’un inconnu anonyme pouvait ouvrir. Les contournements n’avaient rien en commun — un décalage de guillemets, une allowlist shell non appliquée, un AGENTS.md de confiance — mais les trois chaînes se terminaient par la même étape : lire un vrai identifiant dans le processus de l’agent. Bromure Agentic Coding ne durcit pas cette dernière étape. Il vide le processus : l’exfiltration s’exécute et expédie un leurre.

Trois éditeurs, trois bugs de harnais sans lien entre eux, un même geste final : lire la clé API ou le token présent dans le processus de l’agent et l’expédier dehors. Dans un profil Bromure, cette valeur est un placeholder brm_…. L’exfiltration réussit, et l’attaquant reçoit une chaîne qui ne veut rien dire hors de votre Mac.

À Black Hat USA 2026 cette semaine, Novee a présenté « Trusted Enough to Run: Breaking AI Agents in Official Workflows ». Le chercheur Elad Meged a testé la configuration par défaut de trois agents de code contre les dépôts publics de chaque éditeur, et a cassé les trois. Les comptes rendus sont parus le 6 août.

Le point d’entrée était le même dans chaque cas, et c’est le moins cher qui soit : une issue GitHub ou une pull request ouverte par un inconnu anonyme sans le moindre privilège. L’agent lit ce texte non fiable dans le cadre de son travail, et le harnais — le code autour du modèle qui décide de ce qu’il peut exécuter — échoue à garder ce texte du bon côté de la ligne. Novee a retrouvé les mêmes défauts vulnérables sur plus d’une centaine de dépôts publics au-delà des trois éditeurs testés.

Les trois chemins d’attaque n’ont rien à voir entre eux. Ils se terminent au même endroit, et c’est cette fin commune qu’il faut retenir.

Trois portes différentes, un seul mur du fond

Claude Code

La chaîne enregistrée sous CVE-2026-54316 exploitait deux coutures. La première était un décalage entre la façon dont Claude Code validait une commande et celle dont le shell l’interprétait réellement : une chaîne entre guillemets, anodine pour le validateur, se parsait autrement une fois entre les mains de bash, et un drapeau git push --receive-pack a traversé vingt-trois contrôles de sécurité qui faisaient tous, individuellement, leur travail. La seconde couture était l’exfiltration. Un tac en lecture seule — la commande qui affiche un fichier à l’envers — suffisait à lire un secret et à le pousser dehors, inversé, via les logs GitHub Actions, et une variante faisait fuiter la clé API caractère par caractère en incrémentant le compteur public de téléchargements d’un modèle HuggingFace puis en le relisant. Anthropic a publié des correctifs.

Gemini CLI

Noté CVSS 10.0. En mode headless, Gemini CLI faisait automatiquement confiance au dossier de travail et chargeait la configuration d’agent qu’il y trouvait — sans sandbox, sans approbation — si bien qu’une pull request déposant un fichier de config s’exécutait avant même l’initialisation de la sandbox. Deux hypothèses du harnais ont cédé : l’allowlist shell « restreinte » n’était jamais appliquée à l’exécution, et l’assainissement d’environnement qui purgeait les secrets du processus enfant les laissait dans le processus parent, lisibles via /proc. Google a livré un changement cassant du modèle de confiance headless dans 0.39.1 et 0.40.0-preview.3.

Codex

Codex exécute deux passes d’agent sur un espace de travail partagé. .git et .codex étaient protégés en écriture ; pas AGENTS.md, rechargé comme instructions de confiance à chaque exécution. La première passe, qui traite l’entrée non fiable, écrivait un AGENTS.md empoisonné ; la seconde, « sûre », le chargeait comme autorité et propageait les instructions malveillantes avec des permissions élevées. OpenAI a séparé les passes en jobs isolés en trois jours.

Portes différentes, même mur du fondClaude Code · CVE-2026-54316validateur et shell parsent lachaîne autrementgit push --receive-pack passe 23 teststac lit ; les logs Actions exfiltrentcompteur HuggingFace fuit char par charGemini CLI · CVSS 10.0le mode headless se fie au dossierla config s’exécute avant la sandboxliste « restreinte » jamais appliquéeenfant purgé, secrets du parentlisibles via /procCodex · AGENTS.mddeux passes, un même espace.git et .codex protégés ; pas AGENTS.mdla passe 1 empoisonne le fichierla passe 2 « sûre » le lit en autoritépropagé, privilèges élevésLa dernière étape communelire un vrai secret dans le processusou l’environnement et l’envoyer
Trois contournements de harnais sans rapport, divulgués ensemble. Les portes diffèrent — un décalage de guillemets, une allowlist non appliquée plus une fuite /proc, un fichier d’instructions de confiance qui survit d’une passe à l’autre. Le mur du fond est identique : chaque chaîne se termine en lisant un vrai identifiant dans le processus ou l’environnement de l’agent et en l’expédiant quelque part.

La partie qui ne change pas

Le propre résumé de Novee est qu’« aucune de ces failles n’était une simple erreur de configuration : c’étaient des décisions de sécurité correctes qui cédaient aux points de passage ». C’est la bonne lecture côté harnais. Mais regardez où chaque chaîne se termine.

Le tac de Claude lit un identifiant et le compteur HuggingFace fait fuiter la clé API. La faille /proc de Gemini expose les secrets du processus parent. La seconde passe élevée de Codex porte les tokens de l’environnement dans une instruction contrôlée par l’attaquant. Trois coutures sans lien s’ouvrent de trois façons différentes, et chacune finit par tendre la main vers le même objet : un vrai secret qui attendait, posé dans le processus ou l’environnement de l’agent, qu’on le lise.

C’est l’hypothèse que partagent tous ces exploits — si vous obtenez de l’exécution de code en tant qu’agent, l’identifiant est à portée de main. Il l’est presque toujours. La ANTHROPIC_API_KEY, le GITHUB_TOKEN, les clés AWS : la façon ordinaire de lancer un agent de code les met toutes dans l’environnement du processus, parce que c’est là que les outils s’attendent à les trouver.

Bromure Agentic Coding n’essaie pas de gagner la course à l’armement du harnais. Il retire ce que le harnais protège.

Là où la chaîne casse

Bromure exécute l’agent de chaque profil dans une VM Linux jetable sur Apple Silicon, et fait passer chaque octet de son trafic réseau par un proxy sur l’hôte, hors de la boîte où tourne l’agent. Vous pouvez faire tourner le même agent autonome de tri d’issues que Novee a attaqué — en l’hébergeant vous-même au lieu de lui confier un runner de workflow. Rejouez les trois chaînes contre cette architecture : chacune se défait, et l’étape de l’identifiant se défait de la même façon pour les trois.

L’identifiant n’est jamais dans le processus

C’est la garantie qui neutralise l’étape finale commune. Les vraies clés Anthropic, OpenAI et xAI sont injectées dans la VM sous forme de placeholders brm_… et échangées contre les vraies valeurs seulement au proxy de l’hôte, sur le fil, pour le seul hôte de destination auquel chacune appartient. La clé n’est jamais dans la mémoire de la VM, jamais dans /proc, jamais dans une variable d’environnement lisible par un processus enfant ou parent, jamais dans un log. tac lit un leurre. Le compteur HuggingFace fait fuiter un leurre caractère par caractère. Le scrape de /proc renvoie un leurre. Chaque exfiltration de la conférence s’exécute jusqu’au bout et expédie une chaîne qui ne se résout que sur un seul Mac, qui n’est pas la machine où elle a tourné.

Le RCE atterrit dans une boîte effaçable

« Exécution de code sur l’hôte qui fait tourner l’agent » : c’est la formule de l’advisory de Gemini, et cet hôte est la frontière de confiance que Bromure déplace. L’exécution pré-sandbox de Gemini et le contournement shell de Claude obtiennent tous deux une VM Linux jetable, à un hyperviseur de macOS, en NAT, d’où rien de votre réseau n’est joignable. Erase home jette ce que l’exécution a écrit et remet le répertoire home dans son état post-clone. L’exploit qui possède l’agent possède une VM que vous alliez de toute façon jeter.

L’écriture vers main est refusée au proxy

Les deux escalades visent le même trophée : la chaîne de Claude pousse avec git push --receive-pack, celle de Gemini pousse du code malveillant dans la branche principale. C’est le versant supply chain — un checkout compromis devient un paquet empoisonné en aval. Réglez le garde-fou GitHub sur lecture seule ou blocage destructif et git-receive-pack est refusé au proxy de l’hôte, où un garde contourné dans la VM n’a pas voix au chapitre. Les fetchs passent toujours ; le push qui empoisonnerait la branche, non.

Le fichier d’instructions est noté avant d’être obéi

L’entrée, c’est du texte d’issue et de PR non fiable que le harnais n’a pas su contenir, et toute la chaîne Codex vit dans un AGENTS.md écrit. Bromure note le contenu des résultats d’outils que l’agent lit — pages web, corps d’issues, contenus de fichiers — avec un modèle local de détection d’injection de prompt, et note les fichiers d’autorité eux-mêmes (CLAUDE.md, AGENTS.md et leurs variantes) avec un classifieur dédié plus un scanner de payloads Unicode invisibles. L’AGENTS.md empoisonné entre les deux passes de Codex est précisément ce que ce second détecteur est fait pour attraper, et il tourne sur l’hôte avant que le modèle n’agisse sur le fichier.

Deux de ces réglages sont actifs par défaut, et le broker d’identifiants n’est pas un réglage du tout — c’est la façon dont un profil Bromure détient un secret, tout simplement. Les canaux dérobés complètent le tableau : le log GitHub Actions, le compteur HuggingFace, l’hôte sortant inattendu traversent tous le proxy de l’hôte, et le Security Log et le Trace Inspector enregistrent l’hôte, le statut et le rapport de substitution de chaque requête — le canal auxiliaire qui a fait fuiter un leurre est aussi une ligne sur une liste que vous pouvez lire.

Un poste de travail normalle processus détientvraie clé API, token, accès clouden env, dans /proc et sur disquel’exploit les littac, /proc ou une passe élevéel’exfiltration livre une vraie cléet il peut écriregit push atteint la brancheun paquet empoisonné part en avalDans un profil Bromurele processus détientplaceholders brm_, résolus au proxyla vraie valeur n’entre pas en VMl’exploit les litmême tac, même /proc, même passel’exfiltration livre un leurreet l’écriture est refuséegit-receive-pack bloqué au proxyla VM d’exécution est jetable
La dernière étape commune, à deux endroits. Sur un poste de travail normal, l’exploit lit un identifiant vivant directement dans le processus et l’exfiltration livre une vraie clé. Dans un profil Bromure, la même lecture réussit contre un placeholder, l’écriture qui empoisonnerait la branche est refusée au proxy, et la boîte où l’agent a tourné est jetable.

Le harnais continuera de casser

Les trois correctifs provoqués par Novee sont tous bons, et tous étroits. Anthropic a corrigé le décalage de guillemets ; Google a changé le modèle de confiance headless ; OpenAI a séparé les passes. Chacun referme la couture précise qui a été trouvée. Aucun ne change ce que les trois exploits avaient en commun : un identifiant en état de marche posé dans le processus de l’agent, à prendre. Le prochain bug de harnais — et Novee a trouvé les mêmes défauts sur une centaine d’autres dépôts, donc il y en aura un — tendra la main vers lui à nouveau.

Vous pouvez répondre à chaque divulgation par un patch et une revue de configuration, et vous devriez. Mais le patch ferme une porte dans un agent, et la revue se fait dépôt par dépôt, version par version. L’identifiant qu’un exploit fonctionnel vient chercher est le même pour les trois agents et pour chaque bug futur, et c’est la seule chose que vous pouvez mettre hors de portée en une seule décision : gardez le vrai secret sur l’hôte, donnez un placeholder à l’agent, et laissez le proxy produire la vraie valeur uniquement pour l’hôte auquel elle est adressée.

Installez Bromure Agentic Coding, et donnez à l’agent une boîte — et un identifiant — que vous acceptez de perdre.