Skip to main content

npm Shrinkwrap revisité : verrouiller les dépendances npm avec Package-Lock et Yarn.Lock

Écrit par
Headshot of Assaf Hefetz

Assaf Hefetz

10 janvier 2018

0 minutes de lecture

En 2016, la gestion des dépendances a fait la une dans le monde entier lorsqu’un développeur inconnu a dépublié un petit package Node.js appelé left-pad — ce qui a cassé des milliers de projets qui en dépendaient et mis hors service certains des plus grands sites web au monde. npm, Inc. a dû intervenir et restaurer le package pour mettre fin au chaos.

Cette histoire montre à quel point les dépendances peuvent être importantes. Si elles sont absentes ou différentes de ce qui était prévu, les conséquences peuvent être majeures.

Oubliez ShrinkWrap : un verrouillage élégant des dépendances pour Node

Le verrouillage ou « épinglage » des dépendances est une bonne pratique largement répandue dans les écosystèmes Ruby, Python et autres. L’idée consiste à figer la version d’un package et de ses dépendances afin que, lors du déploiement d’un projet, la même version de chaque dépendance soit toujours installée, pour garantir une installation fiable et prévisible.

Dans Node.js, le verrouillage était beaucoup moins courant jusqu’à récemment. npm Shrinkwrap était la solution, mais elle présentait plusieurs problèmes. D’abord, le fichier shrinkwrap.json, qui contient des informations sensibles sur vos dépendances, peut être inclus lors de la publication d’un package. Ensuite, Shrinkwrap rend plus difficile l’ajout de nouvelles dépendances. Enfin, shrinkwrap présente un risque de sécurité : il est vulnérable aux attaques par exécution de code à distance si les URL HTTPS ne sont pas utilisées.

La communauté npm s’est récemment réjouie de l’arrivée d’une solution plus récente et élégante intégrée à npm 5 : package-lock.json. Les utilisateurs de Yarn disposaient déjà de yarn.lock, qui intègre le verrouillage des dépendances pour Node. Nous allons examiner ces deux solutions dans un instant.

Mais d’abord, faut-il verrouiller les dépendances ?

Avantages et inconvénients du verrouillage (épinglage) des dépendances

De nombreux spécialistes, ainsi que le guide du gouvernement américain destiné aux fabricants de logiciels, « Before you ship », recommandent d’épingler toutes les dépendances des projets. Voici pourquoi :

  • Changements incompatibles - les nouvelles versions des dépendances peuvent introduire des changements incompatibles que votre code ne prend pas en charge ou qui ne sont pas compatibles avec votre implémentation spécifique.

  • Bugs ou problèmes - une nouvelle version peut introduire des problèmes qui ont échappé aux développeurs du package. Comme vous ne pouvez pas tester chacune de vos dépendances dans votre code à chaque mise à niveau, mieux vaut utiliser par défaut une version dont le bon fonctionnement dans votre environnement a déjà été vérifié.

  • Prévisibilité - le développement agile et la livraison continue reposent sur la capacité à déployer automatiquement des logiciels de manière prévisible. Les mises à niveau de packages susceptibles d’entraîner un comportement incohérent ou imprévisible vont à l’encontre de cette prévisibilité.

L’épinglage des packages présente aussi des inconvénients :

  • Sécurité - Snyk propose un outil qui détecte les vulnérabilités des logiciels open source ; nous connaissons donc bien les risques inhérents aux dépendances. Nos données montrent qu’au moins 76 % des projets de la communauté Node.js utilisent des packages présentant des vulnérabilités connues. En verrouillant ou en épinglant vos dépendances, vous verrouillez aussi les vulnérabilités qu’elles contiennent. Si un problème de sécurité est découvert et que l’auteur du package publie un correctif, vous continuerez à utiliser l’ancienne version vulnérable.

  • Modules tiers - si vous êtes l’auteur d’un module tiers dont d’autres projets dépendent, épingler vos dépendances obligera vos utilisateurs à conserver les mêmes versions. La documentation Python sur la création de packages indique, par exemple, que cette pratique est trop restrictive et n’est pas considérée comme une bonne pratique. Merci à Dustin Ingram de l’avoir signalé.

Même si le risque pour la sécurité est important, la plupart des développeurs auront du mal à renoncer aux avantages d’installations et de déploiements prévisibles. Notre recommandation : verrouillez vos dépendances, mais intégrez l’analyse des vulnérabilités à votre processus de build.

Si vous avez mis en place des tests et une surveillance de sécurité, vous saurez quand une dépendance devient obsolète et que des vulnérabilités sont découvertes, et pourrez alors la mettre à jour. Dans l’idéal, vous mettriez immédiatement à jour tous les composants open source de votre projet dès la publication de nouvelles versions, afin de vous protéger au maximum contre les failles et les vulnérabilités. Au minimum, effectuer une mise à niveau dès qu’une vulnérabilité connue est découverte semble être un compromis acceptable.

Verrouiller les dépendances avec package-lock

package-lock.json est une nouvelle fonctionnalité de npm 5 qui décrit l’arbre exact des dépendances généré lors des installations précédentes. Il est ainsi possible de générer un arbre de dépendances identique lors des installations ultérieures, quelles que soient les mises à jour intermédiaires des dépendances.

npm 5 crée automatiquement le fichier package-lock, qui doit être ajouté au contrôle de version. Même si cela peut être fastidieux, le contrôle de version présente deux avantages majeurs :

  • En validant package-lock dans le dépôt, vous ajoutez une couche de sécurité. Vous pouvez utiliser des valeurs d’intégrité SHA pour vérifier que ce que vous installez correspond exactement à ce que vous aviez prévu.

  • La validation de package-lock accélère l’installation, car npm n’a pas besoin de résoudre les métadonnées des packages déjà installés.

Le format package-lock.json est identique à celui de npm-shrinkwrap.json. Si npm-shrinkwrap.json est présent, package-lock.json est totalement ignoré.

Voici quelques-unes des variables du fichier package-lock.json :

  • name - package-lock.json définit un verrouillage pour un package spécifique ; vous indiquez ici le nom du package.

  • version - la version que vous souhaitez verrouiller.

  • lockfileVersion - la version du fichier package-lock, à partir de 1.

  • packageIntegrity - une valeur d’intégrité générée à partir de package.json. Elle peut être produite par des modules comme ssri.

  • preserveSymlinks - indique si l’installation a été effectuée avec la variable d’environnement NODE_PRESERVE_SYMLINKS.

  • dependencies - une association entre le nom du package et des objets de dépendance. Ces objets possèdent les propriétés suivantes :

    • version - un identifiant unique de ce package, qui peut être utilisé pour en récupérer une nouvelle version. Consultez la documentation package-lock pour savoir comment spécifier une version pour différentes sources de packages.

    • bundled - indique si la dépendance est intégrée au package et, si c’est le cas, si elle doit être extraite du module parent plutôt que d’être installée séparément.

    • dev - indique si cette dépendance est une dépendance de développement ou une dépendance transitive de l’une d’elles.

    • Les autres propriétés sont integrity, resolved, optional et dependencies (les sous-dépendances de cette dépendance).

Consultez la documentation de package-lock.json pour en savoir plus, et découvrez l’article de Jiří Pospíšil pour connaître les bonnes pratiques de verrouillage des fichiers avec npm 5.

Verrouiller les dépendances avec yarn.lock

Yarn intègre le verrouillage des dépendances, comme Ruby. Pour garantir des installations cohérentes sur différentes machines, Yarn a besoin de savoir précisément quelles versions de chaque dépendance ont été installées.

Yarn génère automatiquement un fichier yarn.lock, situé à la racine de votre projet. Comme le fichier package-lock de npm, yarn.lock doit être ajouté au contrôle de version.

Voici à quoi ressemble ce fichier (exemple tiré de la documentation de yarn.Lock).

       # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 
       # yarn lockfile v1 
       package-1@^1.0.0: 
           version "1.0.3" 
           resolved "https://registry.npmjs.org/package-1/-/package-1-1.0.3.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
       package-2@^2.0.0: 
           version "2.0.1" 
           resolved "https://registry.npmjs.org/package-2/-/package-2-2.0.1.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
           dependencies: 
               package-4 "^4.0.0" 
       ...

Lorsque vous ajoutez, mettez à niveau ou supprimez des dépendances, Yarn met automatiquement à jour le fichier yarn.lock. Vous ne devez pas le modifier directement. Yarn ne consulte que le fichier yarn.lock de premier niveau et ignore ceux qui se trouvent dans les dépendances. Le fichier de premier niveau contient tout ce qui est nécessaire pour verrouiller les versions de l’ensemble des packages de l’arbre des dépendances.

Pour en savoir plus, consultez la documentation de yarn.lock ou ce guide détaillé de Pluralsight, qui explique Yarn et notamment la fonctionnalité yarn.lock.

Conclusion

Les dépendances sont importantes. Pour éviter les mauvaises surprises, et parce qu’à chaque installation d’un package, les versions de ses dépendances imbriquées peuvent varier, la plupart des développeurs front-end verrouillent ou épinglent leurs dépendances. Les utilisateurs de npm étaient à la traîne, mais ce n’est plus le cas avec la nouvelle fonctionnalité package-lock de npm 5.

Il existe désormais au moins deux façons pratiques de verrouiller les packages avec npm : le fichier package-lock.json de npm 5, qui remplace shrinkwrap par une solution plus élégante, et le mécanisme de verrouillage intégré à Yarn, appelé yarn.lock.

Notre conseil : verrouillez vos dépendances, mais ne les perdez pas de vue. Mettez en place un processus de surveillance continue des vulnérabilités de vos dépendances à l’aide d’un outil comme Snyk. Et de temps à autre, réexaminez votre arbre des dépendances et mettez activement à jour les dépendances obsolètes afin de vous protéger au maximum contre les failles et les vulnérabilités à venir.