Le ver est passé open source
Au cours de la semaine du 11 mai 2026, les auteurs de Shai-Hulud — le ver auto-réplicatif de la chaîne d'approvisionnement npm qui dévore les comptes de mainteneurs depuis septembre 2025 — ont eux-mêmes divulgué leur code source. Dès le week-end, OX Security avait identifié quatre paquets npm typosquattés publiés depuis un même compte ; l'un est une copie quasi mot pour mot du ver divulgué, un autre un bot DDoS écrit en Golang, et les deux derniers de simples infostealers qui expédient des clés SSH et des portefeuilles crypto vers des C2 bas de gamme. Le seuil d'entrée pour forker une attaque de la chaîne d'approvisionnement vient de chuter sévèrement, et les premiers à installer ces paquets ne sont plus des humains.
La semaine dernière, les auteurs de Shai-Hulud — le ver npm
qui ronge les comptes de mainteneurs depuis septembre
2025 — ont divulgué leur propre code source. Dès le week-end,
un compte npm nommé deadcode09284814 avait publié quatre
typosquats réutilisant ce code. L'un était le ver, quasi à
l'identique. Un autre, un bot DDoS écrit en Golang.
Deux étaient de simples infostealers qui envoient en POST vos
clés SSH, votre ~/.aws/credentials et votre coffre MetaMask
vers une IP louée. Deux mille six cent soixante-dix-huit
installations plus tard, la question n'est plus « est-ce que
quelqu'un va armer la fuite ». La question est de savoir lequel
des agents qui tapent npm install dans votre terminal cet
après-midi en attrapera un.
Il existe un phénomène qui se produit, périodiquement, dans le
logiciel offensif : un outil fermé est balancé sur un forum, et la
population de gens capables de le faire tourner passe de
« l'équipe unique qui l'a écrit » à « n'importe quel ado avec un
VPS ». Mimikatz. EternalBlue. Le code source de Conti. À chaque
fois, la capacité n'a pas changé — elle a simplement cessé
d'être rare. Le gros titre de la semaine dernière, rapporté par
BleepingComputer
sur la foi des recherches d'OX Security,
c'est que Shai-Hulud — le ver dont nous avions
parlé quand il avait dévoré
quarante-deux paquets @tanstack en six minutes — a rejoint cette
liste.
Le fork n'est pas théorique. Dès dimanche, OX avait documenté
quatre paquets malveillants publiés depuis un seul compte npm
nommé deadcode09284814 :
chalk-tempalte— un typosquat dechalk-template, embarquant une copie quasi mot pour mot du code source divulgué de Shai-Hulud, y compris le comportement signature de Shai-Hulud consistant à téléverser les identifiants volés sous forme de dépôts GitHub publics auto-générés. La lecture d'OX : « le code du malware Shai-Hulud est une copie quasi exacte du code source divulgué, sans aucune technique d'obfuscation », ce qui suggère un nouvel acteur qui ne s'est même pas donné la peine de limer les numéros de série.axois-utils— un typosquat deaxios-utils, livrant une charge utile Golang qu'OX appelle Phantom Bot : floods HTTP, TCP, UDP et reset, persistance via le dossier de démarrage Windows et les tâches planifiées, et un C2 àb94b6bcfa27554.lhr.life. Votre machine de dev, enrôlée.@deadcode09284814/axios-util— un typosquat différent, une charge utile différente : clés SSH, variables d'environnement, identifiants AWS/GCP/Azure, expédiés vers80.200.28.28:2222. « Plutôt direct », pour reprendre les mots d'OX.color-style-utils— un infostealer pur et simple qui récupère votre IP, votre géolocalisation et les données de portefeuille crypto, et envoie le tout en POST àedcf8b03c84634.lhr.life.
Téléchargements hebdomadaires cumulés au moment de la publication d'OX : 2 678.
Il y a deux histoires entremêlées ici qui méritent d'être
démêlées. La barbante, c'est que npm a des typosquats. C'est le
cas ; ça l'a toujours été ; ça le sera toujours, pour la même
raison qu'il y a des chiens nommés Rex au parc — les noms sont
gratuits et l'espace de noms est plat. L'intéressante, c'est que
la barrière pour faire tourner un ver de la classe de Shai-Hulud
vient de s'effondrer. Jusqu'à la semaine dernière, il fallait
l'outillage de l'équipe d'origine, l'infrastructure de l'équipe
d'origine, la discipline de l'équipe d'origine pour ne pas se
faire prendre. Aujourd'hui, il faut un clone GitHub d'un dépôt
public, un tunnel lhr.life et la patience de taper
npm publish. Les quatre paquets trouvés par OX en sont,
collectivement, la preuve.
Pourquoi le nombre d'acteurs compte plus que le nombre de paquets.
Un seul ver sophistiqué est, en un sens, un adversaire tractable.
Il a des tics. Il a une infrastructure. Il a des habitudes. On
peut écrire des signatures de détection contre lui. Aikido,
Socket, Snyk, Wiz — les maisons de surveillance de la chaîne
d'approvisionnement npm qui se sont jetées sur l'incident
@tanstack du 11 mai — l'ont attrapé en quelques heures,
précisément parce qu'elles surveillaient la même famille depuis
huit mois.
Une famille de vers dérivés écrits par des gens qui ont
téléchargé le code source depuis un site de partage de texte,
c'est une autre forme. Chacun exfiltrera vers un C2 différent,
embarquera une clé RSA différente, choisira une combinaison
différente de fichiers à lire, et élira domicile dans un espace
de typosquats différent. Certains seront prudents ; la plupart
seront négligents d'une manière qui les fera prendre rapidement ;
l'un d'entre eux, la prochaine fois qu'on écrira un billet comme
celui-ci, sera assez sophistiqué pour qu'on ne l'attrape pas en
une semaine. Le problème de détection des défenseurs s'élargit de
« repérer Shai-Hulud » à « repérer tout ce qui veut lire
~/.ssh/id_ed25519 depuis l'intérieur d'un script prepare ».
C'est une surface bien, bien plus large.
La forme du chemin d'installation a également changé, et c'est la
partie qui devrait inquiéter quiconque fait tourner un agent de
codage. Un développeur humain qui voulait chalk-template, en
2024, aurait lu le nom du paquet dans un tutoriel, l'aurait tapé,
et aurait remarqué la coquille quand chalk-tempalte serait
revenu avec deux cents téléchargements et un inconnu comme
publicateur. Un
agent de codage
à qui on demande en 2026 « ajoute de la couleur à la sortie de
mon CLI » installera ce que le gestionnaire de paquets lui
renverra. L'agent ne voit pas le champ « publicateur ». L'agent
ne remarque pas que le README fait trois lignes. L'agent fait
trente npm install cette heure-ci parce que l'utilisateur fait
le travail d'une petite équipe et que l'agent est payé à la
tâche, pas à l'installation.
Ce que fait vraiment un clone de Shai-Hulud non obfusqué.
Le paquet chalk-tempalte est, écrit OX, « presque sans aucun
changement » par rapport au code source divulgué. Cela signifie
que les mêmes mécaniques que nous avons décortiquées dans
Le ver qui s'écrit dans .claude
s'appliquent, avec un nouveau pli significatif : le canal
d'exfiltration est GitHub lui-même.
Le tour signature de Shai-Hulud — préservé par le copycat parce
que copier est plus facile que réécrire — c'est que les
identifiants volés ne partent pas vers un serveur de dépôt caché.
Ils partent vers un dépôt GitHub public fraîchement créé,
publié à l'aide d'un jeton GitHub que le malware vient juste de
voler à la victime. Les secrets de la victime sont posés dans un
dépôt public, possédé par le compte GitHub de la victime
elle-même, à la portée de n'importe qui sur Terre, jusqu'à ce
que quelqu'un remarque et que le dépôt soit détruit. Le playbook
standard du défenseur — bloquer le domaine d'exfiltration,
chercher du DNS sortant inhabituel — ne détecte pas cela, parce
que le DNS sortant, c'est api.github.com, à qui votre machine
de développeur parle deux cents fois par heure de toute façon.
Le bundle d'identifiants quitte votre laptop déguisé en
git push.
Une fois les secrets publics, n'importe qui qui surveille le flux GitHub — et plusieurs personnes surveillent le flux GitHub pour exactement cette raison — peut les ramasser. Le ver n'a pas besoin de maintenir son C2 en vie. GitHub est le C2.
Il y a un tout petit détail dans cette image qui mérite qu'on s'y
attarde. Le clone de Shai-Hulud, chalk-tempalte, ne s'appuie
même pas sur sa propre infrastructure pour recevoir le butin. Il
utilise l'identité GitHub de la victime elle-même pour publier
les secrets volés à la victime elle-même dans un dépôt public
sur GitHub. Le C2 sur lhr.life n'est qu'une sauvegarde. Le
canal principal, c'est git push. Pour un défenseur qui surveille
l'egress, c'est indiscernable de la CI normale du développeur.
Pour GitHub, c'est — jusqu'à ce que quelqu'un signale le dépôt —
un projet public légitime appartenant à un vrai utilisateur.
L'exfiltration est blanchie à travers l'identité de la victime.
Ce que fait l'agent pendant que vous faites défiler.
Si vous avez un agent de codage dans votre terminal — Claude
Code, Cursor's CLI, Codex CLI, Aider, au choix — il y a une
probabilité non nulle que, pendant le temps qu'il vous a fallu
pour lire le dernier paragraphe, l'agent ait lancé un
npm install en votre nom. Peut-être deux. Les agents de codage
ne s'arrêtent pas pour admirer les arbres de dépendances. La
raison même pour laquelle vous en avez acheté un, c'est qu'il
ne s'arrête pas.
Les paquets qu'OX a attrapés sont des typosquats, une catégorie
d'erreur très spécifiquement adaptée à la vitesse des machines.
Un humain qui doit taper chalk-template mettra les lettres
dans le bon ordre parce qu'il l'a fait cent fois. Un modèle qui
a ingéré tous les posts de Stack Overflow sur Terre a vu
chalk-template et chalk-tempalte dans le même corpus
d'entraînement — ce dernier typiquement à l'intérieur d'une
capture d'écran de l'erreur de quelqu'un d'autre — et, sur un
prompt du genre « ajoute une sortie colorée à mon CLI »,
émettra parfois la coquille mot pour mot. L'agent ne sourcille
pas. Le gestionnaire de paquets ne sourcille pas. Le script
prepare s'exécute.
Ce n'est pas un mode de défaillance hypothétique. C'est le mode de défaillance pour lequel la famille Shai-Hulud a été conçue. Le ver original se propageait en volant des jetons de mainteneur et en s'en servant pour republier des versions compromises de paquets légitimement populaires. Les copycats n'ont pas encore les jetons de mainteneur ; ce qu'ils ont, c'est l'espace de noms des typosquats, dans lequel les agents sont particulièrement doués pour tomber.
Où Bromure s'inscrit dans cette histoire.
Bromure Agentic Coding est la configuration
dans laquelle l'agent de codage tourne à l'intérieur d'une VM
Linux jetable par tâche, avec le dossier projet monté, l'egress
arbitré et les identifiants conservés côté hôte macOS de
l'hyperviseur. Nous avons détaillé l'architecture dans le
billet sur Bitwarden CLI
et le billet @tanstack. Ce
qui suit, c'est ce qui arrive précisément à chacun de ces quatre
paquets à l'intérieur de cette frontière.
Passons en revue les quatre paquets, un par un, parce que c'est dans les détails que l'architecture gagne sa place.
chalk-tempalte cherche $GH_TOKEN.
Le coup signature du clone de Shai-Hulud, c'est de prendre le
jeton GitHub du développeur et de s'en servir pour créer un dépôt
public sur le propre compte du développeur. À l'intérieur d'une
VM Bromure, le $GH_TOKEN qu'il lit est un stub — une chaîne
syntaxiquement valide qui commence par ghp_ et qui existe pour
exactement cette raison. La première action du runner est
POST /user/repos contre api.github.com. Le proxy d'egress côté
hôte reconnaît api.github.com comme un endpoint en liste
blanche, mais uniquement pour les opérations que la tâche courante
a effectivement demandées — git push vers le dépôt sur lequel la
tâche travaille, gh pr create contre ce même dépôt,
gh api repos/that/repo/issues. « Créer un nouveau dépôt public
sur le compte de l'utilisateur » ne fait pas partie de cette
liste, parce que l'utilisateur ne l'a pas demandé. Le proxy
refuse de substituer le vrai jeton, et le stub part en tant que
stub. GitHub renvoie 401. Le canal d'exfiltration du ver — le
canal malin, celui conçu pour contourner le filtrage DNS
d'egress — ne s'ouvre jamais.
Le canal de secours, le tunnel lhr.life à
87e0bbc636999b.lhr.life, n'est pas non plus en liste blanche.
La trace enregistre la tentative. Les octets ne quittent pas la
machine.
axois-utils installe Phantom Bot pour la persistance.
Le bot Golang essaie de s'écrire dans le dossier de démarrage
Windows et de créer une tâche planifiée. La VM Bromure est un
guest Linux, donc la persistance spécifique à Windows est, pour
ainsi dire gratuitement, un no-op. Sur une variante Linux de la
même charge utile — que quelqu'un livrera, sous peu — le bot
s'écrirait dans /etc/cron.d/ ou ~/.config/systemd/user/. Ces
deux chemins se trouvent à l'intérieur du disque copy-on-write
jetable du guest. Le prochain bromure reset, ou la fin
naturelle de la tâche courante, fait tomber le disque. La
persistance disparaît sans aucune chasse.
Pendant ce temps, la connexion sortante du bot vers
b94b6bcfa27554.lhr.life n'est pas sur la liste blanche d'egress
de la tâche, parce qu'aucune tâche de codage légitime ne parle à
un tunnel lhr.life fraîchement enregistré. Le bot téléphone à
la maison dans un socket fermé. La trace de la session enregistre
la tentative — utile demain matin quand une liste d'IOC sera
publiée.
@deadcode09284814/axios-util envoie des identifiants bruts en POST.
La plus simple des quatre charges utiles est aussi celle qui a le
moins à attraper. Le runner lit le ~/.ssh, le ~/.aws du
guest, les variables d'environnement, et les envoie en POST à
80.200.28.28:2222. Le dossier SSH est vide. Le fichier AWS est
un stub. Les variables d'environnement sont soit des stubs, soit
absentes. L'IP de destination n'est pas en liste blanche. Soit la
connexion est bloquée au niveau du proxy, soit elle quitte l'hôte
en transportant une charge utile de placeholders. Les deux
résultats conviennent.
color-style-utils cherche des portefeuilles qui n'existent pas.
Le voleur de crypto est le paquet dont le modèle de menace
suppose le plus clairement que le navigateur du développeur se
trouve sur la même machine. Il lit des chemins comme
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<MetaMask-id>/
et les équivalents pour Phantom et Keplr. Aucun de ces chemins
n'existe sur la VM Bromure. La VM n'a pas votre profil Chrome.
La VM n'a pas d'extension de portefeuille installée. La VM
n'est, par conception, le navigateur principal de personne. Le
runner trouve un dossier vide et passe à autre chose.
C'est la partie qui n'est pas une histoire d'identifiants qui vivent sur l'hôte. Les portefeuilles vivent sur l'hôte parce que, sur un laptop normal, le navigateur du développeur et l'agent de codage du développeur partagent un système de fichiers. Bromure ne rend pas le portefeuille plus solide ; il le rend inaccessible depuis l'endroit où le ver tourne. Le ver ne peut pas lire ce qui n'est pas sur son disque.
Ce qui fait encore mal.
Il y a des recoins de cette histoire où la VM par tâche de Bromure n'est pas une solution, et ils méritent d'être nommés à voix haute.
Le dossier projet est monté.
Les fichiers que le ver écrit dans le dossier projet — y
compris la persistance de type .claude/router_runtime.js
que nous avons couverte dans le billet @tanstack — sont
durables au-delà des resets de tâche, parce que c'est tout
l'intérêt de monter le dossier projet. La défense, ici, ce
n'est pas la VM. C'est git status et un coup d'œil de cinq
secondes au diff avant de pousser. La trace facilite le
repérage des sessions qui ont ajouté des fichiers
inattendus.
La liste blanche d'egress est étroite à dessein.
Le courtier d'identifiants de Bromure fonctionne parce que
la liste blanche est étroite. Si vous mettez npm publish
sur votre propre scope en liste blanche parce que vous
publiez une release aujourd'hui, et qu'il se trouve que vous
installez l'un de ces quatre paquets aujourd'hui, le ver
publiera sous votre scope. Mettez en liste blanche ce dont
la tâche a besoin. Pas un octet de plus.
Un jeton valable pour cette tâche reste un vrai jeton.
Le jeton GitHub stub est échangé contre un vrai au moment du
fil pour les opérations que la tâche a mises en liste
blanche. Si chalk-tempalte parvenait à convaincre l'agent
de faire un git push vers le propre dépôt du projet, ce
push passerait avec un vrai jeton. La frontière protège les
identifiants. Elle ne relit pas le diff. Relisez le diff.
La détection est en aval de la trace.
La trace de session enregistre chaque commande shell, chaque
écriture de fichier et chaque requête sortante. À elle
seule, elle ne classe pas 87e0bbc636999b.lhr.life comme
mauvaise. Elle enregistre que la requête a été faite. Quand
OX publiera une nouvelle liste d'IOC demain matin, votre
recherche prendra deux secondes. C'est la valeur que la
trace ajoute — pas de la magie, juste des reçus.
Une dernière chose.
La fuite n'est pas la nouvelle. La nouvelle, c'est ce que la
fuite rend probable au cours de la prochaine année, à savoir
beaucoup de petits forks à moitié compétents d'un ver que, même
sous sa forme compétente, l'écosystème npm a attrapé de
justesse. Certains forks seront assez bruyants pour avoir droit
à un billet. La plupart resteront dans le registre pendant une
semaine, collecteront quelques milliers de npm install, et
disparaîtront quand quelqu'un finira par déposer un signalement
d'abus. Les deux mille six cent soixante-dix-huit installations
qu'OX a comptées sur les paquets deadcode09284814 ne sont pas
une valeur aberrante. C'est la moyenne.
La question honnête n'est pas « est-ce que mon équipe évitera chaque typosquat empoisonné dans npm ». Les agents tapent vite. Les noms sont gratuits. La question honnête, c'est : quand l'agent en installera un — et au cours de la prochaine année, dans une équipe qui utilise des agents, il le fera — est-ce que le script post-install trouvera les mains du développeur, ou trouvera-t-il une boîte Linux jetable avec des stubs dans les fichiers d'identifiants et un proxy qui ne sait pas qui il est.
Bromure Agentic Coding est la deuxième option. C'est gratuit, open-source, et livré aujourd'hui. L'arbre des forks va empirer avant de s'améliorer.