Skip to content

Back to the catalog

socket.devwriteup

Compromised npm Packages in the AsyncAPI Namespace Deliver Miasma Botnet Loader

Socket's Threat Research Team details a supply-chain compromise of four @asyncapi npm packages (generator-helpers, generator-components, generator, specs) published via a poisoned source commit through GitHub Actions trusted publishing. The packages contain an obfuscated load-time (not install-hook) implant that spawns a detached Node.js process to fetch an IPFS-hosted, AES-256-GCM-encrypted loader delivering the "Miasma" botnet/tasking framework, which supports REST C2, multi-channel comms (Nostr, IPFS, Ethereum RPC, BitTorrent DHT), shell execution, file exfiltration, and systemd persistence. Includes full technical analysis, IOCs, hashes, and MITRE ATT&CK mapping.

TL;DR Compromised @asyncapi npm packages deliver a load-time (no lifecycle hook) implant that pulls an encrypted Miasma botnet framework from IPFS, complete with REST C2 and systemd persistence.
Document

What it is

Four @asyncapi npm versions were compromised on 2026-07-14 to deliver the Miasma botnet loader: generator-helpers@1.1.1, generator-components@0.7.1, generator@3.3.1, and specs@6.11.2 (plus 6.11.2-alpha.1). The immediately prior versions — generator-helpers@1.1.0, generator-components@0.7.0, generator@3.3.0, specs@6.11.1 — are clean, so those are the rollback/pin targets.

Preconditions / exposure — the key operational point

This is not an install-hook attack: there is no pre/postinstall script. The implant runs at module load time, so blocking install scripts does nothing. Presence in a lockfile does not prove execution. Exposure depends on whether the infected file was actually imported by a build, CLI run, test suite, application path, or developer workflow. Socket's guidance: if you can't determine that, treat the host as compromised. The infected files (published paths):

  • helpers: src/utils.js
  • components: lib/utils/ErrorHandling.js
  • generator: lib/templates/config/validator.js
  • specs: index.js

What to do — detections

Hunt for both the package-stage trigger and the payload-stage framework. Monitor for:

  • node -e child processes spawned from package code; detached Node children with stdio: ignore / windowsHide: true
  • Node.js executing from fake NodeJS app-data dirs: %LOCALAPPDATA%\NodeJS\sync.js, ~/Library/Application Support/NodeJS/sync.js, ~/.local/share/NodeJS/sync.js, ~/.config/node/sync.js
  • Writes under ~/.config/.miasma/ (e.g. run/node.lock) and ~/.cache/mesa_shader_cache/
  • systemd user service ~/.config/systemd/user/miasma-monitor.service
  • Connections to 85.137.53.71 (C2 :8080 /api/v1/beacon, upload :8081, proxy-mgmt :8091)
  • Outbound IPFS gateway traffic during package execution; the CID QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9
  • Unexpected Nostr/IPFS/DHT traffic from build runners

Cleanup is more than rm -rf node_modules: a run can leave a background Node process, sync.js, a lock file, a camouflaged runtime file, and a systemd user unit (ExecStart runs node /opt/miasma/bin/miasma-monitor.js).

Why trusted publishing didn't stop it

All publishes are attributed to GitHub Actions via npm trusted publishing with SLSA provenance — but the provenance points to poisoned source commit 3eab3ec9304aa26081358330491d3cfeb55cc245 on asyncapi/generator branch refs/heads/next. The malicious code is in the commit diff, not just the tarball. That commit has forged identity data (Your Name <you@example.com>, GitHub user invalid-email-address). Provenance answered "who published" but not "who poisoned the source." The GitHub API returned 404 for the workflow run at analysis time, so the actor couldn't be recovered from public data.

Caveats

The recovered baked config runs in safeMode: true with propagate, recon, and all ecosystem-spread toggles (npm/pypi/ruby/cargo) off — only persist is on. CollectData and propagation code exist but were disabled in the recovered sample; the operator can swap the off-registry IPFS payload without republishing to npm, so this state isn't fixed. The shell blacklist is a single entry, ["killall"] — it does not restrict general ShellExec. Socket dumped the final 3.09 MB payload without running the final eval, so payload behavior is from static/safe-handler analysis, not live execution.

Organisations named here

  • AsyncAPI
  • T1027
  • T1041
  • T1059.004
  • T1059.007
  • T1102
  • T1105
  • T1195.002
  • T1543.002
  • T1564.001

Technologies named here

  • npm
  • GitHub Actions
  • Node.js
  • IPFS
  • GitHub Actions OIDC Trusted Publishing

Threat actors named here

  • Miasma