Volver al Blog

ChainDrop: 444 paquetes de npm comprometidos y ni un solo parche que aplicar

No es un fallo del código. Es una cuenta robada.
ChainDrop · 444 paquetes · 4 de agosto de 2026

Llevamos el verano discutiendo si los agentes autónomos sirven para algo. El 4 de agosto, entre las 09:35 y las 13:20 UTC, un programa sin una sola línea de inteligencia hizo solo, sin supervisión y en menos de cuatro horas, algo que ningún piloto corporativo ha conseguido: comprometió 444 paquetes de npm, se propagó con las credenciales que iba robando por el camino y, de paso, se llevó las claves de las herramientas de IA de los desarrolladores a los que infectó. Se llama ChainDrop. No es un agente: es un gusano. Y lo que importa para tu empresa es que aquí no hay ningún parche que aplicar.

La cuenta de GitHub del mantenedor de keyv —una librería de almacenamiento clave-valor que casi nadie instala a propósito, porque entra como dependencia de una dependencia— fue comprometida. Con ese acceso, el atacante empujó código malicioso a la rama principal y dejó que el pipeline de publicación legítimo hiciera el resto. A las 09:35 UTC salía [email protected]. A partir de las 09:38 y hasta las 13:20, una segunda oleada completamente automática alcanzó otros 433 paquetes usando las credenciales que la primera había cosechado.

Las cifras conviene darlas con su matiz, porque no todo el mundo cuenta lo mismo. En el número de paquetes hay acuerdo: 444. En el de versiones no: StepSecurity contabiliza 2.212 y Aikido 1.381, seguramente porque cada uno cortó el recuento en un momento distinto de una campaña que seguía moviéndose. Microsoft lo describe con prudencia como «más de 400 paquetes de publicadores no relacionados entre sí». Los tres paquetes de mayor alcance, según StepSecurity, son keyv (153,7 millones de descargas semanales), flat-cache (149,9) y file-entry-cache (147,6). Aikido suma más de 2.000 millones de instalaciones mensuales en el conjunto. Nadie ha contado cuántas empresas hay detrás de esas instalaciones; lo que mide ese número es el calibre de la tubería por la que se coló.

La firma era válida. Y decía la verdad.

Aquí está el detalle que a nosotros nos parece el más importante de todo el episodio, y el que menos se ha contado. [email protected] se publicó mediante trusted publishing con OIDC y salió con procedencia válida: la atestación criptográfica que certifica desde qué repositorio y con qué workflow se construyó ese artefacto. Nadie falsificó nada: el pipeline oficial compiló, firmó y publicó código que ya venía troyanizado desde el repositorio.

Socket lo formula en una frase que merece la pena copiar en la pizarra: la procedencia atestigua la integridad de la construcción, no la del código fuente. Es la diferencia entre el sello del notario y el contenido del contrato. El notario certifica que firmaste tú, en su despacho, ese día. No certifica que supieras qué estabas firmando. La procedencia responde «¿lo construyó el workflow oficial a partir de ese commit?» —sí— y no responde «¿ese commit lo escribió quien debía?».

La consecuencia práctica es incómoda y hay que decirla: si tu política de dependencias es «solo instalamos paquetes con procedencia verificada», el 4 de agosto esa política dio luz verde. No pasa nada por reconocerlo; la procedencia sube el listón y hay que seguir exigiéndola. Pero no es un cinturón de seguridad, y venderla como tal —cosa que hace bastante material comercial— deja a mucha gente tranquila por el motivo equivocado.

El código corre antes de que tú decidas nada

Cada paquete comprometido recibió dos ficheros —setup.mjs, el cargador, y Math_Symbol.js, de 727.680 bytes, que en los paquetes alcanzados por la segunda oleada aparece como math_init.js— y una línea en el package.json: "preinstall": "node setup.mjs". Esa línea es todo el ataque. npm ejecuta los scripts de preinstall antes de terminar la instalación: antes de tus tests, antes de tu análisis de dependencias, antes de que nadie mire un diff. En el portátil del desarrollador y en el runner de integración continua, exactamente igual.

La mitigación cabe en un parámetro: npm ci --ignore-scripts. Y como no nos gusta vender atajos, el matiz: no es gratis. Los paquetes con extensiones nativas que compilan durante la instalación dejan de funcionar, así que hay que identificarlos y permitirlos uno a uno. Es trabajo de una sola vez y no vuelve, y es la diferencia entre «me he traído código malicioso al disco» y «lo he ejecutado con mi usuario».

Se descargó Bun de GitHub, y por eso tu proxy no dijo nada

El cargador no traía intérprete propio: se descargaba Bun v1.3.13 legítimo desde github.com/oven-sh/bun/releases, con el artefacto correcto para cada plataforma (Linux x64 con glibc y con musl, Linux arm64, macOS y Windows). Es la técnica que se conoce como vivir de la tierra, aplicada a la telemetría: el indicador de red que queda en tus logs apunta a github.com, un dominio que nadie bloquea y que en una máquina de desarrollo tiene tráfico durante toda la jornada.

Si tu detección se apoya en «conexiones a dominios raros», aquí no suena nada. Lo que sí es anómalo y sí se puede buscar: un proceso bun ejecutándose en un runner de un proyecto que jamás ha usado Bun, y ficheros nuevos con nombre de utilidad matemática dentro de node_modules. La señal existe, y hay que ir a buscarla al comportamiento del proceso.

Lo que se llevó, y el renglón que casi nadie tiene apuntado

El barrido fue ancho, y cada casa lo mide distinto: StepSecurity habla de unas 140 rutas de credenciales, Aikido de unos 200 patrones de búsqueda y Elastic de más de 300 patrones únicos. Metodologías diferentes, misma conclusión: no buscaba una cosa concreta, buscaba todo. Tokens de npm y de la CLI de GitHub, credenciales de AWS —incluidos Secrets Manager y SSM en dieciséis regiones—, de Google Cloud, de Azure, tokens de HashiCorp Vault, cuentas de servicio de Kubernetes y claves SSH.

Y luego está el renglón nuevo: .claude/credentials.json, .cursor/credentials.json, .openai/auth.json, .anthropic/auth.json. Haz la prueba en tu casa: abre el procedimiento de rotación de secretos de tu empresa y busca esas cuatro rutas. No están. Esas cuentas suelen darse de alta sin pasar por ningún proceso, acaban pagándose con la tarjeta de alguien del equipo y rara vez entran en el inventario. Una clave de API de un proveedor de IA es dinero directo —se consume y se factura— y, a medida que esas herramientas se conectan a repositorios y a documentación interna, cada vez es también una puerta a contexto de la empresa. Es la evolución natural del malware que ya apunta a los desarrolladores y a sus agentes en lugar de al servidor de siempre.

El detalle más desagradable es el que apunta StepSecurity sobre los runners de GitHub Actions: el payload lee la memoria del proceso a través de /proc/<pid>/mem buscando la cadena "isSecret":true. Traducido: los secretos que la plataforma enmascara con asteriscos en el log están en claro en la memoria del proceso que los usa. El enmascarado protege del log, no de quien ya está dentro de la máquina. Es la misma lección que sacábamos del servidor de CI/CD que guarda las llaves de producción, ahora aplicada al runner efímero que la mayoría considera desechable precisamente porque dura cinco minutos.

El centro de mando estaba en Ethereum

El gusano no traía la dirección de su servidor escrita dentro. La consultaba: una llamada eth_call a un contrato de la red principal de Ethereum, 0xE1f2395ee43e45A1556EC6438a88c31B83493103, lanzada contra 75 puntos de acceso RPC públicos hasta que alguno contestara. El dominio de exfiltración observado fue npm-cache.com; Elastic documenta además awqhnjewqjkl[.]icu. Si el contrato no respondía, el gusano caía a buscar la infraestructura de reserva en commits de GitHub. Y Microsoft describe un plan C ruidoso: crear un repositorio público con la descripción «Shai-Hulud: Here We Go Again», el guiño a la familia de gusanos de la que este desciende.

Bloquear npm-cache.com en el proxy está bien y cuesta un minuto. Pero conviene entender qué se consigue: el dominio es la parte desechable. La dirección de destino se lee de una cadena de bloques pública que nadie puede retirar, incautar ni dar de baja, y el atacante la actualiza cuando quiere con una transacción. Las listas negras de dominios siguen sirviendo para lo que sirven, que es bastante; simplemente no cierran esta puerta.

La trampa: revocar el token es lo que la dispara

Si de todo el artículo te quedas con un párrafo, que sea este. El payload deja instalado un vigilante: ~/.local/bin/gh-token-monitor.sh, arrancado por un servicio de systemd en Linux o por un LaunchAgent en macOS. Consulta api.github.com/user cada 60 segundos durante 24 horas. Cuando el token robado deja de funcionar —es decir, en el instante exacto en que tú lo revocas— ejecuta una carga del atacante. Microsoft lo describe como un componente que «mantiene el acceso a credenciales y contiene un manejador destructivo si el token vigilado se revoca».

Esto invierte el reflejo correcto, que es lo que lo hace peligroso. Cualquiera con formación decente, al enterarse de que le han robado un token, entra en GitHub y lo revoca. Es lo primero que enseñamos. Aquí, ese clic es la señal de salida. El orden que recomienda StepSecurity, y que nos parece el único razonable, invierte los dos pasos: primero se busca y se elimina el vigilante —el fichero, su unidad de systemd de usuario, su LaunchAgent— y después se rota, desde un equipo limpio. En los runners también, aunque la intuición diga que ahí no hace falta porque se destruyen solos.

Este es el detalle que convierte el episodio de «susto de la cadena de suministro» en «hace falta un procedimiento escrito». Improvisar la respuesta a las once de la noche, con el equipo de desarrollo mirando y alguien preguntando cada diez minutos si ya está, es exactamente el escenario en el que se pulsa el botón equivocado.

Aquí no hay nada que parchear

Hace una semana escribíamos sobre una biblioteca que nadie eligió y que abrió un agujero en aplicaciones Rails. Aquel caso tenía CVE, aviso oficial, rango de versiones afectadas y lista de versiones corregidas. Podías cruzar una lista con la otra, sacar el número de equipos en el rango y planificar una ventana. Era desagradable, pero era un problema con forma conocida.

Esto no. No hay fallo de código: hay una cuenta robada. No existe un [email protected] que arregle lo que hizo el 6.0.0, porque el 6.0.0 llevaba dentro un pasajero con billete en regla. Lo que hay es volver a una versión anterior —o subir a la limpia allí donde el mantenedor ha republicado, cosa que ha pasado en unos pocos de los 444— y asumir que cualquier máquina que ejecutó la instalación ya entregó sus secretos. La pregunta operativa no es «¿estoy parcheado?»; es «¿qué máquinas hicieron npm install desde las 09:35 UTC del 4 de agosto, y qué credenciales había en cada una?».

El orden en que lo haríamos nosotros

  • Averigua si te tocó, mirando ficheros y no memoria. En los lockfiles (package-lock.json, pnpm-lock.yaml, yarn.lock), qué versiones de la familia keyv, cacheable, flat-cache y file-entry-cache entraron a partir del 4 de agosto. En disco, los ficheros setup.mjs, Math_Symbol.js y math_init.js dentro de node_modules, y cualquier package.json con "preinstall": "node setup.mjs".
  • Antes de tocar un solo token: quita el vigilante. ~/.local/bin/gh-token-monitor.sh, su unidad de systemd de usuario y, en macOS, su LaunchAgent. En los portátiles y en los runners. Este paso va primero; no es negociable.
  • Vuelve a versiones seguras con --ignore-scripts. Reinstalar sin ese parámetro en una máquina con el lockfile todavía envenenado vuelve a ejecutar el cargador.
  • Rota desde un equipo limpio, nunca desde el infectado: tokens de npm, PAT y claves SSH de GitHub, credenciales de cloud, cuentas de servicio de Kubernetes, tokens de Vault. Y la línea que falta en casi todas las listas: las claves de API de las herramientas de IA.
  • Audita la organización de GitHub. Workflows que nadie recuerda haber creado —StepSecurity cita uno llamado «Run Copilot»—, commits atribuidos a [email protected], repositorios públicos nuevos y cambios en .claude/settings.json o .vscode/tasks.json, que es donde el gusano se enganchaba para volver a arrancar.
  • Cruza los logs de auditoría de cloud con esas credenciales, desde el 4 de agosto hasta hoy, buscando uso desde direcciones que no son las tuyas. Rotar sin mirar esto te dice que has cerrado la puerta, no lo que pasó mientras estuvo abierta.
  • Y lo permanente: --ignore-scripts por defecto con una lista corta de excepciones justificadas; un periodo de reposo antes de adoptar una versión recién publicada, que en este caso habría bastado; doble factor en npm; y tokens efímeros en el CI en lugar de PAT de larga vida guardados en disco.

Lo que esto no es

No es un argumento contra npm ni contra el código abierto, y quien lo use como tal esta semana está haciendo trampa. Tu aplicación propietaria arrastra también cientos de dependencias; la diferencia es que no las ves y que, cuando le pasa esto a un proveedor cerrado, te enteras seis meses después por una carta. Aquí la cadena entera es pública: por eso hay cinco análisis forenses independientes publicados en pocos días y por eso puedes buscar los indicadores tú mismo esta tarde.

Y «auditar todas las dependencias» tampoco es un plan. Es lo que se dice en la reunión y lo que nadie hace el martes siguiente: el árbol de dependencias de un proyecto mediano no se lo lee nadie, ni este mes ni el que viene. Lo que sí se sostiene en el tiempo son dos rutinas poco lucidas: credenciales que caducan solas y credenciales que llegan a poco. Un token de npm que expira en una hora y solo puede publicar un paquete vale muy poco robado. Y si el portátil que compila puede desplegar en producción con una clave guardada en disco, el radio de acción de una línea preinstall es toda tu infraestructura, que es una frase que da miedo precisamente porque es literal.

Pero lo que nos vamos a llevar de este episodio es lo del principio. El 4 de agosto el control funcionó exactamente como está diseñado: la atestación se generó, se verificó y era correcta. Y el paquete iba envenenado igual. Eso no se arregla añadiendo otra casilla a la lista de comprobación. Se arregla sabiendo qué pregunta responde exactamente cada control que tienes puesto —la procedencia responde a «¿lo construyó quien dice?», y ahí se acaba su trabajo— y cuál de tus riesgos se queda sin nadie que lo conteste. Quién escribió el commit sigue siendo un asunto de cuentas, contraseñas y segundos factores en el ordenador de otra persona. Es la parte aburrida, y es la que decidió el 4 de agosto.

Nosotros no vendemos herramientas de seguridad de la cadena de suministro ni tenemos un producto que resuelva esto, y quien te diga que lo tiene te está vendiendo una alarma para una puerta que no es la que se abrió. Lo que sí hacemos es el trabajo de alrededor: saber qué aplicaciones tiene una empresa, quién las construye, qué credenciales viven en su pipeline, cuánto duran y hasta dónde llegan. Es lo que hay detrás de nuestros proyectos de datos y aplicaciones y de la consultoría que hacemos sin vender licencias de nadie. Si hoy no puedes contestar qué máquinas de tu empresa ejecutaron npm install la semana pasada, escríbenos y lo miramos contigo.

Nota sobre fuentes. La cronología ([email protected] publicado a las 09:35 UTC del 4 de agosto de 2026 mediante trusted publishing con OIDC y procedencia válida; segunda oleada de 09:38 a 13:20 UTC), el recuento de 444 paquetes y 2.212 versiones, los tamaños de setup.mjs y Math_Symbol.js (727.680 bytes), la descarga de Bun v1.3.13, las ~140 rutas de credenciales, la lectura de /proc/<pid>/mem buscando "isSecret":true, el contrato de Ethereum 0xE1f2395ee43e45A1556EC6438a88c31B83493103 con 75 endpoints RPC, el vigilante gh-token-monitor.sh con sondeo cada 60 segundos durante 24 horas y el orden de remediación, del análisis de StepSecurity. La descripción del gusano, el manejador destructivo al revocarse el token vigilado, el cifrado AES-256-GCM de la exfiltración y el repositorio público de reserva, del análisis de Microsoft Security del 4 de agosto de 2026. El compromiso de la cuenta de GitHub del mantenedor, el recuento alternativo de 1.381 versiones y los más de 2.000 millones de instalaciones mensuales, de Aikido. La frase sobre que la procedencia atestigua la integridad de la construcción y no la del código fuente, de Socket. Los indicadores adicionales y los más de 300 patrones de credenciales, de Elastic Security Labs. Donde las cifras no coinciden entre fuentes lo decimos en el texto en lugar de elegir la más alta. La lectura sobre la procedencia como sello y no como cinturón, el orden de las tareas y la conclusión sobre vida y alcance de las credenciales es nuestra. Imagen de portada: «Eimskip container terminal at Port of Reykjavík», de Quintin Soloviev, vía Wikimedia Commons, bajo licencia CC BY 4.0 (recortada).

Compartir:

Suscríbete a nuestra newsletter

Para recibir historias del mundo IT, novedades de everyWAN y ofertas exclusivas para suscriptores, date de alta a nuestra lista de correo

everyWAN
everyWAN