Retour au blog
21 septembre 2026

Au cœur de Miasma : comment un seul compte npm compromis a déclenché une attaque de la chaîne d'approvisionnement multi-écosystème

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

En juin 2026, des chercheurs ont mis au jour une nouvelle évolution de la famille de malwares Mini Shai-Hulud, baptisée Miasma. Contrairement aux malwares npm classiques qui visent une poignée de paquets, cette campagne a montré comment la compromission d'un seul élément de l'infrastructure de développement peut se propager rapidement à plusieurs écosystèmes : npm, GitHub Actions et même les modules Go.

Les attaquants ne visaient pas les utilisateurs finaux. Ils visaient les éditeurs de paquets, les pipelines CI/CD et les mécanismes de confiance qui permettent au logiciel de passer du portable d'un développeur à la production sans que personne ne le revérifie en chemin. Aucune CVE n'existe pour tout cela, car rien dans le code concerné n'a jamais été défectueux. Le point d'entrée était un identifiant volé, pas une vulnérabilité.

Un point mérite d'être signalé d'emblée : le 12 mai 2026, le groupe à l'origine de cette famille de vers, TeamPCP, a publié sur GitHub le code source complet de Shai-Hulud/Mini Shai-Hulud sous licence MIT. Cela change la lecture de cette histoire. Les quatre vagues qui ont suivi partagent la même boîte à outils et les mêmes techniques, mais un outillage partagé ne signifie plus un opérateur partagé. Une fois le framework public, n'importe quel acteur techniquement compétent peut mener une campagne identique en apparence à celles de TeamPCP. Ce qui suit doit donc être lu comme un recoupement de tactiques, techniques et procédures (TTP), et non comme une attribution confirmée à un groupe unique.

Paquets touchés (npm et Go)

PaquetVersion compromise
hexo-deployer-wrangler1.0.4
hexo-shoka-swiper0.1.10
leo-auth4.0.6
leo-aws2.0.4
leo-cache1.0.2
leo-cdk-lib0.0.2
leo-cli3.0.3
leo-config1.1.1
leo-connector-elasticsearch2.0.6
leo-connector-mongo3.0.8
leo-connector-mysql3.0.3
leo-connector-oracle2.0.1
leo-connector-redshift3.0.6
leo-cron2.0.2
leo-logger1.0.8
leo-sdk6.0.19
leo-streams2.0.1
prism-silq1.0.1
rstreams-metrics2.0.2
rstreams-shard-util1.0.1
serverless-convention2.0.4
serverless-leo3.0.14
solo-nav1.0.1
github.com/verana-labs/verana-blockchain (Go)v0.10.1-dev.20

Socket poursuit l'enquête ; les nouvelles découvertes sont ajoutées à sa page de suivi Miasma.

Si votre arbre de dépendances contient l'un de ces paquets dans les versions indiquées (ou des paquets issus des vagues compromises antérieures de la même campagne, début juin), considérez l'environnement comme potentiellement compromis et appliquez les mesures de remédiation immédiates décrites plus bas.

Chronologie

24 juin 2026
   ↓
Compte de mainteneur LeoPlatform compromis
   ↓
23 versions de paquets malveillantes publiées (à quelques secondes d'intervalle)
   ↓
La charge utile binding.gyp s'exécute à l'installation
   ↓
Identifiants des développeurs et des pipelines CI/CD volés
   ↓
Workflow GitHub Actions (« Run Copilot ») déployé
   ↓
Secrets volés exfiltrés vers des dépôts GitHub publics
   ↓
Module Go (Verana Blockchain) compromis via une technique apparentée
   ↓
Les chercheurs de Socket, Wiz, Sonatype et d'autres identifient et divulguent la campagne

Il s'agit de la quatrième vague de la même famille de campagnes en moins d'un mois : elle est d'abord apparue contre l'espace de noms npm de Red Hat le 1er juin, puis une vague de 57 paquets le 3 juin, puis cette vague LeoPlatform/Go les 24 et 25 juin. Comme indiqué plus haut, TeamPCP a rendu public le framework de ver sous-jacent le 12 mai ; « même famille de campagnes » traduit donc des TTP partagées entre ces vagues, et non la preuve confirmée d'un opérateur unique derrière les quatre.

Ce qui s'est réellement passé

  • Un mainteneur de l'écosystème npm LeoPlatform s'est fait dérober ses identifiants de compte ou son jeton npm, très probablement par un infostealer.
  • L'attaquant a utilisé cet accès volé pour publier des versions trojanisées de paquets que le mainteneur possédait déjà légitimement. Rien n'a eu besoin d'être « piraté » au sens traditionnel, puisque l'attaquant disposait déjà d'une identité de publication de confiance.
  • Quiconque a installé l'une de ces versions a exécuté une charge utile cachée qui a volé ses propres identifiants.
  • Certains de ces identifiants volés appartenaient à des environnements GitHub Actions, ce qui a permis à l'attaquant d'implanter un workflow malveillant et de voler des secrets CI/CD directement depuis le runner.
  • Séparément, un module Go a été compromis par une technique apparentée mais distincte, ce qui montre que la campagne n'est liée à aucun langage ni gestionnaire de paquets en particulier.
  • L'ensemble a été découvert parce que plusieurs équipes de recherche en sécurité indépendantes surveillaient précisément ce schéma, connu des vagues précédentes de la même campagne.

Analyse technique

Étape 1 : mainteneur compromis

Le compte npm czirker, associé à LeoPlatform, a été compromis, très probablement via des identifiants ayant fuité ou un jeton récupéré par un infostealer déjà actif sur la machine du mainteneur.

Étape 2 : publication des paquets malveillants

Avec le jeton volé, l'attaquant a publié 23 versions de paquets malveillantes dans les écosystèmes LeoPlatform et RStreams en une fenêtre de six secondes. Cette vitesse trahit un outil de publication scripté et automatisé, pas une personne téléversant des fichiers à la main.

Le détail le plus concret de toute la campagne se trouve à cette étape : l'attaquant n'a pas seulement publié des versions malveillantes, il a aussi redirigé le dist-tag latest de leo-sdk vers la version empoisonnée 6.0.19, tandis que la version saine 7.1.21 restait disponible uniquement sous le tag awsv3, bien moins visible. Concrètement, tout environnement exécutant npm install leo-sdk sans épingler explicitement une version semver était activement dirigé vers le ver, alors même qu'une version saine existait pendant tout ce temps. La leçon pratique est sans détour : épingler la version, et non le tag, a fait toute la différence entre une installation saine et une compromission.

Étape 3 : exécution à l'installation

C'est là toute l'astuce. La plupart des paquets npm malveillants se font repérer parce que les scanners surveillent les champs de scripts preinstall et postinstall du package.json. Miasma les évite complètement et ajoute à la place un fichier binding.gyp, la configuration que Node.js utilise pour compiler des modules natifs lors de l'installation. Comme binding.gyp fait tout à fait normalement partie de la construction d'extensions natives, il exécute du code pendant npm install sans jamais toucher aux champs que la plupart des outils surveillent.

Cette technique avait deux véritables parades, et il vaut la peine d'être précis sur les deux. La première existait déjà : npm install --ignore-scripts supprime bien le hook implicite node-gyp rebuild déclenché par un fichier binding.gyp, selon la documentation de npm elle-même. Ce n'est pas une défense complète à elle seule, puisqu'un paquet malveillant peut toujours être téléchargé et décompressé, et qu'un rebuild ultérieur sans cette option peut encore le déclencher, mais elle bloque bel et bien ce chemin d'exécution précis. La seconde parade est arrivée trois semaines trop tard : npm v12, livré en juillet 2026, ferme ce vecteur par défaut en traitant le node-gyp rebuild implicite déclenché par binding.gyp exactement comme n'importe quel autre script d'installation, bloqué sauf si le paquet figure explicitement sur une liste d'autorisation. La technique « Phantom Gyp » de Miasma exploitait en fait directement la faille précise que npm v12 a été conçu pour combler.

Étape 4 : collecte des secrets

Une fois en exécution, la charge utile moissonne un large éventail d'identifiants, dont :

  • jetons npm et PyPI
  • Personal Access Tokens GitHub et jetons OIDC GitHub
  • identifiants AWS, Azure et GCP
  • clés SSH
  • chemins de configuration et d'identifiants de Claude Code, Cursor et d'autres assistants de codage IA
  • clés d'API OpenAI
  • jetons Slack et Twilio, fichiers d'authentification Docker, configurations Kubernetes, données Vault et historique du shell

Étape 5 : abus de GitHub

Le malware dépose un workflow GitHub Actions (observé sous le nom « Run Copilot ») qui extrait les secrets directement de la mémoire du runner CI pendant l'exécution. Les données volées sont chiffrées puis poussées vers des dépôts GitHub publics fraîchement créés servant de boîtes aux lettres mortes ; à l'heure où nous écrivons, plus de 550 dépôts portent le marqueur d'exfiltration de la campagne. Un incident apparenté le même jour a vu un attaquant forcer le push d'un commit malveillant sur une GitHub Action largement utilisée et rediriger ses tags de version vers ce commit, de sorte que tout workflow s'exécutant sur ces tags après ce moment lançait directement la charge utile de l'attaquant à l'intérieur du runner GitHub Actions.

Étape 6 : propagation

Machine de développeur compromise
   ↓
Identifiants GitHub du développeur volés
   ↓
Secrets du pipeline CI/CD moissonnés depuis le runner
   ↓
Jeton npm volé utilisé pour publier d'autres paquets malveillants
   ↓
D'autres développeurs installent les paquets compromis
   ↓
(et ainsi de suite)

Chaque compromission réussie devient le tremplin de la suivante. C'est ce qui fait de cette campagne une campagne auto-propagatrice plutôt qu'un incident isolé.

Pourquoi cette attaque est différente

Attaques supply chain classiquesMiasma
Un seul paquetUn écosystème entier
Vol de cryptomonnaiesVol massif d'identifiants
Un seul langagePlusieurs langages (npm, et désormais Go)
Un seul mainteneurL'infrastructure de développement dans son ensemble
Malware statiqueAuto-propagateur, évoluant d'une vague à l'autre

Les incidents supply chain npm antérieurs relevaient surtout du casse éclair : compromettre un paquet, voler des clés de portefeuille ou des jetons de session, passer au suivant. Miasma se comporte davantage comme un ver doté d'un modèle économique. Il traite chaque infection réussie comme le carburant de la vague suivante, et il continue manifestement d'évoluer après plus de trois semaines et quatre vagues distinctes.

Pourquoi GitHub Actions est devenu une cible

GitHub Actions occupe une position singulièrement précieuse dans la chaîne d'approvisionnement logicielle : il détient les jetons OIDC utilisés pour publier de manière fiable et sans identifiants vers les fournisseurs cloud et les registres, il stocke des secrets CI de longue durée, et il exécute des tâches automatisées et sans surveillance par conception. Compromettre un runner CI n'expose pas seulement le portable d'un développeur : cela expose chaque pipeline de déploiement, chaque publication vers un registre et chaque identifiant cloud que ce workflow touche. Le rayon d'impact est bien plus large que celui de l'environnement local d'un développeur pris isolément, et c'est exactement pour cela que la campagne a construit un outillage dédié pour le cibler spécifiquement.

Pourquoi l'extension à Go est importante

La plupart des articles sur cette campagne se sont concentrés sur npm, puisque c'est là que vit la majorité des paquets. Mais la compromission du module Go est sans doute le détail le plus important de toute l'histoire, et c'est celui que la plupart des comptes rendus passent sous silence.

L'échantillon Go n'utilise pas binding.gyp. Il ne s'appuie pas du tout sur les mécanismes de résolution de modules ou de build de Go. À la place, un développeur qui se contente de cloner le dépôt ou de l'ouvrir dans un IDE ou un assistant de codage IA de confiance peut déclencher la charge utile par la seule configuration du projet.

Cela révèle quelque chose d'important : les attaquants ne s'intéressent fondamentalement ni à JavaScript, ni à npm, ni aux mécanismes d'un gestionnaire de paquets en particulier. Ils s'intéressent aux développeurs, partout où ceux-ci font confiance à un outillage automatisé, qu'il s'agisse d'un script d'installation, d'une étape de build ou d'un IDE qui ouvre un dossier. Le langage n'a pas d'importance. Les identifiants des développeurs, si.

La leçon plus large

Ancienne façon de penser :

Nous analysons les dépendances
   ↓
Nous corrigeons les CVE connues
   ↓
Nous sommes en sécurité

Réalité moderne :

Un développeur est compromis
   ↓
Le CI/CD est compromis
   ↓
Un paquet légitime est publié avec du code malveillant
   ↓
Chaque consommateur en aval l'installe
   ↓
Aucune CVE n'est jamais nécessaire à aucune étape

L'analyse des dépendances répond à la question « ce code présente-t-il une faille connue ? ». Miasma met en lumière une question totalement différente, à laquelle la plupart des programmes de sécurité ne sont pas du tout préparés à répondre : « est-ce que je fais confiance aux humains et aux systèmes automatisés autorisés à publier du code dans mon environnement, maintenant, aujourd'hui ? ».

Opportunités de détection

Plutôt que de demander « comment l'arrêter », il est plus utile de se demander où un défenseur avait réellement une chance de remarquer que quelque chose n'allait pas. C'est là que la plupart des articles sur Miasma s'arrêtent, d'où l'intérêt d'approfondir.

Vitesse de publication. 23 versions de paquets publiées en six secondes, ce n'est pas le fait d'un humain. Toute organisation exploitant son propre miroir de registre npm, ou surveillant les événements du registre public pour les paquets dont elle dépend, pourrait signaler automatiquement de telles rafales de publication. Une montée de version isolée est normale. Deux douzaines en quelques secondes constituent un signal en soi, indépendamment du contenu réel des paquets.

Changements de dist-tag sans montée de version demandée par les utilisateurs. Le détournement de leo-sdk a fonctionné précisément parce que latest a été déplacé vers un numéro de version inférieur et d'apparence plus ancienne (6.0.19), pendant qu'une version saine plus récente (7.1.21) se trouvait sous un autre tag. Tout outillage comparant l'évolution des dist-tags d'un paquet dans le temps, et pas seulement sa liste de versions, aurait repéré un latest pointant en arrière dans la séquence des versions, ce qui n'est presque jamais un comportement légitime.

Artefacts de build qui ne correspondent pas au code source. Le paquet leo-sdk compromis est passé d'un index.js de 10 Ko dans la version saine à un index.js de 5 Mo dans la version empoisonnée, accompagné d'un fichier binding.gyp qui n'existe nulle part dans le véritable dépôt GitHub du projet. Comparer, paquet par paquet, ce qui est publié sur le registre avec ce qui est commité dans le gestionnaire de sources aurait fait remonter cela immédiatement. La plupart des organisations n'effectuent jamais cette comparaison.

Builds natifs inattendus sur des paquets JavaScript purs. leo-sdk est un SDK JavaScript sans aucun besoin légitime de compilation native. Un binding.gyp qui apparaît sur un paquet qui n'en a jamais livré auparavant, ou qui n'a aucune raison plausible d'en avoir besoin, est à lui seul un fort signal d'anomalie, bien avant que quiconque n'inspecte ce que fait réellement le fichier.

Nouveaux fichiers de workflow apparaissant sans pull request correspondante. Un fichier de workflow nommé « Run Copilot » qui surgit dans .github/workflows/ sans PR liée, sans revue et sans auteur que quiconque reconnaisse est exactement le type de changement que la protection des branches et les revues obligatoires sont censées intercepter, à condition qu'elles soient effectivement appliquées aux fichiers de workflow en particulier.

Trafic sortant vers des dépôts GitHub fraîchement créés. Comme l'exfiltration reposait sur des dépôts publics nouvellement créés servant de boîtes aux lettres mortes, des runners CI ou des machines de développeurs effectuant des appels d'API sortants pour créer de nouveaux dépôts, ou poussant vers des dépôts qui n'existaient pas une heure plus tôt, constituent un schéma de comportement détectable et assez inhabituel.

Utilisation d'OIDC et de jetons hors de leur schéma habituel. Un jeton OIDC GitHub ou un jeton de publication npm utilisé depuis une plage d'adresses IP, un workflow ou une fenêtre horaire d'où il n'a jamais été utilisé auparavant est un signal classique d'anomalie d'identité, et il ne nécessite aucune connaissance de cette campagne en particulier pour être intercepté.

Aucune de ces méthodes ne nécessite une base de données de CVE ni une connaissance préalable de Miasma en particulier. Elles exigent d'observer réellement comment la confiance, le comportement de publication et l'identité circulent dans votre environnement au fil du temps, et pas seulement ce qui figure dans un fichier manifeste à un instant donné.

Ce que les organisations doivent faire

Immédiatement

  • Faire tourner les identifiants de tout environnement ayant installé un paquet touché, mais seulement après avoir retiré la version malveillante et confirmé l'absence de persistance, pas avant.
  • Mettre à jour ou supprimer les versions de paquets touchées dans les lockfiles, les caches, les conteneurs et les runners CI, et pas seulement dans le manifeste de premier niveau.
  • Révoquer et réémettre tous les jetons GitHub, configurations OIDC ou jetons npm susceptibles d'avoir été exposés.
  • Épingler les GitHub Actions par hash de commit plutôt que par tags de version modifiables.

À moyen terme

  • Introduire des listes d'autorisation de paquets pour ce qui est permis dans les builds CI/CD et de production.
  • Exiger la provenance et la signature des paquets lorsque c'est possible.
  • Tenir un véritable inventaire logiciel (SBOM), et non un instantané figé.
  • Ajouter un délai de carence avant qu'une version de paquet fraîchement publiée ne soit considérée comme fiable dans un build.

À long terme

  • Validation continue de ce qui s'exécute réellement dans votre environnement, plutôt que des audits périodiques.
  • Surveillance des dépôts et du CI/CD pour détecter les changements de workflow ou les comportements de publication inattendus.
  • Analyse permanente des chemins d'attaque : non seulement « ce paquet est-il vulnérable ? », mais « qu'est-ce que quelqu'un pourrait atteindre si ce paquet était malveillant ? ».
  • Surveillance de l'identité des développeurs, puisque dans cette campagne, l'identité était la véritable surface d'attaque.

Pourquoi cela compte pour Penetrify

Miasma n'était pas un problème npm. C'était un problème d'identité. Un problème de CI/CD. Un problème de confiance. Un problème de chemin d'attaque.

Les attaquants n'ont pas découvert une vulnérabilité. Ils ont découvert un chemin allant du poste de travail d'un seul développeur à des milliers de systèmes en aval, et ce chemin existait entièrement à l'intérieur de relations que la plupart des programmes de sécurité ne surveillent jamais réellement : quels humains peuvent publier du code, quels jetons peuvent déclencher un déploiement, quels workflows s'exécutent sans surveillance avec un véritable accès à la production.

npm v12 ferme le vecteur d'exécution précis sur lequel Miasma s'appuyait. Il ne fait rien contre le problème d'identité qui se cache dessous. Comme l'a formulé Moshe Siman Tov Bustan, d'OX Security, dans la couverture du déploiement de npm v12 : une fois qu'un attaquant contrôle les identifiants d'un mainteneur légitime, bloquer les scripts d'installation ne sert à rien, car le code malveillant est livré sous la forme d'une release signée et de confiance.

C'est tout l'argument que cet article développe, formulé plus simplement que nous n'aurions pu le faire. La sécurité moderne de la chaîne d'approvisionnement logicielle doit dépasser l'analyse des dépendances et s'orienter vers une compréhension continue de l'évolution des relations de confiance entre dépôts, pipelines de build, identités et environnements de production, et non se limiter aux CVE présentes dans un fichier manifeste.

Sources

Frequently Asked Questions

Quels types de vulnérabilités Penetrify détecte-t-il ?

Penetrify détecte toutes les catégories de vulnérabilités OWASP Top 10, notamment les injections SQL, XSS, CSRF, IDOR, les failles d'authentification, les mauvaises configurations de sécurité et l'exposition de données sensibles. Il teste également la sécurité des API, la gestion des sessions et les mauvaises configurations courantes dans Supabase, Firebase et Bubble.

Combien de temps dure un test de pénétration IA ?

Un scan rapide se termine en 15–30 minutes. Un scan standard dure 1–2 heures avec une couverture plus large. Un scan approfondi peut durer plusieurs heures pour les applications complexes.

Que contient un rapport Penetrify ?

Chaque rapport comprend un résumé exécutif, un score de sécurité global, des résultats classés par gravité (Critique, Élevé, Moyen, Faible), des étapes de reproduction détaillées et des recommandations de remédiation concrètes rédigées pour les développeurs – pas pour les responsables conformité.

Related articles

Tests de conformité multi-framework : un seul engagement, plusieurs auditeurs
Soumis simultanément aux exigences SOC 2, PCI DSS et HIPAA ? Découvrez comment éviter les tests redondants et satisfaire tous les auditeurs grâce à un programme unifié. Optimisez votre approche en matière de sécurité, incluant des pratiques comme le *Penetration Testing*, l'intégration *CI/CD* sécurisée et une méthodologie *DevSecOps*, tout en restant conforme aux normes *OWASP*.
Qu'est-ce qu'un test d'intrusion ? Un guide étape par étape sur son fonctionnement
Votre application web est-elle vraiment sécurisée ? L'idée d'une seule vulnérabilité cachée menant à une violation de données catastrophique suffit à empêcher n'importe quel fondateur de dormir la nuit. Vous savez que vous devez agir, mais le monde de la cybersécurité peut sembler être un labyrinthe intimidant.
Analyse de conformité PCI DSS : Le guide 2026 de la sécurité automatisée
Le 14 mars 2025, un important détaillant a découvert qu'une simple règle de pare-feu mal configurée lors d'une mise en production un vendredi après-midi avait anéanti trois mois de préparation à la conformité en moins de six minutes. Vous savez probablement déjà que les analyses trimestrielles traditionnelles de conformité PCI DSS donnent l'impression de vérifier son compteur de vitesse…

Explore more