Backdoored AsyncAPI npm Packages Used GitHub Actions Workflow to Steal Publish Token

Summary
An investigation traces five trojanized AsyncAPI npm releases to a vulnerable GitHub Actions workflow. Importing affected modules downloaded an IPFS loader that installed a persistent implant with remote shell capability; the article also details remediation.
Key points
- Five compromised releases across @asyncapi/specs, @asyncapi/generator, @asyncapi/generator-helpers and @asyncapi/generator-components were published on July 14, 2026.
- A pull_request_target workflow ran code from an external pull request with access to repository secrets; obfuscated code reportedly exfiltrated the npm publish token to rentry[.]co.
- Malicious code was placed in normal runtime modules rather than lifecycle hooks, so importing an affected package was enough to trigger a detached downloader that retrieved an encrypted loader from IPFS.
- The recovered implant supports persistence across Windows, macOS and Linux and provides a remote shell capable of running arbitrary commands. Its HTTP command channel could allow an on-path attacker to inject commands.
- The article says credential harvesting and package propagation code were present but disabled in this build. It also finds that config fields suggesting safe mode and limited canary deployment did not constrain execution.
- Attribution remains unresolved: the initial access resembles the prt-scan campaign, the payload identifies as M-RED-TEAM v6.4, and Miasma branding may reflect code reuse, imitation or mislabeling.
- Recommended response includes downgrading to specified unaffected package versions, removing compromised releases, checking hosts that imported the modules, rotating potentially exposed credentials and rebuilding suspected systems.
Article Details
- Attack Vectors
- A flood of pull requests targeted a vulnerable pull_request_target workflow in the AsyncAPI generator repository. Obfuscated JavaScript in PR #2155 exfiltrated the npm publish token to rentry[.]co.
- Using the stolen token, the attacker pushed malicious commits and published five compromised @asyncapi packages.
- Malicious code was inserted into modules executed during normal package use, so importing an affected module with require() triggered the downloader without an npm lifecycle hook.
- The downloader fetched encrypted Node.js loaders from IPFS, wrote them as sync.js in a per-user NodeJS data directory, and launched them as detached processes.
- The implant established persistence through shell startup files on macOS, a Windows Run value, and a systemd user service on Linux. The article notes the Linux service may fail to start because its ExecStart lacks a shell wrapper.
- The implant beaconed over HTTP to 85[.]137[.]53[.]71:8080 and could receive commands, including arbitrary shell commands through child_process.exec().
- Payload updates could be supplied by an operator or retrieved through polling; the article says Ethereum update records were accepted without signature verification.
- Defensive Notes
- Downgrade to @asyncapi/[email protected], @asyncapi/[email protected], @asyncapi/[email protected], and @asyncapi/[email protected].
- Remove all five compromised versions from manifests, lockfiles, caches, internal mirrors, and build images. The article also advises purging the @asyncapi/[email protected] tarball from backing storage and CDN.
- Investigate systems that imported affected modules, not only systems where the packages were installed, because the implant runs when the modules are loaded.
- On suspected hosts, isolate and preserve volatile state; search for the reported drop, lock, identity, and persistence paths, unusual detached Node processes, and connections to the reported C2 infrastructure.
- Rotate credentials accessible to affected developer machines or build hosts from a clean machine, including npm tokens, source-control access, cloud credentials, CI/CD secrets, SSH keys, signing keys, and browser sessions. Rebuild compromised hosts.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| DOMAIN | rentry[.]co | The attacker used this dead-drop domain to exfiltrate the stolen npm publish token. |
| IPV4 | 85[.]137[.]53[.]71 | Attacker C2 address used for HTTP beacons and command traffic. |
| SHA256 | 082d733db0687dcd768104972b065d4b58cb1e6043688c6c20fa3702337f36ab | SHA-256 listed for the compromised @asyncapi/[email protected] package. |
| SHA256 | 34014776d3d3ff11bc4439b02fd7ac0f02a887eb3a052eeafff236e2f6db8ad1 | SHA-256 listed for the compromised @asyncapi/[email protected] package. |
| SHA256 | 9b2e65db653ca8575c9b10eefb9a80c6006404812c2ec212bf5675e3c690233b | SHA-256 listed for the compromised @asyncapi/[email protected] package. |
| SHA256 | 9e214f38537e69bf51c7fa1ddd35ae495e9cb897231ec010baf9e4f29407ee9a | SHA-256 of the recovered stage-3 payload from the generator-family build. |
| SHA256 | bfaeb987faa6de2b5a5eb63b1233d055215b09b0349a9394f2175fd7cdf385e4 | SHA-256 listed for the compromised @asyncapi/[email protected] package. |
| SHA256 | d425e4583cc6185d41e95c45eda00550045a5d1919b9a012236a4520d009dbd7 | SHA-256 listed for the compromised @asyncapi/[email protected] package. |
| SHA256 | f873941d1907a97dc6c718fdecf59fd7d91f3f8212da2f7e5314b878b88bdc0b | SHA-256 of the recovered stage-3 payload from the specs build. |
| URL | hxxp[:]//85[.]137[.]53[.]71:8080 | HTTP C2 endpoint used by the implant for beacons and command traffic. |
| URL | hxxps[:]//ipfs[.]io/ipfs/Qmet4fhsAaWMBUxNDfREHwgiyDeSWy4YSYs9wiKUW5jGyf | Specific IPFS gateway URL identified as the malicious loader source for the specs package branch. |
MITRE ATT&CK
T1027 · Obfuscated Files or InformationThe downloader and loaders used obfuscation, encrypted payload data, and a printable-ASCII rotation.T1059 · Command and Scripting InterpreterThe implant's ShellExec command passed operator-supplied commands to child_process.exec().T1059.007 · JavaScriptJavaScript in the pull request and package modules carried out token theft and downloader execution.T1071.001 · Web ProtocolsThe implant used HTTP for its C2 beacons and command channel.T1105 · Ingress Tool TransferThe downloader retrieved encrypted Node.js loaders from IPFS and wrote them to disk.T1140 · Deobfuscate/Decode Files or InformationThe loader derived keys, decrypted embedded payload data, reversed the ASCII rotation, and evaluated the recovered code.T1195.002 · Compromise Software Supply ChainThe attacker used a stolen npm publish token to publish trojanized AsyncAPI packages.T1543.002 · Systemd ServiceOn Linux, the implant wrote and enabled a systemd user service for persistence.T1546.004 · Unix Shell Configuration ModificationOn macOS, persistence was added by appending a nohup block to shell startup files.T1547.001 · Registry Run Keys / Startup FolderOn Windows, the implant wrote the miasma-monitor value under HKCU Run.T1567.003 · Exfiltration to Text Storage SitesObfuscated JavaScript exfiltrated the npm publish token to the rentry[.]co dead-drop.T1573.002 · Asymmetric CryptographyBeacons and commands used encrypted payloads; the article also describes signing and encryption of beacons.
Malware
Products
@asyncapi/generator@asyncapi/[email protected]@asyncapi/generator-components@asyncapi/[email protected]@asyncapi/generator-helpers@asyncapi/[email protected]@asyncapi/specsThe packages combined see roughly 2.9 million weekly downloads, with @asyncapi/specs alone accounting for around 2.7 million.GitHub ActionsThe asyncapi/generator repository used a GitHub Actions workflow with a pull_request_target trigger. This trigger runs with access to repository secrets even when the workflow checks out code from an external pullnpmWe've identified five trojanized @asyncapi packages published on 2026-07-14. The attacker stole an npm publish token by exploiting a pull_request_target workflow vulnerability in the AsyncAPI generator repository, then
