⏱️ Lectura: 14 min
TanStack, una de las suites de librerías más populares del ecosistema React, publicó este 11 de mayo de 2026 un postmortem técnico sobre el compromiso que afectó a 42 paquetes @tanstack/* en npm. En una ventana de apenas seis minutos, un atacante logró empujar 84 versiones maliciosas al registro sin robar un solo token de npm.
📑 En este artículo
- TL;DR
- Qué pasó con TanStack npm
- Anatomía del ataque a TanStack en npm
- Línea de tiempo: del fork al detonador
- Qué hace el malware una vez ejecutado
- Paquetes afectados y familias limpias
- Por qué importa: el modelo de “trusted publisher” tiene un punto ciego
- Qué hacer si instalaste un paquete @tanstack/* el 11 de mayo
- Lecciones para mantenedores de open source en LATAM
- Preguntas frecuentes
- ¿Mi proyecto está afectado si uso solo @tanstack/react-query?
- ¿Robaron tokens de npm de los mantenedores de TanStack?
- ¿Cómo sé qué versiones específicas son maliciosas?
- ¿Por qué Session/Oxen es tan difícil de bloquear?
- ¿Esto pudo haberse detectado antes de los 20 minutos?
- ¿Tengo que rotar todas mis credenciales aunque no haya evidencia de exfiltración?
- Referencias
El ataque combinó tres técnicas conocidas pero raramente vistas operando en cadena: el patrón “Pwn Request” sobre pull_request_target, envenenamiento de caché en GitHub Actions y extracción en memoria de un token OIDC del propio runner.
TL;DR
- 84 versiones maliciosas en 42 paquetes
@tanstack/*publicadas entre 19:20 y 19:26 UTC del 11 de mayo de 2026. - El ataque combinó
pull_request_target, cache poisoning en GitHub Actions y robo de token OIDC en memoria. - No se robaron tokens npm: el atacante minteó un OIDC token via
id-token: writedurante el release legítimo. - Detectado en 20 minutos por ashishkurmi de StepSecurity; todas las versiones fueron deprecadas.
- Familias confirmadas como limpias:
@tanstack/query*,table*,form*,virtual*,storeystart(meta). - El payload de 2.3 MB exfiltra credenciales via Session/Oxen messenger, sin C2 bloqueable por IP.
- Cualquier host que ejecutó
npm installcon una versión afectada el 11 de mayo debe rotar credenciales AWS, GCP, K8s, Vault, GitHub, npm y SSH. - Fork atacante:
github.com/zblgg/configuration; commit malicioso65bf499dcon prefijo[skip ci].
Qué pasó con TanStack npm
El equipo de TanStack confirma que, entre las 19:20 y 19:26 UTC del 11 de mayo de 2026, un atacante publicó 84 versiones maliciosas distribuidas en 42 paquetes del namespace @tanstack/* en el registro oficial de npm. Cada paquete recibió aproximadamente dos versiones, publicadas con unos seis minutos de diferencia entre ellas. La detección pública la realizó un investigador externo, ashishkurmi de StepSecurity, en apenas 20 minutos desde la primera publicación. Las versiones fueron deprecadas y npm Security inició la remoción de los tarballs.
Lo más inquietante no es el alcance —considerable, dado el tamaño del namespace— sino el método. El atacante nunca tuvo acceso a una credencial de publicación de TanStack. No robó tokens, no hizo phishing a un mantenedor, no comprometió una cuenta de GitHub. En su lugar, encadenó tres debilidades de la infraestructura de CI/CD para que la propia automatización legítima de la organización publicara los paquetes maliciosos en su nombre.
Anatomía del ataque a TanStack en npm
Paso 1: Pull_request_target y el “Pwn Request”
GitHub Actions distingue dos triggers para pull requests. El evento pull_request ejecuta workflows con permisos limitados y exige aprobación manual antes de correr código de un colaborador externo por primera vez. El evento pull_request_target, en cambio, ejecuta con los secretos del repositorio base y bypassa esa aprobación, porque fue diseñado para tareas administrativas como aplicar labels o comentar resultados de benchmarks.
El problema, conocido en la industria de seguridad como “Pwn Request”, aparece cuando un workflow disparado por pull_request_target hace checkout del código de la rama del PR y lo ejecuta. En la práctica eso significa que cualquier fork malicioso puede inyectar código que correrá con los secretos del repositorio base, sin pasar por revisión humana.
En TanStack, los workflows bundle-size.yml y labeler.yml usaban pull_request_target. El primero hacía pnpm install y pnpm nx run @benchmarks/bundle-size:build sobre el merge ref del PR. Eso fue todo lo que el atacante necesitó: con sólo abrir un PR, su payload se ejecutaba en un runner con permisos del repo base.
Paso 2: Envenenamiento de caché entre el fork y la rama main
La GitHub Actions Cache mantiene entradas separadas por scope, normalmente la rama. Pero la documentación oficial reconoce una excepción: los workflows que corren para un pull request pueden escribir a la caché del repositorio base bajo ciertos refs. El malware aprovechó exactamente esto.
Durante el run de bundle-size.yml, el payload bundleado en packages/history/vite_setup.mjs —un archivo JS de unas 30,000 líneas— se ejecutó. Su tarea no fue exfiltrar nada todavía, sino guardar una entrada de caché de 1.1 GB con la clave Linux-pnpm-store-... apuntando al scope refs/heads/main. Esa clave coincidía exactamente con la que release.yml, el workflow real de publicación, buscaría más tarde al correr sobre main.
Después de envenenar la caché, el atacante hizo force-push al PR llevándolo de vuelta al HEAD de main: un diff de cero archivos. Cerró el PR. La evidencia visual del ataque desapareció. La caché envenenada, no.
⚠️ Ojo: Si tu pipeline de release hace restore de caché producida por workflows disparados desde forks, asumí que esa caché es código no confiable. Tratala como input adversarial, no como artefacto reproducible.
Paso 3: Extracción del token OIDC en memoria
Aquí es donde el ataque deja de ser un patrón conocido y pasa a ser una pieza de ingeniería más sofisticada. TanStack usa la modalidad de “trusted publisher” de npm, vinculada por OIDC a TanStack/router release.yml@refs/heads/main. Esto elimina la necesidad de almacenar un NPM_TOKEN: el runner solicita un OIDC token a GitHub durante el workflow, npm lo verifica contra la binding y permite publicar.
Cuando alguien mergeó un PR legítimo no relacionado, release.yml arrancó. El workflow hizo restore de la caché —y obtuvo la versión envenenada—. Los tests fallaron por modificaciones del malware, por lo que el paso Publish Packages nunca corrió. Pero el malware había inyectado código en la fase de teardown del workflow. Como release.yml declaraba permissions: id-token: write, el runner había recibido un OIDC token. El malware lo extrajo de la memoria del proceso de Actions y disparó publicaciones HTTP directas a registry.npmjs.org con ese token. npm vio una request legítimamente firmada por el workflow autorizado y aceptó cada publish.
Línea de tiempo: del fork al detonador
El postmortem documenta la cronología con precisión forense. El flujo completo se puede visualizar así:
flowchart TD
A["Atacante crea fork malicioso 10 may 17:16"] --> B["Commit malicioso con skip ci"]
B --> C["Abre PR #7378 contra TanStack router"]
C --> D["pull_request_target ejecuta workflows"]
D --> E["pnpm install corre vite_setup.mjs"]
E --> F["Cache envenenada de 1.1 GB persistida"]
F --> G["Atacante cierra el PR y borra rama"]
G --> H["8 horas despues merge legitimo en main"]
H --> I["release.yml restaura cache envenenada"]
I --> J["Malware mintea OIDC token en memoria"]
J --> K["Publica 84 versiones en 42 paquetes"]
- 10 de mayo, 17:16 UTC: el atacante crea el fork
github.com/zblgg/configuration(renombrado deliberadamente para evadir búsquedas). - 10 de mayo, 23:29 UTC: commit malicioso
65bf499dcon identidad falsaclaude <[email protected]>y prefijo[skip ci]en el mensaje para suprimir CI. - 11 de mayo, ~10:49 UTC: PR #7378 “WIP: simplify history build” abierto contra
main. - 11 de mayo, 10:49 – 11:11 UTC: múltiples force-pushes; cada uno dispara
bundle-size.yml. El último incluye el commit malicioso. - 11 de mayo, 11:29 UTC: caché envenenada de 1.1 GB persistida en el scope de main.
- 11 de mayo, 11:31 UTC: el atacante limpia el PR a un diff vacío y lo cierra. La rama se elimina.
- 11 de mayo, 19:15 UTC: un merge legítimo (PR #7369) dispara
release.yml. - 11 de mayo, 19:20 UTC en adelante: comienzan las publicaciones maliciosas a
registry.npmjs.org. - 11 de mayo, ~19:40 UTC: ashishkurmi (StepSecurity) reporta públicamente las versiones maliciosas.
Casi nueve horas separaron la siembra de la cosecha. Durante ese tiempo, ningún humano podía ver evidencia del ataque en el repositorio: el PR estaba cerrado, la rama borrada, los commits maliciosos sólo existían en el fork ahora invisible para búsquedas convencionales.
Qué hace el malware una vez ejecutado
El payload final, router_init.js, pesa unos 2.3 MB y está fuertemente ofuscado. Cuando una víctima corre npm install, pnpm install o yarn install contra una versión afectada, npm resuelve una entrada optionalDependencies que apunta a un commit huérfano en el fork. El script prepare se ejecuta como parte del ciclo de vida estándar de npm.
A partir de ahí, el malware:
- Recolecta credenciales de ubicaciones comunes: IMDS de AWS, Secrets Manager, metadata de GCP, tokens de service accounts de Kubernetes, tokens de Vault,
~/.npmrc, tokens de GitHub (variables de entorno, CLIgh,.git-credentials), llaves SSH privadas. - Exfiltra todo lo recolectado a través de la red de mensajería Session/Oxen (
filev2.getsession.org,seed1-3.getsession.org). Como Session es end-to-end encrypted sin servidor de C2 controlado por el atacante, bloquear por IP o dominio es la única mitigación de red posible. - Se auto-propaga: enumera otros paquetes que la víctima mantiene consultando
registry.npmjs.org/-/v1/search?text=maintainer:<user>y los republica con la misma inyección si tiene los tokens necesarios.
Una invocación rápida que cualquier mantenedor puede correr para detectar versiones instaladas en su árbol de dependencias:
# Linux / macOS
npm ls --all 2>/dev/null | grep -E "@tanstack/(router|db|ai|pacer|devtools|cli|hotkeys|intent)"
pnpm why @tanstack/router 2>/dev/null
# Windows (PowerShell)
npm ls --all | Select-String "@tanstack/(router|db|ai|pacer|devtools|cli|hotkeys|intent)"
pnpm why "@tanstack/router"
Paquetes afectados y familias limpias
42 paquetes recibieron versiones maliciosas, principalmente del lado de @tanstack/router*, @tanstack/db*, @tanstack/ai*, @tanstack/pacer*, @tanstack/start-* (los modulares, no el meta), @tanstack/devtools*, entre otros. El issue de tracking #7383 incluye la tabla completa con cada versión afectada y su timestamp de publicación.
Confirmadas como limpias —no fueron tocadas durante la ventana del incidente—:
@tanstack/query*@tanstack/table*@tanstack/form*@tanstack/virtual*@tanstack/store@tanstack/start(el meta-paquete, no@tanstack/start-*)
Si tu proyecto en LATAM usa Query, Table, Form o Virtual exclusivamente, no fuiste afectado por esta ola. La distinción entre start (meta) y start-* (modulares) es importante: el meta-paquete no recibió versiones maliciosas, pero los paquetes específicos que él agrupa transitivamente sí pueden haberlas recibido.
💡 Tip: Antes de corrernpm installmañana, agregá--ignore-scriptscomo flag temporal en tu CI y en tu shell local. Esto previene la ejecución del scriptprepareque detona el payload, aunque rompe paquetes que dependen legítimamente de scripts de build.
Por qué importa: el modelo de “trusted publisher” tiene un punto ciego
OIDC para publicaciones —respaldado por npm, PyPI y otros— resuelve el problema clásico del token compartido. Pero el postmortem de TanStack expone una asunción rota: confiar en que sólo los pasos declarados del workflow usarán el token. Si el atacante consigue ejecutar cualquier código en el runner mientras id-token: write está activo —y un workflow con permisos elevados es exactamente el blanco más atractivo— puede mintear y usar el token sin pasar por los pasos legítimos.
Esto convierte la combinación “pull_request_target + cache de Actions compartida con main + workflow de publish con OIDC” en una bomba de tiempo. El postmortem llama a tratar la GitHub Actions Cache como territorio hostil tan pronto como un PR de un fork puede escribir en ella.
Qué hacer si instalaste un paquete @tanstack/* el 11 de mayo
- Identificar si la versión instalada está en la lista de afectadas (revisar el GHSA enlazado en Referencias).
- Considerar el host como comprometido. No es paranoia: el malware corre con tus permisos.
- Rotar credenciales: AWS, GCP, Kubernetes, Vault, npm, GitHub PAT y deploy keys, llaves SSH, cualquier secreto guardado en el host.
- Auditar logs de los proveedores cloud por accesos anómalos desde IPs no habituales en las últimas 24 horas.
- Correr
pnpm store prune, borrar~/.npmynode_modules, y reinstalar contra versiones limpias una vez deprecadas las maliciosas. - Si trabajás en una organización, notificar al equipo de seguridad y considerar invalidar tokens de sesión de servicios donde el host tenga acceso.
Lecciones para mantenedores de open source en LATAM
El postmortem recomienda explícitamente —y vale para cualquier proyecto que publique paquetes desde GitHub Actions—:
- Migrar workflows que necesiten secretos de PRs a flujos con
pull_requesty aprobación manual. Si necesitáspull_request_target, evitá hacer checkout del código del PR. - No usar la GitHub Actions Cache compartida entre forks y main para builds reproducibles que luego firmen o publiquen artefactos.
- Restringir
permissions: id-token: writeal mínimo absoluto de pasos. Idealmente, sólo al step que publica. - Considerar aislar la publicación en un repositorio dedicado, sin checkout de código de PRs externos.
- Auditar la lista de workflows con
pull_request_targeten todos los repos públicos. StepSecurity y otras herramientas ofrecen escaneo automático. - Loguear y monitorear publicaciones a npm en tiempo real con webhooks o herramientas como Socket: la diferencia entre 20 minutos y 2 horas de detección puede ser miles de hosts comprometidos.
📌 Nota: El nombre del fork —zblgg/configuration— estaba elegido para no aparecer cuando alguien buscara forks de TanStack/router en la UI de GitHub. Esta pequeña capa de stealth indica un atacante familiarizado con cómo los equipos auditan su superficie de exposición.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Mi proyecto está afectado si uso solo @tanstack/react-query?
No. La familia @tanstack/query* está confirmada como limpia. El incidente afectó principalmente a paquetes del lado de Router, DB, AI, Pacer, Devtools y CLI.
¿Robaron tokens de npm de los mantenedores de TanStack?
No. El postmortem es explícito en que ninguna credencial de publicación fue comprometida. El atacante minteó un token OIDC en el runner de GitHub Actions durante un workflow legítimo y lo usó para publicar desde dentro del proceso.
¿Cómo sé qué versiones específicas son maliciosas?
El issue de tracking TanStack/router#7383 y el GitHub Security Advisory GHSA-g7cv-rxg3-hmpx listan cada paquete y versión afectada con la fecha y hora exactas de publicación.
¿Por qué Session/Oxen es tan difícil de bloquear?
Session es un protocolo de mensajería descentralizado encriptado end-to-end. Las exfiltraciones no atraviesan un servidor controlado por el atacante: pasan por la red Oxen y son recogidas más tarde por el destinatario. No hay IP “del atacante” que bloquear; bloquear filev2.getsession.org y los seeds en el firewall es la única mitigación de red disponible.
¿Esto pudo haberse detectado antes de los 20 minutos?
Sí. Detección en tiempo real de publicaciones a npm con tarballs anómalos por tamaño o spike de versiones es factible con herramientas como Socket, StepSecurity o las nuevas alertas de npm. La velocidad de detección dependió de que un investigador externo estuviera vigilando, no de la infraestructura de TanStack.
¿Tengo que rotar todas mis credenciales aunque no haya evidencia de exfiltración?
Sí. La regla en supply-chain attacks es asumir lo peor: si el malware corrió como tu usuario, todo lo que ese usuario podía leer es asumible como exfiltrado. La auditoría de logs llega después, no antes, de la rotación de secretos.
Referencias
- Postmortem oficial de TanStack — análisis técnico completo del incidente con timeline, IOCs y mitigaciones.
- TanStack/router#7383 — issue de tracking con la tabla completa de paquetes y versiones afectadas.
- GitHub Security Advisory GHSA-g7cv-rxg3-hmpx — advisory formal del incidente con metadata machine-readable.
- StepSecurity — empresa del investigador que detectó las publicaciones maliciosas en 20 minutos.
- GitHub Actions: Security Hardening — documentación oficial sobre
pull_request_targety mejores prácticas para workflows con secretos.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
0 Comentarios