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.