¿Por qué ocurrió lo de is-promise y qué podemos aprender de ello?
28 de abril de 2020
0 minutos de lecturaEl 25 de abril de 2020, el desarrollador de JavaScript y mantenedor Forbes Lindesay publicó en npm la versión 2.2.0 de la biblioteca is-promise. Según se informó, esta versión provocó fallas en herramientas populares de compilación para desarrolladores que se usan para crear nuevos proyectos, como create-react-app de Facebook, firebase-tools de Google, angular-cli, entre otras. Forbes solucionó rápidamente los problemas asociados con la versión 2.2.0 y, unas 3 horas después, publicó la versión 2.2.2.
¿Otra vez lo mismo que con left-pad? ¡No exactamente!
Contrario a lo que parece ser una opinión popular entre quienes comparten esta historia, esto no es un problema de la cadena de suministro de software ni se parece al caso que vimos en 2016 con el incidente de left-pad.
Además, el problema se originó en un error involuntario que le podría haber pasado a cualquiera, y Forbes demostró ser un mantenedor responsable al publicar una solución en cuestión de horas. También escribió un análisis posterior al incidente, que te recomiendo leer.
En este artículo quiero explicar qué provocó el incidente con is-promise y qué podemos aprender de él como mantenedores y usuarios.
¿A quiénes afectó el incidente de is-promise?
El paquete is-promise de npm es usado por unas 500 dependencias directas y tiene 12,000,000 de descargas semanales, así que es justo decir que es un paquete popular. Sin embargo, no afectó específicamente miles de compilaciones de equipos: impactó sobre todo a quienes creaban proyectos nuevos con herramientas para desarrolladores.
500 dependencias directas quizá no parezcan tantas, pero si sabes lo complejo que es el ecosistema de npm por sus dependencias anidadas, la cifra de GitHub de 3,500,000 millones de proyectos que dependen de is-promise te dará una mejor idea de su popularidad.
Además, el problema solo habría afectado a quienes usaban Node.js 12.16 o versiones posteriores, una versión LTS. Sin embargo, la mayoría de los problemas que vi reportados en GitHub (por ejemplo: [1], [2]) mencionan a usuarios de Node.js 13 o 14, que no son versiones estables de Node.js y no se recomiendan a menos que quieras probar lo más reciente.
Entonces, ¿qué ocurrió realmente?
El mantenedor se propuso agregar compatibilidad con los módulos ES (ESM) a la biblioteca y permitirle usar simultáneamente tanto el sistema de módulos CommonJS (CJS) de Node.js, vigente desde hace mucho tiempo, como el sistema de módulos de JavaScript estandarizado hace relativamente poco.
Esto ocurrió con la primera versión en 5 años: la 2.1.0, cuyo diff se muestra a continuación. (Consulta la comparación original de is-promise en GitHub) En resumen, las actualizaciones incluyeron:
una exportación predeterminada explícita
el uso de la forma relativamente nueva de especificar el sistema de módulos duales en
package.jsonmediante la claveexports, además de definir la clavetypede ESMuna actualización de las definiciones de TypeScript

“¿Qué salió mal realmente en este caso? El campo exports no se definió correctamente, lo que provocó la siguiente excepción y llevó a los usuarios a reportar problemas en GitHub:
¿Qué podemos aprender de este incidente?
Creo que tanto los mantenedores como los usuarios podemos aprender lecciones que nos ayudarán a prepararnos mejor para futuros incidentes como este. De hecho, Forbes ya tomó medidas al agregar controles como mantenedor para evitar que estos problemas vuelvan a ocurrir.
¿Es un problema de la cadena de suministro de software?
Este error honesto le podría haber pasado a cualquiera. No se debe a que is-promise sea una biblioteca de una sola línea de código, ni es en sí mismo un problema de la cadena de suministro de software.
El incidente de left-pad puso de manifiesto debilidades en la forma en que el registro de npm gestionaba la propiedad de los paquetes, su ciclo de vida y otros aspectos. Como resultado, se implementaron políticas y medidas para evitar que algo así volviera a ocurrir. ¿Qué hizo el registro de npm para solucionar el problema de is-promise? Nada, porque no era necesario. Como ya señalé, esto no es un problema de la cadena de suministro de software.
El versionado semántico importa
Agregar o cambiar la compatibilidad de CommonJS a ESM mediante las claves type y exports en package.json es un cambio que, según la documentación, requiere una versión incompatible. Sin embargo, is-promise@2.2.0 se publicó como un cambio menor y se convirtió en la versión más reciente para muchos paquetes que dependen de él.
Por lo tanto, nuestra segunda lección es prestar la debida atención al versionado semántico: su impacto en nuestro ecosistema es significativo.
Pruebas integrales de paquetes
Una forma de detectar este problema antes del lanzamiento habría sido agregar pruebas integrales del paquete, con el objetivo de probarlo como lo haría un consumidor e incluir pruebas básicas para asegurar que funcione según lo esperado.
Forbes agregó estas pruebas integrales en la última versión. Para un paquete llamado Pie My Vulns, adopté un enfoque un poco distinto, pero similar en concepto, con pruebas integrales. En mis pruebas de CircleCI, inicio Verdaccio como registro de paquetes local, publico el paquete y verifico que npm install, así como algunas API de prueba o comandos de CLI, funcionen según lo esperado.
Usa Node.js LTS
Siempre debes usar Node.js LTS, que al momento de escribir este artículo corresponde a la versión 12. Usar versiones de Node.js de vanguardia, como la 13 y la 14, sin duda te habría expuesto a fallas con el cambio de is-promise.
Sin embargo, ten en cuenta que, en este caso, esto no es del todo cierto, ya que el problema sí afectó a quienes usaban Node.js 12.16 y versiones posteriores, porque incluía la compatibilidad experimental con ESM sin activar una opción. Si todavía usabas la versión 12.15 o una anterior, el problema no te habría afectado.
Esto nos deja otra buena lección: asegúrate de que todas tus pruebas pasen en las versiones LTS de Node.js, aunque agregar compatibilidad con versiones posteriores, como Node.js 14, habría ayudado a detectar el problema en este caso.
¿Estás actualizando demasiado pronto?
Si recibiste un pull request de actualización automática para actualizar a la nueva versión is-promise@2.2.0 y tu entorno tenía las mismas características de impacto descritas anteriormente, también estabas expuesto al problema.
De ahora en adelante, como buena práctica, no te apresures a instalar actualizaciones para evitar problemas como este y paquetes maliciosos. Por eso, las actualizaciones automáticas de Snyk no se apresuran a actualizar tus versiones y esperan 21 días antes de proponer versiones nuevas para tus proyectos.
¿Sabes cómo funciona npx?
Noté que muchos de los problemas reportados mencionaban el uso de npx y quería aclarar algunas dudas: los archivos de bloqueo, en particular package-lock.json o yarn.lock, no sirven de ayuda en este caso, ya que por lo general no se publican con el paquete. Aunque se publicaran, no se usan durante la instalación, que es parte de lo que hace npx al ejecutarse.
Sin embargo, puedes usar el archivo de bloqueo shrinkwrap de npm (npm-shrinkwrap.json) para fijar tus dependencias, como hice con is-website-vulnerable, y asegurarme de que siempre envío el mismo árbol de dependencias: el que siempre pruebo, sin importar que se publiquen versiones nuevas en el registro.
Si quieres saber más sobre cómo funcionan los archivos de bloqueo, también escribí sobre cómo entender los archivos de bloqueo de paquetes en el ecosistema de npm.
Resumen
En conclusión, estos problemas ocurren y debemos agradecer a la comunidad por responder y a Forbes Lindesay por el trabajo que hizo para resolverlos rápidamente. También tenemos muchas lecciones que aprender como desarrolladores y mantenedores, pues todos somos parte de la comunidad de código abierto.
¿Quieres saber más? Te invito a leer el análisis posterior al incidente de Forbes y las lecciones que aprendió.
La versión corregida de is-promise para la rama 2.x es la 2.2.2. También hay una nueva versión principal, la 4.0.0, si buscas actualizar para obtener compatibilidad con los módulos ES o TypeScript.
Empieza con los desafíos de Capture the Flag
Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.