Commissioned, Curated and Published by Russ. Researched and written with AI.
What’s New This Week
As of March 23, 2026, Trivy Docker images 0.69.4, 0.69.5, and 0.69.6 have been confirmed malicious and removed from Docker Hub. The latest tag was pointing to 0.69.6 at the time of discovery. Mirror infrastructure, including mirror.gcr.io, continued serving the compromised images after the Docker Hub originals were taken down. TeamPCP has also defaced 44 internal Aqua Security repositories and deployed a Kubernetes wiper targeting Iranian infrastructure.
Changelog
| Date | Summary |
|---|---|
| 23 Mar 2026 | Initial publication covering the Docker Hub layer of the Trivy supply chain attack. |
On March 22, 2026, attackers pushed two new Docker Hub image tags for Trivy – 0.69.5 and 0.69.6 – without corresponding GitHub releases. The latest tag was updated to point at 0.69.6. Both images contained the TeamPCP infostealer. Combined with 0.69.4, which had been pushed on March 19 as part of the initial Trivy GitHub Actions compromise, this means three consecutive Trivy versions on Docker Hub were malicious. All have since been removed. The last known clean release is 0.69.3.
This is the third layer of a widening supply chain attack. Layer one: a compromised GitHub Actions workflow. Layer two: stolen publish tokens used to spread a self-propagating worm (CanisterWorm) across npm. Layer three: Docker Hub images containing an infostealer deployed to anyone running docker pull aquasecurity/trivy.
Docker Hub trust is load-bearing in CI/CD pipelines
Most CI/CD pipelines treat Docker Hub images from known publishers as safe by default. FROM aquasecurity/trivy:latest or a step that does docker run aquasec/trivy:0.69.4 is not checked, not signed, and not verified. It is trusted because the name looks right.
This is the fundamental problem. Docker Hub publisher accounts are not meaningfully different from any other credential-protected service. If the credentials are stolen, the images get replaced. There is no alert. There is no notification to downstream consumers. Everyone who pulls that tag in the hours or days before detection runs whatever the attacker pushed.
The aquasecurity Docker Hub account is not an “official” Docker Hub image in the formal sense – it is a verified publisher account. That distinction matters slightly for trust hierarchies, but it did not prevent the push. Having the credentials was sufficient.
How the images got there
The initial Trivy GitHub Actions compromise on March 19 gave TeamPCP access to Aqua’s build infrastructure. According to analysis by Paul McCarty at OpenSourceMalware and Wiz Research, the attack vector for the March 22 Docker Hub push was a compromised service account – the “Argon-DevOps-Mgt” bot account (GitHub ID 139343333) – with a long-lived PAT that bridged both Aqua GitHub organizations. Credentials for Docker Hub were among those stolen during the earlier GitHub Actions compromise and exfiltrated to a Cloudflare Tunnel C2.
The timeline of 0.69.5 and 0.69.6 is telling: they were pushed on March 22 with no corresponding GitHub releases or tags. Socket researcher Philipp Burckhardt noted this as an indicator – legitimate Trivy releases track both registries in lockstep. An image version on Docker Hub with no matching GitHub release is a forensic tell that something is wrong. Most pipelines do not check for this.
What the payload did
The malicious Trivy binaries run the legitimate Trivy process in parallel with the infostealer, so scans complete normally and workflows appear to succeed. The malicious code does the following, based on analysis by Wiz Research:
- Sweeps environment variables and the filesystem for credentials: SSH keys, AWS/GCP/Azure credentials, Kubernetes tokens, cryptocurrency wallets across 50+ file paths
- Compresses and encrypts the harvest using AES-256-CBC with RSA-4096 hybrid encryption into
tpcp.tar.gz - Exfiltrates to a typosquatted C2 domain:
scan.aquasecurtiy[.]org(note the transposed letters) - Falls back to creating a
tpcp-docsrepository in the victim’s GitHub account and uploading the bundle as a release asset
On developer machines (detected by checking whether GITHUB_ACTIONS != "true"), the malware additionally installs persistence: a Python script at ~/.config/systemd/user/sysmon.py with a systemd unit that polls an ICP canister for further instructions.
The malicious code runs silently alongside the legitimate scanner. Scans complete, pipelines pass, nothing looks wrong.
The detection gap
The first malicious image, 0.69.4, was pushed on March 19 at 17:43:37 UTC. Public disclosure of the GitHub Actions compromise came later that day. The Docker Hub images were flagged separately – the 0.69.5 and 0.69.6 tags were identified by Socket on March 22, several days into the incident.
Exact pull counts for the compromised images have not been publicly confirmed. The aquasec/trivy Docker Hub repository has tens of millions of historical pulls. During a window of several days spanning a weekend, any pipeline referencing latest or versions 0.69.4 through 0.69.6 pulled the infostealer.
The damage window extended beyond Docker Hub removal. Aqua maintainers noted that mirror.gcr.io continued serving 0.69.4, 0.69.5, and 0.69.6 after the originals were taken down. Pipelines with a layer cache or a local mirror that hadn’t refreshed continued running compromised images. Removal from the origin registry is not the same as removal from production.
Docker Hub’s response
The malicious images were removed from Docker Hub. The latest tag now resolves to a clean image. No public statement specifically from Docker on the Trivy incident has been issued as of this writing. Third-party images that automatically rebuilt against the affected Trivy versions during the attack windows may have incorporated the malicious binaries – the scope of that secondary contamination is not yet fully assessed.
The signing gap
Docker Content Trust (DCT) and Sigstore/cosign exist specifically to address the push problem. If an image is signed by the publisher and the consumer verifies the signature before execution, a malicious image pushed with stolen credentials fails verification – even if it lands at the expected tag.
Aqua Security does use cosign to sign Trivy releases. The signing verification command is:
cosign verify aquasecurity/trivy:latest \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer-regexp=".*"
Most teams do not run this. It is not a default step in standard Docker workflows. docker pull and docker run do not verify signatures by default. DCT can be enforced via DOCKER_CONTENT_TRUST=1 but this is rarely set in CI environments because it breaks workflows that pull from registries without notary support.
The result: the tooling exists, the signatures were available, and the attack succeeded anyway. Signature verification is opt-in, and most teams haven’t opted in.
The fix: pin to digest, not tag
Tags are mutable pointers. The tag 0.69.4 pointed at a clean image, then at a malicious one, then was removed. The tag latest pointed at a malicious image for the duration of the attack. Neither gave any indication that the content had changed.
Image digests are not mutable. A SHA256 digest identifies a specific layer set. If the content changes, the digest changes.
Vulnerable (mutable tag):
FROM aquasecurity/trivy:latest
# or
FROM aquasecurity/trivy:0.69.4
Safe (digest-pinned):
FROM aquasecurity/trivy@sha256:<verified-digest>
The digest for a specific release can be retrieved from Docker Hub or via docker inspect. Pin to a digest that you have independently verified corresponds to a clean release. Update the pin deliberately, as part of a dependency update process, not automatically.
Complement digest pinning with signature verification:
cosign verify aquasecurity/trivy:latest \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer-regexp=".*"
Audit your Dockerfiles and CI configurations for any latest or mutable version tag references to security tooling especially. The irony of a vulnerability scanner being the vector for credential theft makes this category an obvious priority.
The broader structural problem is this: a single stolen credential allowed an attacker to silently replace a widely trusted security tool with an infostealer, serve it to an unknown number of CI/CD pipelines, and have it complete successfully with no visible indication of compromise. The trust model for container images assumes credential security. When that assumption fails, everything downstream fails with it. Digest pinning and signature verification break that chain. Neither is the default. Both should be.