Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go)
- Accueil
- Développement, applications & chaîne logicielle
- Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go)
Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go)
Section intitulée « Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go) »Une dépendance directe se choisit ; une dépendance transitive arrive avec elle, décidée par un mainteneur qu’on ne connaît pas, résolue par un algorithme qu’on n’a pas lu. Elles forment l’essentiel du code exécuté et des vulnérabilités signalées. La position défendue ici : tout verrouiller, tout inventorier, et mettre à jour par lots revus plutôt qu’au fil de l’eau.
On invite un ami à une fête. Il vient avec deux copains, qui amènent chacun leur cousine, et la cousine arrive avec son voisin. À minuit, le salon est plein de gens que personne n’a invités. Cette page explique comment savoir qui est vraiment dans la maison, et comment éviter qu’un inconnu parte avec l’argenterie.
→ Lire l’explication complète de la marmotte
Résolution des dépendances transitives : qui décide de la version
Section intitulée « Résolution des dépendances transitives : qui décide de la version »Le fichier de déclaration exprime des contraintes ; le résolveur du gestionnaire de paquets choisit les versions réelles, et chaque écosystème tranche différemment :
- npm accepte plusieurs versions du même paquet dans l’arbre et déduplique quand les plages se recoupent. Une plage
^1.2.0accepte toute version 1.x ultérieure : sans fichier de verrouillage, l’installation du lendemain peut embarquer un code différent. - pip n’installe qu’une version par environnement : le résolveur cherche une combinaison compatible et peut rétrograder une dépendance pour satisfaire une autre.
- Maven applique la médiation « nearest wins » : la version la plus proche de la racine l’emporte, même si elle est plus ancienne qu’une version exigée plus bas. Un correctif de sécurité tiré en profondeur peut ainsi disparaître silencieusement.
- Go utilise la sélection de version minimale (MVS, Minimal Version Selection) : la version retenue est la plus haute parmi les minimums exigés, sans jamais aller chercher plus récent. Résultat déterministe, et
go.sumfixe les condensés.
Conséquence d’architecture : le fichier de verrouillage (package-lock.json, poetry.lock) est l’artefact de sécurité, pas le fichier de déclaration. Une revue de code qui ignore le diff du verrou revoit la partie qui compte le moins.
Surface d’attaque des dépendances transitives
Section intitulée « Surface d’attaque des dépendances transitives »L’exécution à l’installation. Les scripts preinstall et postinstall de npm s’exécutent pour chaque paquet de l’arbre, transitifs compris, avec les droits de l’utilisateur qui lance l’installation. Sur un agent d’intégration continue (CI, Continuous Integration), ce sont les jetons de publication et les secrets de déploiement qui sont à portée. Contre-mesure : ignore-scripts=true dans .npmrc, avec une liste explicite des paquets autorisés à compiler (pnpm bloque ces scripts par défaut depuis sa version 10).
La mise à jour silencieuse. Un compte de mainteneur compromis publie une version corrective piégée ; toute plage ouverte la récupère à la prochaine résolution. Contre-mesure : verrou versionné, installation en mode strict (npm ci, pip install --require-hashes), et délai de quarantaine avant adoption d’une nouvelle version (option minimumReleaseAge de Renovate). Une version malveillante est en général retirée du registre en quelques jours : attendre une semaine filtre beaucoup.
La confusion de nom. Un paquet interne résolu sur le registre public, interrogé en parallèle. Contre-mesure : portées réservées (@organisation/), registre miroir unique en amont, et refus de toute résolution hors de ce miroir.
Vingt ans de métier apprennent une constante : le paquet qui pose problème n’est jamais celui qu’on a choisi avec soin après trois réunions, c’est celui dont personne ne savait qu’il existait, à la profondeur six.
Ce que le verrouillage ne garantit pas
Section intitulée « Ce que le verrouillage ne garantit pas »Le verrou et les condensés garantissent que le code installé est celui qui a été résolu la première fois. Ils ne disent rien de l’honnêteté de ce code : une version piégée verrouillée reste piégée, avec un condensé parfaitement valide. Les signatures Sigstore et attestations de construction prouvent qui a publié et depuis quel dépôt, pas ce que fait le code ; le débat de fond est traité dans les éditoriaux.
Le verrou a aussi un coût, et c’est le compromis central du sujet : on gagne la reproductibilité, on paie en fraîcheur. La recommandation assumée : verrouiller strictement, et regrouper les mises à jour dans un flux automatisé (Renovate ou Dependabot) qui ouvre une demande de fusion revue, jamais fusionnée seule. La fusion automatique ne se défend que si les tests couvrent les chemins d’exécution et si l’installation n’a accès à aucun secret.
Autre angle mort : l’outil d’analyse de composition logicielle (SCA, Software Composition Analysis) signale la présence d’un paquet vulnérable, pas son atteignabilité. Une CVE (Common Vulnerabilities and Exposures) dans une fonction jamais appelée pèse moins qu’une CVE moyenne sur le chemin d’une requête HTTP (HyperText Transfer Protocol). govulncheck fait cette analyse d’appel pour Go ; ailleurs, le tri reste manuel.
Exemple reproductible : mesurer l’arbre et forcer une version transitive
Section intitulée « Exemple reproductible : mesurer l’arbre et forcer une version transitive »Le bloc suivant utilise npm 10 et Node.js 22, dans un répertoire jetable.
# Projet vierge, une seule dépendance déclarée, scripts d'installation désactivésmkdir demo-transitif && cd demo-transitifnpm init -y
# Nombre de paquets réellement installés (première ligne = le projet lui-même)npm ls --all --parseable | wc -l
# Pourquoi qs est présent : chaîne complète des dépendantsnpm explain qs
# Vulnérabilités connues sur les dépendances de productionnpm audit --omit=dev
# Forcer une version de qs partout dans l'arbre, même si express exige 6.11.0npm pkg set overrides.qs=6.16.0npm install --ignore-scripts
# En CI : installer exactement le verrou, sans le modifier ni exécuter de scriptnpm ci --ignore-scriptsUne seule dépendance déclarée produit près de soixante-dix paquets installés. L’override corrige une vulnérabilité sans attendre que chaque maillon intermédiaire publie une version, mais il sort la bibliothèque de la plage testée par son dépendant : il se justifie par un avis de sécurité, se documente, et se retire dès que l’amont a rattrapé. Un overrides truffé d’entrées vieilles de deux ans est une dette, pas une politique.
Exploitation : inventaire, supervision et reprise
Section intitulée « Exploitation : inventaire, supervision et reprise »Prérequis. Un registre miroir interne (Artifactory, Nexus, Verdaccio) par lequel passe toute résolution, un verrou versionné par projet, et une CI capable de refuser un verrou désynchronisé du fichier de déclaration.
Inventaire. Générer une nomenclature logicielle (SBOM, Software Bill of Materials) au format CycloneDX ou SPDX (Software Package Data Exchange) à chaque construction, et la stocker à côté de l’artefact livré. Elle répond, le jour où l’avis tombe, à la question « ce binaire contient-il X en version Y ? ». La page Développement situe cette brique dans la chaîne de construction.
Supervision. Une analyse à la construction ne voit que les CVE déjà publiées. Les SBOM des versions en production se réanalysent chaque jour contre une base comme OSV (Open Source Vulnerabilities) ; sinon, un artefact livré il y a six mois reste réputé sain par défaut.
Secrets. Les jetons d’accès au registre et de publication vivent dans le gestionnaire de secrets de la CI, à portée minimale et rotation régulière ; jamais dans un .npmrc versionné.
Reprise. Un paquet retiré du registre public casse toute construction qui le résout. Le miroir interne, qui conserve les versions consommées, en fait un non-événement, et sert de point de blocage : retirer une version compromise du miroir l’élimine des constructions à venir.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans inventaire des dépendances transitives, la surface réelle du projet reste inconnue : un paquet installé par un autre paquet n’apparaît dans aucun fichier de déclaration, donc dans aucune revue. La conséquence immédiate est une perte de détection : la publication d’un avis de sécurité sur une bibliothèque de troisième rang ne déclenche rien, puisque rien ne relie cet identifiant au code déployé. S’y ajoute une perte de traçabilité : en l’absence de verrouillage des versions résolues, deux constructions successives du même commit peuvent embarquer des arbres différents, ce qui rend l’analyse d’un incident invérifiable et la reprise incertaine. Enfin, un mainteneur secondaire compromis obtient un chemin d’exécution direct, au même niveau de privilège que le code applicatif, sans franchir la moindre étape de validation.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un programme moderne n’est presque jamais écrit en entier par l’équipe qui le livre. Il réutilise des bibliothèques, c’est-à-dire des morceaux de code publiés par d’autres. Chacune de ces bibliothèques en utilise d’autres, qui en utilisent encore d’autres : ce sont les dépendances transitives. Au moment de l’installation, un outil appelé gestionnaire de paquets lit les versions demandées, calcule la liste complète, télécharge tout et, parfois, exécute des petits programmes fournis par ces paquets. Si une seule de ces pièces est piégée, elle tourne avec les mêmes droits que le reste du programme. Pour limiter le risque, on enregistre dans un fichier de verrouillage la version exacte et l’empreinte de chaque pièce : l’installation suivante reprend exactement la même liste, et une pièce modifiée est refusée. Ce fichier empêche le changement discret, mais il ne dit pas si le code est honnête, et il bloque aussi l’arrivée des corrections. Il faut donc mettre à jour régulièrement, en relisant les changements. On garde enfin la liste complète des pièces de chaque version livrée, et on la compare chaque jour aux failles nouvellement publiées : sans cette liste, une faille annoncée dans une pièce profonde passe inaperçue.
À lire aussi
- SBOM pour inventorier les composants réellement exécutésUne SBOM permet d’identifier les composants qui composent réellement un logiciel, à condition de la produire au bon endroit et de l’exploiter.
- Images Docker de base et confiance héritéeGérer la généalogie des images de base Docker pour maîtriser la confiance, les vulnérabilités et l'exploitation.
- SLSA : niveaux Build, provenance in-toto et vérificationComprendre SLSA (Supply-chain Levels for Software Artifacts), ses niveaux Build, l'attestation de provenance signée et sa vérification avant déploiement.
- L'attaque de la chaîne d'approvisionnement logicielle en une pageComment une dépendance piégée traverse le build, reçoit une signature valide et s'installe chez tous les clients, en cinq étapes animées, avec SolarWinds, xz-utils, event-stream et les parades qui coupent la chaîne.
Pages voisines
La citent
- Pourquoi les builds reproductibles échouent souvent
- SLSA : niveaux Build, provenance in-toto et vérification
- Écrire un outil jetable proprement, sans faille ni dette
- JavaScript et SQL côté web, ce qu'il faut savoir des deux langages
- PSIRT et divulgation coordonnée des vulnérabilités produit sous CRA
- Python pour l'automatisation défensive, usages et garde-fous
- Désérialisation non sécurisée et chaîne de gadgets
- L'attaque de la chaîne d'approvisionnement logicielle en une page
- Rust et la sécurité mémoire par construction, garanties et limites
Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go)
Elle cite
