blog
Learn how TeamPCP exploited mutable GitHub Action tags to compromise Trivy, Checkmarx, and LiteLLM, and how to protect your CI/CD pipelines.
In March 2026, a threat actor known as TeamPCP launched one of the most damaging software supply chain attacks to date. What started as a compromise of Aqua Security’s Trivy vulnerability scanner cascaded into a multi-platform exploit that affected GitHub Actions, VS Code extensions, npm packages, and LiteLLM on PyPI. The root cause? Mutable version tags in GitHub Actions and organizations pulling updates without a cooldown period.
The attack timeline tells the story. On March 19, TeamPCP pushed a malicious v0.69.4 tag to the Trivy repository, force-pushing 75 out of 76 trivy-action tags and 7 setup-trivy tags to compromised versions. The injected payload scraped hosted GitHub runner process memory for secrets marked isSecret: true, swept filesystems for SSH keys and cloud credentials, encrypted everything with AES-256-CBC and RSA-4096, and exfiltrated data to a typosquatted domain. By March 23, Checkmarx KICS GitHub Actions were compromised with all 91 tags hijacked. On March 24, malicious LiteLLM packages appeared on PyPI.
Understanding how GitHub Actions are referenced is essential to grasping why this attack was so effective. When you reference an action in a workflow, you have three options.
Branch references pull the latest commit from a branch:
- uses: aquasecurity/trivy-action@main
Tag references point to a named release:
- uses: aquasecurity/trivy-action@v0.69.4
SHA references pin to a specific, immutable commit:
- uses: aquasecurity/trivy-action@a7b7e234bc249cc10a3d1e87b7c691fb64bc1e16 # v0.69.3
Branch and tag references are mutable. A repository maintainer, or an attacker with write access, can move a tag to point at a completely different commit. When TeamPCP compromised the Trivy repository, they force-pushed tags to malicious commits. Every CI/CD pipeline referencing those tags by name immediately pulled the compromised code on the next run.
SHA references are the only truly immutable option. A commit SHA is a cryptographic hash of the repository state at that point in time. It cannot be moved or reassigned. Pinning actions to full commit SHAs is the single most important defense against this class of attack.
Zizmor is a security linter for GitHub Actions workflows. It catches the exact patterns that TeamPCP exploited. Install and run it against your workflows:
zizmor .github/workflows/
Zizmor flags two findings that are directly relevant to this attack.
Unpinned action references flag any action that uses a branch name or tag instead of a full SHA hash:
error[unpinned-uses]: unpinned action reference
--> .github/workflows/scan.yml:26:15
|
26 | - uses: aquasecurity/trivy-action@v0.69.3
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash ref
|
= note: audit confidence → High
Imposter commits flag references to SHAs that exist in a fork rather than the upstream repository, which could be attacker-controlled:
error[impostor-commit]: action ref is an impostor commit
--> .github/workflows/build.yml:22:15
|
22 | - uses: example/action@abc123def456
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this commit does not belong to the action's repository
|
= note: audit confidence → High
Running zizmor as part of your CI pipeline ensures new workflows and changes to existing ones are validated before they reach production.
Trail of Bits published a Claude Code configuration repository that demonstrates how to help maintain GitHub Actions security standards through AI-assisted development. The key insight is embedding security rules directly into the CLAUDE.md file that guides Claude Code’s behavior:
# GitHub Actions
Pin actions to SHA hashes with version comments: actions/checkout@<full-sha> # vX.Y.Z
Scan workflows with zizmor before committing.
Configure Dependabot with 7-day cooldowns and grouped updates.
With these instructions in place, Claude Code automatically pins new action references to SHA hashes, adds version comments for human readability, and flags any mutable tag references it encounters during code review.
The dependency cooldown period is a critical defense. The TeamPCP attack relied on organizations pulling updates immediately after they were published. A 7-day cooldown ensures that compromised releases have time to be detected and reverted before your pipelines consume them. Configure this in your Dependabot settings:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
If you use Renovate instead of Dependabot, apply the same 7-day cooldown with minimumReleaseAge:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"minimumReleaseAge": "7 days",
"packageRules": [
{
"matchManagers": ["github-actions"],
"groupName": "github-actions"
}
]
}
Combining zizmor scanning, SHA pinning, and cooldown periods creates a layered defense. Zizmor catches misconfigurations before they ship. SHA pinning ensures you run exactly what you reviewed. Cooldown periods give the community time to identify compromised releases before your pipelines adopt them.
GitHub has responded to the TeamPCP campaign and other supply chain attacks with an ambitious 2026 security roadmap that addresses these problems at the platform level.
Deterministic Dependency Locking introduces a new dependencies: section in workflow YAML that locks all direct and transitive action dependencies using commit SHAs, similar to Go modules. Every workflow will execute exactly what was reviewed, and dependency updates will appear as reviewable diffs in pull requests. This feature is expected in public preview within 3 to 6 months, with general availability at 6 months.
Policy-Driven Execution Controls use GitHub’s ruleset framework to centralize control over who can trigger workflows and which events are permitted. Actor rules and event rules address over-permissioned workflows and the unclear trust boundaries that enable attacks like the pull_request_target exploit, which was responsible for the initial trivy compromise. Public preview is expected within 3 to 6 months.
Native Egress Firewall restricts outbound connections from runners. This directly counters the TeamPCP exfiltration technique, where stolen secrets were sent to typosquatted domains. Had an egress firewall been in place, the C2 callbacks to scan.aquasecurtiy.org would have been blocked. Public preview is expected within 6 to 9 months.
These platform-level changes will make secure-by-default the norm rather than the exception. Until they ship, the combination of SHA pinning, zizmor scanning, cooldown periods, and AI-assisted maintenance provides the best defense against GitHub Actions supply chain attacks.
Would you like to learn more about GitHub and GitHub Actions security? Contact us today: sales [at] pumasecurity [dot] com.
Eric Johnson’s experience includes performing cloud security architecture and design, cloud native and Kubernetes assessments, infrastructure as code automation, application security automation, web and mobile application penetration testing, secure development lifecycle consulting, and secure code review assessments.