Retour
Dépendances & supply chain28 juil. 2026 · 6 min de lecture

Axios, trois heures de compromission : ce que le post-mortem apprend à tous les projets JavaScript

par detektd team

Amas de vieilles clés métalliques en gros plan
Photo : Leeroy · CC0 · StockSnap

Quatre mois après, l'incident axios reste le cas de référence de l'année pour la chaîne d'approvisionnement JavaScript. Le 31 mars 2026, un attaquant a pris le contrôle du compte npm du mainteneur principal et publié deux versions malveillantes : axios@1.14.1 et axios@0.30.4. Axios est un client HTTP présent dans une part considérable de l'écosystème : plus de 100 millions de téléchargements hebdomadaires, et environ 174 000 paquets qui en dépendent.

0

paquets npm dépendant d'axios au moment de la compromission (mars 2026)

Le choix des deux numéros de version n'est pas un hasard. 1.14.1 vise la branche actuelle ; 0.30.4 vise l'ancienne branche 0.x, encore utilisée par de nombreux projets qui n'ont jamais migré. En publiant sur les deux lignes à la fois, l'attaquant maximisait le nombre de projets dont la plage de versions accepterait automatiquement la nouvelle release.

Une dépendance fantôme

Le code d'axios lui-même n'a pas été modifié de façon visible. Les versions piégées ajoutaient simplement une dépendance : plain-crypto-js, un typosquat du paquet légitime crypto-js. Axios n'importait jamais ce paquet ; il n'avait aucune utilité fonctionnelle. Son seul rôle était d'être installé et, à l'installation, de déployer un cheval de Troie d'accès à distance (RAT) sur macOS, Windows et Linux.

Cette technique de la « dépendance fantôme » est redoutable parce qu'elle contourne la relecture de code la plus attentive : un audit du code source d'axios n'aurait rien trouvé. La seule anomalie visible était une ligne de plus dans le package.json : une nouvelle dépendance, jamais utilisée par le code.

Faux paquet publié ~18 h avant
Compte npm du mainteneur détourné
axios publié avec la nouvelle dépendance
npm install déploie le cheval de Troie

Une opération préparée

Les analyses publiées par Datadog, StepSecurity et Snyk convergent sur la chronologie. L'opération a été préparée sur environ 18 heures. La dépendance malveillante a été publiée sur npm bien avant les versions d'axios, pour qu'au moment de l'attaque elle ne déclenche pas les alertes qui visent les paquets tout juste créés. Les versions piégées d'axios sont ensuite restées disponibles environ trois heures, de 00 h 21 à 03 h 25 UTC, avant que npm ne les retire.

Trois heures, c'est court à l'échelle humaine et très long à l'échelle d'un registre. Chaque pipeline CI qui a résolu une plage comme ^1.14.0 sans lockfile pendant cette fenêtre a installé la version piégée. Chaque développeur qui a lancé une installation fraîche, aussi. Et l'heure n'avait probablement rien d'anodin : en pleine nuit en Europe, en soirée aux États-Unis, au moment où le moins de gens surveillent.

Pourquoi 0.30.4 : la mécanique des plages semver

Le choix des numéros de version devient limpide quand on regarde comment npm résout les plages. L'accent circonflexe (^), utilisé par défaut quand on installe un paquet, autorise toutes les mises à jour qui ne changent pas le premier chiffre non nul. Pour une version 1.x, cela couvre toute la branche 1. Mais pour une version 0.x, le premier chiffre non nul est le deuxième : ^0.30.0 n'accepte que les 0.30.x. Pour atteindre les projets restés sur la branche 0.30, il fallait donc publier précisément une 0.30.x, ce qu'a fait l'attaquant.

Ce qu'acceptait chaque plage le 31 mars
^1.14.0  = >=1.14.0 <2.0.0    -> resolves to 1.14.1 (poisoned)
^0.30.0  = >=0.30.0 <0.31.0   -> resolves to 0.30.4 (poisoned)
~1.14.0  = >=1.14.0 <1.15.0   -> resolves to 1.14.1 (poisoned)
1.14.0   = exactly 1.14.0     -> unaffected

La plage n'est dangereuse qu'au moment où elle est résolue. C'est toute la différence entre npm install et npm ci. La première résout les plages du package.json et peut réécrire le lockfile si celui-ci est absent ou désynchronisé. La seconde installe exactement ce que dit le lockfile, échoue s'il ne correspond pas au package.json, et ne l'écrit jamais. Un pipeline CI qui utilise npm ci avec un lockfile committé n'a pas pu installer 1.14.1 pendant ces trois heures ; un pipeline qui lance npm install sur un dépôt sans lockfile l'a fait.

Le vrai point d'entrée : le mainteneur

Selon le post-mortem publié par le projet sur GitHub, l'accès au compte n'est pas venu d'une faille de npm. Le mainteneur principal a été visé par une campagne d'ingénierie sociale ciblée, et sa machine a été compromise par un logiciel malveillant, qui a donné accès à ses identifiants npm. C'est un point important : quand la machine d'un mainteneur est compromise, la double authentification ne protège plus grand-chose, puisque l'attaquant agit depuis une session déjà ouverte.

Le schéma n'est pas nouveau. En septembre 2025, le mainteneur de chalk et debug avait été piégé par un email imitant npm, envoyé depuis le faux domaine npmjs.help, qui lui avait soutiré identifiant, mot de passe et code de double authentification. Dans les deux cas, la cible n'était pas le code, mais la personne qui a le droit de le publier.

Vérifier si vous avez été touché

La vérification est simple : chercher, dans chaque projet et chaque lockfile, les deux versions piégées ou la dépendance fantôme. La commande npm ls affiche l'arbre des dépendances installées, y compris transitives :

Chercher les versions piégées dans un projet
npm ls axios plain-crypto-js --all

# or straight in the lockfile
grep -n "plain-crypto-js" package-lock.json

Si la réponse est oui

Mettre à jour axios ne suffit pas : un cheval de Troie d'accès à distance a pu s'installer sur la machine. Il faut traiter la machine ou le runner comme compromis, le réinstaller, et faire tourner tous les secrets qui y étaient accessibles.

La leçon : ne pas installer ce qui vient de sortir

Les versions piégées ont été détectées et retirées en trois heures. Un délai minimal avant d'accepter toute nouvelle version, même d'un seul jour, aurait donc suffi à ne jamais les installer. pnpm le propose nativement :

pnpm-workspace.yaml : refuser les versions de moins de 24 h
# in minutes: a version must be at least a day old to be installed
minimumReleaseAge: 1440
  • Committer le lockfile : une plage ^ n'est alors résolue qu'au moment d'une mise à jour volontaire, pas à chaque build.
  • Surveiller l'apparition de nouvelles dépendances lors d'une mise à jour : c'est précisément ce qu'ajoutait la version piégée.
  • Ne pas fusionner automatiquement les mises à jour proposées par Dependabot ou Renovate dans les heures qui suivent une release.

Repérer une dépendance fantôme avant de l'installer est possible, à condition de regarder ce qui change réellement entre deux versions. La commande npm diff compare deux versions publiées d'un paquet, fichier par fichier, sans rien installer. Appliquée au seul package.json, elle fait apparaître immédiatement une dépendance ajoutée, le signal exact qu'aurait donné axios@1.14.1 :

Comparer deux versions avant d'accepter une mise à jour
npm diff --diff=<package>@<current> --diff=<package>@<candidate> package.json

# a new entry under "dependencies" that the code never imports is a red flag

Côté mainteneurs

Pour ceux qui publient des paquets, la leçon est de réduire au maximum ce qu'un poste compromis permet de faire. npm permet d'exiger la double authentification et d'interdire les jetons pour la publication d'un paquet. Et la publication de confiance (trusted publishing), qui publie depuis la CI avec des identifiants éphémères, retire complètement la possibilité de publier depuis un ordinateur personnel, à condition de protéger ensuite, à leur tour, le dépôt et ses workflows.

Axios, trois heures de compromission : ce que le post-mortem apprend à tous les projets JavaScript | detektd