
The asyncapi generator npm attack backdoored four AsyncAPI packages on July 14, 2026: @asyncapi/[email protected], @asyncapi/[email protected], @asyncapi/[email protected], and @asyncapi/[email protected] (plus a 6.11.2-alpha.1 prerelease). An attacker stole a privileged GitHub personal access token through a pull_request_target vulnerability, then used that token to trigger AsyncAPI's own release pipeline and publish a downloader for a remote access framework tied to the Miasma malware family. This post covers how the attacker got publish rights without touching an npm token, the infection chain from require() to a live remote shell, what is verifiably active versus disabled in the delivered payload, the indicators of compromise, and the first-hour response runbook.
Four AsyncAPI Packages Carried a Miasma RAT Downloader
The asyncapi generator npm attack compromised four packages between 07:10 and 08:30 UTC on July 14, 2026, and StepSecurity, Wiz Research, Aikido Security, and SafeDep independently confirmed the compromise the same day.
@asyncapi/generator is the official code, documentation, and client/server template generator for the AsyncAPI specification, and generator-helpers and generator-components ship as its direct dependencies from the same monorepo. The four packages combined exceed 2.9 million weekly downloads, driven almost entirely by @asyncapi/specs. StepSecurity's disclosure independently confirmed the command and control IP via its Harden-Runner network monitoring, and SafeDep's analysis published the consolidated timeline and per-package download figures used above.
A Stolen Personal Access Token, Not a Stolen npm Token
Answer first: The root cause is a "pwn request": the asyncapi/generator repository ran a pull_request_target-triggered workflow that checked out attacker-controlled pull request code while retaining access to repository secrets, and the fix for that exact hole had been sitting open, unmerged, since May 17, 2026.
The Fix That Sat Open for 58 Days
A contributor opened PR #2078 on April 29, 2026 with a proof-of-concept exploiting the manual-netlify-preview.yml workflow, then followed up with PR #2092 on May 17, 2026 proposing a two-job split to isolate secret access from the untrusted checkout. Fifty-eight days later, the attacker used the still-open hole.
The Pull Request Flood and the Dead Drop
At 05:08 UTC on July 14, the attacker opened 37 pull requests against asyncapi/generator. Most were noise, a fake charity donation page repeated across dozens of PRs. One, PR #2155, hid obfuscated JavaScript after roughly 1,000 bytes of leading whitespace inside a markdown file, built to scan the runner environment and exfiltrate secrets to a rentry.co dead drop. Automated review flagged the obfuscation and the PR was never merged, but the vulnerable workflow had already run to completion by 05:16 UTC, and the attacker retrieved a personal access token belonging to asyncapi-bot, a service account with push access across the AsyncAPI organization.
Publishing Through AsyncAPI's Own Pipeline
At 06:58:42 UTC the attacker pushed commit 3eab3ec9304aa26081358330491d3cfeb55cc245 directly to the next branch, under the placeholder identity "Your Name" <[email protected]>, unsigned, with the message fix: test release workflow on next. Twelve seconds later the release-with-changesets.yml workflow triggered on refs/heads/next, and npm's GitHub OIDC trusted-publisher flow signed and published the first three packages at 07:10 UTC, each carrying a legitimate sigstore and SLSA provenance attestation naming the exact repository, workflow, ref, and commit. The attacker then pivoted to asyncapi/spec-json-schemas, pushing 11 commits between 07:51 and 08:28 UTC to publish the @asyncapi/specs versions. The provenance attestations verified correctly on every version, because they prove which workflow and commit produced a package, not whether that commit was legitimate.
From require() to a Detached Remote Shell
The payload never touches an npm lifecycle hook, and fires the moment a poisoned module is require()d or imported during normal use, building a three-stage infection chain that ends in a persistent, remotely controlled Node.js process.
Stage 1: Injection Technique
The malicious commit modified nine files, but only three carried the payload, using whitespace hiding: the same roughly 7.7KB obfuscated blob is appended after about 350 characters of trailing whitespace on a single line, pushing it off the right edge of any normal diff or editor view.
apps/generator/lib/templates/config/validator.js(after theisTemplateCompatiblefunction) in@asyncapi/[email protected].packages/helpers/src/utils.js(aftertoSnakeCase) in@asyncapi/[email protected].packages/components/src/utils/ErrorHandling.js(in the compiledlib/output) in@asyncapi/[email protected].@asyncapi/[email protected]used a different, unconcealed technique: the downloader is prepended in plain view to the first 25 lines ofindex.js.
Stage 1: Execution
The injected code spawns node -e <payload> with detached: true, stdio: 'ignore', and windowsHide: true, then calls .unref() so the parent process, the build or CI job that loaded the module, exits cleanly with no visible error.
Stage 2: IPFS-Hosted Loader
The spawned process fetches an 8.2 to 8.25MB encrypted object from a public IPFS gateway and writes it to a per-OS directory disguised as a Node.js runtime folder: ~/.local/share/NodeJS/sync.js on Linux, ~/Library/Application Support/NodeJS/sync.js on macOS, %LOCALAPPDATA%\NodeJS\sync.js on Windows. The generator family fetches IPFS CID QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9; the specs variant fetches Qmet4fhsAaWMBUxNDfREHwgiyDeSWy4YSYs9wiKUW5jGyf. The loader derives an AES-256-GCM key via HKDF-SHA256 and decrypts an embedded vault to recover Stage 3.
Stage 3: The M-RED-TEAM Framework
The recovered runtime, roughly 3.09MB per variant and reported by Wiz Research as a modular framework of about 92,000 lines, self-identifies in its own code comments as M-RED-TEAM v6.4. On first run it generates a secp256k1 keypair, writes a lock file at ~/.config/.miasma/run/node.lock to prevent duplicate instances, and installs persistence: a nohup block appended to .zshrc, .bashrc, or .bash_profile on macOS, an HKCU Run value named miasma-monitor on Windows, and a ~/.config/systemd/user/miasma-monitor.service unit on Linux. Aikido Security's analysis notes the Linux unit's ExecStart is malformed and likely fails to start, though the files are still written to disk.
Miasma RAT, M-RED-TEAM, and an Unresolved Attribution Picture
Three attribution signals point in different directions at once, a dead-drop pattern matching the earlier "prt-scan" campaign, a payload that self-labels as "M-RED-TEAM v6.4," and artifact naming that uses Miasma branding throughout, and no vendor covering this incident has made a definitive claim tying them together.
The rentry.co dead-drop slug used in PR #2155 matches naming patterns Wiz Research has previously linked to the prt-scan campaign, a distinct cluster of pull-request-based GitHub Actions secret-theft attacks. The recovered Stage 3 runtime self-identifies as M-RED-TEAM v6.4 in its own comments, the most direct label the code gives itself. Separately, the build target is named miasma-train-p1, runtime state lives under ~/.config/.miasma/, and the persistence artifact is named miasma-monitor, all consistent with the Miasma toolkit documented in prior npm compromises. Aikido Security's writeup cites a SafeDep researcher's side-by-side comparison finding this build differs meaningfully from the earlier Miasma build: this one is Node-based with secp256k1 and AES-GCM over an HTTP command channel, while the earlier one was Bun-based with RSA and AES-CBC, active propagation, and a destructive deadman switch, none of which are active here.
Wiz Research states explicitly that this incident does not appear directly connected to the Shai-Hulud 2.0 compromise of AsyncAPI packages in November 2025, despite both hitting the same ecosystem twice in eight months. Treat "Miasma," "M-RED-TEAM," and "prt-scan" as three separate observed signals rather than a confirmed single operator.
What the Payload Actually Does, and What Is Switched Off
Answer first: The shipped configuration contains a full credential harvester, cross-ecosystem propagation code, and a wipe routine, but Aikido Security's call-graph analysis shows only persistence and a live command shell are actually active. The rest is present in the code and disabled.
Active behavior:
- Beacons to
http://85.137.53.71:8080roughly every 30 seconds, signed and encrypted, including redacted previews ofPATH,HOME,USER, andHOSTNAME, and checks for Cursor, Claude, and VS Code configuration files. - A
ShellExeccommand that runschild_process.exec()against attacker-supplied commands, with the only blacklisted string beingkillall. This alone gives the operator a functioning remote shell regardless of every other toggle. - Persistence, described above, which runs despite a decoy config field named
actualPersist: falsethat the boot function never reads; the real gate istoggles.persist, which istrue.
Disabled in this build: credential harvesting across browsers, SSH keys, npm and GitHub tokens, AWS credentials, macOS Keychain, and crypto wallets is fully implemented but gated behind toggles.recon: false and never runs. Propagation to npm, PyPI, Ruby, and Cargo is implemented but gated off. The wipe routine writes a harmless marker file, ~/Documents/SIMULATION_WIPE_TRIGGERED.txt, instead of deleting anything. None of these toggles are guaranteed to stay off in the next release from the same infrastructure.
Indicators of Compromise and Behavioral Signals
Answer first: Hunt for the exact package versions in manifests and lockfiles first, then pivot to host and network indicators, because the payload fires on require() rather than npm install, so version pinning alone does not confirm the module ran.
File-system indicators:
sync.jsdropped in~/.local/share/NodeJS/(Linux),~/Library/Application Support/NodeJS/(macOS), or%LOCALAPPDATA%\NodeJS\(Windows).- Lock file
~/.config/.miasma/run/node.lock. - Linux persistence unit
~/.config/systemd/user/miasma-monitor.service; WindowsHKCU Runvaluemiasma-monitor.
Network and process indicators:
- Outbound connections to
85.137.53.71on ports 8080 (command and control), 8081 (upload), and 8091 (proxy management), inside RIPE block85.137.53.0/24(AS43641, Netherlands). - Unexpected connections to
ipfs.io, and DHT bootstrap traffic torouter.bittorrent.com:6881anddht.transmissionbt.com:6881. - Unexplained detached
nodechild processes with suppressed stdio spawned from a build or CI step.
Git, GitHub, and CI indicators:
- Unmerged pull requests exploiting
pull_request_targetcheckout of PR head content. - Pushes to release-triggering branches under placeholder git identities (
"Your Name" <[email protected]>), unsigned. - GitHub Actions runs on
refs/heads/nextoutside normal release cadence, or PAT usage fromasyncapi-bot-style org service accounts outside expected patterns.
Immediate Response: The First-Hour Runbook
Treat any machine or CI runner that imported an affected version, not just installed it, as compromised until proven otherwise, because the payload needs zero install-time execution to run.
- Quarantine. Pin
@asyncapi/*dependencies to[email protected],[email protected],[email protected], and[email protected], and remove the malicious versions from manifests, lockfiles, package caches, internal registry mirrors, and build images. - Hunt for execution, not installation. Search build logs and host telemetry for evidence that the poisoned module was actually
require()d or imported. - Inspect endpoints and runners. Check every developer machine and CI/CD runner that touched the packages during the compromise window for the drop, lock, and persistence paths above, and for unusual detached
nodeprocesses. - Block the command and control channel. Deny egress to
85.137.53.71on ports 8080, 8081, and 8091, and flag historical connections to it or toipfs.iofrom build infrastructure. - Rotate every credential the affected runner could see: npm tokens, GitHub personal access tokens (especially org-wide service accounts like
asyncapi-bot), SSH keys, cloud credentials, CI/CD secrets, and signing keys, done from a clean machine. - Audit Git and Actions activity for obfuscated payloads in recent pull requests against
pull_request_targetworkflows, and for placeholder or unsigned commit identities on release-triggering branches. - Freeze production changes that shipped during the compromise window, and rebuild any host confirmed to have run the payload rather than attempting in-place cleanup.
A Signed Package Is Not the Same as a Safe One
This attack cleared every control that verifies where a package came from, because the origin genuinely was AsyncAPI's own workflow. The real gap is verifying whether the commit that triggered that workflow deserved trust, and observing what a module does after it loads.
Static, install-time scanning that only inspects preinstall, postinstall, or install hooks in package.json finds nothing here, because there are none. Registry-side scanning of the published tarball also comes up empty on the real logic, since the tarball ships only a small detached spawn call and the multi-stage framework lives on IPFS, fetched only at runtime. Sigstore and SLSA provenance attestations, increasingly treated as a trust signal on their own, verified correctly for all five malicious versions. They proved the workflow and commit, not that the commit deserved trust.
This is where runtime visibility earns its place alongside dependency scanning rather than replacing it. Kodem's runtime-powered SCA correlates declared dependency inventory against what actually loads and executes in a running application, closing the gap between reachable code and executed code. Kodem's Application Detection and Response is built to catch exactly this class of behavior, a detached child process spawned from inside a dependency, an unexpected outbound connection to an unfamiliar subnet, a file write to a disguised runtime directory, without needing a CVE or a signature for M-RED-TEAM v6.4 to exist first.
Hardening Your Pipeline Against the Next pull_request_target Variant
The specific failure here, a pull_request_target workflow that checks out PR code while retaining secret access, is a well-documented GitHub Actions weakness, and its fix sat open for 58 days. Treat every workflow using that trigger as a live finding, not a backlog item.
- Audit every workflow in every repository for
pull_request_targetorworkflow_runtriggers that check out the head ref of an external pull request, and split secret-bearing steps into a separate job gated by manual approval. - Require branch protection and mandatory review on any branch that can trigger a release workflow, including staging branches like
next. - Replace long-lived personal access tokens on service accounts, such as
asyncapi-bot, with short-lived, narrowly scoped OIDC tokens wherever the CI provider supports the pattern. - Add a cooldown or quarantine window before build and CI systems consume freshly published versions of any dependency, since most malicious releases are identified and pulled within hours.
- Instrument CI runners to detect detached child processes, unexpected egress, and writes to disguised runtime directories, not just to scan manifests at commit time.
- Treat verified provenance, OIDC, and SLSA attestations as proof of workflow origin, not proof of safety, and pair them with behavioral monitoring of what published packages actually do once loaded. Kodem's open-source security solution is built around that pairing for exactly this dependency-risk class.
What This Incident Signals About Trusted Publishing
The asyncapi generator npm attack is the second time in eight months that AsyncAPI's npm presence has been backdoored through a compromised GitHub Actions pipeline rather than a stolen npm credential, and it confirms that verified publishing inherits the trustworthiness of the workflow that produced the package.
- Attackers are increasingly targeting the CI/CD pipeline itself, pwn requests, cache poisoning, token exfiltration, rather than npm accounts directly, because a compromised pipeline produces packages that pass every provenance check.
- Payload delivery is moving off the registry entirely (IPFS, Nostr, Ethereum contracts in this case) specifically to defeat registry-side and tarball-based static scanning.
- Malware authors are building in decoy configuration flags,
safeMode,actualPersist,canaryPercent, that look reassuring on a superficial read but do not gate real behavior, a technique worth expecting again.
One falsifiable prediction: expect the next variant published from this infrastructure to flip toggles.recon and toggles.propagate back on once the current build's coverage saturates, since the harvesting and propagation code is already written and simply disabled, not absent. Kodem's writeup on the TanStack OpenAI supply chain attack covers the same underlying pattern: a pull_request_target weakness feeding a trusted publish pipeline, chained into OIDC token extraction.
Frequently Asked Questions
- What is the asyncapi generator npm attack? The asyncapi generator npm attack is a July 14, 2026 supply chain compromise in which an attacker stole a privileged GitHub personal access token via a
pull_request_targetvulnerability inasyncapi/generator, then used the stolen token to push malicious commits that triggered the project's own release pipeline, publishing four backdoored@asyncapipackages to npm. - Which versions of the AsyncAPI packages are compromised?
@asyncapi/[email protected],@asyncapi/[email protected],@asyncapi/[email protected], and@asyncapi/[email protected](plus prerelease6.11.2-alpha.1) carry the payload. Downgrade to3.3.0,1.1.0,0.7.0, and6.11.1. - How did the attacker publish malicious packages without stealing an npm token? The attacker stole a GitHub personal access token belonging to the
asyncapi-botservice account by exploiting apull_request_targetworkflow that checked out untrusted pull request code with secret access, then used that token to push directly to thenextbranch and let AsyncAPI's own release workflow publish through npm's OIDC trusted-publisher flow. - What is Miasma RAT and what does it steal? Miasma RAT is the credential-stealing malware family the delivered payload is branded with and structurally tied to. This build includes browser, SSH key, npm and GitHub token, AWS credential, and crypto wallet harvesting in its code, but that harvesting is disabled (
toggles.recon: false). The active capability is a remote shell over an HTTP command and control channel. - Is this the same as the Shai-Hulud 2.0 attack on AsyncAPI? No. Wiz Research states this incident does not appear directly connected to the November 2025 Shai-Hulud 2.0 compromise of AsyncAPI packages, even though it is the second GitHub Actions-driven npm compromise the project has suffered in roughly eight months.
- How do I know if my environment was affected? Check lockfiles and manifests for the malicious versions, then search for the drop path
sync.jsin the platform-specific NodeJS directory, the~/.config/.miasma/run/node.lockfile, and unusual detachednodeprocesses, since the payload only runs if the module was actuallyrequire()d. - Why didn't npm's provenance attestation catch this? Provenance attestations verify which workflow, commit, and ref produced a package, not whether that commit deserved trust. All five malicious versions carried valid sigstore and SLSA provenance because AsyncAPI's own release workflow genuinely published them, running on the branch the attacker had compromised.
- What should I do right now if I have these packages installed? Pin to the safe versions, hunt for the file-system and network indicators above, rotate npm tokens, GitHub personal access tokens, SSH keys, and cloud credentials from a clean machine, and audit CI/CD secrets exposed to any runner that imported the affected modules during the compromise window.
The Bottom Line
This attack did not need a stolen npm token, a compromised maintainer, or a typosquatted package name. A dormant GitHub Actions weakness, open and documented for 58 days, was enough to hand an attacker a service account token and, with it, the keys to AsyncAPI's own trusted release pipeline. The packages that came out the other side were genuinely signed, genuinely provenance-attested, and genuinely malicious. Verifying where a package came from and verifying what it does once it loads are two different problems, and this incident is proof that solving only the first one leaves the door open.
References
- StepSecurity, "Compromised next Branch Pushes Malicious @asyncapi/generator, generator-helpers, and generator-components to npm," July 14, 2026.
- Wiz Research, "M-Red-Team: AsyncAPI Supply Chain Compromise via GitHub Actions," July 14, 2026.
- Aikido Security, "AsyncAPI npm packages backdoored via GitHub Actions," July 14, 2026.
- SafeDep, "AsyncAPI Packages Compromised with Miasma RAT," July 14, 2026.
- Malicious commit
3eab3econasyncapi/generator.
Related blogs

AI-BOM: How to Identify, Understand and Govern AI Supply Chain Risk
Kodem AI-BOM extends your SBOM with an AI bill of materials: identify AI packages, verify runtime execution, and govern AI supply chain risk at scale.
6
Stop the waste.
Protect your environment with Kodem.
A Primer on Runtime Intelligence
See how Kodem's cutting-edge sensor technology revolutionizes application monitoring at the kernel level.
Platform Overview Video
Watch our short platform overview video to see how Kodem discovers real security risks in your code at runtime.
The State of the Application Security Workflow
This report aims to equip readers with actionable insights that can help future-proof their security programs. Kodem, the publisher of this report, purpose built a platform that bridges these gaps by unifying shift-left strategies with runtime monitoring and protection.
.avif)
Get real-time insights across the full stack…code, containers, OS, and memory
Watch how Kodem’s runtime security platform detects and blocks attacks before they cause damage. No guesswork. Just precise, automated protection.

