Ataque «Shai-Hulud» a npm: keyv, versiones afectadas, qué hacer

Ya están disponibles los parches de php2shell (CVE-2026-109618 y CVE-2026-109619) para PHP en fin de vida útil. Dado que las respuestas del departamento de ventas están tardando más de lo habitual debido a la elevada demanda, la forma más rápida de obtener los paquetes parcheados es comprar ELS para Runtimes en línea (se abre en una pestaña nueva). Lee las preguntas frecuentes sobre los parches

Ir directamente al resumen generado por IA de esta entrada del blog

  • Resume esta entrada con Perplexity
  • Resume esta publicación con Gemini

La versión resumida

El ataque «Shai-Hulud» a npm volvió a producirse el 4 de agosto de 2026, entrando en el registro a través de keyv y el cacheable familia de bibliotecas de almacenamiento en caché, para luego propagarse por sí sola a cientos de paquetes más. Se trata de un programa de robo de credenciales que se autorreplica. Los investigadores que lo analizaron en profundidad descubrieron que la campaña se identificaba a sí misma en las descripciones de los repositorios de GitHub como «Shai-Hulud: Here We Go Again», lo que la sitúa en la misma línea que los ataques a la cadena de suministro de npm de septiembre de 2025 y el Las campañas de Shai-Hulud y Megalodon que analizamos en junio.

Las cifras varían según quién haya realizado el recuento y cuándo. JFrog documentó más de 400 paquetes en más de 1.700 versiones. CyberScoop informó de que la oleada inicial superó los 440 paquetes en menos de cuatro horas, y que el total final superó los 860 paquetes, con un total combinado de dos mil millones de instalaciones mensuales. Se puede considerar que son más de 400 paquetes y, según algunos recuentos, más de 800.

La cifra que hay que tener en cuenta no es el número de paquetes, sino las cuatro horas. Se produjo una brecha de seguridad en la cuenta de un mantenedor y, en tan solo una tarde, la infección se había propagado a cientos de proyectos no relacionados, cuyos responsables no habían hecho nada malo y no tenían forma de saberlo.

Lo que hace que esta campaña sea interesante de estudiar es que se coló por dos vías completamente diferentes, y la mayor parte de las herramientas defensivas del sector no están orientadas a ninguna de ellas. Para acceder, fue necesario comprometer a un responsable del mantenimiento e introducir código malicioso en un árbol de código fuente. Para propagarse, no fue necesario escribir ningún código en absoluto. Solo se requirió la capacidad de publicar.

Son dos problemas distintos. Requieren dos respuestas distintas.

¿Me afecta el ataque «Shai-Hulud» a npm?

Empieza por aquí antes de leer nada más.

Podrías estar expuesto a un riesgo si has instalado, o si tu integrador ha instalado, cualquier versión de keyv, cacheable, cacheable-request, cache-manager, flat-cache, file-entry-cache, o cualquier cosa que se encuentre bajo el @cacheable a partir del 4 de agosto de 2026. Las emisiones contaminadas documentadas hasta la fecha son keyv 6.0.0, cacheable 2.5.1, @cacheable/memory 2.2.1, @cacheable/net 2.1.1, @cacheable/node-cache 3.1.2 y @cacheable/utils 2.5.1 según el análisis de JFrog, con cache-manager 7.2.10 y cacheable-request 13.0.20 confirmado por Wiz. Según informa CyberScoop flat-cache y file-entry-cache a los treinta minutos aproximadamente de la primera publicación. Comprueba qué ha resuelto tu propio archivo de bloqueo, ya que todas las listas publicadas seguían ampliándose a medida que se iban publicando.

Se considera que has estado expuesto a la segunda ola si consumes productos de la gama @onereach, @or-sdk, @nebula.js, @qlik, @ornikar o @servicetitan scopes, o cualquiera de los otros cientos de paquetes cuyos responsables tuvieran tokens de npm almacenados en un equipo al que hubiera llegado el gusano. Considera sospechosa cualquier instalación de npm realizada el 4 de agosto o después, hasta que hayas comprobado qué es lo que realmente se instaló.

Hay un detalle que reduce considerablemente el radio de la explosión. La carga útil se ejecutó desde un npm preinstall gancho de ciclo de vida, y en npm 12 y versiones posteriores esos ganchos no se ejecutan de forma predeterminada. Si tus constructores y equipos de desarrollo utilizan npm 12 o una versión posterior con la política de scripts predeterminada, o si realizas la instalación con --ignore-scripts, el dropper no se habría ejecutado aunque el archivo tarball infectado hubiera acabado en node_modules, a menos que se hubieran vuelto a activar explícitamente los scripts del ciclo de vida para estos paquetes. JFrog lo deja muy claro, y es, sin duda, la mejor noticia de todo este incidente.

Un detalle lo amplía. keyv y flat-cache No son paquetes que elijan la mayoría de los equipos. Llegan como dependencias transitivas de ESLint y de las bibliotecas de clientes HTTP, varias capas más abajo, en proyectos que ni siquiera saben que están ahí.

Por eso este ataque ha tenido un alcance mayor que sus predecesores. Según datos de telemetría de Wiz citados por CyberScoop, el paquete comprometido más extendido estaba presente en alrededor del 46 % de los entornos en la nube que pueden monitorizar, frente al 28 % aproximadamente de la anterior oleada de Shai-Hulud 2.0. El ataque npm de Shai-Hulud logró su alcance al introducirse a través de una infraestructura en la que nadie piensa.

Cómo se desarrolló el ataque «Shai-Hulud» a npm

El punto de acceso fue la cuenta de GitHub de uno de los responsables del mantenimiento. Wiz describe el keyv y cacheable paquetes que se vieron comprometidos «tras el ataque a la cuenta de un administrador de GitHub, lo que provocó la publicación de versiones maliciosas de los paquetes», y expone la secuencia de los hechos: el atacante utilizó en primer lugar la identidad comprometida para introducir cargas útiles de persistencia en el IDE en el keyv el propio repositorio y, poco después, publicó una nueva versión de keyv que transporta la carga útil.

Ese orden es importante. El código se incorporó al proyecto antes de que se publicara en el registro, por lo que la versión que llegó a npm se generó a través del propio proceso de publicación del proyecto, y no al margen de él. Según lo que se podía deducir de ese proceso, una identidad autorizada había introducido el código en el repositorio y había generado una versión. Para eso está diseñado.

En cada paquete se incluyeron dos archivos. El primero, setup.mjs, es un pequeño dropper ofuscado integrado en package.json como "preinstall": "node setup.mjs". No transporta la carga útil. Descarga el entorno de ejecución de JavaScript de Bun, lo descomprime y lo utiliza para ejecutar el segundo archivo: entre 710 y 728 kilobytes aproximadamente de código JavaScript muy ofuscado, denominado math_init.js en algunos paquetes y Math_Symbol.js en otros.

El hecho de recurrir a Bun en lugar del entorno de ejecución de Node ya presente es una decisión deliberada. De este modo, la carga útil dispone de un entorno de ejecución predecible, independientemente de la versión de Node que tenga la víctima, y el trabajo real se lleva a cabo en un proceso que el gestor de paquetes no supervisa.

La primera puerta: un árbol fuente envenenado

Para keyv y el cacheable paquetes, el código malicioso se encontraba en el repositorio Git de origen. Esto tiene más importancia de lo que podría parecer a primera vista.

Esto significa que el mecanismo de seguridad resiste cualquier intento basado en la suposición de que el código fuente es fiable y solo el artefacto publicado es sospechoso. Si se vuelve a compilar a partir del código fuente, se vuelve a crear la puerta trasera. Si se verifica el artefacto comparándolo con el código fuente, la verificación es correcta. Comprueba el certificado de procedencia y verás que es auténtico, porque la compilación se llevó a cabo realmente en el flujo de trabajo que indica, a partir de la confirmación que indica. El certificado vincula el artefacto a un código fuente y a un proceso de compilación, y ambos eran reales; simplemente, el código fuente ya estaba viciado.

La campaña llevó ese razonamiento hasta sus últimas consecuencias contra un objetivo diferente. JFrog detectó una lógica para casos especiales dirigida a OpenSearch opensearch-js repositorio, que utiliza GitHub Actions OIDC para la publicación segura: dentro de una ejecución de flujo de trabajo cuyo repositorio era opensearch-js y cuyo GITHUB_WORKFLOW_REF coincidente release-drafter.yml, la carga útil solicitó un token OIDC de Actions con un ámbito limitado a npm:registry.npmjs.org, lo canjeó por un token de publicación, publicó un archivo tar malicioso y generó un paquete Sigstore mediante Fulcio y Rekor, de modo que el resultado contara con una procedencia válida procedente de un contexto de flujo de trabajo de confianza.

La defensa más fiable contra esta puerta es que alguien o algo lea el diff.

La segunda puerta: 400 paquetes para los que nadie escribió código

Aquí es donde la campaña adquirió su envergadura, y donde la mecánica difiere del punto de partida de tal manera que cambia la forma de defenderse contra ella.

La carga útil recopila tokens de npm de ~/.npmrc y comprueba su validez con el registro. Cuando encuentra un token que tiene permiso de escritura y bypass_2fa Si se establece en «true», realiza lo siguiente, según la descripción de la rutina que ofrece JFrog: descarga el actual latest archivo tar, copia el ejemplo en ejecución a package/math_init.js, escribe el cargador en package/setup.mjs, sustituye el paquete completo scripts objeto y conjuntos scripts.preinstall a node setup.mjs, incrementa la versión del parche, y a continuación vuelve a empaquetar y publica mediante una autenticación directa PUT al registro de npm.

Vuelve a leer esa secuencia y fíjate en lo que falta. No hay ningún commit. No hay ninguna solicitud de incorporación de cambios. No hay ninguna ejecución de CI. No hay acceso al repositorio de ningún tipo. El árbol de código fuente de cada uno de esos varios cientos de paquetes secundarios estuvo limpio en todo momento y sigue estándolo, porque el código malicioso solo existía dentro de un archivo tar creado en el ordenador del atacante y subido directamente al registro con una credencial robada.

Esa es la mayor parte de este incidente, según el recuento de paquetes: cientos de mantenedores cuyos repositorios nunca se modificaron, cuya revisión de código nunca se eludió, cuya integración continua (CI) nunca detectó nada inusual y cuyos usuarios, a pesar de todo, instalaron malware.

Lo que transportaba la carga útil

El recopilador de credenciales es tan exhaustivo que da la impresión de que se trata de una lista elaborada a lo largo de varias campañas, en lugar de haber sido redactada específicamente para esta. Recorre cientos de rutas configuradas en Linux, macOS y Windows, recopilando tokens de gestores de paquetes, credenciales en la nube de archivos de configuración y servicios de metadatos de instancias, tokens de cuentas de servicio de Kubernetes, todo el almacén KV de HashiCorp Vault, claves SSH, perfiles de VPN, almacenes de credenciales de navegadores, tokens de Stripe y Slack, y claves para servicios de IA como OpenAI, Anthropic, Cursor y Gemini.

En los runners de GitHub Actions también se realiza una volcado de la memoria del proceso en busca de secretos que el runner ya haya descifrado. Detrás de todo ello se esconde un análisis exhaustivo de más de 200 patrones glob en busca de .env archivos, claves privadas, el estado de Terraform y las bases de datos de KeePass.

Los resultados cifrados se almacenan en repositorios públicos de GitHub creados con la propia cuenta de la víctima, con descripciones que siguen el patrón «Shai-Hulud: Here We Go Again». Ox Security ha contabilizado más de 800 repositorios de este tipo que pueden encontrarse en GitHub. Un canal HTTP de reserva apunta a un dominio registrado el 22 de mayo de 2026, más de dos meses antes de que se utilizara.

Ox Security también informa de un mecanismo de sabotaje que los demás informes no describen: si se revoca el token de GitHub robado, el malware emite órdenes de eliminación dirigidas al equipo infectado. Tómalo como una información facilitada por un proveedor, más que como un dato confirmado de forma independiente, pero tenlo en cuenta a la hora de planificar la secuencia de medidas correctivas. Revocar las credenciales antes de haber aislado el equipo puede tener consecuencias.

También se ensañó con tu editor

La parte de GitHub de la propagación se dirige a los equipos de los desarrolladores en lugar de al registro, y es la parte que tiene más probabilidades de seguir pasando desapercibida en algún repositorio.

Mediante el uso de tokens de acceso personal de GitHub sustraídos, la carga útil realiza cinco commits de archivos en hasta 50 ramas por repositorio: .vscode/tasks.json, .vscode/setup.mjs, .claude/math_init.js, .claude/settings.json y .claude/setup.mjs. El tasks.json Esta entrada define una tarea denominada «Configuración del entorno» que se ejecuta node .claude/setup.mjs, y el settings.json La entrada registra un SessionStart hook. La ejecución tiene lugar cuando un desarrollador abre el repositorio en VS Code o inicia una sesión de programación con IA.

Los commits se titulan chore: update config y llevan una línea de coautores falsificada en la que se les atribuye a [email protected]. Cuando el token robado también tiene ámbito de flujo de trabajo, la carga útil escribe un flujo de trabajo de GitHub Actions que extrae los secretos de Actions empaquetándolos en un artefacto y descargándolo en una rama denominada dependabot/github_actions/format/setup-formatter.

Cada uno de esos nombres se ha elegido para que pase desapercibido en un vistazo rápido. Una rama que parece Dependabot, un flujo de trabajo que parece CodeQL, una confirmación que parece una tarea de mantenimiento de la configuración, un artefacto que parece un formateador y una línea de coautores que parece un asistente de IA: son elementos que muchos equipos incluyen ahora de forma legítima en sus repositorios.

Por qué los escáneres tradicionales no detectaron el ataque «Shai-Hulud» a npm

Las herramientas en las que confían la mayoría de las organizaciones se crearon para responder a una pregunta que este ataque no plantea.

Los escáneres de vulnerabilidades comparan tu árbol de dependencias con bases de datos de CVE conocidos. El 4 de agosto no existía ningún CVE para keyv 6.0.0, por lo que no había nada con lo que compararlo. Para cuando se publicaron los avisos, el plazo de cuatro horas ya había vencido y el gusano ya había pasado a otra parte.

Esto no es una crítica a los escáneres. La detección de vulnerabilidades conocidas es una tarea diferente, y es algo que hacen bien.

Los controles de cuentas del Registro también cumplieron con su función. La autenticación de dos factores protege una cuenta de publicación frente a alguien que solo disponga de una contraseña. No protege frente a un token generado por esa cuenta para la automatización, y el gusano buscaba específicamente tokens marcados para eludir la autenticación de dos factores precisamente porque esos son los tokens que funcionan sin la presencia de un usuario. Muchas configuraciones automatizadas de publicación en npm han dependido históricamente precisamente de ese tipo de credencial: un token que permite publicar sin la presencia de un usuario.

La procedencia y la firma son los casos más complicados, ya que en esta campaña funcionaron exactamente según lo especificado y, aun así, dieron un resultado «verde». El intento de OpenSearch es el ejemplo más claro: un archivo tar malicioso publicado a través de un flujo de trabajo realmente fiable, con un paquete Sigstore generado mediante Fulcio y Rekor para que coincidiera.

La procedencia responde a la pregunta «¿procede este artefacto de la fuente y la cadena de suministro que se indica?», y la respuesta fue afirmativa. Merece la pena responder a esa pregunta y no pierde su utilidad en este contexto. Sin embargo, una marca de verificación verde te indica de dónde procede un artefacto, no si lo que procede de allí es seguro.

El análisis de accesibilidad presenta el mismo patrón de lagunas. Determinar que una ruta de código vulnerable nunca se ejecuta resulta útil para priorizar el trabajo relacionado con los CVE. A preinstall No es necesario que se pueda acceder al hook desde tu aplicación. Se ejecuta como parte de la instalación de la dependencia, antes de que tu aplicación lo llame.

Dos puertas necesitan dos cerraduras diferentes

Las dos vías de acceso de esta campaña se bloquean mediante mecanismos totalmente distintos, por lo que conviene precisar qué mecanismo de TuxCare cierra cada puerta.

SecureChain para npm ofrece componentes de código abierto que TuxCare recompila desde el código fuente en su propia infraestructura de compilación y publica en su propio catálogo, junto con certificados de procedencia y una SBOM de CycloneDX para cada artefacto. Los clientes configuran su gestor de paquetes para que utilice SecureChain en lugar del registro público, de modo que TuxCare se convierte en la fuente predeterminada desde la que se resuelven sus compilaciones.

Hay dos formas de hacerlo funcionar. Utilizar únicamente lo que figura en el catálogo de SecureChain, o bien incorporar un gestor de registros por delante, de modo que todo lo que aún no figure en el catálogo se redirija al registro público.

Se cierra la segunda puerta en lo que respecta a los derechos de publicación

Empecemos por el mecanismo de propagación, ya que es la parte más importante del incidente y la que presenta un panorama más claro. El método del gusano consistía en descargar un archivo tar publicado, inyectar dos archivos y sustituir el scripts bloquear, modificar la versión del parche y enviar el resultado al registro con un token robado. Ese ataque requiere, por encima de todo, una cosa: la capacidad de colocar una nueva versión de un paquete en el lugar donde tu resolutor la buscará.

Para cualquier componente del catálogo de SecureChain, la versión que resuelve tu compilación es la que TuxCare ha compilado a partir del código fuente de ese proyecto en su propio entorno de integración continua (CI). El archivo tar que ha vuelto a publicar el atacante no se encuentra en esa ruta, y por mucho que se publique en el registro público, no aparecerá allí. Los archivos maliciosos de esos varios cientos de paquetes secundarios solo existían dentro de un archivo tar subido a npm. Nunca estuvieron en el código fuente. Una compilación a partir del código fuente no los reproduce, y no hay nada que detectar porque no hay nada allí.

Vale la pena explicar claramente el mecanismo. La técnica que permitió que esta campaña alcanzara tal envergadura requiere acceso de publicación a la fuente de la que se genera tu compilación. Si se trata del registro público, basta con un token robado. Si se trata del catálogo de SecureChain, un token de npm robado no le sirve de nada al atacante, ya que publicar en npm no añade una versión a tu compilación.

La primera puerta se cierra al leer el «diff»

El punto de partida es un problema más complicado, y es el que el sector ha venido eludiendo en su mayor parte, porque la respuesta sincera es que alguien tiene que revisar el código.

SecureChain lleva a cabo la detección de código malicioso en su proceso de incorporación de paquetes. El detector analiza las diferencias en el código fuente entre una nueva versión de un paquete y la versión anterior, buscando patrones de comportamiento asociados a la compromisión de la cadena de suministro: ejecuciones sospechosas durante la compilación, ofuscación, exfiltración de datos y otros indicadores de intención maliciosa. Los ganchos de ejecución durante la compilación se incluyen explícitamente en el ámbito de aplicación, que abarca npm preinstall y postinstall, Python setup.py rutas de ejecución e invocaciones de complementos de Maven.

Una versión marcada se retiene y no se lanza. No se incluye en el catálogo, lo que significa que, para empezar, nunca llega a la compilación del cliente.

Piensa en lo que ese detector habría estado observando el 4 de agosto. La diferencia entre la última imagen nítida keyv y keyv La versión 6.0.0 incluye un nuevo archivo llamado setup.mjs, un segundo archivo nuevo de unos 710 kilobytes con código JavaScript muy ofuscado, y un preinstall entrada que aparece en package.json donde una biblioteca de almacenamiento en caché antes no tenía ninguna.

Cada uno de estos casos es una señal que el detector está diseñado para detectar: contenido ofuscado que aparece en un árbol de código fuente, un gancho de ejecución durante la compilación que surge donde antes no existía, y ambos elementos presentes en la misma versión de una biblioteca cuya única función es almacenar pares clave-valor. El detector se basa en lo que hace un cambio, en lugar de buscar coincidencias con archivos que ya se sabe que son maliciosos; por eso, volver a empaquetar la misma técnica con nombres de archivo diferentes no le permite pasar el filtro.

La detección a nivel de código fuente es el mecanismo de SecureChain que aborda directamente la primera puerta. Recompilar a partir del código fuente no sirve de nada cuando es precisamente el código fuente el que ha sido viciado. Ese fue el argumento que esgrimimos sobre la procedencia en junio, y esta campaña no lo ha cambiado. Más bien lo ha reforzado, al poner de manifiesto ambos modos de fallo en una misma tarde.

Medidas de mitigación sin SecureChain

Si tus compilaciones se resuelven directamente desde el registro público, estos son los controles que habrían limitado esta campaña, en el orden en que surten efecto.

Asegúrate de que todas las máquinas de los desarrolladores y todos los servidores de integración continua tengan instalado npm 12 o una versión posterior. El preinstall El cambio por defecto es la diferencia entre un archivo tar contaminado que se encuentra en node_modules y un archivo tarball malicioso que se ejecutó. Si aún no puedes actualizar, instálalo con --ignore-scripts y tratar los paquetes que realmente necesitan pasos de compilación como excepciones específicas.

Establece un límite mínimo de tiempo desde el lanzamiento para que tus compilaciones nunca instalen una versión el mismo día en que sale. Los cuatro gestores de paquetes principales ya incluyen esta función de forma nativa: npm tiene min-release-age A partir de la versión 11.10.0, Bun tiene minimumReleaseAge A partir de la versión 1.3.0, tanto pnpm como yarn cuentan con opciones equivalentes que permiten establecer exclusiones por paquete. Un plazo de entre tres y siete días supone un coste mínimo y habría cubierto por completo el intervalo de cuatro horas de esta campaña. Si solo vas a quedarte con un cambio de configuración de este artículo, que sea ese.

Comprueba los tokens de npm en busca de permisos de escritura combinados con la elusión de la autenticación de dos factores (2FA), que es precisamente la combinación que aprovechó el gusano. Los tokens de publicación deben tener una vigencia limitada, estar restringidos a los paquetes que los necesitan y emitirse para procesos automatizados, en lugar de para personas. Da prioridad a la publicación de confianza cuando el flujo de trabajo lo permita, teniendo en cuenta, a partir del caso de OpenSearch, que esto traslada el límite de confianza a las definiciones de tu flujo de trabajo, en lugar de eliminarlo.

Trata los runners de CI como sistemas de producción que gestionan información confidencial, ya que esta carga útil ha volcado la memoria del proceso del runner. Limita el alcance de las políticas de confianza de OIDC a repositorios y archivos de flujo de trabajo específicos, en lugar de aplicarlas a toda la organización, y exige que se revisen los cambios en .github/workflows.

Revisa lo que tu editor y tus herramientas de programación basadas en IA ejecutan automáticamente desde un repositorio. El .vscode/tasks.json y .claude/settings.json Vector funciona porque, al abrir un proyecto, se ejecuta la configuración que viene incluida en él. En la revisión de código, trata esos archivos como contenido ejecutable, porque lo son.

Consigue SecureChain →

Detección

Busca en todas las ramas de todos los repositorios, no solo en las ramas predeterminadas, los cinco archivos de hook: .vscode/tasks.json que contenga una tarea denominada «Configuración del entorno», .vscode/setup.mjs, .claude/setup.mjs, .claude/math_init.js, y .claude/settings.json con un SessionStart hook. Busca las confirmaciones tituladas chore: update config, sobre todo cuando en la línea de coautores figura el nombre de [email protected].

Busca los nombres de archivo en los árboles de dependencias y en las cachés de paquetes setup.mjs, math_init.js, Math_Symbol.js y router_runtime.js. JFrog publicó el hash SHA-256 de la carga útil que extrajo de keyv 6.0.0, así que, siempre que puedas, busca por esa versión en lugar de solo por los nombres de los archivos.

En los registros de auditoría de GitHub, busca el dependabot/github_actions/format/setup-formatter rama, se ejecutan flujos de trabajo denominados codeql_analysis.yml o «Run Copilot», que crean y eliminan un artefacto rápidamente, y cualquier artefacto llamado format-results. Busca repositorios creados en las cuentas de tu organización cuyas descripciones coincidan con el patrón «Shai-Hulud: Here We Go Again».

En lo que respecta a la red, busca conexiones a npm-cache.com, y para el tráfico saliente hacia puntos finales RPC públicos de Ethereum desde una infraestructura de compilación que no tiene por qué comunicarse con una cadena de bloques. La carga útil lee su lista actual de dominios C2 desde un contrato de la red principal de Ethereum, por lo que ambos son señales de detección útiles y ninguno de ellos constituye un bloqueo fiable.

Lista de comprobación para la corrección de la vulnerabilidad «Shai-Hulud» en npm, por orden de prioridad

  1. Determina si preinstall los scripts pudieran haberse ejecutado. Comprueba la versión de npm en cada equipo y ejecutor que haya instalado dependencias a partir del 4 de agosto de 2026, y si --ignore-scripts estaba en vigor. Si los hooks no se podían ejecutar, lo que te queda por hacer es garantizar la higiene de las dependencias, más que responder a incidentes.
  2. Aísla el sistema antes de revocar. Guarda primero los archivos tar, los registros de npm, los registros de CI, los registros de auditoría de GitHub y las imágenes de los runners. Teniendo en cuenta el comportamiento de eliminación observado tras la revocación de tokens, lleva a cabo el aislamiento secuencial antes de la rotación de credenciales en cualquier host en el que creas que se haya ejecutado la carga útil.
  3. Fija los archivos de bloqueo a las versiones publicadas antes del 4 de agosto de 2026 para cada paquete afectado y vacía las cachés del gestor de paquetes para que los archivos tar contaminados no permanezcan almacenados localmente.
  4. Revoca primero los tokens de npm, empezando por aquellos que tengan permiso de escritura y la posibilidad de saltarse la autenticación de dos factores (2FA); a continuación, revoca los tokens de acceso personal de GitHub y los tokens de Actions.
  5. Rota todo lo que el recopilador pudiera leer, partiendo de la base de que lo haya leído: credenciales en la nube, tokens de cuentas de servicio de Kubernetes, contenidos de Vault, credenciales de bases de datos, claves SSH, perfiles de VPN, tokens de Stripe y Slack, y claves API para servicios de IA.
  6. Busca en todas las ramas de todos los repositorios los cinco archivos de hook y las confirmaciones marcadas, y elimínalos.
  7. Recrea los servidores de CI y los entornos de desarrollo a partir de imágenes limpias. Limpiar archivos individuales no equivale a saber que la máquina está limpia.
  8. Revisa los editores de confianza de npm y las políticas de confianza de OIDC de GitHub, prestando especial atención a los flujos de trabajo de automatización de lanzamientos, ya que estos fueron el objetivo específico de los ataques.
  9. Solo entonces se debe pasar a la prevención: límites de edad para las versiones, npm 12, ámbito de aplicación de los tokens y requisitos de revisión de los flujos de trabajo.

Por qué esto sigue funcionando

La autoría sigue sin estar clara. Los investigadores citados por CyberScoop señalaron que aún no disponen de pruebas contundentes que relacionen este caso con TeamPCP, el actor que comprometió más de mil paquetes a principios de 2026, aunque la posibilidad no se descarta. Varias empresas informaron de la misma carga útil y el mismo patrón en todos los paquetes afectados, lo que apunta a un único actor o a un grupo muy bien coordinado, más que a imitadores.

La cuestión de la atribución es menos interesante que la estructural. La misma técnica sigue funcionando porque, en realidad, no es una técnica, sino una observación sobre cómo se construyen los ecosistemas de paquetes: cualquiera que pueda publicar una versión de un paquete puede ejecutar código en todas las máquinas que lo instalen. Eso no es un error de npm. Es para lo que sirve un script de ciclo de vida, y miles de paquetes legítimos dependen de ese comportamiento.

A esto hay que añadir el hecho de que los derechos de publicación pertenecen a particulares, que son estos quienes poseen los tokens, que los tokens se almacenan en archivos de ordenadores portátiles y que lo primero que hace la carga útil es buscar esos archivos. La propagación del gusano no es una capacidad extraordinaria; se trata del flujo de trabajo habitual de publicación, ejecutado por un programa malicioso en lugar de por un administrador.

En junio, el argumento era que una comprobación de procedencia «verde» es solo una señal más a la hora de decidir si se puede confiar en algo, y no un veredicto definitivo. En agosto, el argumento es más limitado y más mecánico: si la versión a la que apunta tu compilación es la que un atacante con un token robado haya subido más recientemente, entonces todos los demás controles de los que dispones quedan supeditados a una decisión que tú no has tomado.

Así que toma esa decisión a conciencia. Echa un vistazo a las versiones que se resolvieron en tus compilaciones el 4 de agosto y, a continuación, establece un límite mínimo de antigüedad de las versiones antes de la próxima oleada, porque habrá una. Si prefieres que las versiones que se resuelven en tus compilaciones procedan de un catálogo que alguien recompila y revisa, en lugar de de quienquiera que posea un token de publicación, ponte en contacto con nosotros para hablar sobre SecureChain para npm.

PREGUNTAS FRECUENTES

¿Se trata de la misma campaña que la de Shai-Hulud de septiembre de 2025?

Pertenece al mismo linaje —y así se denomina—, pero no se trata del mismo incidente. El ataque npm de Shai-Hulud de agosto de 2026 se produjo a través de keyv y cacheable en lugar de los paquetes afectados anteriormente, y CyberScoop lo describe como una variante de Mini Shai-Hulud. Según las cifras de prevalencia en el entorno facilitadas por Wiz, su alcance fue considerablemente mayor que el de sus predecesores.

El repositorio de GitHub del paquete parece estar limpio. ¿Significa eso que la versión que he instalado también está limpia?

No, y este es el detalle que más se suele pasar por alto. En el caso de los varios cientos de paquetes secundarios, el atacante nunca tocó el repositorio de código fuente en absoluto. El código malicioso solo existía en el archivo tar publicado en npm. Un repositorio limpio no te dice nada sobre lo que tu gestor de paquetes descargó realmente. Comprueba la versión que resolvió tu archivo de bloqueo, no el código fuente.

¿Una firma válida o un certificado de procedencia garantizan que un paquete es seguro?

No. En el caso de OpenSearch, esta campaña generó un paquete Sigstore auténtico, a través de Fulcio y Rekor, para un archivo tar malicioso publicado desde un flujo de trabajo de confianza. La procedencia establece el origen. No evalúa el contenido.

Utilizamos un registro privado que actúa como proxy de npm. ¿Estamos protegidos?

Solo si hace algo más que limitarse a transmitir los artefactos. Un proxy de almacenamiento en caché almacenará y servirá sin problemas la versión alterada, normalmente más rápido de lo que lo haría el servidor de origen. Lo que importa es si hay algo que inspeccione, recompile o retrase lo que pasa a través de él.

Ejecutamos un escáner de vulnerabilidades en cada compilación. ¿Por qué no lo ha detectado?

No se encontró ninguna vulnerabilidad conocida. Los escáneres comparan tu árbol de dependencias con las bases de datos de CVE y, el 4 de agosto, las versiones maliciosas tenían apenas unas horas de antigüedad y aún no se había publicado ningún aviso al respecto. El análisis de vulnerabilidades conocidas y la detección de código malicioso son problemas distintos que requieren herramientas diferentes.

¿Qué habría hecho SecureChain en este caso?

Hay dos aspectos distintos en las dos vías de acceso. En el caso de los varios cientos de paquetes afectados por la republicación de archivos tarball, la versión maliciosa no se encuentra en la ruta desde la que se resuelve tu compilación, ya que SecureChain ofrece la versión que TuxCare ha compilado a partir del código fuente, en lugar de actuar como proxy de lo que haya publicado más recientemente el desarrollador original. Para la inicial keyv y cacheable En el caso del compromiso, en el que el código se introdujo primero en el repositorio, el detector de código malicioso lee la diferencia entre versiones durante la incorporación y retiene la publicación, de modo que la versión contaminada nunca llega al catálogo. Ambas medidas se aplican en todo el SecureChain catálogo, para cada componente que contiene.

Resumen
Ataque «Shai-Hulud» a npm: keyv, versiones afectadas, qué hacer
Nombre del artículo
Ataque «Shai-Hulud» a npm: keyv, versiones afectadas, qué hacer
Descripción
El ataque «Shai-Hulud» a npm afectó a más de 400 paquetes en cuatro horas a través de «keyv» y «cacheable». Versiones afectadas, indicadores de compromiso (IOC) y una lista de medidas correctivas por orden de prioridad.
Autor
Nombre del editor
TuxCare
Logotipo de la editorial

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.

Índice
Obtenga las respuestas de seguridad de código abierto que necesita

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.