Las dependencias transitivas de npm y el riesgo que nunca elegiste

Ya están disponibles los parches de php2shell (CVE-2026-109618 y CVE-2026-109619) para PHP en fin de vida útil. Dado que las respuestas del departamento de ventas están tardando más de lo habitual debido a la elevada demanda, la forma más rápida de obtener los paquetes parcheados es comprar ELS para Runtimes en línea (se abre en una pestaña nueva). Lee las preguntas frecuentes sobre los parches

Ir directamente al resumen generado por IA de esta entrada del blog

  • Resume esta entrada con Perplexity
  • Resume esta publicación con Gemini

Principales conclusiones

  • Una dependencia transitiva es un paquete que se instala porque algo de lo que dependes lo necesita.
  • Un solo paquete de npm puede acarrear cientos de dependencias transitivas, cada una de las cuales puede ejecutar código con tus derechos de acceso.
  • Otros responsables de mantenimiento establecen los rangos en lo más profundo de tu árbol, por lo que las correcciones y los ataques llegan según su propio calendario.
  • La protección debe funcionar a nivel de registro para llegar a los paquetes que tu equipo nunca ha revisado.

Prueba esto en cualquier proyecto en el que lleves trabajando más de unos meses. Abre package.json y cuenta las dependencias. A continuación, ejecuta npm ls --all y fíjate en todo lo que va pasando por la pantalla.

La lista que aparece en package.json suele rondar las docenas. El árbol es mucho más extenso, incluso aunque npm marque las entradas repetidas como duplicadas. Todo lo que hay en ese árbol y que no hayas incluido tú mismo es una dependencia transitiva, y es la parte de tu pila a la que se le presta menos atención, a pesar de que realiza una gran parte del trabajo.

Qué es realmente una dependencia transitiva

Una dependencia directa es aquella que tú mismo has solicitado. Has ejecutado npm install express, aterrizó en package.json, y alguien de tu equipo tomó una decisión al respecto.

Una dependencia transitiva es aquella que no has solicitado. Express necesita sus propios paquetes, esos paquetes necesitan los suyos, y npm resuelve toda la cadena por ti. Nada de esto aparece en el archivo package.json, pero todo ello queda registrado en tu archivo de bloqueo, y todo lo que realmente se instale acaba en node_modules. Cualquier parte del código que se ejecute, ya sea durante la instalación o cuando tu aplicación lo cargue, tendrá el mismo acceso que el proceso que lo ejecute. En tu portátil, eso significa tu cuenta de usuario, y en CI significa el «runner» y todos los secretos a los que tenga acceso.

Es útil darle un nombre a esto, así que llamémoslo «riesgo heredado». El riesgo heredado es la exposición a la que se ve sometido tu proyecto por los paquetes que venían incluidos con los que tú elegiste. No los has evaluado, no has elegido sus versiones y, en la mayoría de los casos, ni siquiera has podido identificarlos. Aun así, los distribuyes.

¿Por qué el árbol crece tanto?

En parte, se debe simplemente a cómo ha evolucionado el ecosistema de JavaScript. npm facilitó la publicación de pequeños paquetes con una única finalidad, y la cultura se adaptó a ello. Una utilidad que hace bien una sola cosa acaba incorporándose a otros cien paquetes, y cada uno de ellos trae consigo sus propias pequeñas utilidades auxiliares. Tal y como se explica en la página de SecureChain para npm de TuxCare, un paquete npm de nivel superior puede incorporar cientos de dependencias transitivas.

La otra parte es cómo se estructura npm node_modules. Siempre que sea posible, npm los polipastos suben los paquetes por el árbol así que la mayoría aparecen una al lado de la otra en la parte superior. Eso es estupendo en cuanto al espacio en disco, pero pésimo para la intuición. Desplázate por node_modules y nada te indica que un paquete tenga cuatro niveles de profundidad y que solo esté ahí porque algo de lo que nunca has oído hablar lo ha solicitado.

Si quieres ver las capas, npm explain <package> Te mostrará todas las rutas que conducen a un paquete determinado. Ejecútalo con algún elemento aleatorio de tu archivo de bloqueo y no te sorprendas si hay más de una cadena que conduce a él.

Esas rangos de versiones no te pertenecen

Esta es la parte que suele confundir a la gente. Cuando añades una dependencia, eliges su rango de versiones. Cuando esa dependencia añade el suyo propio, el responsable del mantenimiento elige el rango y tú lo heredas.

La mayoría de esos intervalos utilizan un símbolo de intercalación, como ^4.17.0, que semver lo trata como Cualquier versión 4.x a partir de la 4.17.0. Tu archivo de bloqueo indica las versiones exactas que funcionaron la última vez, por lo que al confirmarlo y utilizar npm ci En CI es muy importante. Pero en cuanto alguien borra y vuelve a generar el archivo de bloqueo, ejecuta una actualización o realiza una instalación en un proyecto que carece de él, los rangos del árbol pueden resolverse a la versión más reciente que permitan, incluida una que se haya publicado hace una hora.

Eso resulta muy práctico cuando un responsable de mantenimiento publica una corrección. Sin embargo, supone un problema cuando publica cualquier otra cosa.

Los atacantes ya se dieron cuenta de esto hace mucho tiempo. Ya en 2018, un nuevo responsable del mantenimiento del popular event-stream paquete Se ha añadido una dependencia llamada flatmap-stream, que resultó contener código destinado a robar fondos de una aplicación concreta de monedero de Bitcoin. Nadie que utilice event-stream había elegido flatmap-stream. Acaba de llegar.

La misma estrategia se repitió en junio de 2026. Cuando los atacantes consiguieron acceso de publicación al ámbito «Mastra» de npm, subieron más de 140 versiones de paquetes afectadas en menos de dos horas, según informan The Hacker News y Toma de corriente. El código de Mastra en sí no sufrió modificaciones. Lo que cambió fue la lista de dependencias, que ahora incluía easy-day-js, un doble del dayjs biblioteca de fechas. Quienquiera que controlara easy-day-js había publicado una versión 1.11.21 limpia y funcional el día anterior, y luego lanzó la 1.11.22 con un gancho de postinstalación malicioso a primera hora del 17 de junio, poco antes de que salieran las versiones de Mastra. Las nuevas versiones de Mastra solicitaban ^1.11.21, por lo que cada nueva instalación se dirigía directamente a la versión corrupta. Si hubieras revisado una de esas versiones, el único cambio real que habrías encontrado en package.json era una única dependencia nueva. Todo lo peligroso se encontraba un nivel más abajo.

Por qué se tarda tanto en solucionar una vulnerabilidad grave

Las vulnerabilidades conocidas también se acumulan en las capas más profundas del árbol. Cuando Endor Labs analizó los paquetes Java más utilizados para su informe «Estado de la gestión de dependencias» de 2022, descubrió que aproximadamente el 95 % de las dependencias vulnerables eran transitivas. Se trataba de paquetes de Maven, por lo que hay que tomar esta cifra como una referencia de la magnitud del problema en npm, más que como una cifra específica de npm.

Imagina que un CVE afecta a un paquete situado tres niveles por debajo de cualquiera de los que hayas elegido. La corrección se publica en el proyecto original y, en un mundo ideal, tu árbol simplemente la incorpora. En la práctica, eso solo funciona si todos los paquetes que hay entre tú y la corrección admiten la nueva versión. Si algún paquete padre de la cadena ha fijado una versión concreta o ha limitado su rango por debajo de la versión corregida, el proceso normal se detiene ahí mismo. Ahora tienes que esperar a que ese responsable de mantenimiento publique una versión, y posiblemente también a que lo haga el responsable de mantenimiento que está por encima de él.

Por eso npm audit Puede resultar muy frustrante. Es muy útil para indicarte que hay algún problema en lo más profundo del árbol. Sin embargo, es mucho peor a la hora de solucionar el problema, porque a menudo no te corresponde a ti arreglarlo. npm audit fix no aplicará un cambio de versión principal según Semver sin --force, y --force Es una forma bastante directa de averiguar qué partes de tu aplicación fallan.

Además, están anulaciones, que npm se incorporó en la versión 8.3. Una sustitución te permite forzar una versión concreta de un paquete transitivo o sustituirlo por una bifurcación, independientemente de lo que haya solicitado su paquete padre. Esto puede eliminar la vulnerabilidad. Solo tienes que dejar claro lo que has hecho. Ahora estás ejecutando una combinación de paquetes que el responsable del paquete padre probablemente nunca haya probado, y la sustitución suele quedar en tu package.json mucho después de que nadie recuerde por qué está ahí.

El responsable del mantenimiento que no va a volver

Todo eso supone que alguien en las fases anteriores sigue lanzando versiones. En lo más profundo de un árbol de código grande, eso no siempre es así.

Muchos de los paquetes que se encuentran en la parte inferior de un árbol son pequeños y están prácticamente terminados, y algunos de sus mantenedores dejaron el proyecto hace años. Cuando aparece un CVE en uno de ellos, no hay nadie que implemente la corrección, por lo que no hay nada que propagar hacia arriba. El escáner sigue señalándolo y no hay una corrección en el código fuente a la que pueda apuntar una sustitución. Lo único que queda es crear una bifurcación y aplicar el parche uno mismo, encontrar a alguien que adapte la corrección al paquete original o sustituir cualquier paquete de nivel superior que dependa de él. Ninguna de estas opciones es rápida, y la última puede acabar convirtiéndose en una reescritura para la que nadie tiene tiempo.

La vía de retroportabilidad es precisamente para lo que sirve el servicio «Endless Lifecycle Support» (ELS) de TuxCare. Para los paquetes que cubre, el ELS retroporta las correcciones de seguridad a las versiones exactas que ya estás utilizando una vez que finaliza el mantenimiento del proyecto original, de modo que puedas seguir utilizando la versión actual sin verte obligado a actualizar.

Proteger el árbol en su totalidad

La mayoría de las correcciones mencionadas anteriormente se realizan dentro de tu proyecto, paquete por paquete, y normalmente después de que el problema ya se haya incorporado a tu árbol de código. Las sustituciones, las correcciones de auditoría y las fijaciones manuales son formas de corregir algo que ha llegado desde el código fuente original.

La alternativa es cambiar la fuente de origen de todo el árbol. Esa es la idea en la que se basa SecureChain. Sustituye el registro público de npm por un repositorio seleccionado cuidadosamente del que se resuelven tus instalaciones, incluidos los paquetes transitivos. Los paquetes se recompilan a partir de código fuente verificado en entornos aislados y reforzados, y se analizan en busca de malware conocido y patrones de código sospechosos; cualquier elemento que no supere la inspección se bloquea antes de que llegue a tu entorno. Si algo que necesita tu árbol aún no está en el catálogo, TuxCare lo añade. Los paquetes activos se actualizan en virtud del SLA a medida que surgen nuevas vulnerabilidades (CVE), y cuando un paquete gestionado por SecureChain llega al final de su ciclo de vida, ELS continúa la cobertura sin que tengas que hacer nada.

Una vez que configures npm o tu gestor de repositorios para que lo utilice, el trabajo diario no cambia. npm, Yarn y pnpm mantienen los mismos comandos, y SecureChain funciona junto con Artifactory, Nexus o GitHub Packages si ese es el método que tu equipo ya utiliza para gestionar los paquetes. Lo que cambia es que el paquete que se encuentra cuatro niveles más abajo, aquel del que nadie ha oído hablar nunca, pasa por el mismo proceso de recompilación y filtrado que el marco de trabajo que has elegido expresamente.

Por dónde empezar

Antes de cambiar nada, hazte una idea clara de lo que tienes en marcha. Ejecuta npm ls --all y guarda el resultado para poder compararlo más tarde. Elige unos cuantos paquetes que no reconozcas y ejecuta npm explain en ellos. Asegúrate de que tu archivo de bloqueo se haya guardado y de que CI utilice npm ci, por lo que las compilaciones se instalan a partir del archivo de bloqueo en lugar de volver a resolver los rangos, y las versiones solo cambian cuando alguien realiza un commit de un archivo de bloqueo actualizado.

A continuación, revisa tus últimos informes de auditoría y clasifica las conclusiones en dos grupos: las que has elegido tú y las que has heredado. El segundo grupo es un buen indicador de dónde se encuentran realmente los riesgos que has heredado, y es el que más merece la pena analizar con detenimiento.

PREGUNTAS FRECUENTES

¿Qué es una dependencia transitiva?

Una dependencia transitiva es un paquete que tu proyecto instala porque una de tus dependencias lo necesita, no porque lo hayas añadido tú mismo. En npm, las dependencias transitivas se registran en tu archivo de bloqueo y, una vez instaladas, aparecen en node_modules, pero no están declaradas en el archivo package.json.

¿Cuál es la diferencia entre las dependencias directas y las transitivas?

Las dependencias directas son los paquetes que tu proyecto declara en su propio package.json. Las dependencias transitivas son todos los paquetes que estos incluyen, en todos los niveles del árbol. Tú controlas los rangos de versiones de las dependencias directas, mientras que los rangos de las transitivas los establecen sus paquetes padres.

¿Cómo puedo ver mis dependencias transitivas en npm?

Ejecutar npm ls --all para imprimir el árbol completo de dependencias. Para averiguar por qué está instalado un paquete concreto, ejecuta npm explain <package>, que muestra todas las rutas de dependencia que conducen a ella.

¿Cómo puedo solucionar una vulnerabilidad en una dependencia transitiva?

En primer lugar, comprueba si al actualizar la dependencia directa situada por encima se instala la versión corregida. Si el rango de versiones del paquete padre impide la corrección, puedes utilizar las anulaciones de npm, disponibles desde la versión 8.3, para forzar una versión parcheada, aunque estarás ejecutando una combinación que probablemente el mantenedor del paquete padre no haya probado. Si no existe una versión corregida porque el paquete ya no se mantiene, tus opciones son una corrección retroportada, tu propia bifurcación o parche, o sustituir el paquete que depende de él.

¿Suponen las dependencias transitivas un riesgo para la seguridad?

Sí. Cualquier fragmento de código suyo que se ejecute obtiene el mismo acceso que tu compilación o tu aplicación; algunos de ellos ejecutan scripts de instalación, y un solo paquete de nivel superior puede incorporar cientos de ellos. Los ataques a la cadena de suministro, incluidos el de Event-Stream en 2018 y el de Mastra en 2026, entregaron su carga útil a través de una dependencia transitiva recién añadida.

¿Qué es el riesgo hereditario?

El riesgo heredado es la exposición a riesgos de seguridad que asume un proyecto debido a los paquetes que vienen incluidos con las dependencias que ha elegido. Abarca vulnerabilidades, código malicioso y paquetes abandonados en cualquier punto del árbol de dependencias transitivas.

Fuentes

Resumen
Las dependencias transitivas de npm y el riesgo que nunca elegiste
Nombre del artículo
Las dependencias transitivas de npm y el riesgo que nunca elegiste
Descripción
El archivo package.json recoge lo que has seleccionado. Las dependencias transitivas de npm son el resto, y ahí es donde reside gran parte del riesgo de la cadena de suministro.
Autor
Nombre del editor
TuxCare
Logotipo de la editorial

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.

Índice
Obtenga las respuestas de seguridad de código abierto que necesita

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.