Trivy Supply Chain Hack Spreads Infostealer via Docker, Triggers Worm and Kubernetes Wiper
This post was researched and drafted by an AI assistant. All facts have been sourced from linked references. If you spot an error, let me know.
What’s New This Week
The attack is still unfolding as of March 23. Docker Hub images 0.69.5 and 0.69.6 were pushed on March 22 without corresponding GitHub releases, both containing the same TeamPCP infostealer IOCs identified in earlier stages. The latest tag on Docker Hub currently points to the compromised 0.69.6 image, per Socket’s research. Separately, the attacker used a service account token – likely harvested during the GitHub Actions compromise – to deface all 44 repositories in Aqua Security’s aquasec-com GitHub organization in a scripted 2-minute burst on the evening of March 22. The npm-side CanisterWorm campaign had expanded to 135 malicious package artifacts spanning more than 64 unique packages as of March 21, according to Socket.
Changelog
| Date | Summary |
|---|---|
| 23 Mar 2026 | Initial post covering the full attack chain: GitHub Actions compromise, Docker Hub poisoning, CanisterWorm npm campaign, and Kubernetes wiper. |
Aqua Security’s Trivy – one of the most widely used open-source vulnerability scanners in DevSecOps pipelines – has been compromised twice in the span of a month. The second, more damaging wave extended far beyond the scanner itself, hitting Docker Hub, GitHub Actions, the npm registry, and ultimately Kubernetes clusters. The threat actor behind the campaign is tracked as TeamPCP (also known as DeadCatx3, PCPcat, PersyPCP, ShellForce, and CipherForce).
How It Started: Credential Compromise
The initial foothold came from a compromised credential. According to Aqua Security VP of open source Itay Shakury, as reported by The Hacker News, the attacker used that credential to publish a malicious Trivy release (version 0.69.4) by swapping the actions/checkout reference to a malicious imposter commit that downloaded trojanized Go source files from a typosquatted domain, adding --skip=validate to goreleaser to bypass binary validation, then tagging the result as v0.69.4 to trigger the release pipeline.
This was actually the second Trivy supply chain incident. The first, in late February and early March 2026, involved an autonomous bot called hackerbot-claw that exploited a pull_request_target workflow to steal a Personal Access Token. Shakury acknowledged that incomplete containment of that incident set up the second: credentials were rotated, but not atomically, and the attacker may have obtained refreshed tokens in the gap.
The GitHub Actions Compromise
The attacker didn’t stop at the binary release. They force-pushed 75 of 76 version tags in aquasecurity/trivy-action and 7 tags in aquasecurity/setup-trivy to point to malicious commits containing a Python infostealer payload, per Socket security researcher Philipp Burckhardt’s analysis. No new releases were created and no branches were touched – just tag rewrites, which don’t trigger the same notifications as new releases.
The stealer ran in three stages: harvesting environment variables from runner process memory and the file system, encrypting the data, and exfiltrating it to the attacker-controlled server at scan.aquasecurtiy[.]org (note the deliberate typosquat on “aquasecurity”). The credential targets included SSH keys, cloud provider credentials, database credentials, Git and Docker configurations, Kubernetes tokens, and cryptocurrency wallets.
If the outbound exfiltration failed, the payload fell back to staging the stolen data in a public repository named tpcp-docs under the victim’s own GitHub account, using the captured INPUT_GITHUB_PAT environment variable.
Docker Hub Poisoning
The compromise extended to Docker Hub. Versions 0.69.4, 0.69.5, and 0.69.6 were published to the official aquasec/trivy repository containing the same TeamPCP infostealer IOCs. The last known clean release is 0.69.3. Versions 0.69.5 and 0.69.6 were pushed on March 22 without corresponding GitHub releases or tags, per Burckhardt’s follow-up post. All three compromised versions have since been removed, but the latest tag pointed to 0.69.6 during the attack window.
Docker Hub tags are not immutable. Any pipeline that auto-pulled latest or rebuilt against affected versions during the window may have incorporated the malicious binary.
CanisterWorm: From Stolen Credentials to npm Worm
The stolen CI/CD secrets became the attack’s second stage. The attacker used captured npm tokens to compromise legitimate publisher namespaces, replacing package contents with malicious code and then republishing across every package reachable with the stolen credentials.
The campaign, named CanisterWorm by Socket, hit the @emilgroup scope (a cluster of SDK packages associated with Emil Group) and @teale.io/eslint-config. As of March 21, 2026, it had expanded to 135 malicious package artifacts spanning more than 64 unique packages, per Socket’s tracking page.
The malware is technically distinctive. A postinstall hook installs a Python backdoor as a systemd --user service named pgmon. That service polls an Internet Computer Protocol (ICP) canister at tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io every 3,000 seconds, retrieves a URL for a follow-on binary, downloads it to /tmp/pglog, and tracks state in /tmp/.pg_state. The ICP canister acts as a dead-drop C2 – the attacker can rotate second-stage payloads without modifying the implant already installed on compromised systems.
The worm component is what drove the campaign’s scale. A script called deploy.js used captured npm tokens to enumerate every package the token could publish, bumped patch versions, and republished malicious content as latest. Per Aikido security researcher Charlie Eriksen’s analysis, a later variant of @teale.io/eslint-config also harvested npm tokens from .npmrc, environment variables, and npm configuration to make propagation more autonomous.
Aqua Security’s Internal GitHub Org Defaced
On March 22, 2026, the attacker used what appears to be a compromised service account token – GitHub ID 139343333, account Argon-DevOps-Mgt, created 2023-07-12 – to deface all 44 repositories in Aqua Security’s aquasec-com GitHub organization. Every repository was renamed with a tpcp-docs- prefix, descriptions were changed to “TeamPCP Owns Aqua Security,” and all were made public. The entire operation took 79 seconds, running from 20:31:07 UTC to 20:32:26 UTC on March 22, per security researcher Paul McCarty’s forensic analysis.
The aquasec-com organization is distinct from the better-known aquasecurity org that hosts Trivy. The compromised org contains proprietary source code including Tracee, internal Trivy forks, CI/CD pipelines, and Kubernetes operators. McCarty assessed with high confidence that the Argon-DevOps-Mgt service account – a single bot account bridging both GitHub organizations – was the weak link. One compromised token gave the attacker write and admin access to both.
Kubernetes Wiper
TeamPCP has also deployed a new payload that goes beyond credential theft. According to Aikido’s Eriksen, the wiper deploys privileged DaemonSets across every Kubernetes node including the control plane. Iranian nodes get wiped and force-rebooted via a container named “kamikaze.” Non-Iranian nodes receive the CanisterWorm backdoor installed as a systemd service. Non-Kubernetes Iranian hosts receive rm -rf / --no-preserve-root. The shell script uses the same ICP canister linked to CanisterWorm and runs checks to identify Iranian systems.
The wiper also spreads via SSH using stolen keys and exploits exposed Docker APIs on port 2375 across the local subnet.
What to Do Now
The immediate remediation steps, per Aqua Security and the researchers involved:
Safe versions:
If you ran affected versions, assume compromise:
- Rotate all pipeline secrets, cloud credentials, SSH keys, and tokens immediately
- Check your GitHub org for repositories named
tpcp-docs - Block outbound connections to
scan.aquasecurtiy[.]organd45.148.10[.]212 - Review npm packages for unexpected patch versions from
@emilgroupor@teale.io
Structural fixes:
- Pin GitHub Actions to full commit SHA hashes, not version tags. Tags can be rewritten. SHAs cannot.
- Audit bot and service accounts that bridge multiple GitHub organizations. A single long-lived PAT with cross-org access is not a convenience feature – it is a blast radius multiplier.
- Treat
latestDocker tags as untrusted in CI. Pin to a digest.
The real lesson here is not about Trivy specifically. It is about the attack surface of the average DevSecOps pipeline: GitHub Actions pulling by tag, Docker images pulling by latest, npm packages installed via postinstall hooks, service accounts accumulating permissions across org boundaries over time. TeamPCP found one credential and turned it into a campaign that hit the scanner, the GitHub Actions, the container registry, the npm ecosystem, and a security vendor’s internal repositories. That is a long tail from a single stolen token. Most pipelines have several such tokens. Most have never audited them.