La clé supprimée était encore dans l'historique
Le 8 octobre, une campagne baptisée GhostAction a utilisé deux comptes de mainteneurs piratés pour committer un workflow nommé security-audit.yml dans 346 dépôts, dont athenadriver d'Uber et pyxel et ses 18 000 étoiles. Le 9 octobre, Socket comptait plus de 500 comptes et des dizaines de milliers de dépôts. Le workflow lance git log -p --all et passe au grep chaque commit jamais fait à la recherche de clés Anthropic, OpenAI, OpenRouter, GitHub et AWS, y compris celles supprimées un commit plus tard. Dans Bromure Agentic Coding, l'agent ne détient jamais que des substituts, si bien que les clés qu'il écrit dans l'historique d'un dépôt ne valent rien pour qui les trouve.
Supprimer une clé d'un fichier prend un commit. Le commit précédent garde la clé aussi longtemps que le dépôt existe.
Le jeudi 8 octobre à 13 h 40 UTC, le compte GitHub de Takashi Kitao, auteur du
moteur de jeu pyxel, a commencé à committer un fichier nommé
.github/workflows/security-audit.yml. Quatre minutes plus tard, il se
trouvait dans 27 de ses dépôts. À 21 h 10 UTC, le compte henrywoo, auteur
d'origine d'athenadriver chez Uber, a lancé le même balayage. À 21 h 26, il
avait atteint 318 dépôts, dont 279 forks et uber/athenadriver lui-même, où
le compte a un accès en écriture.
Socket a publié son
analyse le 9 octobre
et a attribué ces commits à GhostAction, une campagne de vol d'identifiants
apparue pour la première fois en septembre 2025. À la fin de cette journée, la
mise à jour de Socket comptait plus de 500 comptes GitHub ayant committé le
workflow dans « des dizaines de milliers de dépôts » depuis le 7 octobre. The
Hacker
News
et StepSecurity ont couvert la même vague. Socket a constaté que le workflow
s'était déjà exécuté dans des dépôts touchés, et qu'il se trouvait encore sur
les branches par défaut de kitao/pyxel et uber/athenadriver au moment de
la publication.
Ce que fait security-audit.yml
Le workflow s'appelle « Security Audit ». Il se déclenche sur
workflow_dispatch et à chaque push sur n'importe quelle branche ou tag, sans
aucun filtre. Son étape de checkout utilise fetch-depth: 0, qui récupère
toutes les branches, tous les tags et l'historique complet. Ensuite, une seule
étape fait deux choses.
La première s'en prend aux secrets GitHub Actions du dépôt. Avant de committer
le workflow, l'attaquant a lu les workflows existants du dépôt et a écrit dans
la charge utile les noms des secrets qu'ils utilisent. Pour pyxel, il
s'agissait de CARGO_REGISTRY_TOKEN, PERSONAL_ACCESS_TOKEN, PYPI_PASSWORD
et PYPI_USERNAME.
La seconde est nouvelle dans cette vague. L'étape passe l'arborescence au grep
avec 13 motifs d'identifiants, puis lance git log -p --all et cherche les
mêmes motifs dans jusqu'à 200 000 lignes d'historique :
- Services d'IA : Anthropic (
sk-ant-), clés de projet OpenAI (sk-proj-), OpenRouter (sk-or-) - Gestion de code source : tokens GitHub classiques (
ghp_), tokens à granularité fine (github_pat_), tokens GitLab (glpat-) - AWS : identifiants de clé d'accès, identifiants de clé temporaire, clés secrètes et tokens de session. L'étape garde aussi deux lignes de contexte autour de chaque identifiant de clé, si bien que la clé secrète committée à côté part avec.
- SaaS et cloud : clés d'API Google, tokens Slack, clés SendGrid
Tout part dans un seul POST curl en HTTP non chiffré vers 193.32.204.199.
Chaque exécution transmet le nom du dépôt, qu'elle ait trouvé quelque chose ou
non.
Socket pointe le public visé. Deux des dépôts de henrywoo sont pyllama et
chatllama, des projets d'apprentissage automatique dont les contributeurs et
les auteurs de forks « détiennent probablement ce type de clés ». Une clé
Anthropic ou OpenAI se revend comme de l'inférence facturable, et chaque
prompt envoyé avec elle passe entre les mains de l'acheteur.
L'historique garde tout
Les conseils de remédiation de Socket contiennent une phrase qui devrait
inquiéter quiconque a déjà nettoyé une clé fuitée en modifiant un fichier :
« Faites tourner chaque identifiant trouvé dans n'importe quel commit, qu'il
soit encore ou non dans l'arborescence actuelle. » Une clé retirée au commit
suivant est toujours dans le commit d'avant. git log -p --all lit les deux.
Les agents de code alimentent vite cet historique. Le rapport State of Secrets Sprawl 2026 de GitGuardian a constaté que les commits publics co-écrits par Claude Code fuitaient des secrets dans 3,2 % des cas, contre une base de 1,5 % sur l'ensemble de GitHub public. Le même rapport a compté 1 275 105 secrets fuités liés à des services d'IA en 2025, 81 % de plus que l'année précédente.
Le scénario est familier à quiconque a regardé un agent faire passer un test
d'intégration qui échouait. L'agent a la clé dans son environnement. Il
l'écrit dans un fichier de config, une fixture de test ou un .env qui n'est
pas encore ignoré, parce que ça fait passer le test. Il committe. Plus tard,
le développeur ou l'agent déplace la clé dans une variable d'environnement et
committe à nouveau. L'arborescence a l'air propre. L'historique contient la
clé, chaque fork a l'historique, et un workflow avec fetch-depth: 0 le lit.
Le token qui a écrit le workflow
Socket n'a pas déterminé comment les deux comptes ont été pris. The Hacker
News désigne la source la plus probable : un personal access token issu de
journaux d'infostealers ou de dumps d'identifiants. Les journaux
d'infostealers viennent de machines de développeurs. Sur un portable, le token
GitHub se trouve dans ~/.config/gh/hosts.yml, dans ~/.git-credentials et
souvent dans GH_TOKEN, lisible par tout ce qui tourne sous l'identité du
développeur, y compris ce qu'un agent de code installe ou exécute.
Un seul token suffit. Avec lui, l'attaquant peut lister les dépôts où le
compte a le droit d'écrire, lire leurs workflows pour y trouver les noms de
secrets, et committer dans chacun. Le balayage de henrywoo a couvert 318
dépôts en 16 minutes.
Le même dépôt, écrit depuis une machine Bromure
Dans Bromure Agentic Coding, l'agent tourne dans une VM Linux sur votre Mac, et chaque requête HTTP de la VM traverse un proxy sur l'hôte. Votre clé Anthropic, votre token GitHub et vos clés AWS restent chiffrés sur le Mac. Lâchez maintenant GhostAction sur un dépôt écrit par une machine Bromure, et sur le développeur qui l'a écrit.
L'historique contient des substituts
Dans la VM, chaque identifiant est un substitut qui a la forme du vrai :
ANTHROPIC_API_KEY vaut sk-ant-api03-brm-…, une clé OpenRouter vaut
sk-or-v1-brm-…, GH_TOKEN et ~/.git-credentials contiennent ghp_ suivi
de 36 caractères. Chacun est dérivé de la vraie valeur et d'un sel propre à
l'installation avec HKDF-SHA256, et ne peut pas être reconverti en elle. Le
proxy substitue la vraie valeur sur le réseau, et seulement pour une requête
vers l'hôte auquel l'identifiant appartient.
Donc quand l'agent écrit sa clé dans config/test.py pour faire passer le
test, il écrit ce qu'il a. Quand il committe, le commit contient
sk-ant-api03-brm-…. Quand le commit de nettoyage arrive plus tard,
l'historique garde ce même substitut.
Le grep de GhostAction le trouve. sk-ant- correspond à sk-ant-api03-brm-,
et ghp_ correspond à un token GitHub de substitution. Le POST vers
193.32.204.199 les emporte, et l'acheteur les essaie. Anthropic rejette la
clé et GitHub rejette le token, parce que la seule chose qui en fait des
identifiants valides est le proxy sur votre Mac, pour les requêtes qui
sortent de votre VM. Le conseil de Socket de faire tourner chaque clé trouvée
dans n'importe quel commit a une réponse courte pour ce dépôt : aucune de ces
clés n'a jamais fonctionné hors de votre Mac, et les substituts des
fournisseurs d'IA portent -brm- dans leur nom, ce qui permet de les repérer
lors d'un scan de l'historique.
Le token du portable est aussi un substitut
L'autre moitié de la campagne, c'est le compte lui-même. Faites tourner vos
agents dans une machine Bromure, et le token GitHub qu'ils utilisent reste sur
le Mac. Tout ce qui s'exécute dans la VM, qu'il s'agisse du script
d'installation d'un paquet, d'un stealer ou d'une injection de prompt, trouve
un substitut ghp_ dans ~/.git-credentials et GH_TOKEN. Un attaquant qui
le rejoue depuis sa propre machine reçoit un 401 de GitHub.
Le stealer doit aussi envoyer son butin quelque part, et cette requête
traverse le proxy. Un substitut n'a qu'une destination légitime, l'hôte pour
lequel il a été créé. Le proxy inspecte chaque requête sortante, en-têtes et
corps, à la recherche de tout substitut en route vers un autre hôte. Un token
ghp_ qui part vers le serveur d'un stealer reçoit un HTTP 451, rien n'est
transmis, la VM se met en pause, et This environment may have been
compromised s'ouvre sur le Mac avec un aperçu du token, le nom de
l'identifiant, l'hôte auquel il appartient et l'hôte vers lequel il a été
envoyé.
Un push de workflow attend un clic
Les vers de cette famille n'attendent pas toujours un acheteur. La charge
utile de tensorlake la semaine
dernière committait des fichiers dans les dépôts de ses victimes directement
depuis la machine du développeur. Les nouvelles machines Bromure utilisent
Prompt before write pour chaque forge git. Un push est une écriture : committer
security-audit.yml dans un dépôt depuis la VM s'arrête donc sur une boîte
de dialogue macOS, Allow write on "github.com" from workspace "…"?, qui
montre POST /owner/repo.git/git-receive-pack tel qu'il est passé sur le
réseau. Le balayage de henrywoo a fait 318 de ces écritures en 16 minutes.
Depuis une machine Bromure, la première vous attend, et vous n'avez pas
demandé à votre agent d'ajouter un audit de sécurité à 318 dépôts.
Chaque usage du token est enregistré
La Security Timeline enregistre chaque substitution d'identifiant dans une
ligne Credential brokering : quel token, quel hôte, quelle machine, à
quelle heure. Quand vous devez savoir si votre token GitHub a poussé quoi que
ce soit le 8 octobre entre 21 h 10 et 21 h 26, la réponse est sur votre Mac,
dans un registre que l'agent ne peut pas modifier. Avec le traçage activé,
bromure-cli trace leaks liste chaque requête où un token est apparu sur le
mauvais hôte.
Faire le ménage après une clé
Le conseil habituel après une fuite de clé est de la faire tourner, de
réécrire l'historique avec git filter-repo, de forcer le push, et d'espérer
qu'aucun fork ni miroir n'a copié les anciens commits avant. GhostAction montre
pourquoi cette dernière partie échoue. Socket a compté 279 forks dans un seul
espace de noms, chacun portant l'historique et, désormais, le workflow. Une
clé qui a atteint un commit public est perdue, quoi que montre l'arborescence
actuelle.
Une machine Bromure change ce qui atteint le commit en premier lieu. L'agent
peut être aussi négligent avec les clés que les chiffres de GitGuardian le
disent des agents. Il peut coller une clé dans une fixture, committer un
.env et l'oublier. Il n'a jamais détenu de clé valide, donc rien dans cet
historique ne peut vous nuire.
Le commit contient un substitut
Les agents dans la VM ne voient que des substituts qui ont la forme des
vraies clés. Une clé écrite dans un fichier et committée est une chaîne
-brm- qui ne fonctionne qu'à travers le proxy sur votre Mac.
La récolte obtient des 401
Les motifs de GhostAction trouvent les substituts et les expédient. Anthropic, OpenAI et GitHub les rejettent partout hors de votre Mac.
Un token volé arrête la machine
Le token GitHub dans la VM est lui aussi un substitut. Envoyé ailleurs que vers GitHub, il reçoit un 451, la VM se met en pause, et une alerte nomme le token et l'hôte.
Les pushes attendent un clic
Prompt before write retient chaque push depuis la VM sur une boîte de dialogue macOS qui montre la requête, et la Security Timeline enregistre chaque usage du token.
Le prochain grep
C'est la troisième vague de GhostAction en treize mois. La première, en septembre 2025, a pris 3 325 secrets de CI. La deuxième, du 31 août au 30 septembre de cette année, a visé 2 577 secrets dans 772 dépôts. Celle-ci a ajouté l'historique complet et trois motifs de fournisseurs d'IA. La prochaine vague ajoutera d'autres motifs, et l'historique qu'elle lit ne fait que grandir.
Faites tourner vos agents là où les clés qu'ils manipulent sont des substituts, et l'historique qu'ils laissent derrière eux ne contient rien qu'un acheteur puisse utiliser. Pour en savoir plus sur le fonctionnement des substituts et ce qui se passe quand l'un d'eux fuit, lisez L'agent a divulgué sa propre clé et La clé n'a jamais expiré.