All posts
Security Supply Chain March 2026

The Trivy Supply Chain Attack: A Lesson in Mutable Tags

Between March 19 and 23, 2026, a threat actor known as TeamPCP compromised Aqua Security’s CI/CD pipeline. For four days, anyone who pulled a Trivy container image received malware. Here’s what happened — and what it teaches us about how we reference dependencies.

18,000+
Pipelines compromised
4
Days of exposure
76/77
trivy-action tags overwritten

It was a Wednesday morning when I first saw the advisory come through.

Aqua Security’s CI/CD pipeline had been compromised. Between March 19 and March 23, 2026, a threat actor known as TeamPCP had been pushing backdoored versions of Trivy — a widely used container vulnerability scanner — to Docker Hub. The irony was hard to ignore: the tool people run to find vulnerabilities in their containers had itself become the vulnerability.

If you pulled aquasec/trivy:latest or any of the version tags 0.69.4, 0.69.5, or 0.69.6 during that window, you got malware. An infostealer that quietly harvested CI/CD secrets, cloud credentials, SSH keys, and Docker configurations.

The attackers force-pushed 76 of 77 version tags in the aquasecurity/trivy-action GitHub Action and all 7 tags in aquasecurity/setup-trivy. Every GitHub Actions workflow referencing those tags — and there are millions of them — silently started running attacker-controlled code.

Why this one lands differently

This attack targeted the people who care most about security. If you’re running Trivy in your CI/CD pipeline, you’re doing the right thing. And then the supply chain you trusted handed you malware.

Most teams reference Trivy one of three ways: aquasec/trivy:latest in a Docker pull step; aquasecurity/[email protected] in a GitHub Actions workflow; or a scheduled cron job pulling the latest image nightly. All three were compromised.

The case for pinning to digests

The correct technical response is to pin dependencies to immutable references — content-addressable digests rather than tags.

Tags are mutable. Anyone with push access to a registry can overwrite them. A Docker image digest looks like this:

aquasec/trivy@sha256:4b8c9f2a…

That hash is a cryptographic fingerprint of the exact image content. It cannot be silently replaced. If an attacker overwrites the image at a given tag, the digest changes — your pinned reference breaks visibly rather than invisibly running compromised code.

But pinning creates maintenance overhead you have to account for

When you pin to a digest, you opt out of automatic updates. Security patches, bug fixes — none of it reaches you automatically. Pinning without a maintenance plan trades one risk for another.

Pinning is the floor, not the ceiling

For Docker image digests, pinning works well. A registry digest is a content-addressed hash — if the hash matches, the content matches.

But for GitHub Actions, pinning a commit SHA has a subtle weakness. GitHub resolves commit SHAs globally across forks. An attacker can create a commit in a forked repository, and if a workflow references that SHA, GitHub Actions will happily check out the attacker’s code. The version comment next to the SHA — # v6.0.2 — is free text. Nothing validates it.

RoseSecurity wrote an excellent breakdown of this in SHA Pinning Is Not Enough. Their advice: pin your SHAs, then verify what they point to. Confirm the commit exists on the upstream repo’s actual release branch, not an orphaned fork.


Update, April 2026: A month after the Trivy attack, a malicious version of @bitwarden/cli appeared on npm using the same C2 infrastructure (audit.checkmarx[.]cx) and the same malware patterns. This time the target was a password manager’s CLI — again, a high-trust developer tool sitting in CI/CD pipelines. We wrote about it here.


We run Trivy as part of our managed scanning infrastructure and have been working through some of these same trade-offs. If you’re thinking through how your own supply chain practices hold up, we’re happy to compare notes.