Skip to content

Cybersecurity · Supply Chain

The AsyncAPI Generator Attack: A pull_request_target Misconfig Becomes an npm Compromise

A GitHub Actions misconfiguration let an attacker open dozens of pull requests, steal a privileged bot token, and push a malicious @asyncapi/generator release to npm. Here's what happened and how to check your own workflows.

Abhishek Gupta

Abhishek Gupta

5 min read

The AsyncAPI Generator Attack: A pull_request_target Misconfig Becomes an npm Compromise

Sponsored

Share

On July 14, 2026, someone opened 37 pull requests against the AsyncAPI Generator repository. Most looked routine. One didn’t need to look routine at all, because the workflow that processed it was configured to trust it automatically.

Security researchers reported that the attacker exploited a pull_request_target misconfiguration in AsyncAPI’s GitHub Actions setup to steal a privileged bot personal access token, push directly to the generator repository’s next branch, and publish malicious versions of @asyncapi npm packages, a project family with a combined multi-million weekly download count.

The npm registry’s own metadata backs up the timeline: @asyncapi/generator version 3.3.1 was published on July 14, 2026 at 07:10 UTC. It no longer appears among the package’s published versions today, and the registry’s latest dist-tag currently points back to 3.3.0, consistent with a compromised release being pulled after discovery.

npm registry release timeline for @asyncapi/generator, showing 3.3.1 published July 14 and subsequently unpublished Pulled directly from registry.npmjs.org: 3.3.1 exists in the package’s publish history but not in its current version list.

How the attack worked

The mechanism is a well-documented but still frequently misconfigured pattern in GitHub Actions. Two triggers exist for running workflows on pull requests:

  • pull_request runs with the permissions of the fork the PR comes from. Safe by default, because a malicious PR only gets to run code with its own limited token.
  • pull_request_target runs with the permissions and secrets of the target branch, even for PRs from forks. This exists so that workflows can do things like post a comment on the PR or run a privileged status check without needing the fork to have write access.

The danger appears when a pull_request_target workflow also checks out and executes the pull request’s own code:

# The dangerous pattern
on:
  pull_request_target:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}   # checks out attacker-controlled code
      - run: npm install && npm run build   # runs with target branch's secrets available

Because the job runs with the target branch’s secrets in scope, and because it’s executing code the pull request author controls, the pull request author gets to run arbitrary code with those secrets. In AsyncAPI’s case, that meant an attacker-controlled workflow step could read the bot token used for repository automation and exfiltrate it. From there, the attacker had write access equivalent to whatever that bot account was scoped to, which included pushing to next and, ultimately, triggering a package publish.

37 pull requests is a lot of surface area to test this against. It’s consistent with an attacker probing multiple workflow paths until one of them proved exploitable, which is a useful reminder that a single hardened workflow file doesn’t protect a repo if a sibling workflow makes the same mistake.

Why this keeps happening

This isn’t a novel technique. GitHub has published guidance on pull_request_target risks for years, and several major incidents (in project ecosystems well beyond AsyncAPI) have followed this exact shape. What makes it persistent is that the dangerous configuration and the safe one look almost identical in a workflow file, and the failure mode is silent until someone exploits it. A repo can run the vulnerable pattern for months without incident and look completely fine in every normal CI run.

It’s also a pattern that spreads through copy-paste. Workflow templates get forked between projects, and a pull_request_target block that made sense in its original context (say, commenting on PRs with a bot that has minimal permissions) gets reused in a context where the same token has publish rights. The workflow author often isn’t thinking about the interaction between “this trigger runs with elevated secrets” and “this step happens to also check out PR code,” because those two lines can be dozens of lines apart in the same file.

Auditing your own workflows

If you maintain any package published from a CI pipeline, this is worth ten minutes right now:

# Find every workflow using pull_request_target
grep -rl "pull_request_target" .github/workflows/

# For each match, check whether it checks out the PR head
grep -A5 "pull_request_target" .github/workflows/*.yml | grep -i "ref:.*head"

For any workflow where both conditions are true, checkout of PR code plus a token with write, publish, or deploy scope, you have the same exposure AsyncAPI had. The fix is one of:

  1. Split the workflow. Use pull_request (unprivileged) for anything that needs to build or run PR code, and a separate pull_request_target workflow (privileged, no PR code execution) for anything that needs to comment or label.
  2. Never check out PR code in a pull_request_target job. If you must inspect PR content, do it read-only through the GitHub API rather than executing it.
  3. Scope the token narrowly. A bot token used only for commenting shouldn’t also have push or npm publish rights. If it does, an attacker who compromises the comment workflow gets everything, not just comment access.

The broader pattern

This incident sits alongside the TanStack and React Native Aria npm compromises from earlier this year as another reminder that the weakest link in a dependency’s trustworthiness usually isn’t the code you’re reading, it’s the CI pipeline nobody’s reading. A widely-used generator package with millions of weekly downloads was one workflow misconfiguration away from pushing malicious code into every project that ran npm install during that window.

If you’re auditing your own release pipeline and want a second set of eyes on where privileged tokens flow through your GitHub Actions setup, that’s exactly the kind of CI/CD hardening review our team does before it becomes an incident report instead of an audit finding.

Frequently asked questions

What is pull_request_target and why is it dangerous?
pull_request_target is a GitHub Actions trigger that runs with the permissions and secrets of the target (usually default) branch, even when triggered by a pull request from a fork. That's useful for workflows that need to comment on PRs or run privileged checks, but if that workflow also checks out and executes code from the PR branch itself, an attacker can get their PR code to run with the target branch's elevated secrets.
Am I affected by this if I use @asyncapi/generator?
Check whether you installed a version around July 14, 2026. The registry shows version 3.3.1 was published and then removed; the current published latest is 3.3.0. If your lockfile pinned or your install picked up 3.3.1 during that window, treat your CI secrets and any credentials in that pipeline as potentially exposed and rotate them.
How do I check if my own repo has this misconfiguration?
Search your .github/workflows directory for on: pull_request_target. For each match, check whether the job checks out the PR's head ref (actions/checkout with ref: ${{ github.event.pull_request.head.sha }} or similar) and whether that job's token or secrets have write access. If both are true, you have the same exposure.

Sources

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored