Les 5 dimensions d’une dépendance npm
16 juin 2016
0 minutes de lectureNous parlons souvent du nombre croissant de dépendances npm, et de la façon dont elles nous rendent plus productifs et rapides, mais aussi plus fragiles et potentiellement vulnérables. Mais qu’est-ce qu’une dépendance npm exactement ?
Chez Snyk, notre produit vise à sécuriser les dépendances. Nous avons donc dû définir précisément ce qu’est une dépendance. Cet article aborde les différentes dimensions d’une dépendance et présente les enseignements tirés de notre démarche visant à définir une taxonomie simple, pour vous aider à comprendre comment les regrouper.
Définition de base : du code dont vous dépendez
Dans sa définition la plus élémentaire, une dépendance est simplement un paquet de code dont dépend votre application. Sans ce code, votre application ne fonctionnera pas correctement, et ne pourra peut-être même pas être compilée.
Quelle que soit la manière dont vous les organisez, chaque dépendance a donc un impact sur les fonctionnalités, la fiabilité et la sécurité de votre application. Cela posé, voyons comment les classer.
Dimension 1 : développement ou production
Le premier type de dépendance, et le plus explicite, est la distinction entre développement et production. Dans le fichier package.json, les dépendances de production sont répertoriées explicitement sous dependencies, tandis que celles utilisées uniquement pendant le développement sont appelées devDependencies.
Par défaut, la commande npm install installe à la fois les dépendances de développement et de production de l’application actuelle, mais uniquement les dépendances de production de chaque paquet téléchargé depuis npm.
Par exemple, le paquet npm util utilise les sections de dépendances suivantes dans son fichier package.json :
Si vous exécutez npm install util, seule la dépendance de production inherits sera installée avec le paquet. En revanche, si vous clonez son dépôt node-util et exécutez npm install dans le dossier cloné, inherits et zuul seront tous deux installés.
Comme indiqué, cette séparation est explicitement définie par une application et sa logique est assez simple. Si la dépendance est nécessaire à l’application pour fonctionner, elle doit être une dépendance de production. Si elle est uniquement nécessaire pour les tests ou la compilation, elle doit être une dépendance de développement. Lors de la recherche de vulnérabilités, les dépendances de développement sont peu importantes. C’est pourquoi, par défaut, Snyk teste uniquement les dépendances de production (vous pouvez toutefois modifier ce paramètre à l’aide de l’option --dev).
Notez que peerDependencies et optionalDependencies sont également des dépendances de production, avec une particularité concernant le moment et la manière dont elles sont installées. Nous y reviendrons lorsque nous aborderons la dimension logique ou disque.

L’image ci-dessus montre un exemple de proportion de dépendances de développement et de production, d’après bitHound.
Dimension 2 : directes ou indirectes
Certaines de vos dépendances sont directes (également appelées primaires) : elles sont explicitement déclarées dans votre fichier package.json. La majorité des dépendances sont toutefois indirectes (ou secondaires) : elles sont ajoutées par une dépendance directe (ou une autre dépendance indirecte) pour accomplir sa tâche. Dans la plupart des applications, les dépendances indirectes constituent la grande majorité de la liste. Par exemple, très peu d’applications utilisent directement left-pad, mais quelques applications très connues (comme babel et node) le font. left-pad est ainsi devenu une dépendance indirecte pour un très grand nombre d’applications, ce qui explique l’impact considérable de son retrait du registre.
Lorsqu’un paquet est très éloigné de votre application, enfoui profondément dans l’arbre des dépendances, il est facile de ne pas en avoir connaissance ou de l’oublier complètement. Pourtant, même les dépendances éloignées restent des dépendances. Le retrait de left-pad a cassé des applications, y compris celles qui ignoraient même l’utiliser. Le non-respect de la licence d’une dépendance profondément imbriquée peut toujours entraîner des problèmes juridiques, et une vulnérabilité dans une dépendance indirecte éloignée peut toujours permettre à un attaquant d’accéder à votre système.
Dimension 3 : paquet ou version
Imaginons que vous utilisiez le paquet request en version 2.11.3. Quelle est alors votre dépendance ? Est-ce request, le paquet, ou request@2.11.3, la version précise ?
La réponse complète est : les deux. Vous dépendez clairement de request@2.11.3. Cette version correspond à un programme immuable que vous avez intégré et que vous utilisez dans votre code. Toute faille dans ce paquet, comme cette vulnérabilité d’exposition de la mémoire à distance, aura un impact sur votre code. Vous êtes également tenu de respecter la licence MIT sous laquelle il a été mis à disposition, etc.
Cependant, vous dépendez aussi du projet que constitue le paquet request. Si vous l’utilisez avec une plage semver, vous comptez sur ses auteurs pour ne pas publier de changement incompatible dans une mise à jour mineure. Du point de vue de la sécurité, vous comptez sur eux pour ne pas divulguer leurs identifiants npm ou GitHub, pour rechercher les problèmes de sécurité à l’avance et pour corriger rapidement les vulnérabilités signalées. Avec le temps, vous comptez également sur le projet et ses auteurs pour assurer sa maintenance, corriger les bogues et ajouter des fonctionnalités dans des délais raisonnables. Vous pouvez réduire ce risque en utilisant shrinkwrap pour figer les versions des paquets que vous utilisez, ou en intégrant les dépendances, mais dans les deux cas, vous ne recevrez plus de nouvelles fonctionnalités ni de corrections de bogues.
Le paquet comme la version sont des dépendances, mais vous en dépendez de façons très différentes. Il serait donc préférable de leur donner des noms distincts. Malheureusement, il n’existe pas de nom spécifique pour une combinaison paquet+version, et le terme paquet est utilisé indifféremment pour désigner l’un ou l’autre.
Chez Snyk, lorsque nous parlons de dépendance, nous faisons généralement référence à une combinaison paquet+version, par exemple request@2.11.3. Pour parler du paquet, nous disons explicitement « paquet dépendant ». Cela dit, la taxonomie est complexe sur ce point. Même si nous essayons de suivre ces principes, il nous arrive simplement de dire « paquet » et de laisser le lecteur déterminer ce que nous voulons dire en fonction du contexte…

Les paquets comme request existent en de nombreuses versions. Vous dépendez de la qualité de chacune d’elles et de la capacité du projet à bien les gérer.
Dimension 4 : logique ou disque
Jusqu’ici, tout ce dont nous avons parlé concernait les dépendances logiques, c’est-à-dire la représentation conceptuelle de votre arbre de dépendances. Cependant, l’arbre peut beaucoup changer lorsque les dépendances sont effectivement téléchargées sur le disque. Cela tient en partie aux dépendances peer et facultatives, dont l’installation est assez opportuniste, mais aussi, et davantage encore, à la déduplication de npm3.
Prenons le paquet inflight. Voici son arbre logique de dépendances :
La dépendance wrappy@1.0.2 est utilisée à la fois comme dépendance directe et comme dépendance indirecte via once@1.3.3. Si nous clonons le dépôt inflight, puis exécutons npm install --production et npm ls, nous obtenons ceci :
Comme vous pouvez le constater, wrappy@1.0.2 n’apparaît qu’une seule fois. C’est grâce à la déduplication de npm3, qui repère les paquets répétés et évite d’en créer une copie supplémentaire sur le disque. Une déduplication minimale est également activée par défaut dans npm2, et vous pouvez la lancer explicitement avec la commande npm dedupe.
Notre exemple reste simple, mais les choses peuvent se compliquer et devenir moins prévisibles avec les plages semver et des installations répétées de npm. Vous pouvez examiner vos dépendances logiques et sur disque à l’aide de snyk-resolve, dont Remy a parlé dans cet article de blog.
Pour cette dimension, retenez que vos dépendances logiques et sur disque peuvent différer, et que les dépendances sur disque dépendent de la logique et de l’ordre d’installation. Veillez à vérifier ce qui a réellement été installé pour l’ensemble de l’application, et pas seulement la logique de chaque dépendance directe prise séparément.
Dimension 5 : chemin de dépendance ou dépendance unique
Maintenant que nous avons défini nos dépendances, la dernière dimension porte sur leur décompte. Considérez l’arbre logique de dépendances suivant :
En observant cet arbre, nous pouvons dire que l’application compte 3 paquets dont elle dépend : A, B et C. Nous pouvons également dire qu’elle compte 4 dépendances sur disque (versions comprises) : A@1.0.0, B@1.0.0, B@2.0.0 et C@1.0.0 — la déduplication évitant les doublons. Mais combien compte-t-elle de dépendances logiques ? En compte-t-elle 4, une pour chaque combinaison paquet+version, ou 5, une pour chaque nœud de l’arbre des dépendances ?
Pour distinguer les deux, vous pouvez dire que cette application compte 4 dépendances uniques et 5 chemins de dépendance. Chez Snyk, si, par exemple, B@2.0.0 présente une vulnérabilité connue, nous dirions qu’il existe une vulnérabilité connue, mais deux chemins vulnérables.
En résumé
En résumé, votre ensemble de dépendances comporte plusieurs dimensions, chacune étant plus adaptée à des objectifs différents. Lorsque nous parlons de dépendances, nous devons essayer d’utiliser la même taxonomie dans la mesure du possible, afin de faciliter les échanges.
Voici un aide-mémoire récapitulant les 5 dimensions :
Développement ou production : votre application a besoin des dépendances de développement pour être compilée et testée, et des dépendances de production pour fonctionner.
Directes ou indirectes : votre application ne requiert explicitement que les dépendances directes, mais vos évaluations de la qualité, de la conformité juridique et de la sécurité doivent également couvrir les dépendances indirectes, bien plus nombreuses.
Paquet ou version : la version précise de chaque dépendance a un impact sur votre application déployée, mais votre projet compte sur chaque paquet dont il dépend pour continuer à fonctionner.
Logique ou disque : l’arbre logique des dépendances de votre application peut beaucoup changer lors de leur installation sur le disque. Veillez à vérifier les versions réellement installées.
Chemin ou dépendance unique : lorsque vous décomptez vos dépendances, distinguez le nombre de dépendances uniques des chemins de dépendance, afin d’estimer correctement l’ampleur d’une tâche ou d’un problème.
Remarque : n’hésitez pas à consulter cet article plus détaillé, qui présente des informations et des analyses sur les manifestes de paquets npm et yarn, ainsi que sur le fonctionnement des fichiers de verrouillage pour les applications et les bibliothèques.
Maintenant que vous maîtrisez le vocabulaire, vous pouvez utiliser Snyk pour tester votre application et découvrir combien de dépendances de production vulnérables et de chemins de dépendance elle peut utiliser. Vous pouvez également rechercher les paquets dont vous dépendez dans notre base de données des vulnérabilités pour consulter leur historique de failles de sécurité.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.