Skip to content

37 Decoy Pull Requests Hid the One That Hijacked AsyncAPI's npm Pipeline

DS
LDS Team
Let's Data Science
12 min
On July 14, 2026, an attacker used 36 lookalike pull requests to mask one that stole a privileged GitHub token from the AsyncAPI generator project, then let the project's own automated release pipeline publish five trojanized package versions with valid cryptographic provenance. Multiple security firms put the combined weekly download count near 2.9 million. A fix for the underlying bug had been sitting unmerged for 58 days.

At 5:08 a.m. UTC on July 14, an account nobody at AsyncAPI recognized began filing pull requests against the project's flagship code generator on GitHub. Over the next hour, it filed 36 more, each proposing the same small addition: a page thanking the project's financial donors.

One of the 37 was not a donation page.

Buried inside pull request #2155 was a markdown file padded with roughly a thousand blank characters, hiding a short piece of obfuscated JavaScript past the point where most code editors stop showing text on screen. When AsyncAPI's continuous integration system ran its usual checks against the incoming pull request, that hidden script quietly copied a privileged access token and sent it to a public pastebin. Eight minutes later, the workflow finished. The attacker had what they needed.

By 7:10 a.m. UTC, three new versions of AsyncAPI's npm packages were live on the public registry, each one signed with the project's own cryptographic seal of authenticity. Within the next ninety minutes, two more followed. All five carried a hidden payload built to steal browser passwords, SSH keys, and cryptocurrency wallets, and together the affected packages saw an estimated 2.9 million downloads a week.

Thirty-Six Decoys Covered One Real Attack

AsyncAPI is not a household name outside backend engineering, but its tooling sits deep in the plumbing of event-driven software. The @asyncapi/generator package and its companions scaffold client and server code, documentation, and validation logic from AsyncAPI specification files, the JSON-based format many companies use to describe asynchronous APIs the way OpenAPI describes REST ones. @asyncapi/specs, published from a separate AsyncAPI repository, supplies the canonical schema definitions that the generator, the official parser, and much of the surrounding tooling depend on.

The attack that hit those packages began with a well-documented class of GitHub Actions misconfiguration researchers call a "pwn request." One of AsyncAPI's workflows was set to trigger automatically whenever someone opened a pull request, a routine setup for running checks on incoming code. The problem was how it ran those checks: using the pull_request_target trigger, which grants the workflow full access to the repository's secrets while checking out and executing code from the pull request itself, code an outside contributor fully controls.

Researchers at Wiz, who published one of the first detailed technical breakdowns of the incident, found that the attacker's flood of near-identical pull requests was not random noise. It was camouflage. Automated review tools eventually flagged PR #2155's obfuscated payload as suspicious, but by the time anyone looked closely, the workflow had already run to completion and the token was gone.

A Known Bug Sat Unmerged for 58 Days

The vulnerability was not a surprise to everyone. A contributor had flagged the exact weakness months earlier, opening a proof-of-concept pull request on April 29 that demonstrated how an attacker could exploit the workflow. On May 17, the same contributor followed up with an actual fix, splitting the risky workflow into two separate jobs so that untrusted code would never run in the same context as the repository's secrets.

That fix sat open and unmerged for 58 days. The attack landed on day 59.

Once in possession of the stolen token, the attacker moved fast. At 6:58 a.m. UTC, they pushed a commit directly to the generator repository's next branch under a placeholder identity that read simply "Your Name," the kind of default value that fills in automatically when a developer never configures Git. That single push was enough. It triggered AsyncAPI's real, legitimate release workflow, which built and published the tainted code to npm using the project's official credentials, through npm's GitHub Actions integration known as trusted publishing.

That distinction matters. The attacker never had to steal an npm password or forge an authentication token. They compromised the GitHub side of the pipeline and let AsyncAPI's own trusted automation do the rest, which is why the malicious releases came bundled with valid SLSA provenance attestations, the cryptographic paperwork meant to prove a package was built by an authorized, auditable process. As Chainguard's researchers put it: the provenance proved where the package came from, not that the process producing it could be trusted at that moment.

None of the five malicious releases relied on npm's install, preinstall, or postinstall scripts, the mechanism nearly every prior npm supply chain attack has used to run code the moment a package lands on a machine. The timing looks deliberate. npm shipped its biggest security overhaul in 16 years just six days earlier, on July 8, specifically to block unapproved install scripts by default. The AsyncAPI payload sidestepped that defense entirely by hiding inside ordinary source files and firing only when the infected module was actually imported or required, the moment a build, a test suite, or a running application calls into the library during normal use.

Less than an hour after the first packages went live, the attacker pivoted to a second AsyncAPI repository that separately publishes @asyncapi/specs, pushing a new round of commits between 7:51 and 8:28 a.m. UTC. Two more malicious versions followed, the last one carrying the latest tag npm serves to anyone who installs the package without specifying a version.

Two Months of Warning, Four Hours of Damage

APRIL 29, 2026
A contributor flags the vulnerability
A proof-of-concept pull request demonstrates the pull_request_target weakness in the generator repository's workflow.
MAY 17, 2026
A fix is proposed, then stalls
The same contributor opens a pull request splitting the workflow into isolated jobs. It sits unmerged.
JULY 14, 05:08 UTC
37 pull requests land in an hour
Most propose a harmless donation page. One, PR #2155, hides an obfuscated token-stealing script.
JULY 14, 05:16 UTC
A privileged token is stolen
The malicious workflow finishes running and the attacker retrieves a GitHub access token from a public pastebin.
JULY 14, 06:58 UTC
A malicious commit reaches the release branch
The attacker pushes directly to the generator repository's next branch, triggering the real release workflow.
JULY 14, 07:10 UTC
Trojanized packages go live on npm
Three malicious versions publish with valid provenance signatures, exposing every downstream install.
JULY 14, 07:51-08:30 UTC
A second repository is compromised
The attacker pivots to the repository publishing @asyncapi/specs, releasing two more malicious versions.
JULY 14, 11:18 UTC
All five versions are pulled from npm
Just over four hours after the first malicious release, every affected package reverts to a clean version.

Millions of Downloads, One Credential-Stealing Payload

Once triggered, the injected code launched a hidden background process that quietly pulled a much larger, encrypted second-stage program from a decentralized file-sharing network, then ran it. Researchers who safely decoded that payload without executing it, including teams at Wiz, Socket, Aikido, and SafeDep, found a fully built remote access framework capable of collecting:

  • Saved browser passwords and cookies from Chrome, Brave, Firefox, and Edge
  • SSH private keys and configuration files
  • npm, GitHub, and other developer authentication tokens
  • AWS and other cloud provider credentials
  • macOS Keychain entries
  • Cryptocurrency wallet files

Beyond credential theft, the framework carried the ability to run arbitrary shell commands, maintain persistence across reboots, and reach its operators through at least six separate communication channels, from a conventional command server down to a peer-to-peer network and an Ethereum smart contract, so that blocking any single channel would not sever the connection. Researchers also found capabilities present in the code but switched off in this deployment: automatic spreading to other npm, PyPI, and Cargo packages, and a module built to inject instructions into AI coding assistants including Claude Code, Cursor, and GitHub Copilot.

The payload's internal code comments labeled it "M-RED-TEAM v6.4," while its configuration and file paths carried the branding of Miasma, a credential-stealing toolkit that circulated in earlier 2026 supply chain campaigns, including one that hit Red Hat's own npm packages through a separate trusted-publishing weakness. Researchers at OX Security and SafeDep, who compared the two toolkits in detail, found the underlying code, cryptography, and infrastructure differed enough that this was, at most, a rebrand or a parallel build rather than a direct reuse, leaving attribution genuinely unsettled.

Estimating exactly how many downloads were at risk depends on which source, and which hour of npm's live counters, you trust. Endor Labs, citing npm's own figures for the week ending July 11, put the combined total near 2.96 million, with @asyncapi/specs alone accounting for about 2.74 million. Aikido Security and the artifact-management vendor Cloudsmith independently arrived at figures close to 2.9 million. BleepingComputer, checking npm's live download counters a day after the packages were pulled, tallied a lower but still substantial 2.25 million.

PackageMalicious versionSafe version
@asyncapi/generator3.3.13.3.0
@asyncapi/generator-helpers1.1.11.1.0
@asyncapi/generator-components0.7.10.7.0
@asyncapi/specs6.11.2 and 6.11.2-alpha.16.11.1

Anyone who installed the affected versions during the exposure window should treat the environment as compromised. Recommended steps, drawn from the security firms that analyzed the malware, include:

  • Pin every affected package to the safe version listed above and regenerate lock files
  • Rotate npm, GitHub, and other developer tokens
  • Rotate SSH keys and AWS or other cloud credentials
  • Rotate saved browser passwords and cookies on any machine that ran the packages
  • Audit CI/CD pipelines that ran during the exposure window and rotate any secrets they touched

Provenance Proved Origin, Not Trustworthiness

Chainguard called the July incident at least the third notable pwn-request-style GitHub Actions compromise of a high-download npm namespace in 2026 alone. The pattern recurs because it works. Earlier this year, the identical misconfiguration class let attackers compromise the security scanner Trivy, a breach that eventually reached deep into the AI infrastructure library LiteLLM and its roughly 95 million monthly downloads. In both cases, attackers skipped the registry entirely and went after the CI/CD pipeline that publishes to it, because a stolen GitHub credential can produce a package that looks, cryptographically, exactly like a legitimate release.

There is a harder irony sitting underneath this particular incident. AsyncAPI had already lived through a version of this scenario. Roughly eight months earlier, in November 2025, the project's npm packages carried malware from a second wave of the self-propagating Shai-Hulud worm, which compromised more than 24,000 repositories across the ecosystem that month alone. In its public postmortem afterward, AsyncAPI's team wrote that it planned to stop relying on long-lived npm tokens and move to npm's newer OIDC-based trusted publishing, tying package releases directly to a specific GitHub repository and workflow rather than a password that could leak or age past rotation.

That migration happened. It did not help. The July 2026 attacker never needed an npm token, because trusted publishing worked exactly as designed: it published whatever the authorized GitHub workflow told it to, and the authorized GitHub workflow had already been hijacked one layer up, through the pull_request_target hole nobody had closed. The fix for the last incident became the delivery mechanism for this one.

Cloudsmith, an artifact-management vendor, argued the deeper problem is structural rather than a single AsyncAPI mistake. In the hours right after publication, no security advisory yet named the new versions as malicious, so any team pulling @asyncapi/specs@latest straight from npm during that window would have received a clean result from every conventional scanner. Not because the tooling failed, but because nothing had positioned itself to catch a brand-new, freshly signed, correctly named package before it reached a developer's machine.

Not every researcher treated the malware's sophistication as proof of a well-resourced, professional operation. OX Security noted the payload checked whether it was running inside a virtual machine, whether common endpoint security tools were installed, and whether the system's language was set to Russian, terminating itself rather than run under any of those conditions. That kind of caution, alongside the dormant self-propagation code and the unused AI-assistant poisoning module, points to a toolkit built for a longer campaign than the one actually deployed on July 14.

The Bottom Line

Strip away the obfuscation and the multi-channel command infrastructure, and the AsyncAPI incident comes down to two plain facts. A known, previously reported vulnerability in a GitHub Actions workflow sat unpatched for 58 days. When someone finally exploited it, the project's own legitimate, cryptographically trusted publishing pipeline did exactly what it was built to do: it published what it was told to publish, no questions asked.

That is the uncomfortable lesson for every team that has adopted npm's trusted publishing, OIDC authentication, or SLSA provenance as a supply chain fix. Those tools solve a real problem. They make it much harder to forge or leak the credentials that publish a package. They do nothing to guarantee the pipeline feeding them has not already been compromised somewhere upstream, in a workflow file nobody has re-reviewed since the day it was written.

The pull request that would have prevented this attack existed for nearly two months before it happened. It just never got merged in time.

Sources

Practice interview problems based on real data

1,625 SQL & Python problems across 15 industry datasets — the exact type of data you work with.

Try 250 free problems