Il demandait à être exécuté
Sonatype suit 846 paquets npm malveillants publiés depuis des centaines de comptes jetables sous des noms générés par IA. Aucun ne porte de script d’installation. Le README vous dit de charger le paquet avec require(), et l’import exécute le loader. Les défenses qui reconnaissent un mauvais paquet à son nom, à son auteur ou à son hook déclaré courent désormais après un générateur. Bromure Agentic Coding bloque sur la seule propriété qu’un générateur ne peut pas falsifier : l’âge du paquet.
Les paquets ne portent aucun hook d’installation, donc npm n’a rien à
intercepter. Le README vous dit de les charger avec require(), et l’import
exécute le loader. De plus en plus, le lecteur qui suit cette instruction est
un agent de code.
Sonatype Research Labs a divulgué le 5 août une campagne qu’il appelle Flooding Dropper, comptant 846 composants npm malveillants et le compte grimpe encore. Paul McCarty, chez OpenSourceMalware, suit la charge utile sous le nom WEL1DROPPER. La couverture plus large est parue le 7 août.
Deux détails la distinguent des affaires de supply chain npm du mois écoulé.
Le premier, c’est la façon dont les paquets obtiennent leur nom. L’attaquant
les génère, en faisant tourner le slopsquatting comme une chaîne de
production. Des radicaux récurrents comme bigops et bnpl se recombinent
avec d’autres mots pour donner des noms tels que bigops-api et
dolyame-boxy-desktop-bnpl-card-gallery, beaucoup partageant un motif de
version 35.x.y. La publication se répartit sur des centaines de comptes
jetables portant une poignée de paquets chacun, si bien qu’il n’y a aucun
auteur prolifique à bannir.
Pas de script d’installation
Les paquets ne déclarent aucun hook de cycle de vie, donc npm install
n’exécute rien. L’article de McCarty le dit sans détour : « Les paquets NPM
n’utilisent pas de script preinstall ou postinstall. Ils n’en ont pas
besoin. »
Ils embarquent un _helpers.js bundlé qui s’exécute à l’import du module,
accompagné d’un README qui dit au lecteur de l’importer. Chargez le paquet
avec require(), dit la documentation, et la documentation dit vrai. Le
premier étage prend l’empreinte du système d’exploitation et de
l’architecture du processeur, récupère un second étage natif en HTTPS depuis
l’un de trois hôtes Cloudflare Workers, et se rabat sur la reconstitution du
binaire à partir de fragments base64 logés dans des enregistrements DNS TXT
sous wel1.ru quand la récupération directe échoue.
Le second étage varie selon la plateforme. Linux reçoit un ELF compressé avec UPX qui déploie un implant C2 Sliver. macOS reçoit des contrôles anti-débogage, une sonde VMware, un binaire balise et une persistance par LaunchAgent. Windows reçoit du patching ETW et AMSI, de la détection de bac à sable, de l’exécution réflective en mémoire et une persistance via une clé Run du Registre et une tâche planifiée. McCarty et Sonatype lisent tous deux la campagne comme une évolution de « Moika », la vague de confusion de dépendances d’avril qui avait poussé quelque 250 paquets bâtis autour du vol d’environnement.
L’attaquant a déplacé le déclencheur du gestionnaire de paquets vers le lecteur.
L’automatisation bat la liste de blocage
Le résumé de Sonatype lui-même est la phrase la plus tranchante de toute la couverture :
Lisez cela comme une déclaration sur les défenses. Toute couche qui vous protège en reconnaissant le mauvais paquet, que ce soit par son nom, par son compte de publication ou par son hook déclaré, affronte désormais un générateur. Ces couches attrapent toujours ce qu’elles ont déjà vu. Elles sont notées sur leur capacité à reconnaître une chaîne précise, et la fourniture de chaînes précises coûte le temps d’exécuter un script.
L’angle agentique aiguise le tout. Le slopsquatting est rentable au départ parce que les modèles de langage inventent des noms de paquets qui sonnent juste et qui n’existent pas, et un attaquant qui enregistre ces noms récolte du trafic d’installation gratuitement. Ajoutez le hook d’installation manquant et vous obtenez un paquet dont le déclencheur est une instruction écrite dans un README. Un agent de code est le lecteur qui va halluciner le nom, trouver un paquet posé dessous, lire la documentation et faire ce que la documentation dit. La campagne est taillée pour l’agent aux deux bouts.
Choisissez une propriété que le générateur ne peut pas falsifier.
L’âge n’est pas un nom
Chaque paquet de cette campagne partage une propriété qu’aucune automatisation ne change : il est neuf. Un nom qu’un modèle a inventé mardi dernier ne peut pas avoir sous lui un paquet datant de 2019. Des centaines de comptes jetables publient des centaines de versions flambant neuves, parce qu’un compte jetable n’a aucun historique d’où publier. Le modèle dépend du fait que le paquet soit là au moment où quelqu’un le réclame, donc l’attaquant le crée quelques jours à l’avance tout au plus.
Bromure Agentic Coding exécute l’agent dans une VM Linux jetable sur Apple Silicon et route chaque octet de son trafic réseau par un proxy sur l’hôte, hors de la boîte où tourne l’agent. Les récupérations de paquets pour npm, PyPI, Cargo, RubyGems, Maven, NuGet, les modules Go et Packagist traversent toutes ce proxy, et la première politique qu’elles rencontrent est une horloge.
La barrière d’âge, active par défaut
Bromure refuse les paquets plus jeunes qu’un âge minimum, fixé à deux jours
à la sortie de la boîte. Les références flottantes comme latest ou une
plage semver se résolvent vers la version la plus récente antérieure au
seuil, si bien que les installations ordinaires continuent de fonctionner
et atterrissent sur la version d’hier. Une référence épinglée vers une
version trop fraîche reçoit un 451 avec une erreur Bromure claire qui
nomme ce qui s’est passé. Huit cents paquets coûtent aussi cher à arrêter
que huit, parce que Bromure n’en reconnaît aucun.
Elle s’applique avant que le débat commence
La partie astucieuse de cette campagne, abandonner le hook de cycle de vie
et déplacer l’exécution vers require(), décrit ce qui se passe après que
le tarball a atteint le disque. La barrière d’âge se tient à la
récupération, dans le proxy de l’hôte, en amont de tout cela. Savoir si la
charge utile se serait exécutée à l’installation ou à l’import est une
question qui porte sur un paquet qui n’est jamais arrivé. Les .npmrc et
pip.conf dans la VM peuvent durcir cette politique et ne peuvent pas
l’assouplir.
La réputation, derrière l’horloge
Avec une clé socket.dev, Bromure vérifie chaque paquet dans la base de socket.dev avant de laisser passer la récupération et bloque ceux marqués comme compromis, malveillants ou typosquats. Les recherches OSV bloquent les paquets porteurs de vulnérabilités connues à partir d’une sévérité que vous choisissez. Delpi redirige purement et simplement chaque requête npm vers un registre filtrant. Ces couches reconnaissent des choses, ce qui les rend utiles et les place en second.
Le README est noté pour ce qu’il est
« Chargez ceci avec require() » est une instruction écrite par un
inconnu, qui arrive dans du texte que l’agent lit. Bromure note les sorties
d’outils que l’agent consomme, y compris le contenu de fichiers, les pages
web et la documentation récupérée, avec un modèle local d’injection de
prompt. Il note les fichiers d’instructions que l’agent traite comme
faisant autorité, CLAUDE.md et AGENTS.md et leurs variantes, avec un
classifieur dédié plus un scanner de charges utiles en Unicode invisible.
Rien ne quitte le Mac pour le faire.
La charge utile profile la mauvaise machine
Supposons que la récupération passe quand même, parce que le paquet a dépassé le seuil d’âge ou qu’une entrée d’allowlist l’exempte. Suivez WEL1DROPPER à travers un profil Bromure et regardez-le faire son travail contre une machine que vous alliez jeter.
Le premier geste du loader est de prendre l’empreinte de l’hôte. Dans un
profil Bromure, l’hôte dont il prend l’empreinte est une VM Ubuntu en NAT, à
un hyperviseur de macOS, il prend donc la branche Linux et y plante un implant
Sliver. Il ne demande jamais la branche macOS : l’anti-débogage, la sonde
VMware, le téléchargement de beacon_mac.bin, le LaunchAgent qui survivrait à
un redémarrage, l’intérêt pour les domaines de paiement russes. Tout cela est
du code pour une machine que le dropper n’a jamais vue. Erase home jette
la couche dans laquelle il a atterri et remet le répertoire personnel dans son
état d’après clonage.
Puis l’étage infostealer part chercher ce que son ancêtre Moika allait
chercher : l’environnement. Dans un profil Bromure, l’environnement contient
des placeholders brm_…. Aucune des vraies clés Anthropic, OpenAI et xAI,
aucun des tokens GitHub et GitLab, aucun matériel AWS ni identifiant de
registre ne se trouve dans la mémoire de la VM ni dans ses dotfiles. Bromure
les garde sur l’hôte et les substitue dans la requête au niveau du proxy, sur
le fil, pour l’unique hôte de destination auquel chacun appartient. Le vol
s’exécute jusqu’au bout et expédie des chaînes qui ne résolvent que sur un
seul Mac, lequel n’est pas la machine qui a exécuté le code.
La chasse qu’ils recommandent, déjà en cours
McCarty clôt l’article WEL1DROPPER par des conseils défensifs : chercher les
requêtes DNS TXT vers wel1.ru, examiner les logs de proxy pour du trafic
vers les hôtes Cloudflare Workers, et guetter les fichiers temporaires et les
LaunchAgents égarés.
Relisez le deuxième point. Votre portable ne tient pas de log de proxy. Un
profil Bromure en tient un par construction : chaque requête que fait la VM
traverse le proxy de l’hôte, et le Security Log et le Trace Inspector
enregistrent l’hôte, le statut, la latence et le rapport de substitution de
chacune. Trois destinations *.workers.dev inconnues qui apparaissent
pendant un build, ce sont trois lignes sur une liste que vous pouvez lire, sur
la machine où le build a tourné, sans rien déployer pour les obtenir.
Les registres continueront de retirer ces paquets, et les retraits fonctionnent : 846 d’entre eux ont disparu ou sont en train de disparaître. Mais la campagne dépasse déjà le millier, publiée depuis des comptes qui ne coûtent rien à créer sous des noms qui ne coûtent rien à inventer, et la prochaine vague est à une exécution de script. La reconnaissance est le mauvais concours à disputer.
Un paquet de deux jours ne mérite pas une confiance profonde qui manquerait à un paquet de deux heures. Il se situe hors de la fenêtre dans laquelle opère cette économie, et vous pouvez déplacer votre graphe de dépendances derrière cette fenêtre avec un réglage qui est déjà actif.
Installez Bromure Agentic Coding, et laissez la récupération croiser une horloge avant de croiser votre machine.