Acceder Registrarme

ATAQUE EN NPM: ASÍ OPERÓ EL COMPROMISO DE LA CADENA DE SUMINISTRO EN TANSTACK


Cuando hablamos de seguridad en proyectos modernos, es fundamental entender los riesgos de trabajar con ecosistemas de código abierto como npm y GitHub Actions. Analizar incidentes reales permite comprender cómo vulnerabilidades encadenadas pueden comprometer paquetes y procesos completos. Aunque no veremos todos los detalles forenses, revisaremos los puntos más relevantes de un análisis post mortem y la importancia de proteger la cadena de suministro, especialmente en entornos automatizados. Para ello, tomaremos como referencia el reciente incidente de TanStack.

Autora: Anny Arias (Ver todos sus post)

Ciberseguridad npm TanStack Sistema DevOps

Fecha de publicación: 2026-05-26 08:18:17
Ayúdanos con el arduo trabajo que realizamos.
[OTROS] ATAQUE EN NPM: ASÍ OPERÓ EL COMPROMISO DE LA CADENA DE SUMINISTRO EN TANSTACK

1. Lo que pasó, brevemente

Un atacante publicó 84 versiones maliciosas en 42 paquetes npm de @tanstack/* combinando tres factores: el patrón "Pwn Request" de pull_request_target, el envenenamiento de la caché de GitHub Actions y la extracción en tiempo de ejecución de un token OIDC. No se robaron tokens npm del almacenamiento del repositorio.

2. ¿Qué hace el malware?

Cuando un desarrollador o un entorno de CI ejecuta un comando de instalación (npm install, pnpm install o yarn install), se dispara la lógica maliciosa:

  • Inyección en Dependencias: npm resuelve una entrada comprometida en optionalDependencies que apunta a un commit huérfano en la red de bifurcaciones (forks).

  • Ejecución Inplace: Durante el ciclo de vida prepare, se ejecuta un archivo router_init.js ofuscado de aproximadamente 2,3 MB.

  • Exfiltración de credenciales: El script recopila datos de entornos como AWS IMDS, metadatos de GCP, tokens de Kubernetes, variables de entorno de GitHub y claves privadas SSH.

  • Autopropagación: Busca otros paquetes que la víctima mantenga en npm y los vuelve a publicar de manera automática con la misma inyección.

3. Cronología del incidente

  • Fase 1: Preataque, fase de envenamiento del depósito.
Tiempo (UTC). Evento Técnico
17:16
10 de mayo, 2026.
El atacante crea una bifurcación oculta llamada zblgg/configuration para evadir búsquedas.
23:29
10 de mayo, 2026.
Se crea el commit malicioso 65bf499d que agrega un script de 30 000 líneas con el prefijo [skip ci].
10:49
11 de mayo, 2026.
Se abre la Pull Request (PR) #7378. Los flujos pull_request_target se ejecutan automáticamente sin requerir aprobación previa.
11:11
11 de mayo, 2026.
Un force-push coloca el código malicioso en el HEAD de la PR. El flujo de trabajo compila el código controlado por el atacante.
11:29
11 de mayo, 2026.
Se guarda una entrada de caché corrupta (Linux-pnmp-store-...) de 1.1 GB en el ámbito de la rama main.
11:31
11 de mayo, 2026.
El atacante limpia la PR redirigiéndola al HEAD legítimo y la cierra. La caché dañada persiste.
  • Fase 2: Detonación y respuesta, fase de publicación
Tiempo (UTC) Evento Técnico
19:15
11 de mayo, 2026.
Se vuelve a ejecutar un flujo de producción legítimo en main. El sistema restaura la caché infectada.
19:20
11 de mayo, 2026.
El registro de npm revibe el primer lote de publicaciones maliciosas de 41 paquetes, usando tokens OIDC generados en caliente por el malware.
19:46
11 de mayo, 2026.
Se detecta la brecha, lo hace el investigador externo Ashish Kurmi de StepSecurity y abre el reporte público #7383.
20:19
11 de mayo, 2026.
Comienza la descontinuación por lotes de las versiones afectadas en npm por parte de los mantenedores.
21:30
11 de mayo, 2026.
Se purgan toas las cachés del repositorio en GitHub y se reestructuran las reglas de seguridad de los flujos de trabajo.
22:13
11 de mayo, 2026.
El equipo de seguridad de npm elimina de forma definitiva los archivos tarball maliciosos de su registro.

4. Causa Principal: El encadenamiento de tres vulnerabilidades.

Para explicar el funcionamiento de este ataque, observemos que no se debió a un único fallo, sino a tres factores encadenados. Al igual que en matemáticas o lógica, si removemos un eslabón, la ecuación de la vulnerabilidad da cero.

El patrón "Pwn Request" mediante pull_request_target: El archivo bundle-size.yml utilizaba un disparador con altos privilegios para poder interactuar con las PRs de los forks. El problema radicó en la configuración del bloque de compilación:

on:
  pull_request_target:
    paths: ['packages/**', 'benchmarks/**']
jobs:
  benchmark-pr:
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
      - run: pnpm nx run @benchmarks/bundle-size:build # Ejecuta código del fork

El flujo intentaba aislar los permisos asignando contents: read, pero omitió que las operaciones de guardado de actions/cache usan tokean interno del ejecutor que evade esta restricción.

Envenenamiento de caché entre límites de confianza: El script malicioso inyectado forzó la escritura de datos dentro del directorio compartido de pnpm-store bajo la clave exacta que el flujo legítimo de producción (release.yml) buscaría más tarde: Linux-pnpm-store-${hashFiles('/pnpm-lock.yaml')}. GitHub Actions comparte el ámbito de caché de estas solicitudes con la rama principal, lo que permitió romper la frontera de confianza.

Extracción del token OIDC de la memoria del ejecutor: Ya que el flujo de publicación legítimo requería permisos de escritura de identidad (id-token: write), los binarios del atacante (activos gracias a la caché infectada) escanearon la memoria del proceso del trabajador de GitHub Actions (Runner.Worker) leyendo los mapas de /proc//mem. Volcaron el token OIDC generado en memoria y realizaron peticiones POST directas al registro de npm, puenteando los pasos de validación estándar.

5. Detección e Indicadores de Compromiso (IOC)

Si eres mantenedor o estás realizando una auditoría en tus sistemas, debes verificar las siguientes huellas digitales dentro de tus proyectos para descartar infecciones remanentes del día 11 de mayo de 2026:

  • En el manifiesto del paquete (package.json):

"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
  • Presencia de archivos extraños: Un archivo llamado router_init.js de ~2.3 MB ubicado directamente en la raíz de la instalación.

  • Firmas de Caché: La clave específica Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11.

  • Identidad de commits falsa: Commits firmados por claude (suplantando herramientas de IA).

CONCLUSIÓN

Debemos entender que existe una infinidad de vectores de ataque en el software moderno, más aún si usamos automatizaciones complejas vinculadas a registros públicos de paquetes. Este análisis expone que las configuraciones por defecto y la reutilización de cachés sin aislamiento son áreas críticas que requieren constante revisión según la necesidad de seguridad que se presente.

Conocer las debilidades predefinidas de nuestras plataformas de CI/CD es crucial para no caer en la falsa sensación de seguridad, es decir, no basta con proteger nuestras credenciales estáticas si los flujos dinámicos pueden ser comprometidos en caliente.



...

INFORMACIÓN SOBRE LA AUTORA DEL ARTÍCULO
ANNY CONSUELO ARIAS FIGUEROA : Soy alguien que marca metas muy altas a largo plazo y que siempre se compromete con estas metas, llegando a cumplirlas. Siempre estoy dispuesta a aprender con experiencias ajenas. Me interesa mucho llegar a aprender todo lo que pueda estar a mi alcance, obviamente tomandome su debido tiempo, sin caer en excesos.


  • Debes estar logueado para realizar comentarios