Regresar
408

Supply chain: cómo un npm install puede comprometer tu proyecto

Actualizado: 15/09/2026

Un proyecto nuevo con un puñado de dependencias declaradas termina, tras el primer comando de instalación, con más de mil paquetes en disco. Ninguno de ellos fue elegido por nadie del equipo: llegaron porque una dependencia necesitaba otra, que necesitaba otra.

Cada uno de esos paquetes es código de una persona desconocida que se ejecuta con los mismos permisos que el tuyo. Y varios de ellos pueden ejecutar instrucciones durante la propia instalación, antes de que nadie haya escrito una sola línea.

Lo que realmente se instala

La distancia entre lo que se declara y lo que acaba en disco es la primera cosa que conviene ver con claridad.

Un árbol que parte del proyecto con doce dependencias declaradas, se abre a cuatro paquetes directos y de ahí a catorce paquetes más pequeños, indicando que la cadena continúa hasta mil trescientos paquetes, y señalando que basta con que uno de ellos esté comprometido
Todos ellos pueden ejecutar código durante la instalación, con tus permisos.

Esa estructura explica por qué este riesgo es distinto a los demás. No hace falta que alguien ataque tu aplicación: basta con que comprometa cualquiera de los paquetes de los que depende, incluso uno que nadie ha oído nombrar y que está a cuatro niveles de profundidad.

Y el efecto se multiplica en la otra dirección. Quien logra controlar un paquete popular alcanza de una vez a todos los proyectos que dependen de él, lo que convierte estos ataques en algo muy rentable comparado con atacar objetivos de uno en uno.

Las formas en que esto sale mal

Los incidentes conocidos siguen unos pocos patrones, y reconocerlos ayuda más que cualquier lista de herramientas.

  • Cuenta comprometida. Alguien obtiene el acceso de quien mantiene un paquete legítimo y publica una versión con código añadido. Es el caso más frecuente y el más difícil de detectar, porque el paquete es el de siempre.
  • Traspaso a manos equivocadas. Quien mantiene un proyecto sin tiempo lo cede a alguien que se ofrece amablemente, y esa persona publica después una versión maliciosa.
  • Nombre parecido. Se publica un paquete cuyo nombre difiere en una letra del popular, esperando el error de tecleo. Suena ingenuo y funciona.
  • Nombre interno expuesto. Si una organización usa paquetes internos y alguien registra ese mismo nombre en el repositorio público, algunas configuraciones descargan el público por tener número de versión mayor.
  • Abandono. No es un ataque: el paquete deja de mantenerse, aparece una vulnerabilidad y no hay nadie para corregirla.

El último caso es el más probable en un proyecto pequeño, y por eso conviene mirar la actividad reciente de una dependencia antes de adoptarla, no solo su número de descargas.

La instalación ejecuta código

Este punto merece detenerse porque contradice la intuición de mucha gente: instalar no es copiar archivos.

Varios ecosistemas permiten que un paquete defina instrucciones que se ejecutan automáticamente al instalarse. Existen por motivos legítimos, como compilar componentes nativos, y significan que un paquete comprometido actúa en el momento de la instalación, sin que su código llegue a importarse nunca.

Lo que hace ese código en los incidentes reales es casi siempre lo mismo: buscar credenciales en el entorno, en los archivos de configuración y en las variables del sistema de integración continua, y enviarlas fuera.

De ahí una consecuencia incómoda para el trabajo diario: ejecutar una instalación en una máquina donde hay credenciales de producción es exponerlas a cualquiera de esos mil paquetes. Es un argumento más para separar entornos y para que las claves de producción no estén en máquinas de desarrollo.

Cuando el proyecto no necesita compilación nativa, se puede desactivar la ejecución de esas instrucciones en la instalación. Es una medida de una línea que elimina buena parte de esta superficie.

El archivo de bloqueo es la defensa más barata

De todas las medidas posibles, la que mejor relación tiene entre esfuerzo y protección ya está en casi todos los proyectos, y en bastantes se ignora o se borra por desconocimiento.

El archivo de bloqueo registra la versión exacta de cada paquete instalado, incluidos los que llegaron indirectamente, y su huella criptográfica. Con él, dos instalaciones en momentos distintos producen exactamente lo mismo.

Sin él, cada instalación resuelve las versiones de nuevo según los rangos declarados, así que una versión maliciosa publicada esta mañana puede entrar en el despliegue de esta tarde sin que nadie haya cambiado nada.

Dos reglas sobre el bloqueo

Se confirma en el control de versiones, siempre. Es parte del proyecto, no un archivo temporal.

Y en los servidores de integración y despliegue se usa el comando que instala exactamente lo bloqueado y falla si no coincide, en lugar del comando habitual que puede actualizar versiones.

Actualizar también es un riesgo, y no actualizar lo es más

Aquí aparece una tensión real que no tiene una respuesta cómoda. Actualizar expone a versiones nuevas que podrían estar comprometidas; no actualizar deja vulnerabilidades conocidas abiertas indefinidamente.

La estadística es clara sobre cuál de los dos riesgos se materializa más: las vulnerabilidades conocidas en dependencias desactualizadas causan muchos más incidentes que los paquetes maliciosos. Quedarse quieto no es la opción segura.

El equilibrio razonable en un proyecto pequeño es aplicar rápido las actualizaciones de seguridad, revisar las demás con cierta periodicidad en lugar de a diario, y evitar actualizar todo en bloque el día antes de una entrega.

Ayuda mucho tener activado el análisis automático de vulnerabilidades que ofrecen las plataformas de alojamiento de código, que avisa cuando una dependencia tiene un problema conocido y con frecuencia propone el cambio de versión.

Cómo mirar un paquete antes de adoptarlo

Las descargas semanales son el dato que todo el mundo consulta y el que peor predice el riesgo, porque un paquete muy popular abandonado hace dos años sigue teniendo cifras enormes.

Lo que de verdad informa se revisa en tres minutos en la página del proyecto. Cuándo fue el último cambio, si las incidencias abiertas reciben respuesta, cuánta gente lo mantiene y qué pasó la última vez que se reportó un fallo de seguridad.

La cifra de personas que lo mantienen merece atención especial. Una parte enorme del software que sostiene la industria depende de proyectos que mantiene una sola persona en su tiempo libre, sin ninguna obligación de seguir haciéndolo. No es un reproche a esa persona: es un dato de riesgo que conviene conocer antes de construir encima.

Y hay una comprobación que ahorra sustos: mirar cuántas dependencias arrastra el paquete antes de instalarlo. Varios buscadores lo muestran, y la diferencia entre uno con tres y otro con doscientas es la diferencia entre una decisión pequeña y una grande.

Para lo que va a sostener algo importante, conviene además leer por encima el código. No entero, pero sí lo suficiente para ver si hace lo que dice y si tiene una calidad razonable. Es más rápido de lo que parece y evita adoptar cosas que nadie ha revisado nunca.

Saber qué tienes instalado

Cuando aparece un aviso sobre un paquete comprometido, la primera pregunta es siempre la misma: ¿lo tengo yo? Y responderla sin preparación lleva más tiempo del que debería.

La forma ordenada de tenerlo resuelto es un inventario de lo que compone cada versión desplegada: qué paquetes, en qué versiones, en qué despliegue. Existe un formato estándar para eso y varias herramientas lo generan automáticamente durante la construcción.

Para un proyecto pequeño no hace falta llegar tan lejos, y basta con dos hábitos. El primero es que el archivo de bloqueo esté versionado, que ya cubre la pregunta de qué había instalado en cada momento. El segundo es saber qué versión concreta del proyecto está desplegada en producción, algo que sorprendentemente muchos equipos no pueden responder con certeza.

Con esas dos cosas, comprobar si un paquete afectado está en producción es una búsqueda de treinta segundos en lugar de una tarde de incertidumbre.

Qué hacer cuando sale el aviso

Los incidentes de este tipo se anuncian públicamente, y el orden de actuación evita tanto el pánico como la inacción.

  • Comprueba si te afecta de verdad. No basta con tener el paquete: hay que mirar si la versión instalada está en el rango afectado. Muchos avisos alarmantes no aplican a la versión que uno tiene.
  • Mira si la ruta vulnerable se usa. Una vulnerabilidad en una función que tu código nunca llama tiene un riesgo distinto a una en el camino principal.
  • Actualiza, y si no hay versión corregida, valora quitarlo. Cuando el paquete está abandonado y no va a llegar corrección, sustituirlo es la única salida real.
  • Si hubo ejecución de código malicioso, rota las credenciales. Es el paso que más se omite. Si un paquete comprometido se instaló en una máquina o en el sistema de integración, hay que asumir que todo lo que había en ese entorno quedó expuesto.

Ese último punto conecta con lo dicho antes sobre secretos: la razón de que la rotación sea barata es precisamente poder ejecutarla sin drama un día como este.

Reducir la superficie es mejor que vigilarla

Todas las medidas anteriores gestionan el riesgo de lo que ya está instalado. La más efectiva es tener menos cosas instaladas.

Conviene preguntarse, antes de añadir una dependencia, cuánto código propio ahorra realmente. Añadir un paquete de mil líneas y trescientas dependencias para resolver algo que se escribe en veinte es un intercambio malo, y ocurre con frecuencia porque buscar el paquete es más rápido que pensar la solución.

Conviene también revisar de vez en cuando qué dependencias ya no se usan. Se acumulan restos de funcionalidades que se quitaron, y cada una sigue instalándose y ejecutándose.

Y merece atención especial lo que se añade en el navegador: cada script de terceros incrustado en una página se ejecuta con acceso completo a ella, incluidos los formularios. Un componente de análisis o de chat comprometido puede leer lo que los usuarios escriben.

Qué se le escapa a una IA con esto

Pedir ayuda para resolver algo produce con frecuencia una solución que empieza por instalar un paquete, porque es la forma en que está escrita la mayor parte del material del que aprendió. La dependencia aparece antes que la pregunta de si hace falta.

Hay además un riesgo particular y bien documentado: los modelos a veces citan paquetes que no existen, con nombres plausibles. Si alguien registra ese nombre inventado en el repositorio público, la sugerencia se convierte en un vector de ataque directo.

La comprobación que lo evita es rápida: antes de instalar algo sugerido, mirar el paquete en el repositorio oficial y confirmar que existe de verdad, que su nombre coincide exactamente y que tiene actividad reciente.

Y conviene pedir explícitamente la alternativa sin dependencias. La pregunta de cuánto código propio haría falta para resolver esto sin instalar nada devuelve a menudo una respuesta de veinte líneas que resulta preferible a añadir un árbol entero de paquetes.


Guía de referencia principiante