Le commit était une branche
Air a divulgué Plugin4Shell le 17 septembre : les marketplaces de plugins de Claude Code, Codex, Gemini CLI et Copilot épinglent chaque plugin à un hash de commit de quarante caractères, et les agents extraient ce hash sans jamais vérifier qu'ils l'ont bien obtenu. Un attaquant qui nomme une branche d'après le hash épinglé gagne le checkout, et le programme de mise à jour en arrière-plan fait le reste, sans invite et sans clic. Anthropic et OpenAI ont corrigé. Google ne le fera pas, et Microsoft ne l'a pas fait. Dans un workspace Bromure Agentic Coding, le code qui gagne le checkout atterrit dans une VM qui ne détient que des identifiants leurres, derrière une politique de push et un jeu de règles d'egress appliqués sur l'hôte.
Tous les guides sur la chaîne d'approvisionnement vous disent d'épingler vos dépendances à un hash, parce qu'un numéro de version est une étiquette que quelqu'un peut déplacer, alors qu'un hash est le contenu lui-même. Quatre agents de code ont suivi ce conseil. Aucun n'a vérifié que le hash reçu était bien celui attendu.
Un plugin passe la revue. Vous le lisez, ou votre équipe plateforme le lit, et il rejoint la liste des plugins approuvés, épinglé à un commit : quarante caractères hexadécimaux qui ne peuvent décrire qu'un seul arbre, puisque modifier l'arbre modifie les caractères. C'est tout l'intérêt. N'importe qui disposant des bons accès peut déplacer un tag ou republier une version, mais un hash de commit est censé être un fait.
Le 17 septembre, trois chercheurs d'Air (Or Nevo, Dor Granat et Niv Hoffman) ont publié Plugin4Shell. Leur conclusion : l'épinglage ne vaut que ce que vaut ce qui le résout, et le résolveur de quatre agents de code majeurs ne vérifie pas son propre travail. The Register a couvert l'affaire le jour même. Claude Code, Codex d'OpenAI, Gemini CLI de Google et Copilot de Microsoft sont tous concernés.
Git préfère la ref
Un hash de commit, c'est du contenu, et c'est aussi un nom. Les branches et les tags aussi, et git les résout tous par la même recherche. Donnez à git une chaîne qui est à la fois un nom de branche et un identifiant d'objet, et il doit choisir ; il choisit la branche. Les chercheurs le résument en une phrase : « quand un nom est à la fois une ref valide et un identifiant d'objet, git préfère la ref ».
Git vous prévient, pourtant. Il affiche un avertissement d'ambiguïté pour cette référence, dans un terminal que vous ne lisez pas, parce que c'est un programme de mise à jour en arrière-plan qui exécute le checkout.
Les noms de branches git acceptent quarante caractères hexadécimaux. Un attaquant qui contrôle le dépôt du plugin crée une branche portant exactement le nom épinglé par le marketplace et en fait la branche par défaut. Le clone rapatrie cette branche à côté du commit du même nom. Puis l'agent lance son checkout :
git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Le checkout atterrit sur la branche. Gemini CLI emprunte un autre chemin vers le
même résultat : il récupère le bon commit avec --ref, puis exécute
git checkout FETCH_HEAD, qui se résout vers une branche nommée FETCH_HEAD si
le dépôt en possède une.
Cinq étapes, et la dernière n'a pas de clic
Les chercheurs décomposent l'attaque en cinq mouvements, et les trois premiers sont des choses qu'un marketplace bien tenu fait volontairement.
Plantation. Publier un plugin inoffensif. Il passe la revue au commit
aaa…aaa parce qu'il n'y a rien à lui reprocher.
Adoption. Les développeurs l'installent, épinglé au commit revu. C'est l'étape où vous faites ce que les guides vous disent de faire.
Montée de version. Le marketplace réépingle vers un commit plus récent,
bbb…bbb, lui aussi inoffensif et lui aussi revu.
Rug-pull. L'attaquant crée une branche nommée bbb…bbb, en fait la branche
par défaut, et la fait pointer vers du code malveillant. Il ne republie rien et
l'enregistrement du marketplace ne bouge pas. Les quarante caractères que vous
iriez vérifier sont les quarante caractères que vous avez déjà vérifiés.
Mise à jour automatique vers l'exécution de code. Claude Code et Codex mettent à jour les plugins installés en arrière-plan par défaut. La prochaine mise à jour planifiée extrait la branche. Air décrit cette étape comme « sans invite, sans clic ».
Il existe une seconde porte d'entrée qui saute la plantation : prendre le contrôle du dépôt d'un auteur de plugin légitime et pousser la version malveillante vers tous les agents qui ont déjà le plugin installé. L'astuce de la branche est la même, et il n'y a aucune revue à passer.
Air décrit le résultat comme « une exécution de code à distance complète sur la machine de l'employé », avec les permissions de cet employé. L'attaquant n'a besoin d'aucune élévation de privilèges. Le code s'exécute en tant que la personne à qui appartient le portable, et atteint ses fichiers et ses accès de production.
C'est la troisième fois que ces chercheurs recensent la même forme. Leurs travaux antérieurs sur les skills d'agent malveillantes décrivent une skill qui a atteint plus de 26 000 agents, et une campagne de détournement qui a pris le contrôle de 925 skills déjà utilisées par 134 000 agents. Ces campagnes exigeaient que vous installiez quelque chose de mauvais ; celle-ci exigeait que vous installiez quelque chose de bon et que vous attendiez.
Deux des quatre n'ont pas de correctif
Air a signalé la faille aux quatre éditeurs en juin.
Anthropic a corrigé Claude Code le 17 juin, en version 2.1.179. Le correctif d'OpenAI est arrivé dans Codex 0.146.0 et a été vérifié le 12 août. Google a répondu le 4 août que Gemini CLI est déprécié et ne sera pas corrigé. Google renvoie les utilisateurs vers Antigravity, si bien qu'une installation de Gemini CLI qui reste sur le disque reste vulnérable. Microsoft n'a rien livré pour Copilot. GitHub indique que sa propre plateforme bloque les noms de branches et de tags qui ressemblent à des SHA de commit, ce qui couvre les plugins hébergés sur GitHub et ne fait rien pour un dépôt de plugin hébergé ailleurs, Bitbucket compris. The Register note, en reprenant le chiffre de Microsoft lui-même, que près de 90 % des entreprises du Fortune 500 utilisent Copilot.
Le correctif tient en une ligne, et il doit s'exécuter à l'intérieur de l'agent :
test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort
Résoudre le commit qui a atterri dans la copie de travail, et refuser de continuer s'il ne correspond pas. Aucun marketplace ne peut le faire à votre place, parce que c'est votre agent qui résout l'épinglage sur votre machine, une fois que le marketplace a fini de parler.
Pour un grand nombre de développeurs qui lisent ces lignes aujourd'hui, « mettez votre agent à jour » n'est pas une réponse disponible. Reste l'autre question : que trouve le code lorsqu'il gagne le checkout et commence à s'exécuter ?
Dans un workspace Bromure Agentic Coding
La formule d'Air, s'exécuter avec les permissions de l'employé, est exacte. Un workspace existe pour la rendre fausse.
Dans Bromure Agentic Coding, l'agent s'exécute dans une VM
Linux virtualisée par le matériel sur votre Mac, et la VM ne détient jamais un
véritable identifiant. Au lancement de la session, l'application écrit des
leurres qui préservent la structure dans l'environnement où tourne l'agent :
ANTHROPIC_API_KEY, GH_TOKEN, LINEAR_API_KEY et les autres sous forme de
variables d'environnement, et des faux correspondants dans
~/.git-credentials, ~/.docker/config.json, ~/.kube/config, ~/.aws/config
et les fichiers de configuration MCP de l'agent. Chaque leurre conserve la forme
que ses outils attendent, si bien qu'ils l'acceptent : un leurre GitHub, c'est
ghp_ suivi de 36 caractères, un leurre Anthropic commence par
sk-ant-api03-brm-, un jeton porteur Kubernetes commence par brm-k8s-.
Bromure dérive chacun d'eux de la valeur réelle par HKDF-SHA256 avec un sel
propre à l'installation, ce qui le garde stable d'une session à l'autre et sans
valeur pour qui le récolte.
Les vraies valeurs restent chiffrées sur l'hôte. Un proxy côté hôte les substitue sur le fil une fois la requête sortie de la VM, et seulement lorsque la destination correspond à l'hôte pour lequel cet identifiant a été forgé. Un plugin qui ratisse le répertoire personnel à la recherche de fichiers de configuration, ce qui est la première chose que fait cette classe de charge utile, ne récolte qu'un jeu de leurres bien formés.
Le push doit être approuvé sur l'hôte
La chose la plus précieuse qu'un plugin compromis puisse faire sur une machine de développeur, c'est pousser. C'est ainsi qu'un portable devient cent dépôts.
Guardrails classe chaque appel que l'agent adresse à GitHub. Un git push en
HTTPS arrive au proxy sous la forme git-receive-pack, ce qui compte comme une
écriture. Le mode lecture seule le refuse. Le mode
demander avant d'écrire fait apparaître une fenêtre sur votre Mac, intitulée
Allow write on "<scope>" from workspace "<name>"?, qui montre l'opération
telle quelle plutôt qu'un résumé. Un git fetch compte comme une lecture et
passe sans vous interrompre.
La classification s'exécute dans le proxy de l'hôte, en dehors de la VM. Le code à l'intérieur de l'invité ne peut ni la désactiver ni la contourner, parce qu'il ne s'exécute pas sur la machine qui prend la décision.
L'appel à la maison rencontre une règle que l'invité ne peut pas modifier
Chaque workspace dispose d'un pare-feu d'egress : une liste ordonnée, de style
pf, de règles d'autorisation et de refus portant sur l'hôte, la plage d'IP, le
protocole, le port et, pour le trafic web, le verbe HTTP individuel.
allow web api.example.com GET,POST donne à l'agent des requêtes sans
mutations, au niveau du fil, quel que soit l'outil qui émet la requête à
l'intérieur de la VM.
Deux couches l'appliquent. Le commutateur réseau virtuel applique les règles à chaque flux par IP de destination et par nom d'hôte capté dans le DNS, couvrant le TCP et l'UDP bruts autant que le HTTPS. Le proxy les applique de nouveau par nom de serveur TLS et par méthode HTTP. Bromure détourne le trafic des ports 80 et 443 de la VM vers le proxy sans la coopération de l'invité, si bien que rien à l'intérieur ne peut échapper à l'inspection. Vos modifications atteignent les sessions en cours immédiatement, ce qui compte le jour où vous lisez une divulgation comme celle-ci.
En dessous se trouve le détecteur de compromission, activé par défaut et sans rien à configurer. Le proxy analyse chaque requête sortante en une seule passe à la recherche des leurres forgés pour ce workspace. Un leurre adressé à un hôte pour lequel il n'a jamais été forgé est la signature d'une exfiltration, et le proxy y répond par un HTTP 451 sans transmettre le moindre octet. Il met la VM en pause sur-le-champ et lève une alerte qui propose d'arrêter le workspace, d'exporter le disque et le dossier personnel pour enquête, ou de continuer à vos risques et périls. Bromure marque le workspace comme compromis, et le lancement suivant efface le disque de la VM et le dossier personnel tout en conservant vos jetons, vos clés SSH et vos réglages de workspace. Seul le leurre est parti, il n'y a donc rien à faire tourner.
Zéro clic veut dire qu'il n'y a rien à remarquer
La propriété la plus tranchante de l'étape cinq, c'est son silence. Une mise à jour de plugin en arrière-plan ne produit ni invite ni appel d'outil, si bien que l'interface de l'agent elle-même ne donne au développeur rien à attraper. Le constat d'Air, c'est que l'exploitation se déroule dans la partie du système que vous n'avez aucune raison de surveiller.
Un workspace déplace la surveillance ailleurs. Les moteurs de sécurité écrivent
chaque décision sous forme de ligne dans la fenêtre Security Timeline de votre
Mac : verdicts du pare-feu (evil.example:443 tcp — blocked), applications de
Guardrails (DELETE api.github.com/repos/… — blocked), courtage d'identifiants
y compris la ligne rouge blocked — exfiltration attempt, VM paused, plus les
verdicts de chaîne d'approvisionnement et les détections d'injection de prompt,
en couleurs et filtrables. Une session peut aussi enregistrer une trace chiffrée
des commandes exécutées et des hôtes atteints.
Un plugin qui s'est mis à jour tout seul sans vous le dire doit quand même émettre des requêtes, et chacune franchit une frontière qui tient ses propres notes de l'autre côté de l'hyperviseur.
Vérifiez d'abord la version de votre agent
Claude Code 2.1.179 et Codex 0.146.0 contiennent le correctif. Si vous utilisez Gemini CLI, il n'y en aura pas. Si vous utilisez Copilot, il n'y en a pas encore.
Désactivez la mise à jour automatique des plugins quand vous le pouvez
La propriété zéro clic vient du programme de mise à jour en arrière-plan. Les mises à jour manuelles remettent un humain dans la boucle à l'étape cinq.
Vérifiez vous-même le checkout dans la CI
git rev-parse HEAD après un checkout épinglé, comparé à l'épinglage, c'est
la même vérification d'une ligne que les éditeurs ont dû ajouter. Elle
fonctionne partout où vous clonez par SHA.
Partez du principe que quelqu'un gagnera le checkout
L'épinglage, la revue et la curation du marketplace ont tous tenu ici, et l'attaque a réussi quand même. Le dénouement repose alors sur ce que le code gagnant peut atteindre.
Plugin4Shell attrape le développeur méticuleux aux mêmes conditions que le négligent. Celui qui a installé ce qui était à la mode ne s'en est pas moins bien sorti que celui qui a lu le code source, épinglé le hash et noté l'épinglage. L'adressage par contenu était censé protéger ce second développeur, et il ne l'a pas fait, parce que git résout l'adresse par une recherche qui utilise aussi ces caractères pour désigner autre chose.
Continuez à épingler. Puis partez du principe qu'un contrôle qui repose sur une recherche ou sur une correspondance de noms se verra un jour remettre la mauvaise réponse pendant que vous dormez. Concevez pour ce matin-là : faites tourner l'agent quelque part qui ne détient aucune clé digne d'être prise, et gardez la trace de ce qu'il a fait de l'autre côté d'une frontière que le code ne peut pas atteindre.