TL;DR: Someone opened a pull request against AsyncAPI's CI, used it to steal the project's release-bot token, and let AsyncAPI's own pipeline publish five npm versions with malware in them, each with valid provenance attestations. The payload runs at import, so --ignore-scripts never sees it. Provenance proves who built a package. It says nothing about whether the code is safe. Inside: how the attack chained, the one workflow trigger to grep for this week, and a symlink trick that makes your coding agent's approval box show you the wrong file.

Attack of the Week: the pipeline published it for them

How it runs. Start with a pull request. AsyncAPI had a preview workflow, manual-netlify-preview.yml, wired to pull_request_target. That trigger runs the job with the base repo's secrets, even for an outsider's PR, and this one also checked out the PR's code. So that PR's code ran with the repo's secrets in reach. It read the asyncapi-bot personal access token, a PAT, and copied it out.

That PAT is a real maintainer credential. The attacker used it to push commits to the next branch of asyncapi/generator and master of spec-json-schemas, under a placeholder git identity, and the release pipeline did the rest.

Here's the catch. AsyncAPI publishes through npm trusted publishing with GitHub OIDC, where npm accepts a short-lived token from GitHub in place of a stored npm token. That is the current best-practice setup. The real release workflow saw the new commits and published five versions across four packages, and npm stamped each one with a valid provenance attestation, a signed record of which pipeline built it. That record is accurate. The authorized workflow really did build these packages. It just built the attacker's code.

The payload sits idle until your code loads the module. It fires on import or require, at use-time. npm install --ignore-scripts only blocks install-time scripts, so it misses this one entirely. The first time a build or a test pulls the library in, a remote-access trojan the researchers named Miasma starts up. Its command-and-control falls back across plain HTTP, Nostr relays, and BitTorrent's DHT.

How the AsyncAPI compromise chained: a pull request steals the release-bot token, and the project's own pipeline publishes malware that still carries valid provenance.

Ship This Week.

  • Block the bad versions and regenerate lockfiles so they can't quietly resolve back in: @asyncapi/[email protected], @asyncapi/[email protected], @asyncapi/[email protected], @asyncapi/[email protected] and @asyncapi/[email protected]. The safe build is the release just below each: 3.3.0, 1.1.0, 0.7.0, 6.11.1.

  • Grep your workflows for pull_request_target. A job that also checks out the PR's head and then runs it is the same open door.

  • Keep long-lived GitHub PATs out of any workflow that can run untrusted code. AsyncAPI already published over OIDC; the credential that leaked was the bot's GitHub PAT, sitting in reach of that one job.

  • Read provenance as origin and stop there. Treat a fresh release of a critical dependency as unproven until something other than the provenance badge says it's safe.

Five controls mapped to the attack. Splitting the preview job off pull_request_target stops the leak at stage one, and the rest shrink the blast radius.

Rule of the Week: catch the workflow that ran this

The whole thing turned on one combination: a privileged trigger, a checkout of untrusted code, and a step that runs it. You can flag that pattern in a short CI check, before it ever leaks a token.

Alert on any workflow file where all three are true:

pull_request_target
  + checkout of the PR head
    (head.sha)
  + a step that runs it
    (build, test, or run:)

That combination lets a stranger's code read your repo's secrets. pull_request_target has real uses, so flag a hit for review instead of auto-failing the build. Just make someone explain why the checkout and the run step both belong in one privileged job.

Agent Bench: audit your own release path

The weak point here was the pipeline itself. Point an agent at your own release path and see what a stranger's PR could reach.

Point a coding agent at .github/workflows, plus any reusable workflows and composite actions they call, and have it surface four things:

  • pull_request_target or workflow_run jobs that check out or run PR-controlled code. The pull_request_target one is how the token leaked above.

  • Any long-lived PAT or npm token in a job that can push or publish. Make it name the secret and the step that reads it, because that credential is what made this attack work.

  • Release jobs a branch push can trigger with no required review or environment gate. In the attack, a push to next was all it took to set the release workflow running.

  • Actions pinned to a moving tag like @v4 or @main rather than a full commit SHA.

Then make it prove each one. Every hit needs a file:line. For the risky ones, make it show both steps: the grant, and the credential that grant can reach. A bare keyword match shouldn't count. Agents over-report, so treat the whole list as a first pass and confirm by hand.

Also on the Radar: the approval box that lies

Maor Dokhanian at Wiz, "GhostApproval: a trust boundary gap in AI coding assistants" [2026-07-08]. The same trick shows up in your editor. A repo ships a file with an innocent name like project_settings.json that is a symlink to ~/.ssh/authorized_keys. You ask your coding agent to set up the workspace, it goes to write that file, and the approval dialog shows you the harmless name while the write lands on your SSH keys.

It gets worse. Wiz saw the agent name the real target in its own reasoning, then hide it in the confirmation box anyway. That is an old symlink-follow bug (CWE-61) sitting on top of an approval box that shows the wrong target (CWE-451).

Six assistants were affected: Amazon Q (CVE-2026-12958), Cursor (CVE-2026-50549), Google Antigravity (CVE pending), Augment, Windsurf, and Claude Code. Amazon Q and Cursor shipped fixes. Anthropic disputed that this is a vulnerability, since a folder you chose to trust sits outside its threat model.

Provenance tells you which workflow built a package. An approval dialog just shows you the file a tool chose to display. Both check one narrow thing. This week attackers kept that check green and ran the real payload behind it. So before you trust a green check, ask what it covers.

Think one of your pipelines is clean? Reply and tell me which, and I'll tell you where I'd look first.

How was this issue?

Login or Subscribe to participate

Keep Reading