El lunes 11 de mayo de 2026 a las 19:20 UTC, alguien publicó 84 versiones maliciosas de paquetes de TanStack en npm. Seis minutos después ya estaban disponibles para cualquiera que ejecutara npm install. Veintiséis minutos después del primer paquete publicado, un investigador de StepSecurity lo detectó y empezó a actuar.
En esos 26 minutos, desarrolladores de todo el mundo instalaron sin saberlo un troyano que buscaba tokens de GitHub, claves SSH, credenciales de AWS y las mandaba a servidores controlados por el atacante. OpenAI confirmó que dos de sus empleados fueron comprometidos. Y lo más perturbador: los paquetes maliciosos tenían firmas digitales válidas. Todo parecía legítimo.
Si estás empezando en programación web y alguien te dice "ejecuta npm install", probablemente lo haces sin pensarlo. Este post existe para que entiendas qué pasa exactamente cuando lo haces, por qué puede ser peligroso, y qué puedes hacer para protegerte sin volverte paranoico.
⚠️ Si tienes instalado @tanstack/react-router
Comprueba que tu versión no está entre las publicadas el 11 de mayo de 2026 (las versiones exactas están en el advisory oficial CVE-2026-45321). Si instalaste ese día, rota tus tokens de GitHub y npm inmediatamente. Las versiones actuales son seguras.
Qué es npm y qué hace realmente
npm son las siglas de Node Package Manager. Es el sistema que usa JavaScript para gestionar librerías de terceros — lo mismo que Composer en PHP o pip en Python. Cuando ejecutas npm install nombre-paquete, npm va a un registro central en registry.npmjs.org, descarga el paquete y lo pone en la carpeta node_modules de tu proyecto.
Hasta aquí parece inofensivo. El problema está en lo que ocurre durante y después de esa descarga.
Los paquetes npm pueden incluir lifecycle scripts: comandos que se ejecutan automáticamente en tu ordenador durante la instalación. Los más comunes son preinstall, install, postinstall y prepare. Cuando ejecutas npm install, npm descarga el paquete y luego ejecuta esos scripts con tus permisos de usuario, sin pedirte confirmación.
Eso significa que si un paquete tiene un script malicioso en postinstall, ese script puede leer archivos de tu sistema, abrir conexiones de red, instalar otros programas o enviar datos a donde quiera. Todo automáticamente, todo mientras tú esperas a que aparezca el cursor.
En el caso de TanStack, el archivo malicioso se llamaba router_init.js y se ejecutaba mediante el script prepare. Dos megas y pico de JavaScript ofuscado que hacía exactamente eso.
Qué robaba el malware del ataque TanStack
El código malicioso que se instalaba tenía una misión muy concreta: buscar en tu ordenador todas las contraseñas y claves de acceso que pudieras tener guardadas y mandárselas al atacante.
Para entender qué buscaba, necesitas saber qué es un token. Un token es una contraseña especial que generan servicios como GitHub, npm o AWS para que puedas conectarte desde tu terminal o desde scripts automáticos sin escribir tu contraseña cada vez. Son cadenas largas de caracteres aleatorios que se guardan en archivos de texto en tu ordenador. GitHub los guarda en ~/.git-credentials, npm en ~/.npmrc, y así con cada servicio.
El malware buscaba exactamente esos archivos:
- Tokens de GitHub — para poder hacer commits y acceder a repositorios en tu nombre
- Tokens de npm — para publicar paquetes haciéndose pasar por ti
- Credenciales de AWS, Google Cloud y Azure — acceso a infraestructura en la nube que puede costar miles de euros al mes
- Claves privadas SSH — para conectarse a servidores como si fueras tú
- Variables de entorno — esos archivos
.envdonde guardamos contraseñas de base de datos y API keys (aquí explico por qué nunca deben subir a Git)
Una vez recogido todo eso, lo mandaba al atacante por tres canales distintos a la vez. Uno de ellos usaba una red de mensajería cifrada, con lo que era imposible interceptar el contenido aunque alguien estuviera monitorizando tu tráfico de red.
Luego hacía algo que lo convierte en el primer gusano documentado de npm: con el token de npm robado, buscaba qué otros paquetes publicabas tú en npm y los infectaba también. El malware se reproducía solo saltando de desarrollador en desarrollador.
Para rematar, se instalaba como un proceso en segundo plano que arrancaba solo cada vez que encendías el ordenador — lo que en sistemas tipo Unix se llama un daemon o servicio. Ese proceso comprobaba cada 60 segundos si el atacante seguía teniendo acceso a tu cuenta de GitHub. Si detectaba que habías cambiado el token y ya no podía conectarse, ejecutaba rm -rf ~/, que borra tu carpeta de usuario entera.
Por eso el orden de desinfección importa muchísimo: primero eliminas ese proceso en segundo plano, y solo después cambias los tokens. Si lo haces al revés, el proceso detecta que ya no puede conectarse y borra tus archivos antes de que puedas hacer nada.
Cómo consiguieron publicar sin robar ninguna contraseña
Esta es la parte que más cuesta entender, pero es importante porque rompe una idea que mucha gente tiene: que si proteges bien tu contraseña de npm, nadie puede publicar paquetes en tu nombre. El ataque de TanStack demostró que no siempre es así.
TanStack, como la mayoría de proyectos grandes, usa GitHub Actions para publicar sus paquetes automáticamente. GitHub Actions es un sistema que ejecuta tareas de forma automática cuando pasan cosas en el repositorio: alguien hace un commit, se abre un pull request, se crea una nueva versión... el sistema lo detecta y ejecuta los pasos que le hayas configurado — compilar el código, pasar tests, publicar en npm.
Para publicar en npm de forma automática sin escribir contraseñas, GitHub Actions usa tokens temporales. El atacante no robó ningún token — encontró una forma de colarse dentro del propio sistema de publicación de TanStack y usarlo desde dentro.
El truco fue técnico: abrió un pull request en el repositorio de TanStack de forma que el sistema de automatización ejecutó código del atacante con los permisos del repositorio. Ese código modificó archivos temporales que GitHub Actions guarda entre ejecuciones para ir más rápido. La siguiente vez que TanStack publicó una versión legítima, el sistema de automatización usó esos archivos modificados y publicó también las versiones maliciosas, con el token legítimo de TanStack.
El resultado fue que los paquetes maliciosos tenían todas las firmas digitales correctas. Si antes de instalar hubieras comprobado que el paquete venía firmado por TanStack, habrías visto que sí, que era legítimo. Porque técnicamente lo era: había sido publicado por el sistema de TanStack, solo que ese sistema había sido manipulado. Es la primera vez que se documenta este tipo de ataque con ese nivel de sofisticación en npm.
Por qué node_modules pesa tanto y qué hay dentro
La primera vez que ves una carpeta node_modules de 200 MB para un proyecto de tres archivos, parece una broma. No lo es, y entender por qué existe te ayuda a entender el riesgo. Si Node.js aún te suena a chino, este post explica qué es y cómo funciona npm en la práctica antes de que llegues a necesitar preocuparte por esto.
Cuando instalas un paquete, ese paquete puede depender de otros paquetes, que a su vez dependen de otros. npm descarga el árbol completo de dependencias. Un proyecto típico de React tiene entre 500 y 1.500 paquetes en node_modules, muchos de ellos de autores que no conoces y que nunca has revisado.
Eso no es un defecto de diseño, es una decisión deliberada del ecosistema JavaScript: reutilizar código pequeño en vez de reinventarlo. El problema es que amplía mucho la superficie de ataque. En el caso de TanStack, el paquete @tanstack/react-router tiene 12 millones de descargas semanales. Comprometer un solo paquete afecta potencialmente a millones de proyectos.
Para entender la escala: el ataque del 11 de mayo comprometió 170 paquetes en total entre npm y PyPI, incluyendo Mistral AI, UiPath y OpenSearch. Los paquetes afectados tenían más de 518 millones de descargas acumuladas entre todos ellos.
Qué puedes hacer para protegerte
La respuesta no es dejar de usar npm. Es usarlo con más criterio. Hay cinco cosas concretas que puedes hacer ahora mismo.
1. Desactiva los scripts automáticos durante la instalación
Recuerda que el problema de raíz es que npm ejecuta código de los paquetes automáticamente mientras instalas. Puedes desactivar eso con una sola opción:
# Instalar sin ejecutar ningún script automático del paquete
npm install --ignore-scripts nombre-paquete
# O configurarlo para todos los proyectos de tu ordenador
npm config set ignore-scripts true
Con --ignore-scripts, npm descarga el código pero no ejecuta nada de lo que el paquete quiera ejecutar durante la instalación. El postinstall, el prepare, el preinstall — todo ignorado.
El pero: algunos paquetes necesitan compilar código nativo durante la instalación y sin esos scripts no funcionan. Si después de instalar con --ignore-scripts algo no funciona, puedes ejecutar los scripts de ese paquete específico de forma controlada:
# Ejecutar los scripts de build de un paquete específico
npm rebuild nombre-paquete
Para proyectos propios donde conoces las dependencias, ignore-scripts es la opción más directa. Para proyectos que clonas de otros, es prácticamente obligatorio hasta que hayas revisado qué hay dentro.
2. Usa pnpm en vez de npm
pnpm es un gestor de paquetes alternativo a npm que hace exactamente lo mismo — instala paquetes de JavaScript — pero con una diferencia importante en seguridad: bloquea por defecto los scripts de instalación de paquetes que no están en tu package.json directo.
Es decir: si instalas @tanstack/react-router y ese paquete a su vez depende de otros 40 paquetes, pnpm no ejecuta los scripts automáticos de esos 40 paquetes intermedios. Solo ejecuta los de los paquetes que tú has pedido instalar explícitamente. Eso reduce drásticamente la superficie de ataque en ataques de cadena de suministro como el de TanStack.
# Instalar pnpm (una sola vez en tu ordenador)
npm install -g pnpm
# Desde ese momento, usas pnpm en vez de npm
pnpm install # equivale a npm install
pnpm add react # equivale a npm install react
pnpm remove react # equivale a npm uninstall react
pnpm también es más rápido que npm y ocupa menos espacio en disco porque comparte paquetes entre proyectos en vez de descargarlos una vez por proyecto. La mayoría de proyectos modernos de JavaScript funcionan con pnpm sin cambiar nada más.
Si quieres ir un paso más allá, puedes configurar pnpm para que requiera aprobación explícita antes de ejecutar cualquier script de instalación. En el archivo .npmrc de tu proyecto:
# .npmrc — pnpm pide confirmación antes de ejecutar scripts
enable-pre-post-scripts=false
3. Antes de instalar un paquete nuevo, míralo
No hace falta leer el código entero. Sí vale la pena comprobar cuántas descargas semanales tiene, cuándo fue la última actualización y si el repositorio de GitHub tiene actividad reciente. Un paquete con 12 descargas semanales publicado por una cuenta creada hace dos semanas es una señal de alerta.
# Ver info de un paquete antes de instalarlo
npm info nombre-paquete
# Ver específicamente qué scripts ejecuta
npm info nombre-paquete scripts
4. Usa npm ci en vez de npm install cuando clonas proyectos
npm ci instala exactamente las versiones que hay en el package-lock.json, sin actualizarlas. npm install puede instalar versiones más recientes si el rango de versión lo permite. En proyectos que no son tuyos, npm ci es más predecible y más seguro:
# Al clonar un proyecto de alguien, usa esto en vez de npm install
npm ci
5. Mantén los tokens con el mínimo de permisos
Si tu token de GitHub tiene permisos de escritura en todos tus repositorios y alguien te lo roba, puede hacer commits en tu nombre en todos ellos. Si el token solo tiene permiso de lectura en los repositorios que necesita, el daño es mucho menor.
En GitHub puedes crear tokens con permisos granulares en Settings → Developer settings → Fine-grained personal access tokens. Crea un token distinto para cada proyecto y con solo los permisos que ese proyecto necesita. Si te roban uno, el resto de repositorios quedan intactos.
Y activa las alertas de Dependabot en Settings → Security → Dependabot alerts. No detecta ataques de día cero como el de TanStack, pero sí te avisa cuando una dependencia tiene una vulnerabilidad conocida y publicada.
Lo que pasó exactamente el 11 de mayo y qué debes hacer si lo instalaste
Entre las 19:20 y las 19:26 UTC del 11 de mayo de 2026, el grupo TeamPCP publicó 84 versiones maliciosas de 42 paquetes de @tanstack/router y @tanstack/start. A las 19:46, StepSecurity había detectado el ataque y alertado a TanStack. Las versiones fueron deprecadas.
Si instalaste alguno de esos paquetes durante esa ventana de 26 minutos, sigue este orden exacto:
Primero elimina el daemon de persistencia — si lo eliminas después de revocar los tokens, el daemon detecta el error 401 e intenta borrar tu directorio home:
# En macOS — eliminar el daemon
launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
# En Linux — eliminar el servicio
systemctl --user stop gh-token-monitor
systemctl --user disable gh-token-monitor
rm ~/.config/systemd/user/gh-token-monitor.service
Busca ficheros residuales que permanecen incluso después de desinstalar el paquete:
# Buscar ficheros del malware
find ~ -name "router_runtime.js" -o -name "setup.mjs" 2>/dev/null
Luego rota todas las credenciales que podrían haber sido expuestas: token de GitHub, token de npm, credenciales de AWS, secretos de CI/CD.
El CVE asignado es CVE-2026-45321. Todas las versiones de TanStack disponibles actualmente en npm son seguras — el equipo deprecó las versiones maliciosas y publicó un postmortem completo.
Por qué esto no significa que npm sea inseguro
npm tiene más de dos millones de paquetes. La gran mayoría son seguros y están mantenidos por personas que saben lo que hacen. Este ataque fue sofisticado, tardó meses en prepararse y aprovechó una combinación de vulnerabilidades que la mayoría de proyectos no tiene.
Lo que sí significa es que el ecosistema de dependencias de JavaScript — como el de cualquier lenguaje moderno — tiene una superficie de ataque grande. Cada paquete que instalas es código de terceros que se ejecuta en tu máquina. Tratar eso con la misma precaución que tratas cualquier software que descargas de internet es razonable.
No hace falta revisar el código fuente de cada librería que usas. Sí hace falta saber qué estás instalando, comprobar que viene de una fuente conocida y mantener tus credenciales con el mínimo de permisos necesarios. Con eso cubres la mayoría de los vectores de ataque que existen.
Fuentes de este artículo
Este post está basado en información verificada de: postmortem oficial de TanStack, análisis de StepSecurity, Wiz Blog, advisory CVE-2026-45321 y el comunicado de OpenAI.