TL;DR:
- StepSecurity contó 435 paquetes únicos y 1,557 versiones maliciosas al corte de las 16:20 UTC del 4 de agosto.
- Los paquetes afectados suman más de 2,000 millones de instalaciones mensuales, pero esa cifra no representa descargas del malware ni usuarios únicos.
- El gusano roba credenciales, contamina otros paquetes y consiguió que varias publicaciones llevaran atestaciones SLSA válidas.
ChainDrop, un gusano de cadena de suministro derivado de Shai-Hulud, contaminó 435 paquetes únicos y 1,557 versiones de npm en poco más de dos horas el 4 de agosto de 2026, de acuerdo con el corte más reciente de StepSecurity. El ataque comenzó con keyv@6.0.0 y alcanzó librerías como flat-cache, file-entry-cache y cacheable, presentes como dependencias indirectas en una enorme cantidad de proyectos JavaScript. El malware roba credenciales de desarrollo y nube, usa las llaves obtenidas para publicar más paquetes infectados y deja mecanismos de persistencia en herramientas como Claude Code y Visual Studio Code.
El número de 1,300 paquetes que comenzó a circular durante las primeras horas del incidente no coincide con los inventarios técnicos más recientes. La diferencia importa: confundir paquetes con versiones o repositorios de exfiltración infla el conteo y dificulta que los equipos sepan qué deben buscar en sus archivos de bloqueo.
La cifra de 1,300 mezcló tres métricas distintas
Aikido informó a las 13:37 CEST que había identificado 434 paquetes distribuidos en 1,381 versiones, con más de 2,000 millones de instalaciones mensuales combinadas. Unas horas después, StepSecurity elevó su corte a 435 paquetes y 1,557 versiones. El segundo número creció porque el gusano republicó numerosas versiones históricas de un mismo paquete, no porque cada publicación correspondiera a un nombre nuevo en npm.
La otra cifra que ronda los 1,300 aparece en el análisis de Aikido como el número aproximado de repositorios públicos de GitHub que contenían el indicador Shai-Hulud: Here We Go Again. El malware usaba esos repositorios como puntos de entrega para credenciales robadas y cifradas. Son repositorios de exfiltración, no paquetes de npm.
StepSecurity dividió el alcance comprobado en dos grupos:
- 11 paquetes portadores completos dentro de los proyectos mantenidos por Jared Wray, con el cargador y el gusano de 727 KB.
- 424 paquetes propagados por el propio malware mediante credenciales obtenidas de nuevas víctimas.
- 1,557 combinaciones de paquete y versión contaminadas durante la ventana observada entre las 09:35 y las 11:44 UTC.
Los más de 2,000 millones de instalaciones mensuales tampoco equivalen a 2,000 millones de infecciones. Es la suma del volumen habitual de descarga de las librerías afectadas. Una misma aplicación puede instalar varias de ellas, los sistemas automatizados generan descargas repetidas y muchas copias nunca resolvieron una versión maliciosa.
Un commit contaminado atravesó una cadena de confianza válida
Los investigadores rastrearon el inicio a la cuenta de GitHub del responsable de Keyv, Cacheable y otras utilidades. El atacante introdujo archivos maliciosos directamente en la rama principal, creó etiquetas de lanzamiento y dejó que los flujos legítimos de GitHub Actions publicaran las nuevas versiones en npm mediante OIDC Trusted Publishing.
Eso produjo atestaciones SLSA válidas. La infraestructura confirmó que el paquete había sido construido desde el commit indicado y por el flujo esperado. El problema estaba antes: el commit ya contenía el malware. La procedencia verificó el recorrido de la publicación, pero no pudo decidir si el cambio había sido autorizado por el mantenedor.
Entre los primeros paquetes contaminados estuvieron:
keyv@6.0.0, con unos 153.7 millones de descargas semanales en el conteo de StepSecurity.flat-cache@6.1.24, con unos 149.9 millones por semana.file-entry-cache@11.1.6, con unos 147.6 millones por semana.cacheable-request@13.0.20.cacheable@2.5.1.cache-manager@7.2.10.ecto@5.0.1.
La lista se extendió a paquetes asociados con organizaciones como ServiceTitan, OneReach, Ornikar, Deliveroo, Picsart y Qlik. Varios nombres afectados son dependencias transitivas de herramientas muy usadas. Un desarrollador podía ejecutar el código sin haber agregado Keyv o flat-cache de forma directa a su proyecto.

El preinstall robó credenciales y abrió la siguiente infección
Cada versión portadora añadía un script preinstall que ejecutaba setup.mjs antes de completar npm install. Ese archivo descargaba la distribución oficial de Bun 1.3.13 desde GitHub y la usaba para arrancar Math_Symbol.js, una segunda etapa ofuscada de aproximadamente 728 KB. El tráfico parecía apoyarse en infraestructura legítima porque la descarga provenía del repositorio oficial de Bun.
Una vez activo, el payload buscaba secretos en la computadora del desarrollador o en el runner de integración continua. Los objetivos documentados incluyen:
- Tokens de npm y GitHub, incluidos tokens OIDC de GitHub Actions.
- Credenciales de AWS, Google Cloud y Microsoft Azure.
- Tokens de HashiCorp Vault y cuentas de servicio de Kubernetes.
- Llaves SSH, archivos
.env, estados de Terraform y credenciales de registros Docker. - Claves de Stripe, tokens de Slack y credenciales guardadas por herramientas de IA para programación.
El gusano cifraba los datos y los enviaba principalmente a repositorios de GitHub creados desde las cuentas comprometidas. StepSecurity también observó un canal de mando que resolvía su destino mediante un contrato de Ethereum y usaba npm-cache.com para la comunicación. Después, ChainDrop examinaba qué paquetes podía publicar cada token de npm robado, los reempaquetaba con el mismo gancho de instalación y repetía el ciclo.
La persistencia ampliaba el riesgo. Los análisis de StepSecurity, Socket y Datadog encontraron cambios en .claude/settings.json y .vscode/tasks.json, además de configuraciones de CI, capaces de volver a ejecutar el cargador al abrir o confiar en un repositorio. Borrar node_modules o subir de versión no elimina por sí solo credenciales ya robadas ni esos mecanismos colocados fuera del paquete.
Qué revisar si un proyecto instaló una versión afectada
StepSecurity advirtió que una sola ejecución de npm install durante la ventana de exposición bastaba para activar el cargador. La revisión debe cubrir proyectos, runners de CI y computadoras de desarrollo, incluso cuando Keyv o Cacheable solo aparezcan como dependencias indirectas.
- Busca todos los nombres y versiones afectados en
package-lock.json,pnpm-lock.yamlyyarn.lock. El listado completo cambia con el saneamiento del registro, por lo que conviene usar el inventario vivo de los investigadores. - Revisa la presencia inesperada de
setup.mjs,Math_Symbol.jsomath_init.jsdentro denode_modules, así como cambios en carpetas.claude,.vscodey flujos de GitHub. - Si una versión afectada se ejecutó, trata la computadora o el runner como comprometido. Aísla el entorno, conserva registros y revisa actividad anómala en npm, GitHub y los proveedores de nube.
- Retira primero la persistencia y el monitor de tokens descrito por StepSecurity y Socket. Ese componente puede reaccionar cuando se revoca un token de GitHub.
- Después, revoca y rota desde un equipo limpio todos los secretos accesibles para el proceso: tokens de npm y GitHub, llaves SSH, credenciales de nube, secretos de CI, accesos de Kubernetes y sesiones sensibles.
- Fija versiones limpias exactas, reconstruye los archivos de bloqueo y considera reinstalar con scripts desactivados mientras termina la investigación.
La atestación válida obliga a revisar más que una firma
ChainDrop atravesó controles que habrían detenido publicaciones sin firma porque las versiones iniciales salieron de flujos legítimos de GitHub Actions. El episodio muestra el límite preciso de la procedencia criptográfica: puede vincular un artefacto con su fuente y su proceso de construcción, pero necesita controles adicionales para detectar un commit malicioso dentro de un repositorio ya confiable.
Las defensas que proponen los investigadores incluyen imponer una antigüedad mínima antes de adoptar lanzamientos nuevos, restringir scripts de instalación, fijar dependencias por versión e integridad, vigilar cambios directos en ramas protegidas y mantener un inventario de lo que realmente ejecutan los runners. En este caso, un periodo de espera de varios días habría dejado fuera casi todas las versiones contaminadas, publicadas y detectadas en cuestión de horas.
El conteo puede seguir moviéndose mientras npm y los mantenedores terminan la limpieza. Al corte de las 16:20 UTC, la medida comprobada era de 435 paquetes y 1,557 versiones. El dato más incómodo permanece fijo: el malware llegó con una atestación válida emitida por un flujo legítimo, así que verificar la firma de un paquete ya no basta para confiar en el cambio que esa firma transporta.