Skip to content

Shai-Hulud se hizo con keyv, y salió con una procedencia válida

4 de agosto de 2026 · PlayCISO

Confirmado y en curso. El 4 de agosto de 2026, la cuenta de GitHub del mantenedor de keyv (una biblioteca de almacenamiento clave-valor con unos 127 millones de descargas semanales) se vio comprometida y se usó para publicar malware que roba credenciales en toda la familia de paquetes de ese mantenedor. El equipo de investigación de amenazas de Socket y Aikido han publicado sus análisis; Aikido informa de que la campaña se extendió al menos a 868 paquetes más en 1.381 versiones, que en conjunto suman más de 2.000 millones de instalaciones al mes. Mientras se escribía esto seguían apareciendo nuevas versiones maliciosas. El gusano es Shai-Hulud: la misma familia que hay detrás de las oleadas de 2025, y la misma que llegó a Suno.

Dos minutos sobre cómo funcionó y por qué unas firmas válidas no lo detuvieron. Descárgalo para enseñárselo a tu equipo: sin registro y sin barreras.

Qué se vio afectado

La familia inicial comparte un mismo mantenedor, y por eso la toma de control de una sola cuenta llegó a toda ella a la vez:

  • keyv: 619 millones de descargas al mes
  • flat-cache: 565 millones al mes
  • file-entry-cache: 557 millones al mes
  • cacheable-request: 133 millones al mes
  • @cacheable/utils: 34 millones al mes; cacheable: 29 millones al mes; @cacheable/memory: 28 millones al mes
  • cache-manager: 16 millones al mes; @cacheable/node-cache: 5,9 millones al mes; ecto: 4.000 al mes

La mayoría de los equipos que lean esto no instalan keyv a propósito. Instalan algo que instala algo que lo instala: estos paquetes están en lo más profundo de los árboles de dependencias de herramientas habituales, incluido ESLint. Si tienes un proyecto de JavaScript con un linter, da por hecho que estás dentro del alcance hasta que lo compruebes. La mayoría de los usuarios afectados nunca escribió ninguno de estos nombres.

Socket indica versiones envenenadas concretas, que es lo que de verdad necesitas para una auditoría: keyv@6.0.0, cacheable@2.5.1, @cacheable/utils@2.5.1, @cacheable/memory@2.2.1, @cacheable/net@2.1.1, @cacheable/node-cache@3.1.2, flat-cache@6.1.24, file-entry-cache@11.1.6, cacheable-request@13.0.20 y cache-manager@7.2.10. Trata esa lista como un mínimo, no como un máximo: Socket la actualiza a medida que llegan nuevas variantes, y su página de la campaña es la versión en directo.

Cómo funcionó

Se subieron archivos maliciosos directamente a main y se sacó una release de inmediato. Cada paquete afectado ganó dos archivos (setup.mjs, un dropper ofuscado, y Math_Symbol.js, una carga útil de 728 KB) más un hook "preinstall": "node setup.mjs" en package.json. Un script preinstall se ejecuta automáticamente con npm install. Nadie tiene que importar el paquete, llamarlo ni llevarlo a producción. Instalar es ejecutar.

El dropper descarga un runtime Bun independiente (v1.3.13) y ejecuta la segunda fase con él. No es por comodidad: es evasión. Ejecutarse con Bun esquiva por completo la versión de Node del host, junto con cualquier monitorización, hook o instrumentación a nivel de Node que tengas. Si tu detección da por hecho que el JavaScript malicioso se ejecuta con el Node que instalaste, esto no lo ve.

Lo que recolecta va más allá de los secretos del portátil de un desarrollador. La información describe claves de AWS, GCP y Azure, tokens de HashiCorp Vault, tokens de cuentas de servicio de Kubernetes, credenciales OIDC de GitHub Actions y tokens de publicación de npm, además de .npmrc y la configuración de GitHub CLI. Esa lista es un entorno de CI/CD, no una estación de trabajo, y te dice dónde se diseñó para aterrizar.

La exfiltración se hace por DNS y subiendo los secretos robados a repositorios de GitHub creados por el atacante, que en esta oleada se titulan «Shai-Hulud: Here We Go Again». Después usa las credenciales de publicación robadas para envenenar paquetes de otros mantenedores. Ese es el comportamiento de gusano, y es la razón por la que el número de afectados no dejó de crecer a lo largo del día.

La parte que debería preocuparte: la procedencia aguantó

Las versiones envenenadas se publicaron con una procedencia válida, firmada por GitHub Actions. No se falsificó nada. Ninguna firma falló. Cualquier control de la cadena de suministro que pregunte «¿lo compiló el pipeline de CI real a partir del repositorio real?» recibió un sí verdadero.

Porque así fue. El atacante no derrotó la procedencia: se situó por encima de ella. Se hizo con la cuenta del mantenedor, subió código al repositorio real y dejó que el pipeline legítimo firmara un código auténticamente malicioso. La procedencia da fe del origen, no de la intención, y este incidente es la demostración más limpia de esa distinción que hemos visto.

Si tu política de dependencias trata «firmado con procedencia» como una decisión de confianza y no como un dato más, este ataque la atraviesa sin más. La procedencia responde a «¿viene de donde dice?». Nunca ha respondido a «¿deberías ejecutarlo?». Para revisar el lado de la publicación de tus propios proyectos, usa la lista de comprobación de integridad de releases de GitHub, que cubre las etiquetas protegidas, la publicación de confianza y una aprobación humana de las releases.

El análisis de Socket lo afina aún más: el gusano se propaga reempaquetando los paquetes de otros mantenedores con el mismo hook y republicándolos mediante la publicación de confianza con OIDC de npm, el mecanismo pensado precisamente para eliminar de la CI los tokens de publicación de larga duración. Es una mejora de seguridad real, y el gusano se aprovecha de ella, porque un flujo de trabajo al que se confía la publicación publicará lo que contenga el repositorio. Reforzar la credencial no sirve de nada cuando el atacante es dueño del repositorio para el que se confía en esa credencial.

Se instala en tu editor

La carga útil escribe hooks de arranque automático en los directorios .claude y .vscode. Lee con atención la condición de activación, porque es el peor detalle de este incidente: esos hooks se ejecutan cuando un desarrollador o un agente de programación con IA abre el repositorio clonado. No hace falta ningún npm install.

Eso rompe el modelo de contención con el que trabajan la mayoría de los equipos. «¿Qué máquinas ejecutaron una instalación?» es la pregunta equivocada; la correcta es «¿qué máquinas han clonado o abierto este repositorio desde el día 4?». Un desarrollador que descarga el repositorio para investigar el incidente, o un agente que lo abre para ayudar con la limpieza, ejecuta la carga útil al hacerlo.

La limpieza lo pasa por alto una y otra vez. Los equipos reconstruyen node_modules, rotan todos los tokens y lo dan por cerrado, mientras un hook de arranque automático local del proyecto sigue en una carpeta oculta que git bien puede estar ignorando. Y como los destinos de la exfiltración se resuelven por DNS en lugar de estar codificados, bloquear un dominio no corta necesariamente el canal.

Si usas Claude Code, Cursor, VS Code o cualquier agente que ejecute configuración local del proyecto, revisa esos directorios expresamente en cada repositorio que haya tocado una versión afectada. Un agente que ejecuta un hook al abrir es un mecanismo de persistencia con los privilegios de tu desarrollador.

Qué hacer ahora

  • Da por hecha la ejecución, no solo la exposición. Un hook preinstall se dispara al instalar, también en la CI y también en compilaciones que luego fallaron. Cualquier máquina que ejecutara npm install con una versión afectada está dentro del alcance, no solo los hosts de producción.
  • Después amplía la pregunta más allá de las instalaciones. Por los hooks del editor y de los agentes descritos, «¿quién instaló?» no es el límite. Pregunta quién ha clonado o abierto un repositorio afectado desde el 4 de agosto, incluido quien lo descargó para ayudar a investigar.
  • Revoca, no rotes, y empieza por npm y GitHub. Rotar deja la credencial antigua válida hasta que caduca; revocar la mata ya, y esa es la diferencia que importa cuando un gusano está publicando activamente. Los tokens de npm y GitHub van primero porque son los que lo propagan a otras personas. Después, las claves de AWS/GCP/Azure, los tokens de Vault, los tokens de cuentas de servicio de Kubernetes y todo lo que sea alcanzable desde un host que ejecutó scripts de instalación.
  • Bloquea el ámbito en el proxy de tu registro. La contención más fuerte disponible: deniega por completo keyv, @keyv y cacheable hasta que se confirme que la cuenta está limpia. Fijar versiones es una carrera contra una lista que se sigue actualizando; bloquear el ámbito, no.
  • Fija por hash de integridad, no por rango. Si tienes que seguir instalando, reconstruye los lockfiles con versiones que se sepa que están limpias, sin rangos de acento circunflejo ni de virgulilla y sin npm update. Un rango es una orden de descargar lo más reciente, que es justo el modo de fallo de este caso.
  • Reconstruye en limpio. npm cache clean --force, borra node_modules y reinstala desde un lockfile fijado. Un tarball en caché sobrevive a la fijación de una versión.
  • Revisa los logs de la CI antes que los portátiles. Los agentes de compilación instalan más a menudo que los desarrolladores, tienen mejores credenciales y nadie los vigila.
  • Busca la persistencia, no solo el paquete. Busca setup.mjs y Math_Symbol.js en los árboles de dependencias, e inspecciona los directorios .claude y .vscode de los repositorios afectados en busca de hooks de arranque automático que no hayas escrito tú.
  • Búscalo en Defender. Se ha publicado una consulta KQL de la comunidad para Microsoft Defender for Endpoint que compara tu entorno con la lista en directo de versiones comprometidas de Socket.
  • Impón MFA resistente al phishing en las cuentas de publicación. Todo empezó con la toma de control de una sola cuenta. Todas las consecuencias posteriores se derivaron de ese único hecho.

La incómoda cuestión estructural

La seguridad de la cuenta personal de un solo mantenedor se convirtió, en la práctica, en el control de seguridad de 2.000 millones de instalaciones al mes. No se ha establecido públicamente que esa persona hiciera nada mal, y el ecosistema no le dio ningún mecanismo para cargar con menos peso. Tu superficie de ataque no es tu lista de dependencias: es cada mantenedor que hay por encima de ella, y cada dispositivo desde el que esos mantenedores inician sesión.

Eso no lo puedes auditar. Solo puedes acortar la vida de las credenciales, para que un token robado sea un callejón sin salida y no un punto de apoyo, y decidir de antemano qué harás el día que ocurra, porque ya está ocurriendo más de una vez al año.

Confirmado por Socket y Aikido el 4 de agosto de 2026, y todavía activo: Socket informa de nuevas variantes maliciosas que aparecen en tiempo real y mantiene la lista oficial de paquetes. Las cifras de este artículo son las publicadas en esa fecha y pueden revisarse. En el momento de escribir esto no hemos visto ninguna declaración de npm ni del mantenedor. Una cifra que conviene retener: Socket detectó los artefactos afectados una media de cinco minutos y dieciocho segundos después de su publicación, y aun así llegó a todos estos paquetes. El límite no es la velocidad de detección; es la velocidad de instalación.

Informe de investigación de Socket, con IoC → · Análisis técnico de Aikido → · Wiz sobre la oleada anterior de Shai-Hulud → · Alerta de CISA sobre el compromiso del ecosistema npm →


La Cyber War Room de PlayCISO recrea exactamente este escenario: una dependencia transitiva que nunca elegiste, una rotación de credenciales con un problema de orden y un plazo de comunicación que empezó a correr antes de que lo supieras. Juega gratis un incidente en directo →

🖤 Black Friday: new customers get 3 months of Pro for $5, then $5/month. Ends 30 Nov. Claim 3 months for $5 →