El 4 de agosto de 2026, un sofisticado ataque a la cadena de suministro de software, denominado «ChainDrop», afectó al ecosistema npm tras el acceso no autorizado a la cuenta de GitHub de un responsable del mantenimiento de los paquetes de código abierto Keyv y Cacheable, de amplio uso . El gusano autopropagable resultante se extendió a más de 2 251 versiones de 452 paquetes distintos, con aproximadamente 2 000 millones de descargas mensuales, lo que afectó a organizaciones como Deliveroo, Ornikar, OneReach, Picsart, Qlik y ServiceTitan.
Keyv es un sencillo almacén de clave-valor para Node.js compatible con múltiples backends, mientras que Cacheable es su marco de caché HTTP subyacente. Juntos, estos paquetes constituyen la infraestructura de almacenamiento en caché fundamental en todo el ecosistema de JavaScript. Ambos paquetes son mantenidos por el mismo desarrollador, cuya cuenta de GitHub fue comprometida por el autor del ataque.
El atacante publicó la primera versión maliciosa, keyv@6.0.0, aproximadamente a las 9:00 UTC del 4 de agosto de 2026. El malware es un descendiente de la familia «Mini» Shai-Hulud; la atribución a TeamPCP sigue sin confirmarse. El gusano se propagó rápidamente recopilando tokens de npm y GitHub de entornos comprometidos y utilizándolos para publicar nuevas versiones contaminadas. Socket verificó 2 251 versiones contaminadas en 452 paquetes.
La carga útil roba una amplia gama de datos confidenciales, entre los que se incluyen credenciales de la nube, secretos de CI/CD, tokens de desarrolladores, archivos de configuración de IA y carteras de criptomonedas. Además, implanta hooks en Claude Code y Visual Studio Code para una ejecución persistente basada en el IDE. En el momento de redactar este informe, no se han asignado identificadores CVE a esta campaña.
Acceso inicial
El actor malicioso comprometió la cuenta de GitHub del mantenedor de Keyv, lo que le otorgó acceso directo de «push» a los repositorios de código fuente de Keyv, Cacheable, cache-manager, flat-cache, file-entry-cache y numerosos paquetes relacionados dentro del monorepo del mantenedor.
Publicación maliciosa
A partir de las 9:00 UTC, el atacante introdujo cargas maliciosas de persistencia en el IDE en el repositorio de Keyv y, a continuación, publicó keyv@6.0.0 y las versiones maliciosas posteriores. Dado que los paquetes se compilaron y publicaron a través de sus flujos de trabajo legítimos de GitHub Actions, las versiones comprometidas incluían información de procedencia SLSA válida, lo que les permitió eludir los controles de verificación de integridad. Cada paquete contaminado contenía una entrada «preinstall»: «node setup.mjs» en su archivo package.json, lo que provocaba que el dropper se ejecutara automáticamente antes de que se completara cualquier instalación de npm.
Propagación del gusano
La capacidad de autopropagación del malware le permitió infectar paquetes pertenecientes a otros mantenedores cuyos proyectos dependían de un paquete previamente comprometido. Al recopilar tokens de publicación de npm válidos de los ejecutores de CI/CD y de las estaciones de trabajo de los desarrolladores, el gusano volvía a publicar paquetes posteriores con la carga útil maliciosa inyectada, creando una cadena de compromiso en cascada. Este mecanismo permitió que el ataque escapara del espacio de nombres original de Keyv/Cacheable y se propagara más allá de los límites de la organización.
Paquetes afectados
Entre los paquetes principales comprometidos se encontraban:
|
Nombre del paquete |
Versiones maliciosas afectadas |
Versión verificada como segura / restaurada |
|
keyv |
6.0.0 (y versiones posteriores maliciosas) |
5.6.0 |
|
flat-cache |
6.1.24 |
6.1.23 |
|
caché de entradas de archivo |
11.1.6 |
11.1.5 (o versión estable anterior) |
|
solicitud-almacenable-en-caché |
13.0.20 |
13.0.19 (o versión estable anterior) |
|
gestor-de-caché |
7.2.10 |
7.2.9 |
|
cacheable |
2.5.1 |
2.5.0 (o versión estable anterior) |
|
@cacheable/utils |
2.5.1 |
2.5.0 (o versión estable anterior) |
|
@cacheable/memory |
2.2.1 |
2.2.0 (o versión estable anterior) |
|
@cacheable/node-cache |
3.1.2 |
3.1.1 (o versión estable anterior) |
|
@cacheable/net |
2.1.1 |
2.1.0 (o versión estable anterior) |
|
ecto |
5.0.1 |
5.0.0 (o versión estable anterior) |
Posteriormente, el gusano se propagó a paquetes de organizaciones como Ornikar (entre ellos, @ornikar/eslint-config-*, @ornikar/prettier-config, @ornikar/babel-preset-* y muchos más), Qlik (@nebula.js/nucleus), HubSync (@hubsync/web-sdk-react) y muchos otros. Wiz Research mantiene una lista completa y actualizada continuamente en su repositorio público de GitHub.
Importante: El registro de npm sufrió cambios rápidos a lo largo del incidente. A las 17:40 h IST del 4 de agosto, se restauraron como versiones más recientes versiones limpias anteriores de al menos nueve paquetes fundamentales, entre los que se incluyen keyv@5.6.0, flat-cache@6.1.23 y cache-manager@7.2.9. Las organizaciones deben verificar la exposición utilizando las versiones exactas de los paquetes resueltas en los archivos de bloqueo, y no las etiquetas actuales del registro.
Análisis del malware
Descripción general de la carga útil
El malware se despliega en una arquitectura de dos etapas:
Fase 1: setup.mjs (dropper): este script ligero de Node.js actúa como carga útil inicial. Comprueba si existe el entorno de ejecución de JavaScript Bun y, en caso de que no esté presente, descarga la versión v1.3.13 de Bun desde las versiones oficiales de GitHub. A continuación, transfiere la ejecución al paquete principal del ladrón de información antes de eliminar el directorio temporal del entorno de ejecución para reducir el rastro forense.
Etapa 2: Math_Symbol.js / math_init.js (ladrón de información): un paquete compilado de 727 680 bytes, fuertemente ofuscado, que se ejecuta a través del entorno de ejecución Bun. Este es el motor central de recopilación de credenciales. Se han observado en el entorno real ambos nombres de archivo (Math_Symbol.js y math_init.js), que contienen una funcionalidad idéntica con el mismo hash SHA-1.
Objetivos de la recopilación de credenciales
El ladrón de información enumera y extrae de forma sistemática:
Mecanismos de persistencia
El repositorio de Keyv conservaba ganchos independientes para Claude Code (.claude/settings.json) y Visual Studio Code (.vscode/). Estos mecanismos de persistencia a nivel de IDE pueden ejecutar la carga útil una vez que el usuario confía en el espacio de trabajo o autoriza la configuración del proyecto, lo que proporciona una vía de ejecución secundaria independiente de los scripts del ciclo de vida de «npm install».
El malware también instala un observador de revocación de credenciales —una trampa que activa un controlador local proporcionado por el atacante cuando se renuevan los tokens—. Los responsables de la respuesta deben eliminar este observador antes de renovar cualquier credencial.
Mando y control
El malware recupera sus dominios C2 de un contrato inteligente de Ethereum (StringListStore) mediante una llamada eth_call, en lugar de incrustarlos en la carga útil. Esta resolución C2 basada en la cadena de bloques permite al operador actualizar la infraestructura sin modificar el malware. El historial en cadena muestra que el contrato se configuró inicialmente con tres dominios antes de actualizarse para devolver únicamente npm-cache[.]com. El propietario del contrato recibió fondos de una dirección previamente señalada por actividad fraudulenta.
Exfiltración
Los datos robados se cifran mediante un esquema híbrido de AES-256 y RSA-4096, y luego se exfiltran a través de dos canales:
Las primeras entradas en los repositorios de exfiltración contienen la frase intimidatoria: «Si bloqueas esta clave API, se colgarán los servidores de producción en vivo de todos los clientes de terceros».
Indicadores de compromiso (IoC) conocidos
|
Categoría |
Indicador |
Descripción |
|
Dominio |
npm-cache[.]com |
Dominio principal de C2 y exfiltración de datos; alojado a través de Cloudflare |
|
Dominio |
registry[.]npmjs[.]org |
Punto final de validación de tokens utilizado indebidamente para la verificación de credenciales en tiempo real antes de la exfiltración |
|
Dominio |
eth-mainnet[.]nodereal[.]io |
Punto final RPC de Ethereum utilizado para la recuperación de dominios C2 basados en contratos inteligentes |
|
Dominio |
go[.]getblock[.]io |
Punto final RPC de Ethereum utilizado para la recuperación de dominios C2 basados en contratos inteligentes |
|
Dominio |
eth[.]llamarpc[.]com |
Punto final RPC de Ethereum utilizado para la recuperación del dominio C2 basado en contratos inteligentes |
|
Dominio |
pypi-get[.]com |
Infraestructura adicional controlada por los atacantes |
|
Dominio |
js-mirror[.]com |
Infraestructura adicional controlada por los atacantes |
|
IPv4 |
104[.]21[.]35[.]216 |
Dirección IP de Cloudflare asociada a npm-cache[.]com |
|
IPv4 |
172[.]67[.]167[.]200 |
Dirección IP de Cloudflare asociada a eth[.]llamarpc[.]com |
|
IPv4 |
185[.]44[.]207[.]215 |
Dirección IP asociada a go[.]getblock[.]io RPC de ETH |
|
IPv4 |
35[.]175[.]164[.]77 |
Dirección IP de AWS asociada a eth-mainnet[.]nodereal[.]io |
|
Nombre de archivo |
Math_Symbol.js |
Carga útil ofuscada de un programa de robo de información (727 680 bytes); desplegada a través de scripts de preinstalación del paquete npm |
|
Nombre del archivo |
math_init.js |
Variante de la carga útil del programa de robo de información con funcionalidad idéntica |
|
Nombre del archivo |
setup.mjs |
Dropper de la etapa 1; descarga el entorno de ejecución de Bun y activa la ejecución del infostealer |
|
Hash del archivo (SHA-1) |
35a672cf34b996b91f3e1c28cbf3a05a37e036e4 |
Math_Symbol.js / math_init.js: carga útil del ladrón de información |
|
Hash del archivo (SHA-1) |
686aa40d0fc22c8d569494543a0f891f359f2f99 |
setup.mjs ubicado en el directorio .claude (gancho de Claude Code) |
|
Hash del archivo (SHA-1) |
f525d52ceb966516686b482d3dc0137028cc6a63 |
setup.mjs ubicado en el directorio .vscode (gancho de VS Code) |
|
User-Agent |
Bun/1.3.13 |
Cadena de agente de usuario HTTP utilizada durante la descarga en tiempo de ejecución de Bun y las comunicaciones C2 |
|
Ruta del sistema de archivos |
/tmp/bun-dl-*/ |
Directorio temporal utilizado para la descarga del tiempo de ejecución de Bun y la preparación |
|
Ruta del sistema de archivos |
node_modules/keyv/Math_Symbol.js |
Ubicación de la carga útil del programa de robo de información dentro del árbol de paquetes instalado |
|
Cadena |
Shai-Hulud: Here We Go Again |
Descripción del repositorio de GitHub utilizada para los repositorios de exfiltración |
|
Cadena |
Si bloqueas esta clave API, se colgarán los servidores de producción en vivo de todos los clientes de terceros |
Cadena de intimidación en las primeras confirmaciones de exfiltración |
Detecciones y búsqueda de amenazas
Los equipos de seguridad deben implementar las siguientes medidas de detección:
Medidas de mitigación y corrección
Acciones inmediatas
Recuperación del sistema
Revisión de la nube y del código fuente
Fortalecimiento de la cadena de suministro
Si le preocupa alguna de las amenazas descritas en este boletín o necesita ayuda para determinar qué medidas debe tomar para protegerse de las amenazas más graves a las que se enfrenta su organización, póngase en contacto con su gestor de cuentas o, si lo prefiere, contáctenos para averiguar cómo puede proteger su organización.