Skip to main content

npm Shrinkwrap renovado: cómo bloquear dependencias de npm con Package-Lock y Yarn.Lock

Escrito por
Headshot of Assaf Hefetz

Assaf Hefetz

10 de enero de 2018

0 minutos de lectura

En 2016, la administración de dependencias acaparó titulares en todo el mundo cuando un desarrollador desconocido quitó del registro un pequeño paquete de Node.js llamado left-pad. Esto afectó a miles de proyectos que dependían de ese paquete y dejó fuera de servicio algunos de los sitios web más importantes del mundo. npm, Inc. tuvo que intervenir y restaurar el paquete para poner fin al caos.

Esta historia muestra la importancia que pueden tener las dependencias. Si no están disponibles o son diferentes de lo esperado, pueden provocar grandes problemas.

Olvídate de ShrinkWrap: una forma elegante de bloquear dependencias en Node

Bloquear o “fijar” dependencias es una práctica recomendada muy extendida en Ruby, Python y otros ecosistemas. La idea es congelar la versión de un paquete y sus dependencias para que, al implementar un proyecto, siempre se instale la misma versión de cada dependencia y la instalación sea confiable y predecible.

En Node.js, esta práctica era mucho menos común hasta hace poco. npm Shrinkwrap era la solución, pero tenía varios problemas. Uno de ellos es que el archivo shrinkwrap.json, que contiene información confidencial sobre tus dependencias, puede incluirse al publicar un paquete. Otro es que Shrinkwrap dificulta agregar nuevas dependencias. Además, Shrinkwrap implica un riesgo de seguridad, ya que es vulnerable a ataques de ejecución remota de código si no se usan URL HTTPS.

La comunidad de npm celebró recientemente una solución más nueva y elegante, integrada en npm 5: package-lock.json. Quienes usan Yarn ya contaban con yarn.lock, que ofrece bloqueo de dependencias integrado para Node. Enseguida hablaremos de estas dos soluciones.

Pero primero, ¿deberías bloquear las dependencias?

Ventajas y desventajas de bloquear (fijar) tus dependencias

Muchos profesionales, e incluso la guía del gobierno de EE. UU. para fabricantes de software, “Before you ship”, recomiendan fijar todas las dependencias de los proyectos. Estos son los motivos:

  • Cambios incompatibles: las nuevas versiones de las dependencias pueden introducir cambios incompatibles que tu código no admite o que no son compatibles con tu implementación específica.

  • Errores o problemas: una nueva versión podría introducir problemas que los desarrolladores del paquete no detectaron. Como no puedes probar todas las dependencias de tu código cada vez que se actualizan, es mejor usar de forma predeterminada una versión que ya se haya comprobado que funciona en tu entorno.

  • Previsibilidad: el desarrollo ágil y la entrega continua se basan en la capacidad de implementar software automáticamente de manera predecible. Las actualizaciones de paquetes que pueden causar un comportamiento inconsistente o impredecible van en contra de esa previsibilidad.

Fijar paquetes también tiene desventajas:

  • Seguridad: Snyk ofrece una herramienta que analiza vulnerabilidades de código abierto, por lo que conocemos muy bien los riesgos inherentes a las dependencias. Nuestros datos muestran que, al menos en la comunidad de Node.js, el 76 % de los proyectos usa paquetes con vulnerabilidades conocidas. Al bloquear o fijar tus dependencias, también fijas las vulnerabilidades que puedan tener. Si se descubre un problema de seguridad y el autor del paquete publica una corrección, seguirás usando la versión antigua y vulnerable.

  • Módulos de terceros: si eres autor de un módulo de terceros que, a su vez, es una dependencia de otros proyectos, fijar tus dependencias obligará a tus usuarios a mantener las mismas versiones. Por ejemplo, la documentación de empaquetado de Python indica que esto es demasiado restrictivo y no se considera una buena práctica. Gracias a Dustin Ingram por señalarlo.

Aunque el problema de seguridad es importante, la mayoría de los desarrolladores no podrá resistirse a las ventajas de contar con instalaciones e implementaciones predecibles. Nuestra recomendación: bloquea las dependencias, pero incorpora el análisis de vulnerabilidades a tu proceso de compilación.

Si cuentas con pruebas y monitoreo de seguridad, sabrás cuándo una dependencia queda desactualizada y se descubren vulnerabilidades, y podrás actualizarla en ese momento. En un mundo ideal, actualizarías de inmediato todos los componentes de código abierto de tu proyecto cuando se publiquen nuevas versiones, para obtener la máxima protección contra errores y vulnerabilidades. Actualizarlos al menos cuando se descubre una vulnerabilidad conocida parece una segunda mejor opción aceptable.

Bloquear dependencias con package-lock

package-lock.json es una nueva función de npm 5 que describe el árbol de dependencias exacto generado en instalaciones anteriores. Así, es posible generar un árbol de dependencias idéntico en instalaciones posteriores, independientemente de las actualizaciones intermedias de las dependencias.

npm 5 crea automáticamente el archivo package-lock, que debe agregarse al control de versiones. Aunque esto puede ser engorroso, ofrece dos ventajas principales:

  • Una vez que confirmas package-lock, cuentas con una capa de seguridad adicional. Puedes usar valores de integridad SHA para confirmar que lo que instalas es exactamente lo que esperabas instalar.

  • Confirmar package-lock agiliza el proceso de instalación, porque npm no necesita resolver los metadatos de los paquetes instalados anteriormente.

El formato de package-lock.json es idéntico al de npm-shrinkwrap.json y, si existe npm-shrinkwrap.json, package-lock.json se ignora por completo.

Estas son algunas de las variables del archivo package-lock.json:

  • name: package-lock.json define un bloqueo para un paquete específico; aquí se indica el nombre del paquete.

  • version: la versión que quieres fijar.

  • lockfileVersion: la versión del archivo package-lock, a partir de la versión 1.

  • packageIntegrity: un valor de integridad creado a partir de package.json. Puede generarse con módulos como ssri.

  • preserveSymlinks: indica si la instalación se realizó con la variable de entorno NODE_PRESERVE_SYMLINKS.

  • dependencies: un mapeo entre el nombre del paquete y los objetos de dependencia. Los objetos de dependencia tienen las siguientes propiedades:

    • version: un identificador único para este paquete que se puede usar para obtener una nueva versión. Consulta la documentación de package-lock para obtener más información sobre cómo especificar versiones para distintas fuentes de paquetes.

    • bundled: indica si la dependencia está incluida en el paquete y, de ser así, si debe extraerse del módulo principal en lugar de instalarse como una dependencia independiente.

    • dev: indica si esta dependencia es una dependencia de desarrollo o una dependencia transitiva de una de ellas.

    • Otras propiedades son integrity, resolved, optional y dependencies (las subdependencias de esta dependencia).

Consulta la documentación de package-lock.json para obtener más información y lee la publicación de Jiří Pospíšil para conocer algunas prácticas recomendadas para bloquear archivos en npm 5.

Bloquear dependencias con yarn.lock

Yarn incluye el bloqueo de dependencias, como Ruby. Para obtener instalaciones consistentes en distintas máquinas, Yarn necesita saber exactamente qué versiones de cada dependencia se instalaron.

Yarn genera automáticamente un archivo yarn.lock que se encuentra en la raíz del proyecto. Al igual que package-lock de npm, yarn.lock debe agregarse al control de versiones.

El archivo tiene este aspecto (ejemplo tomado de la documentación 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" 
       ...

Cuando agregas, actualizas o eliminas dependencias, Yarn actualiza automáticamente el archivo yarn.lock. No debes editarlo directamente. Yarn solo consulta el archivo yarn.lock de nivel superior e ignora los archivos yarn.lock que haya dentro de las dependencias. El archivo de nivel superior incluye todo lo necesario para bloquear las versiones de todos los paquetes del árbol de dependencias.

Para obtener más detalles, consulta la documentación de yarn.lock o esta guía detallada de Pluralsight sobre Yarn, que incluye la función yarn.lock.

Conclusión

Las dependencias son muy importantes. Para evitar sorpresas, y porque cada vez que instalas un paquete podrías obtener distintas versiones de sus dependencias anidadas, la mayoría de los desarrolladores de frontend bloquea o fija sus dependencias. Los usuarios de npm se habían quedado atrás, pero eso ya cambió con la nueva función package-lock de npm 5.

Ahora hay al menos dos formas prácticas de bloquear paquetes en npm: el archivo package-lock.json de npm 5, que reemplaza a shrinkwrap con una solución más elegante, y el mecanismo integrado de bloqueo de dependencias de Yarn, conocido como yarn.lock.

Nuestro consejo: bloquea las dependencias, pero no las pierdas de vista. Configura un proceso para monitorear periódicamente las vulnerabilidades de tus dependencias con una herramienta como Snyk. Además, revisa de vez en cuando el árbol de dependencias y actualiza activamente las que estén desactualizadas, para obtener la máxima protección contra errores y futuras vulnerabilidades.