Contents
ToggleExecutive Summary
The irony is hard to miss. Trivy, the vulnerability scanner running inside thousands of CI/CD pipelines, just became the attack vector of a supply chain compromise where most scanners and ASPM could fail.
On March 19, 2026, threat actor TeamPCP compromised Aqua Security’s Trivy vulnerability scanner for the second time in three weeks, (disclosed by Paul McCarty) injecting a multi-stage credential stealer into official GitHub Actions used by over 10,000 CI/CD workflows globally.
The attacker force-pushed 75 of 76 version tags in aquasecurity/trivy-action to malicious commits containing a three-stage infostealer that dumped Runner.Worker process memory, harvested SSH keys, cloud credentials, and Kubernetes tokens. A backdoored Trivy v0.69.4 binary was also published to GitHub Releases, Docker Hub, GHCR, and Amazon ECR.
The root cause was incomplete credential rotation after the February 28 hackerbot-claw breach, which left the attacker with residual access. The compromise has cascaded into the npm ecosystem via CanisterWorm, a self-propagating worm affecting more than 47 packages.

Key Takeaways
- 75 of 76 version tags in aquasecurity/trivy-action were force-pushed to malicious commits containing a three-stage infostealer
- Backdoored Trivy v0.69.4 binary published to GitHub Releases, Docker Hub, GHCR, and Amazon ECR
- Root cause: incomplete credential rotation after the February 28 hackerbot-claw breach
- Compromise cascaded into npm via CanisterWorm, a self-propagating worm affecting more than 47 packages
- Safe versions: trivy v0.69.3, trivy-action @0.35.0 (SHA: 57a97c7e), setup-trivy v0.2.6
TL;DR for Engineering Teams
What it is: Supply chain compromise of Trivy scanner and GitHub Actions (trivy-action, setup-trivy), injecting a multi-stage credential stealer attributed to TeamPCP. No CVE assigned.
Where it bites: Any CI/CD pipeline referencing aquasecurity/trivy-action by version tag (all tags except 0.35.0), setup-trivy (7 tags), or Trivy binary v0.69.4.
Why it matters: Payload runs before the legitimate scan — pipelines appear normal. Stealer dumps Runner.Worker memory, harvests SSH keys, cloud creds, K8s tokens, crypto wallets. 10,000+ workflows affected.
Patch status: Malicious artifacts removed. Tags restored with v-prefix. Immutable releases enabled. Safe versions: trivy v0.69.3, trivy-action v0.35.0, setup-trivy v0.2.6.
Immediate action: Pin to SHA 57a97c7e7821a5776cebc9bb87c984fa69cba8f1. Rotate all pipeline secrets if any workflow ran a compromised tag between March 19 17:43 UTC and March 20 ~05:40 UTC. Block 45.148.10.212 and scan.aquasecurtiy[.]org.
Vulnerability Overview
| Vendor | Aqua Security |
| Product | Trivy, trivy-action, setup-trivy |
| Vulnerability Type | Supply chain compromise / credential theft |
| CWE | CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) |
| CVE | Not yet assigned |
| Attack Vector | Network (CI/CD pipeline execution) |
| Active Exploitation | Confirmed |
| Attribution | TeamPCP (DeadCatx3 / PCPcat / ShellForce / CipherForce) |
| Exposure Window | ~12 hours (March 19 17:43 UTC to March 20 ~05:40 UTC) |
Technical Anatomy

Root Cause: Incomplete Credential Rotation After First Breach
The attack chain begins with the February 28, 2026 incident. An AI-powered bot called hackerbot-claw exploited a misconfigured pull_request_target workflow (apidiff.yaml) in the Trivy repository. This workflow had been present since October 2025 and was flagged by Boost Security’s poutine tool on November 29, 2025 — over three months before exploitation.
Aqua Security rotated credentials after the first incident. The credential rotation was not atomic. The attacker retained access to credentials missed during rotation, enabling the March 19 attack.
Exploit Path
1. Entry point — Compromised credentials and imposter commits. TeamPCP made imposter commits spoofing maintainer DmitriyLewen. At 17:43:37 UTC on March 19, the v0.69.4 tag was pushed. The attacker compromised the aqua-bot service account and used it to push malicious workflows to tfsec, traceeshark, and trivy-action. Additional credentials (GPG keys, Docker Hub, Twitter, Slack tokens) exfiltrated to Cloudflare Tunnel C2.
2. Trigger — Force-pushed 75 version tags with spoofed metadata. Each forged commit started from master HEAD, swapped entrypoint.sh with the infostealer, and cloned the original commit’s metadata including timestamps and commit message. GitHub’s release page showed no visible change. The “Immutable” badge still displayed on each poisoned release.
3. Execution — Three-stage credential stealer (204-line entrypoint.sh). Malicious entrypoint.sh SHA256: 18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a. Lines 4–105: infostealer. Lines 106–204: legitimate Trivy scan. Scan runs normally after the stealer, so pipeline output looks clean.
Stage 1 — Collection: GitHub-Hosted vs Self-Hosted Runners
On GitHub-hosted Linux runners, the malware uses passwordless sudo to dump Runner.Worker process memory via /proc/[pid]/mem, searching for the JSON pattern {“value”:”<secret>”,”isSecret”:true} stored in the .NET process heap.
On self-hosted runners, a comprehensive Python filesystem credential harvester targets the following categories:
| Category | Targets |
| Reconnaissance | hostname, whoami, uname -a, ip addr, printenv |
| SSH | ~/.ssh/id_rsa, id_ed25519, id_ecdsa, authorized_keys, known_hosts; /etc/ssh/ssh_host_*_key |
| Git | ~/.git-credentials, ~/.gitconfig |
| AWS | ~/.aws/credentials, ~/.aws/config, AWS_* env vars, EC2 IMDS 169.254.169.254, ECS 169.254.170.2 |
| GCP | ~/.config/gcloud/*, application_default_credentials.json, GOOGLE_APPLICATION_CREDENTIALS |
| Azure | ~/.azure/*, AZURE_* env vars |
| Kubernetes | ~/.kube/config, /etc/kubernetes/*.conf, service account tokens, kubectl get secrets -A -o json |
| Docker | ~/.docker/config.json, /kaniko/.docker/config.json |
| Environment files | .env, .env.local, .env.production, .env.staging across /opt, /srv, /var/www, /app, /data, /tmp |
| Database | ~/.pgpass, ~/.my.cnf, ~/.mongorc.js, redis.conf, env vars: DATABASE, DB_, MYSQL, POSTGRES, MONGO, REDIS, VAULT |
| Package managers | ~/.npmrc, ~/.vault-token, ~/.netrc |
| CI/CD configs | terraform.tfvars, terraform.tfstate, .gitlab-ci.yml, .travis.yml, Jenkinsfile, .drone.yml, ansible.cfg |
| TLS private keys | *.key, *.pem, *.p12, *.pfx; /etc/ssl/private/, /etc/letsencrypt/ |
| Crypto wallets | Bitcoin, Litecoin, Dogecoin, Zcash, Ethereum keystores, Cardano signing keys, Solana keypairs, Ledger files, Anchor deploy keys |
Stage 2 — Encryption
Collected data is encrypted with AES-256-CBC (key derived via PBKDF2 from 32 random bytes). The session key is wrapped with an embedded RSA-4096 public key using OAEP padding. Both files bundled into tpcp.tar.gz. Only the RSA private key holder can decrypt.
Stage 3 — Exfiltration
Primary: HTTPS POST to scan.aquasecurtiy[.]org (45.148.10.212) with header X-Filename: tpcp.tar.gz. Fallback: if C2 unreachable and INPUT_GITHUB_PAT available, creates public tpcp-docs repo on victim’s GitHub account and uploads the bundle as a release asset with timestamped tag (data-YYYYMMDDHHMMSS). Data stored on GitHub’s own infrastructure — unlikely to be blocked by corporate firewalls.
The “Immutable” Badge Deception
GitHub’s release UI displayed an “Immutable” badge on each poisoned tag. The attacker appears to have published immutable releases when poisoning the tags, locking in the malicious state. Each poisoned tag showed “0 commits to master since this release” — for a tag from 2020, this should show hundreds. The count was zero because the malicious commit’s parent was master HEAD.
CanisterWorm Cascade into npm
Stolen npm tokens triggered a follow-on attack. TeamPCP injected CanisterWorm into 47+ npm packages via ICP canister dead-drop C2. A newer variant self-propagates during postinstall by harvesting npm tokens. Persistence via systemd user service masquerading as “pgmon”.
Affected Versions
| Component | Vulnerable | Safe Version | Notes |
| trivy-action | Tags 0.0.1–0.34.2 | v0.35.0 (SHA: 57a97c7e) | 75 of 76 tags compromised |
| setup-trivy | 7 tags compromised | v0.2.6 | Same payload in action.yaml |
| trivy binary | v0.69.4 | v0.69.3 | Published to Docker Hub, GHCR, ECR |
| Homebrew | Picked up v0.69.4 | Rolled back to v0.69.3 | Built from source; binary OK |
| apt/pkg managers | Not affected | N/A | Not impacted |
Exposure Analysis
| Environment | Risk | Reason |
| CI/CD pipelines (tag-pinned) | Critical | Direct credential theft from Runner.Worker memory |
| Self-hosted runners | Critical | 50+ credential paths swept; SSH, cloud, K8s, crypto wallets |
| Docker builds (trivy:0.69.4) | High | Backdoored binary with ICP canister C2 polling |
| npm downstream (stolen tokens) | High | CanisterWorm self-propagation; more than 47 packages |
| Pipelines pinned to SHA | Not affected | Immutable reference, not vulnerable to tag repointing |
Detection Guidance
Indicators of Compromise
| Type | Indicator |
| Exfiltration domain | scan.aquasecurtiy[.]org (typosquat) |
| Exfiltration IP | 45.148.10.212 |
| Cloudflare Tunnel C2 | plug-tab-protective-relay.trycloudflare.com |
| Initial C2 (first breach) | recv.hackmoltrepeat.com |
| Fallback exfil repo | tpcp-docs |
| entrypoint.sh SHA256 | 18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a |
| Malicious commit parent | 57a97c7e7821a5776cebc9bb87c984fa69cba8f1 |
| Exfil data header | X-Filename: tpcp.tar.gz |
| Backdoor dropper | ~/.config/sysmon.py |
| CanisterWorm persistence | ~/.config/systemd/user/pgmon.service |
| Malware self-ID | TeamPCP Cloud stealer |
Forgery Detection Indicators
- Unsigned commits: original Trivy releases were GPG-signed by GitHub. Replacement commits are unsigned.
- Impossible timestamps: forged commits clone original dates (2021–2024) but parent is dated March 2026.
- Single-file modifications: each malicious commit only modifies entrypoint.sh (tree is master HEAD, not original).
- “0 commits to master since this release” on GitHub release page for tags that should show hundreds of commits.
Verification Steps
- Search workflow files: grep -r “aquasecurity/trivy-action” .github/workflows/
- Check if any workflow ran a version-tag reference (not SHA) between March 19 17:43 UTC and March 20 05:40 UTC
- Check self-hosted runners for ~/.config/sysmon.py and pgmon.service
- Search GitHub org for tpcp-docs repositories (indicates successful exfiltration)
- Run StepSecurity trivy-compromise-scanner for automated log auditing
Detection Campaign
A Phoenix campaign has not been generated because this was a compromise of a specific version of Trivy,
setup-trivy to safe version v0.2.6
Remediation Guidance
Immediate Actions
- Pin trivy-action to SHA: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
- Pin setup-trivy to safe version v0.2.6
- Rotate all pipeline secrets if any workflow ran a compromised tag during the exposure window
- Block scan.aquasecurtiy[.]org, 45.148.10.212, and plug-tab-protective-relay.trycloudflare.com
- Search GitHub org for tpcp-docs repositories and audit self-hosted runners for persistence
Long-Term Hardening
- Pin all GitHub Actions to full SHA hashes (zizmor, pinact, StepSecurity secure-repo, Renovate pinGitHubActionDigests)
- Audit all pull_request_target workflows — if it checks out PR head code, it is vulnerable
- Minimise token permissions; prefer read-only tokens with narrowest scope
- Enable immutable releases on repositories publishing GitHub Actions
- Require GPG-signed commits for release tags
Phoenix Security Recommendations
The Trivy compromise demonstrates why vulnerability management cannot stop at scanning known CVEs. This attack had no CVE, no CVSS score, no NVD entry. Traditional scanner-based workflows would not have flagged it.
Attack surface management: Phoenix identifies CI/CD pipelines running compromised components and maps whether references use mutable tags or immutable SHAs.
Contextual deduplication: Organisations using Trivy across dozens of repositories get a single prioritised backlog rather than duplicate alerts per repository.
Reachability analysis: Phoenix maps which instances actually executed compromised code versus those merely declared in workflow files.
Remediation campaigns: Create a campaign in Phoenix to track SHA migration, secret rotation, and runtime exposure elimination.
Ownership attribution: Map each affected workflow to the owning team for coordinated response across hundreds of repositories.
Appendix: Full List of Compromised trivy-action Tags
All 75 compromised tags with their malicious commit SHAs (source: Socket Security). The only clean tag is 0.35.0 (commit 57a97c7e).
| Tag | Commit SHA | Tag | Commit SHA | Tag | Commit SHA |
| 0.0.1 | f7773844 | 0.2.2 | 3dffed04 | 0.14.0 | 2297a1b9 |
| 0.0.2 | f5c9fd92 | 0.2.3 | cf1692a1 | 0.15.0 | 2b1dac84 |
| 0.0.3 | 22e864e7 | 0.2.4 | 848d665e | 0.16.0 | f4f1785b |
| 0.0.4 | 6ec7aaf3 | 0.2.5 | fa4209b6 | 0.16.1 | 3d1b5be1 |
| 0.0.5 | 555e7ad4 | 0.3.0 | 8cfb9c31 | 0.17.0 | 985447b0 |
| 0.0.6 | 794b6d99 | 0.4.0 | 18f01feb | 0.18.0 | 85cb72f1 |
| 0.0.7 | 506d7ff0 | 0.4.1 | 7b955a5e | 0.19.0 | 38623bf2 |
| 0.0.8 | 91d5e0a1 | 0.5.0 | d488f438 | 0.20.0 | 9092287c |
| 0.0.9 | 252554b0 | 0.5.1 | fa78e67c | 0.21.0 | b7befdc1 |
| 0.0.10 | b9faa60f | 0.6.0 | a5b4818d | 0.22.0 | 9ba3c3cd |
| 0.0.11 | 3c615ac0 | 0.6.1 | 6fc874a1 | 0.23.0 | fd090040 |
| 0.0.12 | c19401b2 | 0.6.2 | 2a51c5c5 | 0.24.0 | e0198fd2 |
| 0.0.13 | 4209dcad | 0.7.0 | ddb66974 | 0.25.0 | ddb94181 |
| 0.0.14 | 61fbe20b | 0.7.1 | aa3c46a9 | 0.26.0 | b7252377 |
| 0.0.15 | 0d49ceb3 | 0.8.0 | 4bdcc5d9 | 0.27.0 | 66c90331 |
| 0.0.16 | 2e7964d5 | 0.9.0 | b745a35b | 0.28.0 | c5967f85 |
| 0.0.17 | 1d74e4cf | 0.9.1 | da73ae07 | 0.29.0 | 9c000ba9 |
| 0.0.18 | 3201dddd | 0.9.2 | 7550f14b | 0.30.0 | ad623e14 |
| 0.0.19 | ea56cd31 | 0.10.0 | 8aa8af3e | 0.31.0 | 85190378 |
| 0.0.20 | 9738180d | 0.11.0 | e53b0483 | 0.32.0 | fd429cf8 |
| 0.0.21 | ef3a510e | 0.11.1 | 276ca968 | 0.33.0 | 19851bef |
| 0.0.22 | bb75a905 | 0.11.2 | 8ae5a08a | 0.33.1 | 91e7c2c3 |
| 0.1.0 | 9e8968cb | 0.12.0 | 820428af | 0.34.0 | ab6606b7 |
| 0.2.0 | 7f6f0ce5 | 0.13.0 | cf19d27c | 0.34.1 | a9bc513e |
| 0.2.1 | 0891663b | 0.13.1 | 405e91f3 | 0.34.2 | ddb9da44 |
If your workflow references any of the above tag@SHA combinations, it was executing malicious code.
Timeline
| Date | Event |
| October 2025 | Vulnerable pull_request_target workflow (apidiff.yaml) added to Trivy repo |
| November 29, 2025 | Boost Security’s poutine tool flags the workflow — no action taken |
| February 20, 2026 | hackerbot-claw account created on GitHub |
| February 28, 2026 | Trivy repository fully compromised — releases deleted, malicious VS Code extension pushed |
| March 1, 2026 | Aqua Security discloses, rotates credentials (incompletely), releases v0.69.2 |
| March 3, 2026 | Trivy v0.69.3 released (current safe version) |
| March 19, 17:43 UTC | TeamPCP pushes v0.69.4; backdoored binaries to GitHub, Docker Hub, GHCR, ECR |
| March 19, ~19:00 UTC | 75 trivy-action + 7 setup-trivy tags force-pushed. Socket detects 182 threat feed entries. |
| March 19, ~19:15 UTC | First detection by Paul McCarty |
| March 19–20 | CrowdStrike Falcon detects malicious script execution on customer runners |
| March 20, ~05:40 UTC | Malicious artifacts removed by Aqua Security |
| March 20 | Tags restored with v-prefix convention, immutable releases enabled |
| March 20–21 | CanisterWorm detected spreading across 47+ npm packages via stolen token |
Reference
This article relies on the fantastic contributions of the community and great researchers
- Acqua Sec Official Advisory
- Open Source Malware – Teampcp aquasec github compromise
- Socket.dev – Trivy Under Attack Again: GitHub Actions Tag Compromise
- Aikido Security – TeamPCP deploys CanisterWorm on NPM
- Aikido Security – CanisterWorm Gets Teeth: TeamPCP’s Kubernetes Wiper Targets Iran
How Phoenix Security Fixes What Actually Matters
Your team doesn’t have a finding problem. They have a fixing problem.
Most platforms hand you a list. Phoenix hands you a plan — ranked by real risk, mapped to the team that owns it, with a clear path to close it.
What remediation looks like with Phoenix:
- One backlog, not five. Findings from SAST, SCA, containers, and cloud — deduplicated, correlated, and surfaced in a single prioritized queue. No more reconciling lists across tools.
- Ownership that sticks. Team attribution and inheritance mean the right ticket goes to the right engineer, first time. No routing, no guessing, no back-and-forth.
- Campaigns that move the needle. Group related findings into targeted remediation campaigns. Track progress, measure closure rates, and report real reduction — not raw counts.
- AI that does the legwork, not the deciding. Phoenix’s Remediator agent drafts fixes, creates tickets, and opens PRs. Your team reviews, approves, and merges. Every fix is traceable.
Phoenix Security changes the game.

The results are clear:
- Bazaarvoice saved $6.3M in developer time and for teams removed critical in the first weeks of adoption
- ClearBank cut critical container vulnerabilities by 96–99% and reclaimed 4 hours per engineer per week.
- IAS saved an equivalent of 1.5M in development hours and reduced SCA-to-container noise by 82.4%
- Optimizely has been able to act on vulnerabilities sitting on the backlog.
The pattern is the same every time: teams that move from find and report to analyze and fix close more risk with less effort.

The results are clear:
- Bazaarvoice saved $6.3M in developer time and for teams removed critical in the first weeks of adoption
- ClearBank cut critical container vulnerabilities by 96–99% and reclaimed 4 hours per engineer per week.
- IAS saved an equivalent of 1.5M in development hours and reduced SCA-to-container noise by 82.4%
- Optimizely has been able to act on vulnerabilities sitting on the backlog.
Or learn how Phoenix Security slashed millions in wasted dev time for fintech, retail, and adtech leaders.