Miasma v3 Hit 4 AsyncAPI Packages — Did NPM's Defenses Work, or Just Get Dodged?

On 14 July 2026, malicious versions of three AsyncAPI packages were published to npm: @asyncapi/generator@3.3.1, @asyncapi/generator-helpers@1.1.1, and @asyncapi/generator-components@0.7.1 after an attacker gained push access to the repository's next branch and triggered the project's own CI/CD release pipeline. The malicious packages carry valid npm OIDC provenance attestations and execute a multi-stage downloader when the library is loaded via require().

July 14th, 2026

You’re Joking, Not Another One?!

On July 14, 2026 at 07:10 UTC, an attacker gained push access to the asyncapi/generator repository's next branch and pushed a single malicious commit under an unconfigured git identity ('Your Name' <you@example.com>). This triggered the project's legitimate release-with-changesets.yml GitHub Actions workflow. 

Three packages: @asyncapi/generator@3.3.1, @asyncapi/generator-helpers@1.1.1, and @asyncapi/generator-components@0.7.1 followed by @asyncapi/specs later on were published to npm with valid SLSA provenance attestations via npm's GitHub OIDC trusted-publisher integration, making the packages appear fully legitimate. 

Each package contained an identical, albeit familiar,  ~7.7KB obfuscator.io-obfuscated dropper injected into a single source file, padded with ~1000 leading spaces to hide it from diff views.

The dropper fires on require(), not at install time, spawning a detached, hidden Node.js process that downloads a second-stage payload from an IPFS gateway and writes it to an OS-specific hidden directory disguised as a Node.js runtime folder before exiting cleanly. None of the affected packages have a preinstall, postinstall, or install script. The dropper fires when the poisoned module is require()'d during normal use. While install-time execution fires on every single npm install, whether the package's code ever runs or not, load-time execution only fires for the subset of installs where something actually calls into the generator. This suggests that the hardening of npm is working, with the default script-blocking actually changing attacker behavior; you can find more information about this change and install scripts in our blog about the v12 changes.

Affected Packages

  • @asyncapi/specs@6.11.2

  • @asyncapi/specs@6.11.2-alpha.1

  • @asyncapi/generator@3.3.1

  • @asyncapi/generator-helpers@1.1.1

  • @asyncapi/generator-components@0.7.1

For Semgrep Customers

  1. Trigger a new scan if you haven't recently on your projects

  2. Check the advisories page to see if any projects have installed these package versions recently: https://semgrep.dev/orgs/-/advisories

  3. Use the Dependency filter to see if you’re using any of these packages: https://semgrep.dev/orgs/-/supply-chain/t/dependencies?packages=@asyncapi/generator%3Aexact-name,@asyncapi/generator-helpers%3Aexact-name,@asyncapi/generator-components%3Aexact-name

Indicators of Compromise

  • C2 host: 85.137.53.71: ports 8080 (commands), 8081 (credential exfil), 8091 (proxy management) and ipfs.io

  • Malicious commits: 3eab3ec9304aa26081358330491d3cfeb55cc245 (generator); 36269ce8…, 49cb17a9…, 61a930fc…, 689f5b96…(spec-json-schemas)

  • Attacker push identity: "Your Name" <you@example.com>, GitHub login invalid-email-address, unsigned

  • IPFS payload CIDs: QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9 (generator attack); Qmet4fhsAaWMBUxNDfREHwgiyDeSWy4YSYs9wiKUW5jGyf (specs attack)

  • Drop paths: ~/.local/share/NodeJS/sync.js (Linux); ~/Library/Application Support/NodeJS/sync.js (macOS); %LOCALAPPDATA%\NodeJS\sync.js (Windows)

  • Alternative C2 channels: Nostr relays wss://relay.damus.io, wss://relay.nostr.com/; Ethereum mainnet dead-drop contract 0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710; BitTorrent DHT rendezvous via router.bittorrent.com:6881, dht.transmissionbt.com:6881

  • Campaign markers: target.name = miasma-train-p1; attacker root EC public key 0432fa4b…

  • Obfuscation marker: a single very long _0x…-prefixed line in validator.js / utils.js / ErrorHandling.js (generator family) or index.js (specs)

Inside Miasma v3

The payload self-identifies internally as "Miasma v3.", and traces its lineage to the earlier Miasma attacks Miasma and Miasma v2. It follows a three-stage chain, with a require triggering the first payload, which then downloads a secondary stage with the real malware.

  1. Stage 1 fires on require(), spawning a detached, hidden Node process and immediately returning control to the host process so nothing looks wrong.

  2. Stage 2 downloads a second-stage script from a public IPFS gateway into an OS-specific hidden directory disguised as a Node.js runtime folder, then exits cleanly.

  3. Stage 3 decrypts (AES-256-GCM plus a rotation cipher) into a 3.08 MB bundled application: Miasma RAT itself.

But what’s its goal? Once it establishes itself on the system the payload then connects to a C2 server, harvests credentials, attempts to spread and gains persistence through AI tooling and more typical registry/crontab approaches:

  • Six independent C2 (command-and-control) channels - HTTP, Nostr relay, IPFS, BitTorrent DHT, a libp2p P2P mesh, and an Ethereum dead-drop contract — coordinated by a failover module. Blocking one channel doesn't sever control.

  • Credential harvesting across browser secrets (Chrome, Brave, Edge), SSH keys, ~/.npmrc, ~/.gitconfig, AWS and Kubernetes credentials, Docker credentials, and macOS Keychain, with dedicated targeting of GITHUB_TOKEN, NPM_TOKEN, and PYPI_TOKEN.

  • AI coding-assistant poisoning - a module capable of injecting malicious context into AI coding-assistant sessions (StepSecurity names Claude Code, GitHub Copilot, and Cursor).

  • LAN lateral movement via subnet scanning, service discovery, and mDNS.

  • A complete worm-propagation and metamorphic engine - five modules covering build-toolchain injection, runtime injection, and vector selection, plus a mutation engine that re-obfuscates each propagated copy. The baked configuration caps propagation at four generations and the payload header marks it as generation three of that lineage.

  • Persistence via systemd, crontab, launchd, or Registry autostart, plus C2-controlled self-destruct and arbitrary shell execution.

Interestingly, though: npm propagation was explicitly disabled in the baked config (propagate.npm: false). The worm machinery is fully present and fully built; it was switched off for this campaign, not left out of the malware. This is an interesting choice by the authors. Were they perhaps trying to ensure the compromise went undetected by security researchers? 

In addition to that, the attacker didn't steal an npm token or exploit a bug in GitHub Actions this time either. They gained push access to two source repositories and let each project's own release pipeline do the publishing. On one hand, needing a genuinely compromised push credential, rather than a stolen npm token, a pwn-request-style malicious PR, or a poisoned build cache like we saw across all the Shai-Hulud campaigns shows that the vectors that used to work against npm-publishing pipelines without any account compromise at all is a real step up in difficulty for the attacker. 

On the other hand, the payload that dropped through that narrower door is a professionally built, multi-channel RAT (Remote Access Trojan) framework, complete with credential harvesting, worm propagation, and an AI-coding-assistant poisoning module. The fact that the attackers likely disabled the spreading mechanism intentionally – to stay undetected – suggests a growing level of sophistication and professionalism behind these attacks.  And while the hardening of the npm ecosystem is clearly changing attacker behaviour, blocking install scripts is not a be-all-end-all-solution.