5 points par GN⁺ 2025-09-17 | 1 commentaires | Partager sur WhatsApp
  • Une attaque de la chaîne d’approvisionnement a touché plus de 40 packages de l’écosystème NPM, dont le populaire @ctrl/tinycolor, avec injection d’un malware auto-propagateur capable d’infecter en cascade les secrets des environnements de développement jusqu’aux identifiants CI/CD. Les versions compromises ont été retirées de npm
  • La charge utile exécute de manière asynchrone un bundle Webpack (bundle.js, ~3.6MB) pendant l’installation npm, puis procède à une collecte étendue d’identifiants via les variables d’environnement, le système de fichiers et les SDK cloud
  • La logique malveillante utilise NpmModule.updatePackage pour patcher et republier de force d’autres packages, provoquant une propagation en cascade, et injecte un workflow shai-hulud dans GitHub Actions pour exfiltrer les secrets d’organisation via toJSON(secrets)
  • Les données collectées sont exfiltrées via la création du dépôt GitHub public Shai-Hulud, en se faisant passer pour une activité de développement légitime, ce qui augmente fortement la furtivité
  • L’opération agit discrètement en visant les tokens et les points de terminaison de métadonnées de AWS/GCP/Azure/NPM/GitHub, ainsi qu’en recherchant des secrets sur la base de TruffleHog
  • Il est demandé de retirer immédiatement les packages, nettoyer les dépôts, remplacer l’ensemble des identifiants, ainsi que de vérifier les journaux CloudTrail/GCP Audit, bloquer les webhooks et mettre en place des politiques de protection de branche / Secret Scanning / cooldown

Packages affectés

  • 195 packages/versions au total ont été signalés, notamment @ctrl/tinycolor(4.1.1, 4.1.2), de nombreux packages de l’espace de noms @ctrl/, la famille de modules @crowdstrike/, ainsi que ngx-bootstrap/ngx-toastr/ng2-file-upload/ngx-color à travers l’écosystème Angular/Web UI, la pile mobile @nativescript-community/ et @nstudio/, la toolchain sciences de la vie teselagen/, ainsi que ember-*, koa2-swagger-ui, pm2-gelf-json, wdio-web-reporter
  • Pour chaque package, il faut se référer au tableau de l’article source pour les versions exactes et croiser précisément leur présence dans vos projets
    • Exemples : @ctrl/ngx-emoji-mart 9.2.1, 9.2.2, @ctrl/qbittorrent 9.7.1, 9.7.2, ngx-bootstrap 18.1.4, 19.0.3–20.0.5, ng2-file-upload 7.0.2–9.0.1

Actions immédiates requises

Identifier et supprimer les packages compromis

  • Vérifier la présence de packages infectés dans le projet : npm ls @ctrl/tinycolor
  • Désinstaller immédiatement les packages compromis : npm uninstall @ctrl/tinycolor
  • Rechercher localement les traces connues via le hash de bundle.js : sha256sum | grep 46faab8a...

Nettoyer les dépôts infectés

  • Supprimer le workflow GitHub Actions malveillant : retirer .github/workflows/shai-hulud-workflow.yml
  • Détecter et supprimer la branche distante shai-hulud : git ls-remote ... | grep shai-hulud puis git push origin --delete shai-hulud

Faire tourner immédiatement tous les identifiants

  • Remplacement complet requis pour les tokens NPM, GitHub PAT / secrets GitHub Actions, clés SSH, identifiants AWS/GCP/Azure, chaînes de connexion DB, tokens tiers et secrets CI/CD
  • Rotation complète nécessaire, y compris pour les éléments stockés dans AWS Secrets Manager / GCP Secret Manager

Auditer l’infrastructure cloud pour détecter une compromission

  • AWS : dans CloudTrail, vérifier les horaires et motifs d’appels à BatchGetSecretValue, ListSecrets, GetSecretValue, et contrôler via le IAM Credential Report la création ou l’usage anormal de clés
  • GCP : dans les Audit Logs, vérifier les accès à Secret Manager et la présence éventuelle d’événements CreateServiceAccountKey

1 commentaires

 
GN⁺ 2025-09-17
Commentaire Hacker News
  • En tant qu’utilisateur de paquets hébergés sur npm, j’ai le sentiment qu’il n’est pas réaliste de surveiller moi-même toutes les dépendances, y compris les dépendances de mes dépendances, et comme je ne suis pas non plus expert TypeScript/JavaScript, je pense que je ne repérerais pas facilement le code malveillant dissimulé par un attaquant ; ces derniers temps, je réfléchis à une manière de mettre à jour en « mode différé », c’est-à-dire n’accepter que des versions qui ont déjà un certain âge au lieu de toujours prendre la toute dernière, en partant du principe que si un paquet est exposé publiquement depuis environ 6 semaines, il y a de fortes chances qu’un malware ait été découvert ; ce n’est pas une méthode parfaite, et j’aimerais qu’il existe un outil permettant de faire une exception pour appliquer immédiatement la dernière mise à jour en cas de problème de sécurité

    • La méthode est d’ailleurs mentionnée directement dans l’article : il existe une fonctionnalité appelée NPM Package Cooldown Check, qui fait automatiquement échouer le build lorsqu’une version de paquet publiée dans le délai défini par l’organisation (2 jours par défaut) est ajoutée à une pull request ; comme la majorité des attaques sur la supply chain sont détectées dans les 24 heures, même un très court délai d’attente peut réduire l’exposition au risque

    • Comme il est difficile d’inspecter l’ensemble des dépendances, j’aimerais défendre l’idée qu’il faut réduire autant que possible leur nombre et n’utiliser que des paquets connus et dignes de confiance ; à moins d’être dans un environnement suffisamment contrôlé pour faire confiance à tous les auteurs, conserver une certaine dose de « not-invented-here » reste au fond un choix raisonnable

    • J’ai l’habitude de fixer explicitement les versions dans package.json et d’utiliser npm ci pour n’installer que celles indiquées dans package-lock.json ; j’exécute npm audit en CI pour être alerté si des vulnérabilités apparaissent dans les paquets ; de cette manière, les paquets sont quasiment « gelés », et l’ancienneté des paquets elle-même réduit la probabilité d’infection

    • Pour ma part, je pousse encore plus loin et je ne mets à jour les dépendances que lorsqu’un bug affecte réellement mon environnement d’utilisation ; même en présence d’une faille de sécurité, si elle ne m’impacte pas, je passe mon tour ; la plupart des développeurs mettent leurs dépendances à jour bien trop souvent sans nécessité, alors qu’il vaudrait mieux ne le faire qu’en cas de vrai besoin ; si les mises à jour sont fréquentes ou compliquées, alors je n’utilise tout simplement pas ce paquet, ou je le « gèle » selon mes critères

    • Avec l’outil uv de Python, on peut limiter les mises à jour de manière similaire ; par exemple, une commande comme uv lock --exclude-newer $(date --iso -d "2 days ago") permet d’exclure les versions publiées dans les 2 derniers jours

  • Ce problème existe parce que les nouveaux paquets ou les nouvelles versions ne sont pas surveillés ; la meilleure approche serait de séparer, comme Debian, une distribution stable ne recevant que des correctifs de sécurité et de bugs, et des distributions testing/unstable surveillées par les mainteneurs des paquets ; tous ceux qui travaillent sur des dépôts centralisés de paquets open source (NPM, Python, Rust, etc.) sont confrontés au même problème

    • Il y a un problème de culture chez les développeurs ; avoir des centaines de dépendances (transitives) et les mettre à jour automatiquement sans réflexion fait partie du problème ; exposer ses environnements de build et d’exécution à autant de code tiers implique une responsabilité

    • Les distributions ressentent elles aussi de plus en plus le poids du nombre de paquets à maintenir ; c’est d’ailleurs en grande partie pour cela que les écosystèmes propres à chaque langage (CPAN, Maven, RubyGems, etc.) se sont développés ; les distributions Linux seules ont du mal à fournir toutes les applications souhaitées par les utilisateurs, d’où l’apparition de multiples canaux comme freshmeat, linuxbrew, flatpak, PPA, etc. ; je doute qu’une communauté quelconque ait les moyens de surveiller et supporter les multiples branches de tant de bibliothèques diverses

    • En tant que développeur Debian, il devient de plus en plus difficile de repérer les changements réels en amont à cause du « bruit » croissant (en particulier les simples changements de style ou les mises à jour d’outillage) ; j’aimerais que ce type de changements soit évité, sauf s’il s’agit de refactorings nécessitant réellement une revue humaine, de corrections de bugs, d’ajouts de fonctionnalités, ou de résultats d’outils permettant d’identifier du code potentiellement problématique

    • En Rust, il existe un système appelé cargo vet, auquel participent des entreprises comme Google et Mozilla pour partager et vérifier automatiquement des paquets

    • Je pense qu’il existe des moyens de garder une certaine décentralisation tout en ajoutant quelques garde-fous ; par exemple, imposer pour les paquets d’une certaine taille une double approbation depuis deux comptes avec 2FA, ou n’autoriser l’upload sur npm des paquets populaires que via un système de builds reproductibles ; cela ne reviendrait pas à abandonner complètement la décentralisation, seulement à demander un petit effort supplémentaire sur les gros projets

  • À cause des attaques répétées sur la supply chain ces derniers temps, je réfléchis beaucoup plus sérieusement au server-side rendering (sans JavaScript) ; grâce à HTMX, j’ai réalisé qu’on peut aller vraiment très loin sans JavaScript, et l’application pourrait même être plus rapide et plus stable de cette façon

    • Je voudrais souligner que l’environnement JS traditionnel est en réalité le sandbox le plus sûr ; depuis presque 30 ans, du code JS non fiable s’exécute sur des milliards d’appareils, et les attaques de grande ampleur réussies contre les moteurs de navigateur se comptent sur les doigts d’une main ; en revanche, l’environnement NodeJS et npm a besoin d’une refonte complète du point de vue de la sécurité ; des épisodes comme leftpad viennent de cette culture qui consiste à publier sur npm même de simples snippets de code

    • Je trouve étrange que ce type d’attaque soit automatiquement ramené à un problème propre à un environnement particulier (JavaScript) ; en réalité, le plus gros problème est peut-être que même les mesures de sécurité déjà disponibles sur npm ne sont pas du tout appliquées dans d’autres environnements (PyPI, Crates, etc.)

    • Le vendoring peut réduire l’exposition, mais ce n’est pas une solution au problème de fond ; si NPM prenait vraiment la sécurité au sérieux, il faudrait rendre obligatoires la 2FA pour la publication, un scan préalable des paquets, et même une signature par clé matérielle ; semver ou CRC ne suffisent pas ; tout cela devrait être intégré nativement au système de gestion de paquets

    • En réalité, ce n’est pas un problème propre à JavaScript, mais au fait que les développeurs ne surveillent pas suffisamment les nouvelles dépendances qu’ils ajoutent ; cela pourrait tout aussi bien s’appliquer à d’autres écosystèmes comme Rust ou Go

    • Tous les langages qui dépendent fortement d’un package manager et disposent d’une bibliothèque standard pauvre sont vulnérables de la même manière ; à long terme, je me dis qu’il faudrait peut-être revenir à davantage de JavaScript vanilla ; Rust présente la même forte dépendance aux paquets ; au contraire, Go est un cas exemplaire sur ce point

  • Je pense qu’il faut un système léger permettant de suivre du code signé sur les commits/releases avec des clés de confiance, puis de l’installer et de le vérifier ; il existe déjà un mécanisme de provenance npm avec sigstore, mais cela ne semble pas encore largement utilisé et paraît limité pour l’instant à la validation de l’éditeur

  • Dès 2016, cette vulnérabilité avait déjà été signalée à NPM (avis CERT), mais la réponse de NPM avait été WAI (working as intended)

    • Pour ceux qui ne savent pas ce que signifie WAI : c’est généralement l’abréviation de « working as intended »

    • Même en l’absence totale de script postinstall, il semble qu’il suffise au final d’importer le module pendant le processus de build, au démarrage du serveur ou lors des tests pour exécuter le malware ; de toute façon, après npm install, il y a forcément un moment où quelque chose sera effectivement exécuté...

  • Cela me rappelle un commentaire vu ici lors de l’affaire left-pad : il était question d’un mainteneur npm renommé avec 600 paquets npm et 1 200 lignes de code JavaScript ; un exemple que j’aimerais citer est esbuild, qui n’a quasiment pas de dépendances externes et utilise uniquement la bibliothèque standard de Go
    Même parmi les autres projets dits « next-gen », quand on regarde la chaîne de dépendances, biomejs et swc en ont aussi relativement peu ; mais si l’on examine le code source Rust original, biomejs et swc ont en fait de nombreuses dépendances ; je m’attends à ce que l’écosystème cargo suive le même chemin à mesure que ce type de projets se répand ; si quelqu’un connaît un gros projet écrit avec une discipline stricte comme esbuild, je suis preneur

    • L’une des raisons pour lesquelles je suis passé à Go est justement la tendance des bibliothèques purego ; en général, elles ne dépendent que de la bibliothèque standard et de golang.org/x, et peuvent être compilées sans CGO, ce qui leur donne une excellente portabilité ; go mod vendor permet de gérer le risque à court terme, mais ce n’est pas une solution de fond ; Go reste lui aussi vulnérable, faute de vérification des paquets de bout en bout (signatures / vérification de clés, etc.) ; l’attention se concentre beaucoup sur l’infrastructure CI/CD, mais si l’on pouvait build et déployer sans transmettre de clés de signature, la sécurité pourrait aussi y gagner ; je pense que les package managers devraient encourager les signatures GPG, et que les commits git devraient eux aussi être signés avant diffusion

    • Le cas d’eslint est particulièrement frustrant, si l’on regarde son graphe de dépendances ; si les mainteneurs ne font pas de la réduction des dépendances une priorité, il ne reste plus qu’à migrer vers une autre solution comme oxlint

    • La solution consiste à implémenter soi-même les fonctionnalités simples et à réduire les dépendances externes ; en pratique, rien que cela permet souvent de supprimer les deux tiers des dépendances ; surtout pour quelque chose de simple comme left-pad, le faire soi-même et le garder sous contrôle avec de petites unités et des tests n’ajoute pas une charge de maintenance énorme ; il faut éliminer sans hésiter les dépendances inutiles

    • Ce qui apparaît dans le Cargo.toml racine d’un projet Rust concerne tout l’espace de travail ; les dépendances réelles de chaque crate sont bien plus modestes ; il faut regarder plus en profondeur pour comprendre la vraie structure des dépendances

    • L’inconvénient, c’est qu’il faut maintenant savoir lire aussi du Golang pour auditer des projets JavaScript ; et comme un node install.js est de nouveau exécuté en post-installation, au final il ne reste qu’à faire une confiance totale ou à lire tout le code

  • Je n’arrive pas à croire que npm exécute encore par défaut les scripts postinstall de toutes les dépendances ; Pnpm ou Bun ne les exécutent que si elles figurent dans une liste d’autorisation, et Composer n’exécute tout simplement pas de scripts de cycle de vie pour les dépendances ; compte tenu du risque que représentent les paquets de dépendance dans les environnements de build ou de développement, cette approche me paraît plus sûre

    • Je me demande pourquoi ce genre d’attaque à grande échelle semble beaucoup plus rare avec d’autres package managers (par exemple le build.rs de Rust, Python, Java, etc.) ; pourtant, au-delà de postinstall, c’est en principe possible dans quasiment tous les écosystèmes, mais les incidents semblent se concentrer surtout autour de npm

    • J’ai vu que Pnpm a changé sa valeur par défaut pour bloquer les scripts ; je serais curieux de connaître les retours de la communauté (sur l’expérience utilisateur quand il faut autoriser des scripts, sur l’abus de la commande allow, etc.), et la communauté du packaging Python discute aussi de sujets similaires autour des variantes de wheels ; j’aimerais m’inspirer de l’expérience d’autres écosystèmes

  • Cette attaque s’est désormais propagée à plus de 180 paquets ; voir le blog d’Aikido Security

  • Je me demande qui a été le premier à découvrir cette attaque ; il est intéressant de voir que les blogs attribuent le mérite de manière différente ; Aikido dit « nous avons découvert une attaque à grande échelle », et Socket, Ox, Safety, Phoenix, Semgrep, etc. la présentent chacun à leur façon

    • Je suis Mackenzie d’Aikido ; la première personne à avoir signalé l’affaire est le développeur Daniel Pereira, qui l’a transmise à Socket, et Socket a été le premier à analyser les 40 premiers paquets et le malware ; ensuite, Aikido a identifié 147 paquets supplémentaires ainsi que le paquet Crowdstrike ; en réalité, c’est Step qui a compris en premier que le malware était un ver auto-propagateur ; il est intéressant de voir plusieurs organisations jouer indépendamment des rôles différents

    • Il semble que plusieurs développeurs l’aient découvert à peu près au même moment, et Step comme Socket citent chacun des personnes différentes ; au final, les éditeurs de sécurité du secteur l’ont détecté chacun par leurs propres moyens, qu’il s’agisse d’analyse de code par IA (Socket, Aikido) ou de monitoring de pipeline eBPF (Step)

    • Si autant d’éditeurs l’ont détecté indépendamment, on peut se demander pourquoi ils ne partagent pas directement cette technologie avec npm pour bloquer l’enregistrement même des paquets malveillants ; sinon, ils ne pourraient plus vendre de système d’alerte précoce, ce qui explique peut-être qu’ils ne le fassent pas

    • L’article de l’OP cite textuellement : « @franky47 a découvert ce phénomène puis l’a signalé rapidement à la communauté via une issue GitHub »

  • Je trouve le nom donné par l’attaquant, « Shai Hulud », assez bien trouvé : donner à un véritable malware de type ver le nom d’un ver géant ; même le bundle.js principal fait 3,6 Mo, et jusqu’aux variantes du malware, tout devient énorme de façon très npm

    • J’ai le pressentiment qu’un jour une attaque de supply chain finira aussi par en attirer accidentellement une autre

    • Les malwares aussi suivent la loi de Moore : le virus tequila de 1991 faisait 2,6 Ko, aujourd’hui on est en plusieurs Mo