11 de mayo de 2026 — En cuestión de minutos, 84 versiones maliciosas de paquetes @tanstack/* fueron publicadas en npm. No porque alguien robara una contraseña. Sino porque el atacante logró que el propio sistema de publicación de TanStack lo hiciera por él.
Referencia: Tanstack-npm-packages-compromised
¿Qué pasó?
Entre las 19:20 y 19:26 UTC, el grupo conocido como TeamPCP publicó versiones comprometidas de 42 paquetes del namespace @tanstack, incluyendo @tanstack/react-router, que acumula más de 12.7 millones de descargas semanales. En horas, el mismo código malicioso se propagó a paquetes de Mistral AI, UiPath, y decenas de otros mantenedores.
Lo que hace a este incidente técnicamente significativo: los paquetes fueron publicados usando la identidad legítima del pipeline de TanStack, con credenciales válidas, y con certificados de procedencia SLSA Build Level 3 correctamente firmados por Sigstore. Es el primer caso documentado de un gusano npm que produce atestaciones SLSA válidas para paquetes maliciosos.
Cómo funciona el ataque (sin exagerar)
El atacante encadenó tres vulnerabilidades que por separado serían manejables, pero juntas abrieron la puerta completa:
1. Pwn Request via pull_request_target
El atacante abrió un pull request desde un fork llamado zblgg/configuration contra el repositorio principal de TanStack. El workflow bundle-size.yml usaba el trigger pull_request_target, que corre en el contexto de seguridad del repositorio base pero ejecutaba código del fork. Ese código no exfiltró nada de inmediato.
2. Envenenamiento de caché de GitHub Actions
El código malicioso calculó de antemano la clave de caché que usaría el workflow de release (release.yml) y guardó en esa caché una versión modificada del store de pnpm. La entrada envenenada —1.1 GB— esperó sin ser detectada durante casi ocho horas, hasta que un push legítimo a main activó el proceso de publicación.
3. Extracción de tokens OIDC desde memoria del runner
Cuando el workflow de release corrió con el store envenenado, los binarios maliciosos localizaron el proceso Runner.Worker, leyeron su espacio de memoria en /proc/<pid>/mem, y extrajeron el token OIDC que npm usa para autenticar publicaciones. Con ese token —válido, legítimo— publicaron directamente a registry.npmjs.org. El workflow oficial terminó con status: failure. Los 84 paquetes maliciosos ya estaban publicados.
El problema con SLSA en este contexto
SLSA es una especificación útil: atestigua que un paquete fue construido por un workflow específico en un repositorio específico. Eso es verdad en este caso — el paquete sí fue construido por release.yml en TanStack/router. Lo que SLSA no garantiza es que el código que se estaba construyendo fuera seguro, ni que el workflow haya corrido de forma íntegra.
La configuración correcta incluye rama y archivo de workflow específicos:
# Seguro
Trusted publisher:
Repository: tanstack/router
Workflow: .github/workflows/release.yml
Branch: refs/heads/mainEste ataque demuestra que la provenance attestation es una capa necesaria, pero no suficiente.
Qué hace el payload una vez instalado
El archivo router_init.js (2.3 MB, no declarado en el campo files del paquete) hace varias cosas:
Se daemoniza inmediatamente: forkea un proceso hijo completamente desvinculado del terminal.
Recolecta credenciales: variables de GitHub Actions, AWS, HashiCorp Vault, Kubernetes, SSH,
~/.npmrc,~/.git-credentials, historial de Claude Code (~/.claude/projects/*.jsonl).Exfiltra vía red P2P de Session/Oxen (
*.getsession.org). Bloqueo DNS requerido, IP no es suficiente.Se auto-propaga: republica todos los paquetes del dueño del token robado con el mismo payload.
Persistencia que sobrevive al npm uninstall
El gusano escribe hooks en .claude/settings.json y .vscode/tasks.json para re-ejecutarse al abrir el editor. Instala además un servicio de sistema:
Linux:
~/.config/systemd/user/gh-token-monitor.servicemacOS:
~/Library/LaunchAgents/com.user.gh-token-monitor.plist
Este servicio consulta la API de GitHub cada 60 segundos. Si el token es revocado, ejecuta rm -rf ~/. Por eso el orden de remediación importa: primero deshabilitar el monitor, después rotar credenciales.
Qué hacer si usaste alguna versión afectada
Las versiones comprometidas de @tanstack/react-router son 1.169.5 y 1.169.8. Para verificar:
find node_modules/@tanstack -name "router_init.js" -exec shasum -a 256 {} \;
# Hash: ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266cSi hay exposición, el orden correcto es:
Deshabilitar el servicio de monitoreo antes de cualquier otra cosa.
Eliminar hooks de editor en
.claude/settings.jsony.vscode/tasks.json.Rotar credenciales: tokens npm y OIDC primero, luego GitHub PATs, AWS, Vault, K8s, SSH.
Bloquear
*.getsession.orga nivel DNS.Revisar workflows con
pull_request_targetque también escriban al caché.
Contexto más amplio
Este no es un incidente aislado. El grupo TeamPCP (también conocido como DeadCatx3, ShellForce) opera desde septiembre de 2025 con variantes del gusano Shai-Hulud. Cada ola ha escalado: el primer Shai-Hulud afectó 500+ paquetes; el segundo introdujo hooks preinstall; la versión de abril atacó SAP e Intercom; esta última es la primera en producir atestaciones SLSA válidas.
Unit 42 documentó que TeamPCP anunció una asociación con el grupo de ransomware Vect, aunque aún no se ha confirmado actividad de ransomware vinculada a los datos robados.
Una reflexión final
Este ataque no explotó una vulnerabilidad en npm ni en Sigstore. Explotó la brecha entre lo que un mecanismo de confianza puede verificar y lo que los usuarios asumen que verifica. SLSA certifica el proceso de build, no la integridad del código. Es una distinción que vale la pena tener clara antes de que otro incidente la haga evidente.
Referencia original: Snyk Security — TanStack npm Packages Hit by Mini Shai-Hulud
CVE: CVE-2026-45321 | GHSA: GHSA-g7cv-rxg3-hmpx | Severidad: Crítica



