Les dépendances transitives de npm et le risque que vous n'avez jamais choisi

Les correctifs php2shell (CVE-2026-109618 et CVE-2026-109619) pour les versions de PHP en fin de vie sont désormais disponibles. Les réponses du service commercial étant plus lentes que d'habitude en raison d'une forte demande, le moyen le plus rapide d'obtenir les paquets corrigés est d'acheter ELS for Runtimes en ligne (s'ouvre dans un nouvel onglet). Consulter la FAQ sur les correctifs

Accédez directement au résumé généré par l'IA de cet article de blog

  • Résumez cet article avec Perplexity
  • Résume cet article avec Gemini

Principaux enseignements

  • Une dépendance transitive est un paquet que vous installez parce qu'un autre paquet dont vous dépendez en a besoin.
  • Un seul paquet npm peut entraîner des centaines de dépendances transitives, chacune étant capable d'exécuter du code en utilisant vos droits d'accès.
  • D'autres responsables définissent ces plages au plus profond de votre arborescence, de sorte que les correctifs et les attaques surviennent selon leur propre calendrier.
  • La protection doit s'appliquer au niveau du registre pour couvrir les paquets que votre équipe n'a jamais examinés.

Essayez cette méthode sur n'importe quel projet sur lequel vous travaillez depuis plus de quelques mois. Ouvrez package.json et comptez les dépendances. Ensuite, exécutez npm ls --all et observez tout ce qui défile à l'écran.

La liste figurant dans package.json se compte généralement par dizaines. L'arborescence est bien plus longue, même si npm marque les entrées en double comme « dédupliquées ». Tout ce qui figure dans cette arborescence et que vous n'avez pas répertorié vous-même est une dépendance transitive, et c'est la partie de votre pile qui retient le moins l'attention alors qu'elle effectue une grande partie du travail.

Qu'est-ce qu'une dépendance transitive, au juste ?

Une dépendance directe est celle que vous avez demandée. Vous avez exécuté npm install express, il a atterri à package.json, et un membre de votre équipe a pris une décision à ce sujet.

Une dépendance transitive, c’est une dépendance que vous n’avez pas demandée. Express a besoin de ses propres paquets, ces paquets ont besoin des leurs, et npm gère toute la chaîne pour vous. Rien de tout cela n’apparaît dans le fichier package.json, mais tout cela est consigné dans votre fichier de verrouillage, et tout ce qui est effectivement installé se retrouve dans node_modules. Tout élément qui s'exécute, que ce soit pendant l'installation ou lorsque votre application le charge, bénéficie des mêmes droits d'accès que le processus qui l'exécute. Sur votre ordinateur portable, cela correspond à votre compte utilisateur, et dans l'environnement de CI, cela correspond au « runner » et à tous les secrets auxquels il a accès.

Il est utile de donner un nom à ce phénomène : appelons-le « risque hérité ». Le risque hérité correspond à l'exposition à laquelle votre projet est soumis en raison des bibliothèques fournies avec celles que vous avez choisies. Vous ne les avez pas évaluées, vous n'avez pas choisi leurs versions et, dans la plupart des cas, vous n'avez même pas pu les identifier. Pourtant, vous les intégrez à votre produit.

Pourquoi cet arbre devient-il si grand ?

Cela s'explique en partie par la manière dont l'écosystème JavaScript s'est développé. npm a facilité la publication de petits paquets à usage unique, et cette tendance s'est généralisée. Un utilitaire qui fait bien une seule chose se retrouve intégré dans une centaine d'autres paquets, et chacun d'entre eux apporte à son tour ses propres petits outils auxiliaires. Comme l'indique la page « SecureChain for npm » de TuxCare, un paquet npm de niveau supérieur peut entraîner des centaines de dépendances transitives.

L'autre aspect concerne la manière dont npm organise node_modules. Partout où c'est possible, npm des palans hissent des colis dans l'arbre La plupart d'entre eux sont donc affichés côte à côte vers le haut. C'est idéal en termes d'espace disque, mais très peu intuitif. Faites défiler la page node_modules et rien ne vous indique qu'un paquet se trouve à quatre niveaux de profondeur et qu'il n'est là que parce qu'un élément dont vous n'avez jamais entendu parler l'a demandé.

Si vous souhaitez voir les calques, npm explain <package> vous indiquera tous les chemins menant à un paquet donné. Lancez-le sur un élément choisi au hasard dans votre fichier de verrouillage et ne soyez pas surpris si plusieurs chaînes y mènent.

Vous ne possédez pas ces plages de versions

C'est là que beaucoup de gens se trompent. Lorsque vous ajoutez une dépendance, vous choisissez sa plage de versions. Lorsque cette dépendance en ajoute une à son tour, c'est son responsable qui choisit la plage, et vous la reprenez telle quelle.

La plupart de ces plages utilisent un signe « caret », comme par exemple : ^4.17.0, ce qui semver considère que n'importe quelle version 4.x à partir de la 4.17.0. Votre fichier de verrouillage indique précisément les versions qui ont fonctionné la dernière fois ; c'est pourquoi il faut valider ces modifications et utiliser npm ci en matière de CI, c'est très important. Mais dès que quelqu'un supprime puis recrée le fichier de verrouillage, lance une mise à jour ou effectue une installation dans un projet qui n'en dispose pas, les plages de l'arborescence peuvent se résoudre à la version la plus récente autorisée, y compris celle qui a été publiée il y a une heure.

C'est pratique quand un responsable publie un correctif. En revanche, cela pose problème lorsqu'il publie autre chose.

Les pirates l'ont compris depuis longtemps. En 2018 déjà, un nouveau responsable de la maintenance du célèbre event-stream colis a ajouté une dépendance appelée flatmap-stream, qui s'est avérée contenir du code destiné à dérober des fonds à partir d'une application spécifique de portefeuille Bitcoin. Aucun utilisateur de event-stream avait choisi flatmap-stream. Ça vient d'arriver.

La même manœuvre s'est reproduite en juin 2026. Lorsque les pirates ont obtenu un accès en écriture à l'espace de noms npm de Mastra, ils ont publié plus de 140 versions de paquets compromises en moins de deux heures, selon les informations publiées par The Hacker News et Prise. Le code de Mastra lui-même n'a pas été modifié. Ce qui a changé, c'est la liste des dépendances, qui comprend désormais easy-day-js, un sosie du dayjs bibliothèque de dates. Celui qui contrôlait easy-day-js avait publié la veille une version 1.11.21 propre et fonctionnelle, puis avait mis en ligne la version 1.11.22, comportant un hook post-installation malveillant, tôt dans la matinée du 17 juin, peu avant la publication des versions de Mastra. Les nouvelles versions de Mastra demandaient ^1.11.21, de sorte que chaque nouvelle installation aboutissait directement à la version corrompue. Si vous aviez examiné l'une de ces versions, le seul véritable changement que vous auriez constaté dans package.json Il ne s'agissait que d'une seule nouvelle dépendance. Tout ce qui présentait un danger se trouvait un niveau plus bas.

Pourquoi la correction d'une vulnérabilité profonde prend-elle autant de temps ?

Les vulnérabilités connues s'accumulent également au plus profond de l'arborescence. Lorsque Endor Labs a analysé des paquets Java largement utilisés dans le cadre de son rapport « State of Dependency Management » de 2022, il a constaté qu'environ 95 % des dépendances vulnérables étaient transitives. Il s'agissait de paquets Maven ; ce chiffre doit donc être considéré comme une indication de l'ordre de grandeur pour npm plutôt que comme un chiffre propre à npm.

Imaginons qu’un CVE touche un paquet situé trois niveaux en dessous de celui que vous avez choisi. Le correctif est publié en amont et, dans un monde idéal, votre arborescence l’intègre automatiquement. En pratique, cela ne fonctionne que si tous les paquets situés entre vous et le correctif autorisent la nouvelle version. Si un paquet parent de la chaîne a verrouillé une version précise ou limité sa plage en dessous de la version corrigée, la résolution normale s’arrête là. Vous devez alors attendre que ce responsable publie une nouvelle version, et peut-être aussi que le responsable situé au-dessus de lui fasse de même.

C'est pour cette raison que npm audit Cela peut être très frustrant. Cet outil est très efficace pour vous signaler qu'un problème se cache quelque part au cœur de l'arborescence. En revanche, il est bien moins efficace pour résoudre le problème, car la solution ne dépend souvent pas de vous. npm audit fix n'appliquera pas de changement majeur selon la norme SemVer sans --force, et --force C'est une méthode assez brutale pour identifier les parties de votre application qui présentent des dysfonctionnements.

Et puis il y a remplacements, ce qui npm a été ajouté dans la version 8.3. Une substitution vous permet d'imposer une version spécifique d'un paquet transitif, ou d'y substituer une branche dérivée, indépendamment de ce qu'a demandé son paquet parent. Cela peut éliminer la vulnérabilité. Veillez simplement à indiquer clairement ce que vous avez fait. Vous utilisez désormais une combinaison de paquets que le responsable du paquet parent n'a probablement jamais testée, et la substitution a tendance à rester dans votre package.json bien après que personne ne se souvienne plus pourquoi il est là.

Le responsable de la maintenance qui ne reviendra pas

Tout cela suppose qu'un contributeur en amont continue de publier des versions. Au cœur d'une arborescence complexe, ce n'est pas toujours le cas.

De nombreux paquets situés au bas d’une arborescence sont de petite taille et pratiquement finalisés, et certains de leurs responsables ont quitté le projet il y a des années. Lorsqu’une vulnérabilité CVE est détectée dans l’un d’entre eux, personne n’est là pour mettre en place le correctif, et il n’y a donc rien à propager vers le haut. Le scanner continue de la signaler, et il n’existe aucun correctif en amont vers lequel une redirection pourrait pointer. Il ne reste alors plus qu’à créer un fork et à appliquer le correctif soi-même, à trouver quelqu’un qui acceptera de rétroporter un correctif, ou à remplacer le paquet de niveau supérieur qui en dépend. Aucune de ces solutions n’est rapide, et la dernière peut se transformer en une réécriture pour laquelle personne n’a le temps.

C'est précisément pour cela que le service « Endless Lifecycle Support » (ELS) de TuxCare a été conçu : pour assurer la rétroportabilité. Pour les paquets qu'il prend en charge, l'ELS rétroporte les correctifs de sécurité vers les versions exactes que vous utilisez déjà, une fois que la maintenance en amont a pris fin, ce qui vous permet de conserver votre configuration actuelle sans être contraint de procéder à une mise à niveau.

Protéger l'arbre dans son ensemble

La plupart des corrections mentionnées ci-dessus s'effectuent au sein de votre projet, un paquet à la fois, et généralement après que le problème a déjà été intégré à votre arborescence. Les remplacements, les corrections d'audit et les ancrages manuels sont autant de moyens de corriger un élément provenant de l'amont.

L'alternative consiste à modifier la provenance de l'arborescence dans son ensemble. C'est le principe sur lequel repose SecureChain. Ce service remplace le registre public npm par un référentiel contrôlé à partir duquel vos installations sont résolues, y compris les paquets transitifs. Les paquets sont recompilés à partir de sources vérifiées dans des environnements sécurisés et isolés, puis analysés à la recherche de logiciels malveillants connus et de modèles de code suspects ; tout élément ne passant pas l'inspection est bloqué avant d'atteindre votre environnement. Si un élément dont votre arborescence a besoin ne figure pas encore dans le catalogue, TuxCare l'ajoute. Les paquets actifs sont corrigés dans le cadre du SLA à mesure que de nouvelles vulnérabilités CVE sont publiées, et lorsqu’un paquet géré par SecureChain arrive en fin de vie, ELS assure la continuité de la couverture sans que vous ayez quoi que ce soit à faire.

Une fois que vous avez configuré npm ou votre gestionnaire de référentiels pour qu’il y accède, votre travail quotidien ne change pas. npm, Yarn et pnpm conservent les mêmes commandes, et SecureChain fonctionne en parallèle avec Artifactory, Nexus ou GitHub Packages si c’est ainsi que votre équipe gère déjà ses paquets. Ce qui change, c’est que le paquet situé quatre niveaux plus bas, celui dont personne n’a jamais entendu parler, est soumis au même processus de recompilation et de filtrage que le framework que vous avez choisi délibérément.

Par où commencer ?

Avant d'apporter la moindre modification, assurez-vous d'avoir une vision claire de ce que vous utilisez. Exécutez npm ls --all et enregistrez le résultat afin de pouvoir le comparer ultérieurement. Choisissez quelques paquets que vous ne reconnaissez pas et lancez la commande npm explain sur ceux-ci. Assurez-vous que votre fichier de verrouillage a bien été validé et que CI utilise npm ci, de sorte que les builds s'installent à partir du fichier de verrouillage au lieu de recalculer les plages, et que les versions ne changent que lorsqu'un utilisateur valide un fichier de verrouillage mis à jour.

Examinez ensuite vos derniers rapports d'audit et classez les constatations en deux catégories : celles que vous avez vous-même identifiées et celles dont vous avez hérité. La deuxième pile vous donne une bonne idée de l'ampleur réelle des risques dont vous avez hérité, et c'est celle qui mérite le plus d'être examinée de plus près.

FAQ

Qu'est-ce qu'une dépendance transitive ?

Une dépendance transitive est un paquet que votre projet installe parce qu'une de vos dépendances en a besoin, et non parce que vous l'avez ajouté vous-même. Dans npm, les dépendances transitives sont enregistrées dans votre fichier de verrouillage et, une fois installées, apparaissent dans node_modules, mais elles ne sont pas déclarées dans le fichier package.json.

Quelle est la différence entre les dépendances directes et transitives ?

Les dépendances directes sont les paquets que votre projet déclare dans son propre package.json. Les dépendances transitives correspondent à l'ensemble des paquets que ces derniers impliquent, à tous les niveaux de l'arborescence. Vous contrôlez les plages de versions des dépendances directes, tandis que celles des dépendances transitives sont définies par leurs paquets parents.

Comment puis-je consulter mes dépendances transitives dans npm ?

Exécuter npm ls --all pour afficher l'arborescence complète des dépendances. Pour savoir pourquoi un paquet spécifique est installé, exécutez la commande npm explain <package>, qui affiche tous les chemins de dépendance qui y mènent.

Comment puis-je corriger une vulnérabilité dans une dépendance transitive ?

Commencez par vérifier si la mise à jour de la dépendance directe située au-dessus permet d’intégrer la version corrigée. Si la plage de versions du paquet parent bloque le correctif, vous pouvez utiliser les remplacements npm, disponibles depuis la version 8.3 de npm, pour forcer l'installation d'une version corrigée, même si vous utiliserez alors une combinaison que le responsable du paquet parent n'a probablement pas testée. S'il n'existe pas de version corrigée parce que le paquet n'est plus maintenu, vos options sont les suivantes : un correctif rétroporté, votre propre fork ou patch, ou le remplacement du paquet qui en dépend.

Les dépendances transitives constituent-elles un risque pour la sécurité ?

Oui. Tout code de ces bibliothèques qui s'exécute bénéficie des mêmes droits d'accès que votre build ou votre application ; certaines d'entre elles exécutent des scripts d'installation, et un seul paquet de niveau supérieur peut en intégrer des centaines. Les attaques par la chaîne d'approvisionnement, notamment celle visant Event-Stream en 2018 et la compromission de Mastra en 2026, ont injecté leur charge utile via une dépendance transitive récemment ajoutée.

Qu'est-ce que le risque héréditaire ?

Le risque hérité correspond à l'exposition à des failles de sécurité qu'un projet subit en raison des paquets fournis avec les dépendances qu'il a choisies. Il englobe les vulnérabilités, les codes malveillants et les paquets abandonnés, où qu'ils se trouvent dans l'arborescence des dépendances transitives.

Sources

Résumé
Les dépendances transitives de npm et le risque que vous n'avez jamais choisi
Nom de l'article
Les dépendances transitives de npm et le risque que vous n'avez jamais choisi
Description
Votre fichier `package.json` répertorie les éléments que vous avez choisis. Les dépendances npm transitives constituent le reste, et c'est là que réside une grande partie des risques liés à la chaîne d'approvisionnement.
Auteur
Nom de l'éditeur
de TuxCare
Logo de l'éditeur

Rejoignez plus de 4 500 professionnels de Linux et de l'Open Source!

2 fois par mois. Pas de spam.

Table des matières
Obtenez les réponses à vos questions sur la sécurité des logiciels libres

Rejoignez plus de 4 500 professionnels de Linux et de l'Open Source!

2 fois par mois. Pas de spam.