Signed, Trusted, and Malicious: What the AsyncAPI npm Compromise Broke
The AsyncAPI packages were signed, trusted-published, and hostile. The payload ran at import time, not install time. Why that defeats the new npm defaults.
On 14 July 2026, GitHub turned on a three-day default cooldown for Dependabot version updates. On the same day, someone published five trojanised versions across four AsyncAPI packages — with valid provenance signatures, through npm trusted publishing, from the project’s own release pipeline.
The timing is a coincidence. The lesson is not.
Microsoft’s analysis, published the following day, is worth reading in full. But the detail that should change how you think about build security is buried in the mechanics: the payload executed at import time, not install time. It never touched a lifecycle hook. Every affected package declared no preinstall, install, or postinstall script in package.json — deliberately, to slip past scanners that look for exactly that.
Which matters, because blocking install scripts is precisely the mitigation the ecosystem just standardised on.
What actually happened#
The timeline is tight. Three packages were republished with injected loaders around 07:10 UTC: @asyncapi/[email protected], @asyncapi/[email protected], and @asyncapi/[email protected]. Then @asyncapi/[email protected] at 08:06:20, and the stable 6.11.2 at 08:30:09 carrying a byte-identical payload. The first downstream fetch into a Yarn cache was observed at 08:49:22.
Call it a 100-minute window from first publish to first confirmed downstream pull. That is not an outlier. GitHub’s own review of 21 widely reported supply chain incidents between 2018 and 2026 found the same shape repeatedly — Solana web3.js, Axios, ua-parser-js, Ledger Connect Kit were each caught within hours of publication. The chalk/debug incident was live for roughly two hours.
The entry point was not npm. It was a GitHub Actions workflow. A pull request targeted a Netlify preview workflow that used pull_request_target, which runs the job in the base repository’s security context, and then checked out the untrusted commit. That handed the attacker the asyncapi-bot personal access token. From there they pushed commits that triggered the project’s real release workflows, and those workflows published the poisoned artefacts with genuine OIDC provenance.
The loader pulled roughly 8.2 MB of encrypted second stage from IPFS, decrypted with static embedded keys, and harvested over 100 environment variables — source control tokens, npm auth tokens, AWS, Azure, GCP, Kubernetes, and AI platform keys including OPENAI_API_KEY and ANTHROPIC_API_KEY. Six capability modules were present in the runtime but disabled in this build. Whoever shipped it was not finished.

Provenance attests to the pipeline, not the intent#
This is the uncomfortable part, and I want to be direct about it.
Build provenance answers one question: was this artefact produced by the build system it claims to have been produced by? For AsyncAPI, the honest answer was yes. The signature was correct. The trusted publishing flow worked as designed. The attestation was truthful.
It was also completely irrelevant, because the attacker owned the pipeline.
If your dependency policy is “we only install packages with verified provenance,” you do not have a control. You have a filter that removes the least sophisticated class of attacker and gives you a green tick while doing it. Signed, trusted-published, and malicious are not mutually exclusive states. We covered the wider framework — SBOMs, SLSA levels, signing infrastructure — in our software supply chain security piece, and none of it is wasted work. But provenance is an integrity guarantee about origin, and this attack did not forge origin. It borrowed it.
The same logic applies to SLSA level shopping. A hardened build with attestation is worth having. It does not survive a stolen bot token, because the token makes the attacker a legitimate participant in the hardened build.
npm closed one door in July. The attacker used another.#
npm v12 shipped in July 2026 with three defaults flipped. Install scripts no longer run automatically — allowScripts is off, and you opt packages in via npm approve-scripts, review pending ones with npm approve-scripts --allow-scripts-pending, and block with npm deny-scripts. Git dependencies are blocked unless --allow-git says otherwise. Remote URL sources are blocked unless --allow-remote says otherwise. The behaviour was available behind warnings from npm 11.16.0 so teams could see what would break.
This is a good change. It closes a door that campaigns against Axios in March and Mastra AI in June walked straight through, and the ecosystem should have done it years ago.
It does nothing for AsyncAPI.
An import-time payload does not need a lifecycle hook. It needs your application, or your test suite, or your build step, to require() or import the module — which is the entire reason you installed it. npm install --ignore-scripts is not a boundary here. Neither is npm v12’s default. The code runs when your code runs, in your process, with your environment.
There is a category error worth naming: install-script blocking treats installation as the dangerous act. For anything you actually execute, installation was never the dangerous act. Execution was. We just had a decade of incidents where the two happened to coincide.
Update latency is now a security control#
Here is the shift nobody is announcing loudly. Dependabot’s default three-day cooldown is an admission that being current is not the same as being safe.
For a decade, every dependency-hygiene guide said the same thing: patch fast, keep the lag small, automate the bumps. That advice was calibrated for a threat model where the risk was a known CVE sitting unpatched in your tree. It is the wrong calibration when the dominant risk is a hostile version published forty minutes ago.
Cooldown does not detect anything. It buys time for someone else’s detection to fire — registry scanning, advisory publication, the maintainer noticing. Given how many of these compromises are pulled within hours, three days of latency filters most of them at zero engineering cost. GitHub kept security updates immediate, which is the right split: known-vulnerability patches still go fast, routine version churn waits.
If you run your own update automation rather than Dependabot, add the delay yourself. It is the cheapest control on this list by a wide margin.
What to actually do#
Ranked by return on effort.
Audit your workflows for pull_request_target first. This breach was a CI configuration failure, not a registry failure. Any workflow that runs in the base repository’s context and checks out untrusted refs is a credential handout. Search your organisation for it today. While you are there, replace long-lived bot PATs with short-lived OIDC tokens scoped to the job.
Pin by integrity hash and mean it. Lockfiles with integrity entries, committed, and CI that fails on drift. npm ci rather than npm install. Renovate or Dependabot proposes changes; nothing floats in.
Put egress control on build agents. The second stage came from IPFS and phoned home to a specific host on specific ports. A default-deny egress policy on CI runners — registry and artefact store only — breaks the download and the exfiltration even when the loader executes. This is the control that would have contained this attack, and it is the one most teams skip because it is inconvenient during the first week.
Run installs in a network-restricted sandbox. Resolve and fetch in one step with network access, then execute builds and tests in a container with no outbound route. Import-time code still runs; it just has nowhere to send the environment it just read.
Monitor publish cadence, not just versions. A package that has shipped every six weeks for two years publishing an alpha and a stable within twenty-five minutes is a signal. So is a first-ever publish from a new workflow file. This is a small piece of operational automation that pays for itself the first time it fires.
Rotate what a build agent can see. If a compromised import reads over 100 environment variables, the question is what those variables unlock. Scope them down until the answer is boring.

Why AI stacks carry more of this risk#
An LLM application pulls an enormous transitive tree — SDK clients, tokenisers, vector store drivers, orchestration frameworks, evaluation harnesses — and it loads nearly all of it at import, in a process that by design holds provider API keys, database credentials, and often production data in memory. That is the exact target profile the AsyncAPI payload was written for. Any serious AI implementation inherits a dependency surface far wider than the team reviewing it, and the data platforms underneath tend to run their ingestion jobs on the same runners.
It gets sharper in regulated domains. A compromised build of a Hospital Management System or a school ERP is not an incident you triage quietly — it is a notifiable breach involving patient or minor records, with a regulator-facing clock attached. In those environments the egress and sandbox controls above stop being hygiene and start being the difference between a contained scare and a disclosure.
The blunt version: the ecosystem spent five years making it hard to publish a package you did not own. AsyncAPI shows what happens when someone publishes a package they do own, from the pipeline that owns it, signed correctly. The remaining defences are the ones that assume the code is already running and limit what it can reach.
If your build agents can reach the open internet, your provenance guarantees end at the first import statement. We work with engineering teams on CI hardening, dependency policy, and build-time egress control for regulated systems — start a conversation.