Retour
Dépendances & supply chain5 août 2026 · 6 min de lecture

ChainDrop : un ver npm a publié 444 paquets piégés avec des signatures valides

par detektd team

Chaîne rouillée en gros plan au-dessus d'une grille métallique
Photo : Abhinav fr · CC0 · StockSnap

Le 4 août 2026, plusieurs équipes de recherche, dont Microsoft Threat Intelligence et Elastic Security Labs, ont identifié une attaque de chaîne d'approvisionnement de grande ampleur sur npm. Baptisée ChainDrop, elle utilise une nouvelle variante de Shai-Hulud, le ver voleur d'identifiants apparu sur npm en septembre 2025. Bilan provisoire : 444 paquets compromis selon plusieurs équipes (« plus de 400 » selon Microsoft), issus de plusieurs éditeurs sans lien entre eux, et cumulant environ 2 milliards de téléchargements mensuels.

0

paquets npm compromis, pour environ 2 milliards de téléchargements par mois (août 2026)

Passer par la porte d'entrée

Les attaquants n'ont pas commencé par voler un jeton npm. Ils ont compromis le compte GitHub d'un mainteneur de plusieurs utilitaires Node.js très utilisés (keyv, cacheable, flat-cache, file-entry-cache), puis poussé du code directement sur la branche principale. Ils ont ensuite laissé les pipelines GitHub Actions du mainteneur faire le reste : construire le paquet et le publier sur le registre npm, comme pour n'importe quelle release.

Le choix des cibles n'a rien d'anodin. Presque personne n'installe flat-cache ou file-entry-cache directement : ce sont des dépendances profondes, tirées par des outils omniprésents ; file-entry-cache est par exemple une dépendance d'ESLint. Compromettre un seul de ces paquets, c'est atteindre des millions de projets qui ne savent même pas qu'ils l'utilisent.

Compte GitHub du mainteneur compromis
Pipeline CI légitime déclenché
Versions signées publiées sur npm
npm install exécute le script et vole les identifiants

Une fois le paquet installé, un script lancé automatiquement à l'installation déclenchait la charge utile : un voleur d'identifiants très obfusqué, visant en priorité les environnements d'intégration continue, où les jetons de publication sont disponibles. Chaque jeton récupéré servait ensuite à infecter les autres paquets sur lesquels la victime avait des droits de publication. D'où le caractère auto-propagateur de l'attaque, et sa vitesse : plusieurs analyses font état de centaines de paquets touchés en quelques heures.

Pourquoi les signatures n'ont rien empêché

C'est le point le plus contre-intuitif de l'attaque. Depuis plusieurs années, l'écosystème npm pousse la « provenance » : une attestation cryptographique qui prouve qu'un paquet a été construit à partir d'un dépôt précis, par un workflow précis. C'est une vraie avancée contre les jetons volés utilisés depuis la machine d'un attaquant. Mais ici, les versions piégées venaient bien du bon dépôt, du bon workflow, du bon compte. Les attestations étaient donc parfaitement valides.

Une signature prouve qui a publié un paquet. Elle ne dit rien de ce qu'il contient.

Un an de vers sur npm

ChainDrop est le dernier épisode d'une série qui a commencé à l'automne 2025. Le 8 septembre 2025, un mainteneur très suivi a été piégé par un email d'hameçonnage envoyé depuis le faux domaine npmjs.help, qui imitait une procédure de réinitialisation de la double authentification. En quelques minutes, 18 paquets, dont chalk et debug qui totalisent plus de 2 milliards de téléchargements hebdomadaires, ont été republiés avec du code qui détournait les transactions de cryptomonnaies dans le navigateur. Les versions ont été retirées en environ deux heures.

Une semaine plus tard, GitHub était alerté de la première vague de Shai-Hulud : le premier ver auto-réplicant documenté sur npm, qui volait les jetons des mainteneurs pour infecter leurs autres paquets. GitHub a retiré plus de 500 paquets compromis, puis publié le 23 septembre 2025 un plan de durcissement : double authentification obligatoire pour la publication locale, jetons limités à sept jours, généralisation de la publication de confiance (trusted publishing) et abandon progressif des anciens jetons.

ChainDrop montre la limite de ces mesures, qui étaient nécessaires : elles rendent plus difficile la publication depuis l'extérieur. Elles ne protègent pas contre un attaquant qui passe par le pipeline légitime lui-même. Le verrou s'est déplacé du registre npm vers les comptes GitHub des mainteneurs et la protection de leurs branches.

Anatomie d'une installation npm

Pour comprendre pourquoi un simple npm install suffit, il faut regarder ce que fait réellement la commande. Pour chaque paquet de l'arbre de dépendances (pas seulement ceux déclarés dans le package.json, mais tous les paquets transitifs), npm exécute les scripts de cycle de vie déclarés : preinstall, install, puis postinstall, plus prepare pour les dépendances installées depuis Git. Ces scripts tournent avec les droits de l'utilisateur et héritent de son environnement. Sur un poste, cela veut dire l'accès aux fichiers de configuration (~/.npmrc, ~/.aws, ~/.ssh) ; sur un runner CI, aux secrets exposés comme variables d'environnement.

Le lockfile, souvent présenté comme une protection, en offre une plus étroite qu'on ne le croit. Chaque entrée de package-lock.json contient un champ integrity, un hash SHA-512 de l'archive attendue. Il garantit qu'une version donnée n'a pas été modifiée après coup. Il ne dit rien d'une nouvelle version : si une mise à jour fait passer le lockfile à une version piégée, le hash de cette version piégée y est enregistré en toute bonne foi.

Une entrée de package-lock.json
"node_modules/keyv": {
  "version": "x.y.z",
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-x.y.z.tgz",
  "integrity": "sha512-..."   // proves THIS tarball wasn't altered, not that it's safe
}

La provenance, enfin, est une attestation signée via Sigstore qui relie un paquet publié au dépôt, au commit et au fichier de workflow qui l'ont construit. La commande npm audit signatures vérifie les signatures du registre et ces attestations pour les paquets installés. C'est une excellente défense contre un jeton volé utilisé depuis l'ordinateur d'un attaquant : la version publiée n'aurait pas de provenance, ou une provenance incohérente. Contre ChainDrop, elle ne pouvait rien : le dépôt, le commit et le workflow étaient bien les bons : c'est leur contenu qui était malveillant.

Ce qui aurait limité les dégâts

Puisque la signature ne suffit pas, la défense se joue côté consommateur, avant et pendant l'installation. La première ligne de défense est de n'installer que des versions connues, et de ne pas exécuter leurs scripts d'installation par défaut :

Installer depuis le lockfile, sans scripts d'installation
# CI: exact versions from package-lock.json, no install-time scripts
npm ci --ignore-scripts

# or make it the default for the project (.npmrc)
ignore-scripts=true

Les utilisateurs de pnpm partent avec un avantage : depuis pnpm 10, les scripts d'installation des dépendances sont bloqués par défaut, et seuls les paquets explicitement autorisés peuvent en exécuter. C'est exactement le comportement qui neutralise ce type de charge utile.

pnpm-workspace.yaml : seuls ces paquets peuvent exécuter un script
onlyBuiltDependencies:
  - esbuild   # native binary, genuinely needs its postinstall
  - sharp
  • Committer le lockfile et installer avec npm ci : une version piégée n'entre pas tant que personne ne met à jour volontairement.
  • N'autoriser les scripts d'installation que pour les paquets qui en ont réellement besoin (compilation native, par exemple).
  • Ne jamais exposer de jeton de publication aux jobs CI qui installent des dépendances : séparer build et publication.
  • Côté mainteneur : protéger la branche principale (revue obligatoire, pas de push direct) et exiger une clé de sécurité matérielle sur le compte GitHub.

Si un paquet touché a été installé

Retirer le paquet ne suffit pas : le script a déjà pu s'exécuter. Il faut faire tourner tous les secrets accessibles depuis la machine ou le runner concerné : jetons GitHub et npm, clés cloud, variables d'environnement de CI.

Côté application, la conséquence la plus fréquente d'une compromission de ce type est la fuite d'un secret qui finit, plus tard, exposé dans un bundle ou une configuration. C'est pour cela que detektd vérifie à chaque scan les dépendances visibles côté client et leurs versions, ainsi que les clés et jetons présents dans le JavaScript servi aux visiteurs.

ChainDrop : un ver npm a publié 444 paquets piégés avec des signatures valides | detektd