Attacker opened 475+ malicious PRs across GitHub in 26 hours using AI-generated payloads
The campaign exploited pull_request_target workflow triggers in GitHub Actions
Wiz tracked 6 attack waves starting March 11, with at least 2 npm packages compromised
Malicious code hid in CI files like conftest.py, package.json, and Makefile
10% success rate still led to dozens of repository compromises and credential theft
A single threat actor opened over 475 malicious pull requests on GitHub in 26 hours. The PRs looked like routine CI updates. They were not. Each one contained code designed to steal credentials, tokens, and secrets from any repository that ran them through its CI pipeline.
How the Attack Worked
Security researcher Charlie Eriksen publicly identified the campaign on April 2, 2026. The attacker, operating under the GitHub account ezmtebo, targeted repositories configured with the pull_request_target workflow trigger in GitHub Actions.
Here is why that trigger matters. Normal pull_request workflows run in the context of the fork. They cannot access the target repository's secrets. But pull_request_target runs in the context of the base repository. It has access to everything: environment variables, deployment keys, API tokens, package publishing credentials.
The attacker knew exactly which repositories used this trigger. They scanned GitHub for workflow files containing pull_request_target, built a target list, and automated the PR creation. Each PR title followed a pattern: "ci: update build configuration." Boring enough that a maintainer might merge it without looking twice.
The injected code lived in files that CI pipelines typically trust: conftest.py for Python projects, package.json for Node.js, Makefile for C/C++, and build.rs for Rust. These are files that get executed during builds. A maintainer reviewing the PR might focus on source code changes and miss the subtle additions to build configuration files.
The pull_request_target trigger exists for legitimate reasons. Some workflows need to comment on PRs, add labels, or run checks that require write access to the repository. GitHub's documentation explicitly warns about the security implications, but many developers copy workflow templates without understanding the trust boundary they are crossing. One misconfigured YAML file turns your entire CI secret store into an open door.
Six Waves, Not One
Wiz Research tracked the full campaign and found it was bigger than the initial disclosure suggested. The threat actor (tracked as "prt-scan") ran six separate waves starting March 11, 2026, three full weeks before public disclosure.
The first waves used crude bash scripts. Simple curl commands that exfiltrated environment variables to an external server. Easy to spot if you knew what to look for. But by wave four, the payloads had evolved. The attacker started using AI-generated, language-aware code that matched the coding style of each target repository.
A Python project received Python-style exfiltration code. A Rust project got Rust-compatible build scripts. The AI-generated payloads blended in with existing code patterns, making manual review harder. Across all six waves, the attacker opened well over 500 malicious PRs and successfully compromised at least two npm packages.
The compromised npm packages are particularly dangerous. When an attacker gains publishing credentials for a popular package, every project that depends on it becomes vulnerable. One compromised package can cascade through thousands of downstream projects. This is how SolarWinds happened. This is how the xz utils backdoor worked. The pattern keeps repeating because the attack surface keeps growing.
Why 10% Was Enough
A 10% success rate sounds low. It was not. Out of 500+ PRs, roughly 50 repositories executed the malicious code. Each compromised repository leaked its full set of CI secrets: npm tokens, AWS keys, database credentials, deployment API keys.
The math works in the attacker's favor. Sending 500 PRs costs nothing. Each PR is a few lines of code generated by an AI model. The automation runs in minutes. But a single compromised npm token can give access to a package downloaded millions of times per month. One successful credential theft can be worth more than all 500 PRs combined.
This is the economics of supply chain attacks. The cost of attack approaches zero while the potential payoff scales with the dependency graph of the compromised target. A developer maintaining a popular open source project on their lunch break is defending against the same attack that would target a Fortune 500 company.
Most of the compromised repositories were small to mid-size projects. Not the high-profile targets you might expect. But small projects are often dependencies of larger ones. A test utility with 200 stars might be a transitive dependency of an enterprise framework used by thousands of companies. The attacker does not need to compromise the fortress. They compromise the supply chain.
What makes the prt-scan campaign different from earlier supply chain attacks is the AI component. Previous attackers had to manually craft payloads for each target language and build system. That limited how many repositories they could target in a given timeframe. AI removes that bottleneck. Generate a Rust payload for a Rust project. Generate a Python payload for a Python project. Generate a Go payload for a Go project. All automatically. All looking like they belong there.
This is not theoretical. Wiz confirmed the later waves used AI to match coding conventions, variable naming patterns, and comment styles of each target repository. A human reviewer scanning the diff would see code that looks like it was written by a contributor who knows the project. The social engineering is baked into the code itself.
What You Should Do Right Now
First, audit your GitHub Actions workflows. Search for pull_request_target in any .github/workflows/ file. If you are using it, you probably should not be. The GitHub documentation itself warns against it for exactly this reason. Switch to pull_request where possible.
If you genuinely need pull_request_target (some workflows require it for labeling or commenting on PRs from forks), lock it down. Add explicit conditions that prevent workflow execution on PRs from unknown contributors. Use if: github.event.pull_request.head.repo.full_name == github.repository to ensure only internal PRs trigger the workflow.
Second, review your CI secrets. Rotate any tokens, keys, or credentials stored in GitHub Actions secrets. If your repository was targeted (check your closed PR list for suspicious "ci: update" PRs from unknown accounts), rotate everything immediately.
Third, pin your GitHub Actions to specific commit SHAs instead of version tags. A compromised action at v2 can be silently updated. A pinned SHA cannot. Tools like StepSecurity's Harden-Runner can automate this.
Fourth, enable GitHub's secret scanning and push protection. These features detect when secrets are accidentally committed and block pushes containing known credential patterns. They are free for public repositories and included in GitHub Advanced Security for private ones.
Fifth, consider using OpenSSF Scorecard or similar tools to evaluate the security posture of your dependencies. If a package you depend on has misconfigured CI workflows, you inherit that risk. The Scorecard checks for exactly these kinds of misconfigurations and gives each project a security rating. It takes five minutes to run and could save you from becoming a downstream casualty.
The prt-scan campaign is not the first supply chain attack on GitHub and it will not be the last. But the combination of AI-generated payloads, automated targeting, and trusted CI file injection makes it more sophisticated than previous attempts. The only defense is treating your CI pipeline with the same security posture as your production infrastructure.
The Bottom Line
475 malicious PRs in 26 hours. AI-generated payloads that match each target's coding style. At least 2 npm packages compromised. 10% success rate across 6 attack waves spanning a month.
The attack targeted a known dangerous pattern in GitHub Actions (pull_request_target) that many repositories still use carelessly. The fix is straightforward: audit your workflows, switch triggers, rotate secrets, pin action versions.
Supply chain security is not optional. If your open source project has CI/CD and accepts external contributions, you are a target. The barrier to launching these attacks dropped to near zero the moment AI could generate repository-specific payloads at scale.
I maintain several open source repositories. After reading the Wiz report, I audited every workflow file across all of them. None used pull_request_target, but two had actions pinned to version tags instead of SHAs. Fixed in ten minutes. That is the ratio to keep in mind: ten minutes of prevention versus weeks of incident response if a token gets leaked.
The uncomfortable truth is that open source security depends on individual maintainers making the right configuration choices, often without security training, often unpaid, often maintaining projects in their spare time. Campaigns like prt-scan exploit that gap systematically. The tools to defend against it exist. Using them is the part most developers skip.
Top comments (0)