Skip to main content

Mini Shai-Hulud ataca AntV: más de 300 paquetes npm maliciosos publicados mediante una cuenta de mantenedor comprometida

Escrito por
blog feature supply chain sbom

18 de mayo de 2026

0 minutos de lectura

Un ataque a la cadena de suministro que afecta al ecosistema de visualización de datos @antv y a paquetes npm relacionados se está propagando activamente a través del registro de npm. El ataque, atribuido a un grupo de amenazas llamado TeamPCP y presentado como otra ola de la campaña Mini Shai-Hulud, publicó más de 300 versiones maliciosas en 323 paquetes durante una ráfaga automatizada de 22 minutos el 19 de mayo de 2026. En conjunto, los paquetes representan aproximadamente 16 millones de descargas semanales.

El vector de ataque fue una cuenta comprometida de un mantenedor de npm. El malware integrado en los paquetes afectados roba secretos de desarrolladores y credenciales de la nube, establece acceso persistente de C2 e intenta propagarse a otros paquetes mediante tokens de npm robados.

En resumen

Tipo de ataque

Cadena de suministro, cuenta de mantenedor comprometida

Actor de amenazas

TeamPCP (alias: DeadCatx3, PCPcat)

Campaña

Mini Shai-Hulud (activa desde sep. de 2025)

Fecha del incidente

19 de mayo de 2026, 01:39–02:06 UTC

Paquetes comprometidos

637 versiones maliciosas en 323 paquetes

Descargas semanales estimadas

~16 millones

Comportamiento del malware

Robo de credenciales, recopilación de secretos de la nube, persistencia y propagación como gusano

Cobertura de Snyk

Avisos en Snyk Vulnerability Database; Zero Day Report disponible en la aplicación

Acción inmediata

Fija versiones anteriores al 19 de mayo, ejecuta npm install --ignore-scripts y rota todas las credenciales

Paquetes afectados

La cuenta de npm atool comprometida mantenía 547 paquetes. La ventana de publicación maliciosa afectó a más de 300 en dos oleadas:

  • Primera oleada: 01:39–01:56 UTC (~317 versiones)

  • Segunda oleada: 02:05–02:06 UTC (~314 versiones)

Entre los paquetes afectados con más descargas están:

Entre los paquetes principales de @antv comprometidos están @antv/g2, @antv/g6, @antv/x6, @antv/l7, @antv/s2, @antv/f2, @antv/g, @antv/g2plot, @antv/graphin y @antv/data-set, además de paquetes sin ámbito como echarts-for-react, timeago.js, size-sensor y canvas-nest.js.

AntV es un conjunto de herramientas de visualización de datos originario de Alibaba, muy utilizado en paneles empresariales, herramientas de informes financieros y plataformas de análisis de grafos. Su amplia adopción convierte el portafolio de paquetes de la cuenta del mantenedor en un objetivo de gran valor.

Cómo funciona el ataque

Etapa 1: Compromiso de la cuenta del mantenedor

El ataque comienza con el compromiso de la cuenta de npm atool. La forma en que se obtuvo la cuenta aún está bajo investigación. Al controlar la cuenta, el atacante obtuvo acceso de publicación a los 547 paquetes que mantiene.

Etapa 2: Publicación maliciosa automatizada

El atacante publicó versiones maliciosas en dos oleadas rápidas y publicó la mayoría de los paquetes dos veces (un pequeño número de paquetes de prueba iniciales recibió tres versiones). Cada archivo tarball de paquete malicioso contiene dos elementos añadidos:

  • Un index.js: en la raíz, con una carga útil de JavaScript de Bun muy ofuscada de 498 KB

  • Una modificación de package.json para agregar: "preinstall": "bun run index.js"

El hook del ciclo de vida preinstall se activa automáticamente cuando un desarrollador ejecuta npm install, antes de que se ejecute cualquier otra lógica de instalación.

Etapa 3: Inyección de un commit huérfano para la procedencia de Sigstore

Una de las técnicas más sutiles de esta oleada consiste en inyectar una dependencia opcional que apunta a un commit huérfano del repositorio legítimo antvis/G2:

"optionalDependencies": {
  "@antv/setup": "github:antvis/G2#1916faa365f2788b6e193514872d51a242876569"
}

Es importante señalar que la dependencia obtenida de GitHub usa el mismo método de ataque que se utilizó en el ataque anterior a la cadena de suministro TanStack Shai-Hulud.

El atacante creó el commit, pero falsificó su autoría para que pareciera ser de huiyu.zjt <huiyu.zjt@ant.com> (un mantenedor real). No necesitó acceso de escritura al repositorio objetivo. El atacante bifurcó antvis/G2, creó un commit huérfano con la carga útil y luego eliminó la bifurcación. El almacenamiento de objetos de GitHub conserva los commits de las bifurcaciones eliminadas hasta que se ejecuta la recolección de basura, por lo que el commit malicioso sigue disponible mediante su hash.

Obtener ese commit permite ejecutar la carga útil con credenciales de Git, aunque parezca provenir de un repositorio confiable.

Con los tokens OIDC robados de GitHub Actions, el malware puede solicitar certificados de firma a Fulcio (https://fulcio.sigstore.dev) y crear declaraciones de procedencia in-toto mediante Rekor (https://rekor.sigstore.dev), para producir paquetes con atestaciones SLSA Build Level 3 criptográficamente válidas.

La clave es que las firmas son legítimas porque la canalización de compilación fue comprometida. La procedencia de Sigstore te indica qué canalización produjo un artefacto, no si esa canalización se comportó como debía. Por eso, es un error común pensar que las pruebas de atestación por sí solas son una señal infalible de legitimidad para los paquetes publicados.

Etapa 4: Recopilación de credenciales

Cuando bun run index.js se ejecuta en la máquina de un desarrollador o en un ejecutor de CI, la carga útil apunta a más de 80 variables de entorno y más de 100 rutas de archivos. Los tipos de credenciales objetivo incluyen:

  • AWS: Claves de acceso (AKIA[0-9A-Z]{16}), tokens de sesión, EC2 IMDS (169.254.169.254), metadatos de ECS (169.254.170.2), Secrets Manager

  • GCP: JSON de cuentas de servicio, credenciales predeterminadas de la aplicación

  • Azure: Credenciales de entidad de servicio

  • GitHub: PAT y tokens OIDC (gh[op]_[A-Za-z0-9]{36,})

  • npm: Tokens de publicación con el alcance bypass_2fa

  • Infraestructura: Tokens de servicio de Kubernetes, tokens de HashiCorp Vault

  • Bases de datos: Cadenas de conexión de MongoDB, MySQL, PostgreSQL y Redis

  • Servicios: Claves de Stripe, tokens de Slack, configuraciones de autenticación de Docker

  • Claves SSH: ~/.ssh/id_*

En los entornos de GitHub Actions, la carga útil intenta leer secretos directamente de la memoria del proceso Runner.Worker mediante /proc/{pid}/mem, para eludir por completo el enmascaramiento de secretos.

Etapa 5: Exfiltración de datos

Todas las credenciales recopiladas se serializan como JSON, se comprimen con gzip, se cifran con AES-256-GCM y, luego, la clave de cifrado se protege con RSA-OAEP usando una clave pública del atacante codificada de forma fija. Esto significa que los defensores que encuentren los datos exfiltrados no podrán determinar qué se robó.

La exfiltración se realiza a través de dos canales:

  1. C2 principal: https://t[.]m-kosche[.]com:443/api/public/otel/v1/traces (disfrazado de datos de seguimiento de OpenTelemetry)

  2. Depósito secundario en GitHub: Si encuentra un token de GitHub, la carga útil crea repositorios en la cuenta de la víctima con nombres inspirados en Dune (combinaciones de palabras como sardaukar, fremen, atreides con sandworm, ornithopter, stillsuit, más un número del 0 al 999), que contienen archivos README con frases características de oleadas anteriores de la campaña Shai-Hulud, escritas al revés (por ejemplo, “niagA oG eW ereH :duluH-iahS”). Los datos robados se guardan como results/results-<timestamp>-<counter>.json. Las solicitudes HTTP usan "python-requests/2.31.0" como User-Agent.

Etapa 6: Persistencia

La carga útil instala varios mecanismos de persistencia que sobreviven a una simple desinstalación con npm uninstall:

  • Secuestro de agentes de programación con IA: Crea .claude/settings.json con un hook SessionStart que ejecuta node .claude/setup.mjs. Esto vuelve a ejecutar el malware cada vez que un desarrollador abre una nueva sesión de Claude Code en un directorio afectado. Cabe señalar que este patrón también se parece a uno que hemos visto en campañas anteriores de ataques a la cadena de suministro, como TanStack.

  • Hooks del IDE: Modifica .vscode/tasks.json con "runOn": "folderOpen" para activarse al abrir el proyecto.

  • Daemon a nivel del sistema operativo: Instala ~/.local/share/kitty/cat.py, un daemon en segundo plano que consulta cada hora la API de búsqueda de commits de GitHub en busca de comandos que contengan la palabra clave firedalazer. Los comandos se firman con RSA-PSS usando una clave pública de 4096 bits codificada de forma fija, lo que permite al atacante enviar código Python arbitrario para ejecutarse en máquinas comprometidas mucho después de la infección inicial. El daemon se registra como un servicio de usuario de systemd en Linux (~/.config/systemd/user/kitty-monitor.service) y como LaunchAgent en macOS (~/Library/LaunchAgents/com.user.kitty-monitor.plist).

Monitor de tokens: ~/.local/bin/gh-token-monitor.sh consulta los tokens de GitHub robados cada 60 segundos, lo que permite al atacante reaccionar rápidamente si un token está por vencer.

Etapa 7: Propagación como gusano

La carga útil busca tokens de npm con el alcance bypass_2fa y los usa para volver a publicar otros paquetes que la cuenta comprometida puede publicar. En los entornos de GitHub Actions, intercambia el token OIDC de Actions por tokens de publicación de npm para cada paquete mediante:

POST https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/<package-name>

También inyecta un flujo de trabajo de GitHub Actions en una rama llamada chore/add-codeql-static-analysis, con un archivo de flujo de trabajo llamado "Run Copilot" (.github/workflows/codeql.yml). El flujo de trabajo vuelca toJSON(secrets) en un artefacto llamado format-results.txt y luego se limpia a sí mismo al eliminar la ejecución del flujo de trabajo.

Análisis del impacto

El alcance directo del ataque incluye cualquier entorno de desarrollador o de CI que haya ejecutado npm install con una versión afectada del paquete entre las 01:39 y aproximadamente las 02:18 UTC del 19 de mayo de 2026.

Los entornos de CI/CD corren un riesgo elevado. En ellos, la carga útil puede leer todos los secretos del proceso ejecutor, no solo los que se pasan explícitamente como variables de entorno. Si se instaló una versión afectada, debe considerarse comprometido cualquier secreto al que tenga acceso el ejecutor de GitHub Actions, incluidos los tokens OIDC, los secretos del repositorio y los secretos de la organización cuyo alcance incluya ese repositorio.

Las máquinas de los desarrolladores también presentan un riesgo importante. Los mecanismos de persistencia hacen que eliminar únicamente los paquetes afectados no elimine la amenaza. El hook de .claude/settings.json, la tarea de VS Code y el daemon del sistema siguen activos hasta que se eliminan explícitamente.

El componente de autopropagación implica que cualquier token de npm capturado de una máquina de desarrollador o de un ejecutor de CI podría usarse para infectar paquetes adicionales fuera del portafolio inicial de la cuenta atool, ampliando el alcance del ataque más allá de AntV y los paquetes relacionados.

Detección

Revisa tu archivo de bloqueo. Si tu package-lock.json o yarn.lock hace referencia a algún paquete mantenido por la cuenta atool, comprueba si la versión resuelta se publicó entre las 01:39 y las 02:18 UTC del 19 de mayo de 2026.

Usa Snyk para analizar tus proyectos. Snyk publicó avisos en Snyk Vulnerability Database para las versiones afectadas de los paquetes y desplegó una notificación en la aplicación y un Zero Day Report para que los clientes investiguen la exposición en sus organizaciones.

snyk test

Para analizar rápidamente un paquete específico de tu árbol:

snyk test --file=package-lock.json

Busca artefactos de persistencia. Si instalaste alguno de los paquetes afectados, busca lo siguiente:

# AI agent hook
cat .claude/settings.json 2>/dev/null | grep -A5 SessionStart

# VS Code task hook
cat .vscode/tasks.json 2>/dev/null | grep "folderOpen"

# C2 daemon
ls ~/.local/share/kitty/cat.py 2>/dev/null
ls ~/.local/bin/gh-token-monitor.sh 2>/dev/null
systemctl --user status kitty-monitor 2>/dev/null  # Linux
launchctl list com.user.kitty-monitor 2>/dev/null   # macOS

# Dead-drop repositories on your GitHub account
gh repo list --json name,description | grep -E "sardaukar|mentat|fremen|atreides|harkonnen|gesserit|fedaykin|tleilaxu"

Indicadores a nivel de paquete:

  • Presencia del script preinstall bun run index.js en node_modules/<package>/package.json

  • SHA256 de la carga útil: a68dd1e6a6e35ec3771e1f94fe796f55dfe65a2b94560516ff4ac189390dfa1c

  • Dependencia opcional que hace referencia a github:antvis/G2#1916faa365f2788b6e193514872d51a242876569 (o a los commits 7cb42f57561c / dc3d62a2181b)

Indicadores de red:

  • Solicitudes salientes a 169.254.169.254 o 169.254.170.2 (puntos de conexión de metadatos en la nube) desde entornos que no son de nube

  • Solicitudes HTTP a t.m-kosche.com:443 con rutas de OpenTelemetry

  • Llamadas a la API de GitHub con el User-Agent python-requests/2.31.0 que no provienen de procesos reales de Python

Mitigación

Si no tienes certeza de si fuiste afectado, considéralo un compromiso confirmado. Como la exfiltración está cifrada con RSA, no puedes recuperar lo que se extrajo.

Paso 1: Elimina la persistencia antes de revocar los tokens.

El daemon gh-token-monitor.sh consulta los tokens cada 60 segundos. Si un token de GitHub vence o se revoca mientras el daemon está en ejecución, puede desencadenar más acciones maliciosas. Primero detén y elimina los mecanismos de persistencia:

# Remove systemd service (Linux)
systemctl --user stop kitty-monitor
systemctl --user disable kitty-monitor
rm -f ~/.config/systemd/user/kitty-monitor.service

# Remove LaunchAgent (macOS)
launchctl unload ~/Library/LaunchAgents/com.user.kitty-monitor.plist
rm -f ~/Library/LaunchAgents/com.user.kitty-monitor.plist

# Remove C2 daemon and monitor
rm -f ~/.local/share/kitty/cat.py
rm -f ~/.local/bin/gh-token-monitor.sh

# Remove editor hooks
rm -f .claude/settings.json  # or edit to remove SessionStart hook
# Edit .vscode/tasks.json to remove any "runOn": "folderOpen" tasks

# Check for injected GitHub workflows
git log --oneline --all | grep -i codeql

Paso 2: Limpia npm y reinstala con versiones seguras.

# Remove node_modules
rm -rf node_modules

# Downgrade affected packages to pre-May 19 versions in package.json, then:
npm install --ignore-scripts

El uso de --ignore-scripts evita que se ejecuten los scripts del ciclo de vida preinstall, postinstall o prepare durante la instalación. De todos modos, esto debería ser una práctica estándar en entornos de CI.

Paso 3: Rota todas las credenciales.

Asume que todo lo accesible desde cualquier equipo o ejecutor de CI donde se haya instalado un paquete afectado está comprometido:

  • Tokens de publicación de npm

  • Tokens de acceso personal de GitHub y secretos de Actions

  • Claves de acceso de AWS (y cualquier rol de IAM accesible desde los ejecutores afectados)

  • Claves de cuentas de servicio de GCP

  • Principales de servicio de Azure

  • Tokens de cuentas de servicio de Kubernetes

  • Tokens de HashiCorp Vault

  • Claves SSH presentes en los equipos afectados

  • Cadenas de conexión a bases de datos

  • Cualquier clave de API o token de servicio almacenado en variables de entorno o archivos de configuración

Paso 4: Audita GitHub para detectar flujos de trabajo inyectados y repositorios de dead drop.

# Check for attacker-injected branches
git branch --all | grep "codeql-static-analysis"

# Check for Dune-named repositories created on your account
gh repo list --json name | grep -E "sardaukar|mentat|fremen|atreides|harkonnen"

# Check npm audit log for unexpected publishes
npm access ls-packages

Paso 5: Evita futuras exposiciones.

  • Activa la autenticación de dos factores de npm con protección de publicación en todos los paquetes de la organización.

  • Agrega npm install --ignore-scripts a la configuración de CI de forma predeterminada.

  • Fija las dependencias en versiones exactas mediante archivos de bloqueo con verificaciones de integridad.

  • Considera una política de enfriamiento del registro: marca y retén los paquetes publicados en los últimos 7 días.

  • Usa Snyk para monitorear continuamente tu árbol de dependencias y detectar paquetes maliciosos a medida que se identifican.

  • Te recomendamos especialmente que consultes y sigas el repositorio de prácticas recomendadas de seguridad de npm para aplicar controles y prácticas de seguridad que eviten futuros incidentes de malware.

La campaña más amplia: las oleadas de Shai-Hulud

El ataque a AntV es la última oleada de una campaña que TeamPCP lleva adelante desde septiembre de 2025. La progresión muestra una escalada constante en alcance, persistencia, sofisticación y abuso de infraestructura confiable:

Oleada

Fecha

Objetivo principal

Alcance

Técnica característica

Oleada 1 (Shai-Hulud)

Sep. de 2025

npm (general)

~4 paquetes

Primer gusano de npm con propagación automática

Oleada 2 (SHA1-Hulud)

Nov. de 2025

Zapier, Posthog, Postman

Más de 600 paquetes

Escape de contenedores; malware destructivo

Oleada 3 (Mini, SAP)

Abr. de 2026

SAP CAP-JS, MBT

4 paquetes

Inyección del hook SessionStart de Claude Code

Oleada 4 (TanStack)

11 de mayo de 2026

@tanstack/*

84 versiones/42 paquetes

Primera procedencia SLSA válida mediante secuestro de OIDC

Oleada 5 (AntV)

19 de mayo de 2026

@antv/* y 310 más

637 versiones/323 paquetes

Cuenta de mantenedor comprometida; C2 ampliado

Snyk ha cubierto en profundidad las oleadas anteriores:

Cada oleada reutilizó y amplió la carga útil ofuscada basada en el entorno de ejecución de Bun, al agregar nuevos mecanismos de persistencia (el hook SessionStart de Claude Code apareció por primera vez en la oleada 3), nueva infraestructura de exfiltración y métodos nuevos para falsificar una procedencia confiable. El problema de la procedencia SLSA merece especial atención: una certificación válida de Sigstore confirma qué canalización produjo un paquete, no si esa canalización fue comprometida. Confiar en la procedencia como señal de confianza sin auditar también la configuración de la canalización subyacente deja una brecha que esta campaña ya explotó en dos oleadas consecutivas.

Para ver una guía en video sobre cómo mitigar la campaña más amplia de Shai-Hulud con Snyk:

Shai-Hulud NPM Attack: Remediation with Snyk

Ataque Shai-Hulud a NPM: mitigación con Snyk — Guía para identificar y mitigar paquetes comprometidos con las herramientas de Snyk.

Cronología del ataque (19 de mayo de 2026, UTC)

Hora

Evento

01:39

Se publica la primera versión maliciosa del paquete desde la cuenta atool

01:56

Finaliza la primera oleada de publicaciones (~317 versiones)

02:05

Comienza la segunda oleada

02:06

Finaliza la segunda oleada (~314 versiones adicionales)

~02:18

Se detecta el ataque; investigadores de seguridad comienzan a enviar reportes

En curso

La investigación continúa; podrían identificarse más paquetes

Cobertura de Snyk

Snyk publicó avisos sobre las versiones afectadas de los paquetes en la Snyk Vulnerability Database. Los clientes de Snyk pueden usar el Zero Day Report en la aplicación para investigar qué proyectos de su organización están afectados. Las entradas de la Snyk Vulnerability Database sobre los paquetes afectados reflejan su estado de seguridad actual.

Para conocer el contexto de este tipo de ataque, la lección de Snyk Learn sobre Compromiso de paquetes legítimos explica cómo el compromiso de cuentas de mantenedores se relaciona con el panorama más amplio de amenazas a la cadena de suministro.

Protege tu cadena de suministro con Snyk

Los problemas de seguridad de la cadena de suministro afectaron al 87 % de las personas encuestadas. Protege la tuya con Snyk.