Dentro del compromiso de keyv en npm: malware preinstall, procedencia confiable y hooks de IDE
4 de agosto de 2026
0 minutos de lecturaEl 4 de agosto de 2026, atacantes comprometieron la ruta de publicación de keyv y otros paquetes de npm relacionados. Las versiones maliciosas agregan un hook preinstall que ejecuta un cargador ofuscado antes de que se inicie el código de la aplicación y, luego, lanza una carga útil de segunda etapa mucho más grande.
Se trata de un incidente activo en la cadena de suministro de software, no de una prueba de concepto. Snyk Security Research descargó y comparó de forma independiente los tarballs publicados sin instalarlos, enumeró todos los paquetes que npm devolvió para el mantenedor jaredwray e identificó 11 versiones maliciosas con los mismos dos archivos de carga útil. A las 11:16 UTC, ocho de esas versiones todavía tenían la etiqueta latest.
Resumen
Incidente: Código malicioso integrado en paquetes legítimos de npm
Paquete principal:
keyv@6.0.0Versiones afectadas detectadas por Snyk: 11 en
keyv, paquetes relacionados concacheableyectoEjecución:
"preinstall": "node setup.mjs"Carga útil:
setup.mjscarga una segunda etapa de 727,680 bytes llamadaMath_Symbol.jsEstado observado a las 11:16 UTC: Ocho versiones maliciosas seguían con la etiqueta
latest. Se habían eliminado tres.Aviso:
SNYK-JS-KEYV-18515941CVE/GHSA: Al momento de la investigación, no se había asignado ningún CVE ni GHSA
Severidad operativa: Crítica, porque la instalación permite ejecutar código controlado por el atacante con los privilegios del desarrollador o del ejecutor de CI
Remediación: Fija la versión o vuelve a una versión anterior que se sepa que es segura. No se había publicado ninguna versión sucesora segura de
keyv@6.0.0.Acción inmediata: No instales las versiones afectadas. Si ejecutaste una, aísla el host, busca y deshabilita la persistencia y, luego, rota las credenciales expuestas desde un sistema limpio.
Severidad y estado del aviso
Snyk publicó SNYK-JS-KEYV-18515941 mientras preparábamos este borrador. El aviso clasifica keyv@6.0.0 como código malicioso integrado, según CWE-506.
Durante nuestra investigación, no se había asignado ningún CVE ni registro de GitHub Advisory. La instalación afectada ejecuta código controlado por el atacante sin necesidad de privilegios adicionales ni interacción con la aplicación, usando los permisos y las credenciales disponibles para el desarrollador o el proceso de CI.
Qué confirmó Snyk de forma independiente
Consultamos el endpoint de búsqueda del registro de npm para los paquetes mantenidos por jaredwray. El endpoint devolvió 61 nombres de paquetes. Para cada nombre, recuperamos el manifiesto del registro, examinamos todas las versiones publicadas el 4 de agosto y verificamos sus scripts de ciclo de vida y los metadatos de los tarballs.
La revisión detectó estas versiones maliciosas y confirmó que los demás paquetes del ámbito @keyv no se habían comprometido:
keyv@6.0.0, publicada a las 09:35:00 UTC@cacheable/net@2.1.1, publicada a las 10:09:44 UTC@cacheable/node-cache@3.1.2, publicada a las 10:10:34 UTCcacheable@2.5.1, publicada a las 10:10:44 UTCflat-cache@6.1.24, publicado a las 10:10:55 UTC y eliminado posteriormente@cacheable/memory@2.2.1, publicada a las 10:11:29 UTCcacheable-request@13.0.20, publicada a las 10:11:24 UTC y eliminada posteriormentefile-entry-cache@11.1.6, publicada a las 10:13:02 UTC@cacheable/utils@2.5.1, publicada a las 10:14:21 UTCcache-manager@7.2.10, publicado a las 10:14:41 UTC y eliminado posteriormenteecto@5.0.1, publicada a las 10:28:01 UTC
La última entrada es importante para delimitar el alcance del incidente. ecto@5.0.1 apareció después de las primeras alertas públicas, y sus archivos setup.mjs y Math_Symbol.js son idénticos byte por byte a los de keyv@6.0.0. Las primeras listas de paquetes que omiten ecto están incompletas.
En nuestra captura de las 11:16 UTC, npm había eliminado flat-cache@6.1.24, cacheable-request@13.0.20 y cache-manager@7.2.10, y había vuelto a asignar sus etiquetas latest a 6.1.23, 13.0.19 y 7.2.9. Las otras ocho versiones afectadas seguían disponibles y tenían la etiqueta latest. El estado del registro puede cambiar rápido durante un incidente activo, por lo que los mirrors privados y los archivos de bloqueo siguen siendo parte de la investigación incluso después de que npm elimina una versión.
El tarball publicado difiere en tres puntos
Descargamos keyv@6.0.0 y su versión candidata, keyv@6.0.0-rc.1, directamente del registro y comparamos todos los archivos mediante SHA-256. No instalamos el paquete ni ejecutamos ninguna de las dos cargas útiles.
Solo hay tres rutas diferentes:
package.jsoncambió de la versión6.0.0-rc.1a la6.0.0, agregó los dos archivos de carga útil a la lista de archivos publicados y agregó el scriptpreinstall.Se agregó
setup.mjscon un tamaño de 29,918 bytes.Se agregó
Math_Symbol.jscon un tamaño de 727,680 bytes.
Todos los archivos de dist/ son idénticos byte por byte entre la versión candidata y la versión estable comprometida. La biblioteca sigue funcionando con normalidad después de la instalación, mientras que el hook de ciclo de vida se ejecuta por separado. Esa pequeña diferencia es una pista útil para la detección y una técnica eficaz para ocultarse.
El cambio en el manifiesto es directo:
El manifiesto de keyv@6.0.0 en el registro informa el siguiente valor de integridad del tarball:
Nuestros hashes independientes son:
Luego repetimos la verificación de hashes de la carga útil para cada versión afectada que pudimos recuperar durante nuestro análisis inicial. Los nueve tarballs disponibles en ese momento contenían el mismo archivo setup.mjs de 29,918 bytes y el mismo archivo Math_Symbol.js de 727,680 bytes, con exactamente los hashes indicados arriba. Más tarde, npm eliminó una de esas versiones. Para detectar el problema, usa los hashes y el comportamiento del ciclo de vida, en lugar de depender de un único nombre de paquete.
Cómo se ejecuta el malware
npm ejecuta automáticamente preinstall durante la instalación de dependencias. El desarrollador no necesita importar keyv, iniciar una aplicación ni llamar a una API vulnerable. Basta con resolver e instalar el paquete afectado.
El análisis estático de setup.mjs muestra verificaciones de plataforma para Linux, macOS y Windows, uso de child_process.execFileSync, operaciones del sistema de archivos, acceso HTTPS y manejo del entorno de ejecución de Bun. El cargador menos ofuscado, almacenado en .claude/setup.mjs, identifica la versión 1.3.13 de Bun y el nombre del archivo de segunda etapa, math_init.js. El cargador puede descargar desde GitHub una versión adecuada de Bun si no está instalado, ejecutar la carga útil más grande y eliminar los artefactos temporales del entorno de ejecución.
El análisis independiente del malware incluido en los informes del incidente indica que la segunda etapa cifrada apunta a tokens de GitHub y npm, credenciales de la nube, claves privadas, cadenas de conexión a bases de datos, tokens de Vault, tokens de cuentas de servicio de Kubernetes y memoria de los ejecutores de GitHub Actions. También informa sobre un mecanismo de persistencia, gh-token-monitor, que monitorea un token de GitHub robado y ejecuta un handler proporcionado cuando el token deja de funcionar.
No ejecutamos ni desciframos de forma independiente Math_Symbol.js, por lo que esos detalles sobre las capacidades de la segunda etapa deben conservar esa atribución. La guía operativa coincide con el análisis previo de Snyk sobre el mismo patrón de persistencia gh-token-monitor: contiene y deshabilita el monitor antes de revocar el token.
Una segunda vía de ejecución apunta a las herramientas de desarrollo
El hook del ciclo de vida de npm es solo un punto de entrada. Un registro de la API de GitHub del commit d8c850c7 muestra que se agregaron cinco archivos al repositorio de keyv:
.claude/settings.json.claude/setup.mjs.claude/math_init.js.vscode/tasks.json.vscode/setup.mjs
La configuración de Claude registra un comando SessionStart que invoca .vscode/setup.mjs. La tarea de VS Code usa runOn: "folderOpen" e invoca .claude/setup.mjs. Estos archivos crean una vía de ejecución adicional cuando un desarrollador o agente de programación confía en la configuración local del proyecto y la activa. Según la confianza del espacio de trabajo y la configuración del usuario, VS Code puede pedir confirmación antes de permitir tareas automáticas; por eso, abrir un checkout indica exposición, pero no demuestra que se haya ejecutado código.
GitHub verificó criptográficamente el commit, cuya identidad de autor es github-actions[bot]. La insignia de verificación demuestra que GitHub firmó el objeto del commit. No demuestra que el cambio haya sido autorizado por el mantenedor del proyecto. La evidencia respalda que se comprometió una cuenta, credencial, sesión o ruta de publicación. No identifica a la persona que la operó, y se debe tratar al mantenedor como una víctima del incidente.
La procedencia válida firmó la versión maliciosa
El manifiesto de npm identifica a GitHub Actions como el publicador de confianza de keyv@6.0.0 y enlaza a una atestación de npm para la versión. El código fuente malicioso estaba presente en el estado etiquetado del repositorio, por lo que el flujo de trabajo legítimo compiló y atestiguó el artefacto malicioso.
El parche del commit de publicación también agregó una prueba que ejecutaba setup.mjs mediante execFileSync. Un commit posterior eliminó solo esa prueba. Por lo tanto, ejecutar el conjunto de pruebas de la versión podría haber ejecutado el malware en CI antes de la publicación.
La procedencia sigue siendo una evidencia valiosa del origen de la compilación. Este incidente muestra sus límites: la procedencia puede atestiguar fielmente una compilación cuyo código fuente o contexto del flujo de trabajo ya se haya comprometido.
Impacto y posible alcance
Los principales paquetes afectados tienen volúmenes de instalación muy altos. npm registró 619,682,667 descargas de keyv, 579,751,309 de flat-cache y 571,240,025 de file-entry-cache entre el 5 de julio y el 3 de agosto.
Estas cifras se superponen ampliamente porque los paquetes dependen unos de otros y aparecen en las mismas cadenas de herramientas. Miden el alcance en el ecosistema, no la cantidad de hosts comprometidos. Además, el período de exposición de cada versión maliciosa fue mucho menor que un mes.
Snyk observa un gran alcance en los proyectos monitoreados. El uso transitivo es importante porque flat-cache y file-entry-cache suelen incorporarse a través de herramientas de desarrollo como ESLint. Las laptops de desarrolladores y los ejecutores de CI son objetivos especialmente valiosos porque a menudo contienen credenciales de GitHub, npm, servicios en la nube, firma y despliegue.
Al momento de redactar este texto, no hay un recuento público verificado de las ejecuciones exitosas de la segunda etapa ni de las credenciales exfiltradas. Los paquetes se publicaron activamente en el registro de producción de npm y, en nuestra captura, ocho seguían con la etiqueta latest, lo que confirma una distribución real y no un exploit de laboratorio. El aviso de Snyk clasifica la madurez del exploit como Atacado, pero no publica la cantidad de víctimas.
Cómo detectar la exposición
Primero, inspecciona el árbol de dependencias resuelto, incluidas las dependencias transitivas:
Busca en los archivos de bloqueo, ya que una versión afectada puede seguir fijada después de que npm cambie una dist-tag:
Inspecciona los manifiestos instalados para encontrar el hook exacto, sin ejecutar el código del paquete:
Busca indicadores de la carga útil y de persistencia:
También inspecciona los repositorios a los que se pueda acceder con credenciales expuestas de GitHub para encontrar archivos .claude/settings.json o .vscode/tasks.json inesperados, archivos de flujo de trabajo que contengan toJSON(secrets) y artefactos de GitHub Actions creados recientemente.
Los clientes de Snyk pueden usar el nuevo aviso sobre keyv@6.0.0 y deben volver a ejecutar las pruebas de dependencias y monitorear los proyectos a medida que se actualiza la información sobre el incidente:
La lección de Snyk Learn sobre paquetes legítimos comprometidos ofrece más contexto para integrar la integridad de las dependencias y los controles del registro en los flujos de trabajo de desarrollo habituales.
Remediación
Si se instaló un paquete afectado
Trata la máquina o el ejecutor como potencialmente comprometido, aunque ya hayas eliminado node_modules.
Aísla el host del acceso normal a la red. Conserva los registros, los datos de procesos, los logs de npm, la salida de los trabajos de CI y las marcas de tiempo del sistema de archivos para la investigación.
Busca la persistencia de
gh-token-monitorantes de revocar las credenciales de GitHub. Revisa~/.local/bin/gh-token-monitor.sh,~/.config/gh-token-monitor/,~/.config/systemd/user/gh-token-monitor.servicey~/Library/LaunchAgents/com.user.gh-token-monitor.plist.Deshabilita cualquier mecanismo de persistencia que encuentres. Coordina su eliminación con el equipo de respuesta a incidentes y conserva una copia forense. El monitor podría detectar la revocación.
Rota las credenciales desde un sistema que sepas que está limpio. Incluye tokens de acceso personal (PAT) y tokens de aplicaciones de GitHub, tokens de npm, AWS, GCP, Azure, Vault, Kubernetes, credenciales de bases de datos, claves privadas y cualquier secreto disponible para los trabajos de CI afectados.
Revisa los registros de auditoría de las cuentas y la nube. Busca publicaciones inesperadas en npm, creación de repositorios, modificaciones de flujos de trabajo, artefactos de Actions, llamadas a API de la nube y uso de tokens desde ubicaciones desconocidas.
Elimina los artefactos afectados de los registros privados y las cachés. Eliminar un paquete de npm no borra las copias que ya estén almacenadas en un proxy interno o en la caché de un desarrollador.
Fija versiones que sepas que están limpias antes de volver a instalar
Las siguientes versiones anteriores no tenían ningún hook del ciclo de vida de instalación en sus manifiestos del registro cuando las revisamos:
keyv@5.6.0 es la última versión estable y limpia de 5.x en nuestra captura. Volver de keyv@6.0.0 a 5.x puede requerir cambios en el código. 6.0.0-rc.1 tenía una salida dist/ idéntica byte por byte y no incluía un hook del ciclo de vida, pero los equipos de producción deberían preferir una versión estable consolidada, salvo que hayan validado explícitamente la versión candidata.
Después de agregar las sobrescrituras, vuelve a generar el archivo de bloqueo sin ejecutar scripts del ciclo de vida:
Deshabilitar los scripts del ciclo de vida limita esta vía de ejecución. Algunos paquetes legítimos requieren scripts de instalación, por lo que los equipos deberían mantener una lista de permitidos acotada en lugar de habilitar los scripts de forma global. Las prácticas recomendadas de seguridad para npm de Snyk explican con más detalle las instalaciones deterministas, los controles de scripts y la revisión de paquetes.
Cronología del incidente
Todas las horas que se indican a continuación corresponden al 4 de agosto de 2026, en UTC.
09:02 a 09:17: El commit
ee2681a9preparakeyv@6.0.0, agrega el hook del ciclo de vida, los archivos de carga útil y una prueba que ejecuta el cargador. GitHub registra una hora de autoría de las 09:02 y una hora de confirmación de las 09:17.09:04: El commit verificado
d8c850c7agrega los hooks de ejecución de Claude y VS Code.09:23: El commit
f97eabcdelimina la prueba de preinstalación.09:30 a 09:32: Se publican varias versiones 6 de
@keyv/*sin el hook malicioso.09:35: npm publica
keyv@6.0.0con el hook malicioso.09:51: El commit
1f79edd8agrega los archivos de carga útil en los espacios de trabajo de@keyv/*, lo que crea un riesgo para las publicaciones posteriores.09:49 a 09:51: Los problemas de GitHub
#2044,#2045y#2046reportan el incidente. Más tarde, la API de problemas devolvió410 Gone.10:09 a 10:14: Se publican las versiones maliciosas de la familia Cacheable.
10:18 y 10:20: El investigador de seguridad Charlie Eriksen publica las dos alertas públicas proporcionadas.
10:28:
ecto@5.0.1se publica con la misma carga útil, lo que amplía a 11 la lista de paquetes vinculados al mantenedor.10:39: Cambian los metadatos del registro de npm después de que se elimina
cacheable-request@13.0.20.10:42: Cambian los metadatos del registro de npm después de que se elimina
flat-cache@6.1.24.11:11: Cambian los metadatos del registro de npm después de que se elimina
cache-manager@7.2.10.11:16: La captura del registro de Snyk todavía detecta ocho versiones maliciosas con la etiqueta
latest.Durante la redacción: Snyk publica
SNYK-JS-KEYV-18515941y cerca de 70 avisos más.
Esta cronología refleja un incidente en curso. Vuelve a revisar los manifiestos de npm, las etiquetas dist-tag y los avisos de Snyk justo antes de la publicación.
Webinar a pedido
OpenAI se calificó a sí misma y luego vulneró producción
Mira la grabación a pedido para descubrir por qué la autovalidación falla por diseño, por qué una pila multimodelo empeora el problema y cómo es la validación independiente en la práctica. Llévate un marco para gobernar todos los activos de IA de tu entorno, sin importar qué laboratorio los haya creado.
