🛡️ Seguridad 🟨 JavaScript & TypeScript

Supply chain attacks: cómo protegerte de paquetes npm maliciosos

En 46 minutos, una dependencia comprometida infectó decenas de miles de entornos. Repasamos los vectores reales de los supply chain attacks en 2026, el caso LiteLLM como ejemplo, y las prácticas concretas que de verdad reducen el riesgo.

📅 8 de abril de 2026 ⏱️ 9 min de lectura ✍️ Equipo ProgramacionWebs

npm install ejecuta código arbitrario en tu máquina. Esa frase debería resultar obvia para cualquiera que lleve un tiempo en el ecosistema, pero la forma en que solemos tratar las dependencias —como texto declarativo en un package.json, no como software de terceros con permisos de ejecución— hace que se nos olvide constantemente. En 2026 esa distracción colectiva ha tenido consecuencias medibles: campañas de malware que se propagan solas entre paquetes, cuentas de mantenedores secuestradas para publicar versiones envenenadas, y un incidente que demostró que ni siquiera las herramientas que usas para auditar tu cadena de suministro están a salvo de ser el punto de entrada.

Los tres vectores reales

Casi todos los incidentes de supply chain de los últimos años caen en una de tres categorías, y conviene distinguirlas porque la defensa es distinta para cada una.

Typosquatting. Un paquete con un nombre casi idéntico a uno popular (reqeusts en vez de requests, expres en vez de express) que confía en que alguien lo instale por error de tecleo o por una dependencia transitiva mal escrita. Es el vector más antiguo y el más barato de montar para un atacante: publicar un paquete nuevo en npm o PyPI no requiere ninguna reputación previa.

Mantenedores comprometidos. Un atacante obtiene el token de publicación de una cuenta legítima —por phishing, por credential stuffing, o por robo directo de un token filtrado en otro sitio— y publica una versión maliciosa de un paquete real, con historial y confianza reales. Esto es mucho más peligroso que el typosquatting porque el paquete ya está instalado en miles de proyectos que reciben la actualización automáticamente en el siguiente npm install o build de CI.

Compromiso en cascada a través de la cadena de build. El más sofisticado de los tres: el atacante no ataca directamente el paquete que le interesa, sino una pieza de la infraestructura que ese paquete usa para publicarse (un escáner de seguridad, una acción de CI, un token de un pipeline). Desde ahí, salta a paquetes que en apariencia no tienen relación entre sí, simplemente porque comparten la misma infraestructura de publicación comprometida.

El caso LiteLLM: cuando la herramienta de seguridad es el vector de entrada

En marzo de 2026 ocurrió un incidente que ilustra el tercer vector mejor que cualquier explicación teórica. LiteLLM es una librería Python que actúa como gateway unificado para más de 140 proveedores de modelos de lenguaje, con alrededor de 95 millones de descargas mensuales; frameworks de agentes como CrewAI y DSPy la usan como capa de interoperabilidad, y Microsoft GraphRAG la integra para dar soporte a proveedores no-OpenAI.

La cadena de compromiso empezó fuera de LiteLLM por completo:

  1. 19 de marzo — un atacante identificado como el grupo TeamPCP obtuvo credenciales comprometidas de Trivy, un escáner de vulnerabilidades muy usado en pipelines de CI/CD, y publicó una versión envenenada del propio escáner.
  2. 20-22 de marzo — con tokens de npm robados de entornos que usaban el Trivy comprometido, el ataque se propagó de forma semiautomática: identificaba qué paquetes podía publicar cada token robado, subía una versión de parche y repetía el proceso, expandiéndose por decenas de paquetes adicionales sin intervención manual constante.
  3. 24 de marzo, 10:39 y 10:52 UTC — usando el mismo patrón de credenciales comprometidas, se publicaron dos versiones maliciosas de LiteLLM en PyPI: 1.82.7 y 1.82.8.

La versión 1.82.7 inyectaba código directamente en litellm/proxy/proxy_server.py, activo solo si el proyecto usaba el proxy. La 1.82.8 era más peligrosa: incluía un fichero litellm_init.pth, un mecanismo del propio intérprete de Python que ejecuta código automáticamente en cada arranque, sin necesidad de que nadie importe el módulo explícitamente. El payload buscaba variables de entorno, claves SSH, credenciales de AWS/GCP/Azure y tokens de Kubernetes, los cifraba y los exfiltraba contra un dominio que imitaba al oficial (models.litellm.cloud, que no pertenece a LiteLLM).

PyPI puso ambas versiones en cuarentena a las 11:25 UTC, 46 minutos después de la primera publicación. En ese margen tan corto se registraron 46.996 descargas (32.464 de la 1.82.8, 14.532 de la 1.82.7). Como el payload de la 1.82.8 se ejecutaba durante la propia instalación —no al importar la librería—, se estima que unos 23.000 entornos quedaron comprometidos solo con hacer pip install, sin que el proyecto llegara siquiera a usar el paquete en ejecución.

LiteLLM respondió rotando credenciales de mantenedores, contratando una investigación forense externa (Mandiant) y reconstruyendo su pipeline de CI/CD desde cero antes de publicar una versión limpia. El propio equipo confirmó que todas las versiones 1.82.6 e inferiores no estaban afectadas, y que los usuarios de la imagen Docker oficial del proxy (con dependencias fijadas) y los clientes de LiteLLM Cloud no se vieron expuestos.

El otro gran patrón de 2026: gusanos que se autopropagan en npm

El ecosistema npm ha tenido su propia versión de ataques en cascada, con un matiz distinto: en vez de depender de un humano que publica manualmente cada paquete comprometido, el malware se propaga solo. En febrero de 2026, una campaña bautizada como “Sandworm mode” identificó 19 paquetes typosquatted que, una vez instalados, robaban credenciales del entorno y las usaban para infectar automáticamente otros paquetes a los que esas credenciales tenían acceso de publicación, sin intervención adicional del atacante.

En mayo, Microsoft documentó una campaña distinta centrada específicamente en robar credenciales de AWS, tokens de HashiCorp Vault y secretos de pipelines de CI/CD a través de paquetes typosquatted publicados desde una cuenta de mantenedor recién creada, con 14 paquetes maliciosos publicados en una ventana de cuatro horas. Y en junio, el secuestro de una única cuenta de mantenedor permitió republicar más de 140 paquetes del framework de agentes Mastra AI con una dependencia typosquat inyectada (easy-day-js), afectando de un solo golpe a todo un ecosistema de paquetes que hasta ese momento eran legítimos.

Prácticas concretas que sí reducen el riesgo

Ninguna de estas prácticas es una bala de plata por separado, pero combinadas cambian sustancialmente la ventana de exposición real de un proyecto.

Lockfiles siempre, npm ci en CI, nunca npm install. El lockfile (package-lock.json, pnpm-lock.yaml) fija la versión exacta —y el hash de integridad— de cada dependencia, directa y transitiva. npm ci instala estrictamente desde el lockfile y falla si package.json y lockfile no coinciden, eliminando el margen para que CI resuelva silenciosamente una versión distinta a la que alguien revisó en el código.

Evita rangos de versión amplios en dependencias críticas. ^1.82.0 acepta cualquier 1.x.x posterior con la misma versión mayor, incluida una versión maliciosa publicada hace cinco minutos, en la primera instalación limpia que se haga después. Fijar una versión exacta (1.82.6 sin ^ ni ~) no es paranoia si esa dependencia toca credenciales, red o procesos de build.

Desactiva los scripts de instalación por defecto. npm install --ignore-scripts evita que cualquier dependencia (directa o transitiva) ejecute código arbitrario durante la instalación, que es precisamente el vector que explotó litellm_init.pth. Cubre la inmensa mayoría de proyectos web sin fricción real; los pocos paquetes que de verdad necesitan compilar algo nativo se pueden ejecutar manualmente y de forma consciente.

# En package.json
{
  "scripts": {
    "postinstall": "echo 'scripts de terceros deshabilitados por defecto'"
  }
}

# O directamente en la instalación / CI
npm ci --ignore-scripts

Minimiza dependencias, sobre todo transitivas. Cada dependencia que añades no es solo su código: es todo su árbol de dependencias, con sus propios mantenedores, sus propios tokens de publicación, y su propia superficie de ataque. Antes de instalar un paquete para resolver algo trivial, evalúa si vale la pena el coste de confianza frente a escribir esas quince líneas tú mismo.

Exige provenance y firma cuando esté disponible. npm soporta npm provenance, que vincula criptográficamente un paquete publicado con el pipeline de CI exacto que lo generó, usando Sigstore. No evita todos los ataques, pero hace mucho más difícil publicar una versión maliciosa sin dejar un rastro verificable de que no vino del pipeline oficial.

Genera y revisa un SBOM (Software Bill of Materials). Saber exactamente qué paquetes —con qué versiones exactas— corren en producción es lo que te permite responder en minutos, no en días, a la pregunta “¿me afecta el incidente que acaban de anunciar de tal paquete”.

Si sospechas que ya te ha afectado

Un playbook mínimo, pensado para actuar en minutos y no en horas:

  1. Aísla, no borres todavía. Desconecta de red la máquina o el pipeline afectado antes de limpiar nada; necesitarás preservar evidencia para saber qué se exfiltró realmente.
  2. Rota todos los secretos accesibles desde ese entorno: claves de API, credenciales de nube, tokens de CI/CD, claves SSH. Asume que cualquier secreto legible desde el proceso comprometido ya está en manos del atacante.
  3. Busca artefactos conocidos del ataque concreto (en el caso LiteLLM, el fichero litellm_init.pth y tráfico saliente hacia dominios que imitan al oficial).
  4. Audita el historial de versiones instaladas en todos los entornos —desarrollo, CI, producción—, no solo el que detectaste primero: los ataques en cascada rara vez se limitan a una sola máquina.
  5. Reconstruye el pipeline de publicación desde credenciales nuevas antes de considerar cerrado el incidente, no solo el entorno donde se detectó.

La lección de fondo de 2026 no es “usa menos dependencias” —ninguna aplicación moderna se construye realista sin cientos de ellas— sino que la confianza en una dependencia no es binaria ni permanente: un paquete seguro hoy puede estar comprometido en la próxima versión, publicada por la misma cuenta de siempre. Las prácticas de esta sección no eliminan ese riesgo, pero reducen drásticamente cuánto puede propagarse antes de que alguien lo note.

Compartir