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

Une tous les trois jours

StepSecurity a publié samedi le décompte d'une année d'attaques par paquets malveillants : 56, dont 6 sur les six premiers mois et 50 sur les six mois et demi écoulés depuis février, soit environ une tous les trois jours depuis mars. Puis le rapport nomme la raison pour laquelle elles continuent de fonctionner : des secrets sur les machines de développement et les runners CI, des versions latest non épinglées, un accès réseau sortant ouvert. Aucune de ces trois choses n'est une propriété d'un paquet. Toutes trois décrivent la machine sur laquelle l'installation s'exécute, et la machine qui installe le plus de paquets a désormais un agent de codage dessus.

Un paquet malveillant se déclenche à l'installation. Il n'y a pas de chemin de code vulnérable à atteindre, pas d'exploit à écrire, pas de fenêtre à corriger. À une tous les trois jours, le paquet dans lequel il arrive a cessé d'être la bonne question à poser.

Samedi, StepSecurity a publié un décompte sur douze mois des attaques sur la chaîne d'approvisionnement open source. Cinquante-six. « Sur les six mois d'août 2025 à janvier 2026, nous avons recensé 6 incidents. Sur les six mois et demi écoulés depuis février 2026, nous en avons recensé 50. » Depuis mars, le rythme se maintient autour de neuf par mois, soit « environ une tous les trois jours ».

StepSecurity vend un registre filtrant : lisez le rapport en sachant ce qu'il vend. Le décompte tient quand même. C'est le relevé public le plus rigoureux de paquets malveillants que quiconque ait publié, et la définition qui le sous-tend est volontairement étroite.

Ce que compte ce nombre

Le rapport écarte les vulnérabilités. Pas de CVE, pas de bloc unsafe qui s'est révélé l'être. Il compte les paquets que quelqu'un a empoisonnés délibérément.

C'est pour cela que le nombre est exploitable. Une vulnérabilité exige un second événement : il faut que quelqu'un trouve votre instance, atteigne le chemin de code concerné et en tire quelque chose. Vous pouvez faire tourner une version vulnérable pendant un an sans que rien n'arrive. Un paquet malveillant saute tout cela. Comme le formule le rapport, il « s'exécute au moment même où une machine de développement ou un runner CI l'installe ». L'installation est l'exploitation. Vous n'obtenez jamais de fenêtre entre divulgation et correctif, parce que personne n'a rien divulgué et que personne n'a livré de correctif. Vous obtenez un numéro de version, puis une course pour déterminer qui l'a récupéré.

Mars 2026 en a vu 13. Avril 9, mai 9, juin 10, juillet 6. En cinq jours de mars, une opération suivie sous le nom de Team PCP a dérobé 78 330 secrets à 2 186 organisations, dont 92 entreprises cotées, en empoisonnant 76 des 77 tags de la GitHub Action Trivy et une poignée d'autres composants de build. axios, avec plus de 100 millions de téléchargements hebdomadaires, a livré deux versions malveillantes portant un cheval de Troie d'accès à distance multiplateforme. Six vers apparaissent dans l'année : Shai-Hulud à deux reprises, CanisterWorm, Mini Shai-Hulud, Miasma et ChainDrop ce mois-ci. npm, PyPI, RubyGems, Composer, crates.io, GitHub Actions. « Tous les grands écosystèmes de paquets et toutes les plateformes CI/CD ont été touchés. »

Deux entrées de cette liste comptent ici. Le rapport note que hackerbot-claw, « un bot propulsé par l'IA », a visé les GitHub Actions de Microsoft, Datadog et la CNCF, et que « le ver Miasma ciblait spécifiquement les agents de codage IA ».

Les trois conditions, dont aucune n'est un paquet

Puis le rapport fait quelque chose d'utile. Sous un intertitre qui demande pourquoi ces attaques continuent de fonctionner, il donne trois réponses : « des secrets dans les pipelines CI/CD et sur les machines de développement, des versions latest non épinglées, un accès réseau sortant ouvert ».

Rien dans cette liste ne décrit un paquet. Pas d'obfuscation, pas de distance de typosquatting, pas d'astuce de hook d'installation. Toutes trois décrivent la machine qui exécute l'installation : ce qu'elle détient, comment elle choisit une version, et où elle est autorisée à se connecter. Changez-en une seule et la plupart des cinquante-six cessent d'être rentables.

Latest non épinglé

Quelque chose dans la chaîne de résolution dit « la plus récente ». Une plage flottante, une dépendance transitive, un lockfile reconstruit, un agent qui ajoute une bibliothèque pour vous. L'attaquant a seulement besoin que le résolveur préfère une version publiée il y a quelques minutes.

Des secrets sur la machine

~/.ssh, ~/.aws, ~/.kube, ~/.npmrc, un GH_TOKEN dans l'environnement, un login de registre dans ~/.docker/config.json. Rien de tout cela n'appartient au build, et ce que le build vient de lancer peut tout lire.

Sortant ouvert

Le butin doit sortir, et le binaire de seconde étape doit arriver. Ce sont deux connexions sortantes ordinaires depuis une machine qui autorise les connexions sortantes vers partout.

Ce qui doit s’aligner1 · le paquetune version empoisonnéeparaît et vit une heureou deux2 · le résolveurla chaîne demande la plusrécente, et la plus récenteest celle qui est empoisonnée3 · le disquele code d’installation lit~/.ssh ~/.aws ~/.npmrcet l’environnement4 · le filune connexion sortantel’emporte, et ramène laseconde étapeles étapes 2, 3 et 4 sont décidées par votre machine, pas par le paquet« des secrets dans les pipelines CI/CD et sur les machines de développement,des versions latest non épinglées, un accès sortant ouvert » — StepSecurityc’est la seulepartie que vousne contrôlez pas
Une attaque par paquet malveillant a besoin que quatre choses s'alignent, et seule la première concerne le paquet. Les trois autres sont des propriétés de la machine : comment elle choisit une version, ce qu'elle détient, et où elle est autorisée à se connecter.

Quelle machine installe le plus de paquets aujourd'hui

La défense sur laquelle tout le monde comptait, c'était l'attention. Vous lisez le diff. Vous remarquez qu'une bibliothèque de cache a ramassé une dépendance nommée d'après un formateur de dates à une lettre près.

Deux choses ont dépassé cela. Le rythme, d'abord : personne ne relit les dépendances transitives tous les trois jours. Puis qui procède à l'installation. Un agent de codage ajoute une bibliothèque parce que vous lui avez demandé de construire quelque chose, résout l'arbre, lance l'installation et rapporte que les tests passent. Vous lisez le diff qu'il a produit, ce qui est la partie qui mérite votre temps. Une tarball n'apparaît jamais dans un diff. La liste de StepSecurity comprend déjà un ver parti à la recherche de ce mode de travail.

Gardez l'agent. Déplacez le contrôle là où sa vitesse cesse de compter.

Trois conditions, trois contrôles, tous du côté du Mac

Bromure Agentic Coding exécute l'agent dans une VM Ubuntu jetable sur le framework Virtualization d'Apple, avec seulement les dossiers que vous avez choisis montés dedans et un proxy côté hôte pour unique voie de sortie. Ce placement met chacune des trois conditions de StepSecurity devant un contrôle que le build ne peut pas atteindre.

Le latest non épinglé devient une horloge. Chaque récupération de paquet traverse d'abord le proxy de l'hôte : npm, PyPI, Cargo, RubyGems, Maven, NuGet, modules Go, Packagist. Le filtre d'ancienneté est actif par défaut à deux jours. Les références flottantes se résolvent vers la version la plus récente antérieure au seuil, si bien que latest continue de fonctionner et cesse de signifier « publiée pendant que vous lisiez ceci ». Une référence épinglée vers quelque chose de trop frais revient en 451 avec une erreur Bromure dessus. En plus de cela, vous pouvez activer les recherches OSV au seuil de sévérité de votre choix, faire passer les paquets par la base de réputation de socket.dev ou le registre filtrant de Delpi, et retirer à la volée preinstall, install, postinstall et prepare des tarballs npm. Bromure réécrit la tarball et met à jour le hash des métadonnées du registre pour que la vérification propre à npm passe toujours. Les .npmrc et pip.conf à l'intérieur de la VM peuvent resserrer chacun de ces réglages et ne peuvent pas les relâcher.

Les secrets sur la machine sont des leurres. Les vrais identifiants n'entrent jamais dans la VM. Le proxy échange un espace réservé contre le vrai jeton sur le fil ; le kubeconfig de l'invité est synthétique, avec des certificats client jetables ; AWS reçoit un assistant credential_process qui remet au SDK un faux de quarante caractères pendant que l'hôte resigne SigV4 avec le vrai matériel, si bien que tout ce qui contourne le proxy récolte une InvalidSignatureException d'Amazon. Le code d'installation qui lit ~/.aws/credentials trouve un fichier, le lit, et exfiltre un leurre. Chaque identifiant porte aussi une politique d'écriture (Désactivée, Bloquer les opérations destructrices, ou Lecture seule) appliquée dans le proxy de l'hôte, plus Exiger une approbation à l'utilisation, qui transforme tout usage unique en une boîte de dialogue de consentement avec une autorisation de cinq minutes, d'une heure, ou du reste de la session.

Le sortant ouvert répond à une règle que vous avez écrite. Sous Garde-fous, la table Connexions sortantes est un pare-feu par profil : une ligne par règle, chacune avec une action, un protocole (tcp, udp, web ou any), un hôte ou un CIDR, des ports, et pour web une liste de méthodes HTTP. Les règles s'appliquent de haut en bas, la première correspondance l'emporte, et Trafic non correspondant est un interrupteur que vous mettez sur Autoriser ou Refuser. Deux couches appliquent le même jeu de règles : le commutateur virtuel par IP de destination et par les noms d'hôtes appris en écoutant les réponses DNS de l'invité, et le proxy par SNI TLS. L'interception transparente est active par défaut, donc un script de build qui désactive HTTPS_PROXY ne change rien aux connexions qui sortent.

L'attaque crates.io de la semaine dernière montre ce que cette règle attrape. Dans l'analyse de StepSecurity, le script de build du dropper décodait une URL en base64 et récupérait un binaire spécifique à la plateforme depuis une IP nue sur le port 9089, avec validation de certificat désactivée, puis détachait le processus enfant pour que Cargo ne l'attende pas. Avec le trafic non correspondant refusé, cette récupération n'aboutit jamais, et le code de sortie du script de build cesse d'être une question intéressante.

Une installation, deux machinesSur le portablele résolveurprend ce qui a été publié en dernierles scripts d’installations’exécutent en votre nom, avant tout importles identifiants~/.ssh ~/.aws ~/.npmrc $GH_TOKENle filatteint tout hôte, sans journalDans un espace de travail Bromurefiltre d’ancienneté, actif par défaut à deux joursles références flottantes passent sous le seuil · uneversion épinglée trop fraîche reçoit un 451retirés de la tarballhash des métadonnées réécrit, npm vérifie toujoursdes leurres, échangés sur le filles vraies valeurs restent sur le Mac · SigV4 resignénon correspondant : refus · Journal de sécurité
Les mêmes quatre étapes, exécutées dans un espace de travail Bromure. L'étape un se produit toujours, parce que personne ne peut empêcher quelqu'un de publier une mauvaise version. Les étapes deux, trois et quatre rencontrent chacune un contrôle qui vit sur le Mac, hors de tout ce dans quoi l'installation peut écrire.

Quatre-vingt-six minutes

Revenez à cette même attaque pour comprendre pourquoi une horloge bat un jugement. Les versions empoisonnées d'arrayref, internment et append-only-vec ont vécu 86, 90 et 107 minutes avant que crates.io ne les retire. Pour orienter les résolveurs vers elles, l'attaquant a rétracté les versions saines, de 0.3.9 jusqu'à 0.3.5, dans une rafale qui a pris seize secondes, laissant une seule version que personne n'avait retirée. La phrase la plus tranchante de StepSecurity sur l'incident : « L'attaquant a transformé la propre fonction de sécurité du registre en canal de livraison. » Et arrayref lui-même ne portait aucun code malveillant. La charge utile est entrée par un typosquat de proc-macro2 au moment du build.

Un filtre d'ancienneté de deux jours n'a jamais à reconnaître quoi que ce soit de tout cela. Une version de quatre-vingt-six minutes est plus jeune que le seuil, et le temps qu'elle soit assez vieille pour être installée, crates.io l'a déjà supprimée. L'attaquant a construit une fenêtre plus courte que celle que le filtre ignore.

Attaque cinquante-sept

Le rapport se referme sur une phrase à garder : « 56 attaques en 12 mois. L'attaque numéro 57 est déjà en préparation. »

Vous ne saurez pas quel paquet la porte, et vous ne le lirez pas. Votre agent non plus. Cinq mois à une tous les trois jours dépassent largement la cadence à laquelle qui que ce soit relit, et parler de négligence ne mène nulle part. Vous décidez en revanche de ce sur quoi elle atterrit : si la version dont elle a besoin est une que votre résolveur acceptera, si les secrets qu'elle vient chercher sont réels, et si l'adresse qu'elle veut joindre est une vers laquelle votre machine routera. Trois réglages, tous de votre côté de la frontière.


Sources : StepSecurity, « The State of Open Source Supply Chain Attacks » (22 août 2026) · StepSecurity, « Rust Supply-Chain Attack: arrayref, internment, and append-only-vec Poisoned by the proc-macro1 Build-Time Dropper » (20 août 2026) · StepSecurity, « Team PCP Stole 78,330 Secrets From 2,186 Organizations » (13 août 2026)