Contents
ToggleExecutive Summary
Aqua Security’s Trivy scanner has been compromised three times in under a month by a threat actor that still has access to the organisation’s infrastructure. What started as a stolen token from a misconfigured GitHub Actions workflow on February 28 has escalated into the most consequential CI/CD supply chain attack since the SolarWinds campaign — and as of March 23, it is not contained.
We previously covered a similar attack with TJ Actions, GitHub Action compromise, and an early article on the Action compromise. This is a deep dive
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 — tracked as TeamPCP — force-pushed 76 of 77 version tags in aquasecurity/trivy-action to inject a three-stage credential stealer into every CI/CD pipeline that referenced the action by tag. Over 10,000 GitHub workflow files pull trivy-action. The malicious payload dumped Runner.Worker process memory, swept 50+ filesystem credential paths, encrypted everything with AES-256 + RSA-4096, and exfiltrated it to attacker-controlled infrastructure. The legitimate Trivy scan ran after the stealer, so pipeline output looked completely normal.

Key Takeaways
- 76 of 77 trivy-action version tags were force-pushed to malicious commits — only v0.35.0 was protected by GitHub’s immutable releases
- Backdoored Trivy binaries v0.69.4, v0.69.5, and v0.69.6 were published to Docker Hub, GHCR, and ECR
- Root cause: incomplete credential rotation after the February 28 hackerbot-claw breach left the attacker with residual access
- The attacker still has access as of March 22 — Aqua confirmed “additional suspicious activity” and engaged Sygnia for incident response
- All 44 repositories in Aqua’s internal GitHub org (aquasec-com) were defaced, exposing proprietary code and infrastructure configs
- Stolen npm tokens triggered CanisterWorm, a self-propagating worm infecting 47+ packages via ICP canister dead-drop C2
- The Aqua Security GPG signing key (ID E9D0A3616276FA6C, in use since 2019) must be considered compromised — it was exposed during the release build
- Safe versions: trivy v0.69.2–v0.69.3, trivy-action v0.35.0 (SHA: 57a97c7e), setup-trivy v0.2.6
- Update 23/3/2026:
- v0.69.5 and v0.69.6 were Docker-only — no GitHub releases, no tags, pushed directly to the registry
- The
latesttag on Docker Hub pointed to v0.69.6 — everydocker pull aquasec/trivy:latestwas backdoored - mirror.gcr.io continued serving v0.69.4–v0.69.6 after Docker Hub removal — pipelines still pulling cached copies
- Docker Hub tags are not immutable — same structural weakness as Git tags, now exploited in the same campaign
- Third-party images that auto-rebuilt against affected versions are a secondary blast radius
- Downstream tool maintainers are already revoking tokens and adopting trusted publishing Socket confirmed IOCs in the binaries:
scan.aquasecurtiy.org,payload.enc,tpcp.tar.gz,tpcp-docs
TL;DR for Engineering Teams
What it is: Multi-stage supply chain compromise of Aqua Security’s Trivy scanner, trivy-action GitHub Action, setup-trivy GitHub Action, and Aqua’s internal GitHub organisation. Attributed to TeamPCP (DeadCatx3/PCPcat/ShellForce). No CVE assigned for the supply chain attack. GitHub Security Advisory: GHSA-cxm3-wv7p-598c and GHSA-69fq-xp46-6×23.
Where it bites: Any CI/CD pipeline referencing aquasecurity/trivy-action by version tag (76 of 77 tags poisoned, only v0.35.0 safe). Any pipeline pulling trivy binary v0.69.4, v0.69.5, or v0.69.6. Any pipeline using aquasecurity/setup-trivy (7 tags compromised). Old tags without a v prefix (0.0.1–0.34.2) should be considered compromised; new v-prefixed tags (v0.0.1–v0.34.2) point to original legitimate commits.
Why it matters: The stealer runs before the legitimate Trivy scan — pipeline output looks normal. It dumps Runner.Worker process memory for CI/CD secrets, sweeps 50+ filesystem credential paths on self-hosted runners (SSH, AWS/GCP/Azure, K8s, Docker, databases, crypto wallets), encrypts with AES-256 + RSA-4096, and exfiltrates silently. The attacker still has access to Aqua’s infrastructure as of March 22. Stolen credentials are being weaponised across the npm ecosystem via a self-propagating worm.
Patch status: Malicious artifacts removed from distribution channels (repeatedly — the attacker keeps returning). New v-prefixed tags published. Immutable releases enabled. Safe versions: trivy v0.69.2–v0.69.3, trivy-action v0.35.0, setup-trivy v0.2.6.
Immediate action: 1. Pin trivy-action to SHA 57a97c7e7821a5776cebc9bb87c984fa69cba8f1 — do not trust any version tag 2. Rotate ALL secrets if any pipeline ran a compromised component during any exposure window 3. Block IOCs: scan.aquasecurtiy[.]org, 45.148.10.212, tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io, and all Cloudflare Tunnel C2 domains 4. Search your GitHub org for tpcp-docs repositories (indicates successful exfiltration) 5. Check self-hosted runners and developer machines for /tmp/pglog, ~/.config/sysmon.py, pgmon.service 6. Monitor — this is an active campaign, not a contained incident
Vulnerability Overview
| Field | Value |
|---|---|
| Vendor | Aqua Security |
| Products | Trivy, trivy-action, setup-trivy, aquasec-com org |
| Vulnerability Type | Supply chain compromise / credential theft / org takeover |
| CWE | CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) |
| CVE | Not assigned (GHSA-cxm3-wv7p-598c, GHSA-69fq-xp46-6×23) |
| Attack Vector | Network (CI/CD pipeline execution) |
| Active Exploitation | Confirmed and ongoing as of March 23 |
| Attribution | TeamPCP (DeadCatx3 / PCPcat / PersyPCP / ShellForce / CipherForce) |
| Incident Response | Sygnia engaged by Aqua Security |
| Status | NOT CONTAINED — attacker demonstrated continued access March 22 |
What Is Trivy, and Why Does It Matter?
Aqua Security and the Trivy Project
Aqua Security is an Israeli-American cloud security company founded in 2015. Their open-source project Trivy — a portmanteau of “Tri” (triage/scan) and “V” (vulnerabilities) — has become the most widely adopted open-source vulnerability scanner in the container and cloud-native ecosystem. The tool’s GitHub repository has over 24,000 stars, and the trivy-action GitHub Action is referenced in more than 10,000 workflow files across GitHub.
Trivy matters to this story for a specific reason: it sits inside CI/CD pipelines. Every time a developer pushes code, opens a pull request, or builds a container image, Trivy runs. It has access to the pipeline’s secrets, the runner’s filesystem, and the network. That position of trust — scanning code for vulnerabilities from inside the build process — is exactly what the attacker exploited.
What Trivy Scans
Trivy is a comprehensive security scanner that detects known vulnerabilities (CVEs), generates Software Bills of Materials (SBOMs), analyses licences, and scans for misconfigurations and exposed secrets. It operates across multiple scan targets:
| Target | What Trivy Scans | Typical CI/CD Usage |
|---|---|---|
| Container images | OS packages (Alpine, Debian, RHEL, etc.) and language-specific dependencies (npm, pip, Maven, Go modules) baked into the image | Scan every image before pushing to a registry |
| Filesystem/repository | Source code dependencies, lockfiles, IaC templates (Terraform, CloudFormation, Kubernetes manifests) | Scan every PR for vulnerable dependencies |
| Kubernetes clusters | Running workloads, node configurations, RBAC policies, network policies | Audit live clusters on a schedule |
| AWS accounts | IAM misconfigurations, S3 bucket policies, security group rules | Cloud posture checks |
| SBOM files | Ingest CycloneDX or SPDX SBOMs generated by other tools and scan them for known vulnerabilities | Supply chain transparency |
Trivy works by downloading vulnerability databases (maintained by Aqua from sources like NVD, vendor advisories, and GitHub Security Advisories), matching them against the packages and dependencies it discovers in the scan target, and reporting the results. The database is rebuilt daily and distributed as an OCI artifact via GitHub Container Registry.
A typical Trivy command looks like this:
# Scan a container image for vulnerabilities
trivy image –severity HIGH,CRITICAL nginx:1.25
# Scan a filesystem for vulnerable dependencies
trivy fs –scanners vuln,secret .
# Generate an SBOM in CycloneDX format
trivy image –format cyclonedx –output sbom.json myapp:latest
How the GitHub Actions Work — and Why They Were the Perfect Target
Trivy is distributed through multiple channels: direct binary download, Homebrew, apt/rpm packages, Docker images, and GitHub Actions. The GitHub Actions are where the attack surface lies.
aquasecurity/trivy-action is the primary integration point. When a developer adds it to a workflow, it:
- Calls aquasecurity/setup-trivy to download and install the Trivy binary on the runner
- Executes entrypoint.sh — the script that configures scan parameters and runs Trivy
- Produces scan results in the specified format (table, JSON, SARIF for GitHub Security tab)
A typical workflow reference looks like this:
– name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: ‘myapp:${{ github.sha }}’
format: ‘sarif’
output: ‘trivy-results.sarif’
severity: ‘CRITICAL,HIGH’
aquasecurity/setup-trivy handles the binary installation. It downloads a specific Trivy version, verifies it, and places it on the runner’s PATH. Workflows can call it directly for manual control over caching and version pinning.
– name: Install Trivy
uses: aquasecurity/setup-trivy@v0.2.0
with:
cache: true
version: v0.69.3
The Trust Chain That Broke
Here is the critical point: when a workflow says uses: aquasecurity/trivy-action@0.33.1, GitHub resolves the tag 0.33.1 to a commit SHA at runtime. If that tag is force-pushed to point at a different commit, the workflow silently pulls the new code. The runner executes whatever entrypoint.sh contains — with full access to the pipeline’s secrets, the runner’s filesystem, network, and any credentials passed to the workflow.
The trust chain works like this:
Developer writes workflow → references trivy-action@0.33.1
↓
GitHub resolves tag 0.33.1 → commit SHA (mutable pointer)
↓
Runner downloads entrypoint.sh from that commit
↓
entrypoint.sh executes with full runner permissions
– Access to ${{ secrets.* }}
– Access to GITHUB_TOKEN
– Access to runner filesystem
– Access to runner network
– Access to /proc/[pid]/mem (on Linux with passwordless sudo)
In the legitimate case, entrypoint.sh configures scan parameters and runs Trivy. In the compromised case, entrypoint.sh runs a credential stealer for 105 lines and then runs the legitimate Trivy scan for the remaining 99 lines. The scan output looks normal. The pipeline passes. The developer never knows.
This is what makes CI/CD supply chain attacks uniquely dangerous compared to traditional application vulnerabilities: the malicious code runs with the privileges of the build system, not the application. It has access to every secret the pipeline can reach. And because the scanner itself is the payload, the very tool that is supposed to detect problems is the problem.
Over 10,000 workflow files on GitHub reference trivy-action. Every one of them trusts that the tag they pinned to still points to the code they reviewed when they set it up. That trust was broken on March 19 when TeamPCP rewrote 76 of 77 tags in under an hour.
Technical Anatomy

Root Cause: A Misconfigured Workflow, an Incomplete Rotation, and a Service Account That Bridges Two Orgs
The kill chain spans five weeks and at least three distinct attack phases. Each phase exploited a different consequence of the same root cause: a long-lived, over-privileged Personal Access Token for the aqua-bot service account (named ORG_REPO_TOKEN in GitHub Actions secrets) that had repo-scope — and likely admin-scope — access across the entire aquasecurity GitHub organisation. That token was used in at least 33 workflows.
Phase 1 — The Pwn Request (February 27-28)
Trivy’s repository had a pull_request_target workflow (apidiff.yaml) since October 2025. Boost Security’s poutine tool flagged it on November 29, 2025 — three months before exploitation. On February 27, attacker MegaGame10418 opened PR #10252 and closed it immediately. The workflow triggered anyway, checked out the attacker’s fork code with elevated permissions, and exfiltrated the aqua-bot PAT via a /proc/[pid]/mem memory dump technique to recv.hackmoltrepeat.com.
On February 28, an AI-powered bot called hackerbot-claw validated the stolen credentials by creating branches named 🤖🦞 on aquasecurity/trivy — the same emoji pattern used in the bot’s own PRs. The attacker then took over the repository: privatised it, renamed it, deleted releases between v0.27.0 and v0.69.1, and published a malicious VS Code extension to Open VSX.
Phase 2 — The Scanner Becomes the Weapon (March 19)
Aqua rotated credentials after the February incident. The rotation was not atomic. Subsequent investigation confirmed the attacker retained residual access via credentials missed during rotation.
Twenty days later, at 17:43:37 UTC on March 19, TeamPCP struck. Using the residual access, the attacker:
- Made imposter commits spoofing maintainer DmitriyLewen (on aquasecurity/trivy) and Guillermo Rauch / rauchg (on actions/checkout), with forged author names, emails, timestamps, and commit messages
- Pushed the v0.69.4 tag pointing to the poisoned commit. A v0.70.0 tag was briefly created first, then deleted — a botched first attempt that reveals the attacker initially tried a higher version before falling back to blend into the existing release series
- Compromised the aqua-bot service account and used it to push malicious workflows to tfsec, traceeshark, and trivy-action
- Exfiltrated additional Aqua credentials (GPG signing key, Docker Hub tokens, Twitter, Slack) to Cloudflare Tunnel C2 at plug-tab-protective-relay.trycloudflare.com
- Force-pushed 76 of 77 version tags in trivy-action to malicious commits containing the credential stealer
- Published 7 releases on setup-trivy in under 3 minutes (v0.1.0 through v0.2.5), rewriting every historical version’s release artifacts via direct API calls
The poisoned release pipeline ran the imposter checkout action, which downloaded backdoored Go source files and a linter config override from scan.aquasecurtiy[.]org (note the typosquat). GoReleaser built multi-platform binaries with –skip=validate to bypass build output validation. The binaries were pushed to Docker Hub, GHCR, ECR, and GitHub Releases.
Phase 3 — Continued Access and Internal Org Defacement (March 22)
On March 22, the attacker demonstrated that containment had failed again:
- Published malicious Docker images v0.69.5 and v0.69.6 to Docker Hub, both containing the C2 domain hardcoded in the binary (confirmed by StepSecurity via strings analysis)
- Defaced all 44 repositories in the aquasec-com GitHub organisation (Aqua’s internal org for proprietary code) in a 2-minute automated blitz — renaming every repo with a tpcp-docs- prefix and setting descriptions to “TeamPCP Owns Aqua Security”
- Published internal Aqua repositories publicly on GitHub, exposing source code for Tracee, internal Trivy forks, CI/CD pipelines, Kubernetes operators, and team knowledge bases
Forensic analysis by OpenSourceMalware traced this to the Argon-DevOps-Mgt service account — a bot account that bridges both the public aquasecurity org and the private aquasec-com org. The attacker tested the stolen token 7 hours before the defacement by creating and deleting a ghost branch (update-plugin-links-v0.218.2) on aquasecurity/trivy-plugin-aqua.
Aqua confirmed on March 23 that the incident is “part of an ongoing and evolving attack, with the threat actor reestablishing access.” They engaged Sygnia for forensic investigation and are working to validate that all access paths have been identified.
Update 23-Mar-2026
Expanded v0.69.5/6 details: pushed WITHOUT corresponding GitHub releases, latest tag pointed to v0.69.6, Socket binary analysis confirming IOCs (payload.enc, tpcp.tar.gz), mirror.gcr.io serving cached copies after removal, third-party auto-rebuilt images as secondary blast radius, unauthorized repos in public aquasecurity org
Why it matters:
The Docker-only publish (no GitHub release) is a new escalation pattern — attacker bypassed the release pipeline entirely
The Imposter Commit Technique
The attack on the release pipeline is technically notable. The attacker did not modify the Trivy source code directly. Instead, they:
- Created a dangling commit on actions/checkout (SHA 70379aad) that replaced the real Node.js checkout action with a composite action downloading backdoored Go source files from the typosquatted C2
- Created a second dangling commit on aquasecurity/trivy (SHA 1885610c) that pinned actions/checkout to the imposter SHA — which looks like a security best practice — added –skip=validate to GoReleaser, and scattered cosmetic quote-style changes throughout the YAML to bury the real modifications in diff noise
- Both commits were unsigned and used forged author metadata
This exploits a subtle GitHub platform behaviour: a uses: owner/repo@<hash> reference does not verify that the hash belongs to the named repository. Due to Git’s fork object sharing, any commit from any fork is reachable by SHA. The build fetched backdoor Go source files from the C2, compiled them into the Trivy binary alongside the legitimate scanner, and published the result through official channels.

The Three-Stage Credential Stealer
The payload injected into trivy-action’s entrypoint.sh (204 lines) and setup-trivy’s action.yaml operates in three stages:
Stage 1 — Collection
On GitHub-hosted Linux runners, the malware uses passwordless sudo to dump Runner.Worker process memory via /proc/[pid]/mem, parsing readable memory regions and searching for {“value”:”<secret>”,”isSecret”:true} — the internal format GitHub Actions uses to store secrets in the .NET process heap. Only secrets explicitly referenced in the workflow (via ${{ secrets.* }}) are loaded into memory, but GITHUB_TOKEN is always available.
On self-hosted runners, a base64-encoded Python filesystem harvester (self-identified as “TeamPCP Cloud stealer”) sweeps 50+ paths:
| Category | Targets |
|---|---|
| SSH | ~/.ssh/id_rsa, id_ed25519, id_ecdsa, authorized_keys, known_hosts; /etc/ssh/ssh_host_*_key |
| AWS | ~/.aws/credentials, config, EC2 IMDS (169.254.169.254), ECS container creds (169.254.170.2) |
| GCP | ~/.config/gcloud/*, application_default_credentials.json |
| 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 |
| Database | ~/.pgpass, ~/.my.cnf, ~/.mongorc.js, redis.conf |
| CI/CD configs | terraform.tfvars, terraform.tfstate, .gitlab-ci.yml, Jenkinsfile, ansible.cfg |
| Package managers | ~/.npmrc, ~/.vault-token, ~/.netrc |
| TLS private keys | .key, .pem, .p12, .pfx; /etc/ssl/private/, /etc/letsencrypt/ |
| Crypto wallets | Bitcoin, Ethereum, Solana keypairs, Cardano signing keys, Ledger files |
| Environment files | .env, .env.local, .env.production across /opt, /srv, /var/www, /app, /data, /tmp |
Stage 2 — Encryption
Collected data is encrypted using 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 are 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 the C2 returns non-2xx and INPUT_GITHUB_PAT is available, the malware creates a public repository called tpcp-docs on the victim’s own GitHub account and uploads the bundle as a release asset with a timestamped tag. The data sits on GitHub’s own infrastructure — unlikely to be blocked by corporate firewalls.
The backdoored binary (v0.69.4, v0.69.5, v0.69.6)
The compromised Trivy binaries run the legitimate scanner in parallel with a malicious process. On developer machines (detected by checking GITHUB_ACTIONS != “true”), the malware writes a Python dropper to ~/.config/systemd/user/sysmon.py. After a 5-minute sleep, it polls an ICP canister (tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io) every ~50 minutes. The canister returns a URL pointing to the next-stage payload. A youtube.com URL acts as a kill switch (dormant state). As of March 22, the canister was actively serving kamikaze.sh, an iteratively developed payload, before being taken offline for policy violations.
Core Behaviours
| Behaviour | Detail |
|---|---|
| Process memory dumping | Reads /proc/[pid]/mem of Runner.Worker to extract GitHub Actions secrets |
| Filesystem credential sweep | 50+ paths across SSH, cloud, K8s, Docker, databases, crypto, CI/CD configs |
| Hybrid encryption | AES-256-CBC + RSA-4096 wrapping — only attacker’s private key can decrypt |
| Dual exfiltration | Primary C2 (typosquat domain) + fallback (victim’s own GitHub account) |
| Imposter commits | Forged author metadata, dangling commits not attached to any branch |
| Tag poisoning | Force-pushed existing version tags — no notifications, no diff, no alerts |
| Counter-incident response | Deleted disclosure discussion, flooded replacement with 47 spam bots in 12 seconds |
| Persistence | systemd user service (pgmon/sysmon.py) on developer machines, ICP canister dead-drop C2 |
| Self-propagating worm | CanisterWorm harvests npm tokens during postinstall, infects downstream packages automatically |
| Org-level takeover | Defaced 44 internal repos via compromised service account bridging two orgs |
CanisterWorm: The npm Cascade
Stolen npm tokens from the Trivy compromise triggered CanisterWorm, a self-propagating worm detected by Aikido Security on March 20 at 20:45 UTC. The worm uses an ICP (Internet Computer Protocol) canister as a decentralised dead-drop resolver for C2 — the first documented abuse of this technique.
Compromised packages: 28 in @emilgroup, 16 in @opengov, plus @teale.io/eslint-config, @airtm/uuid-base32, @pypestream/floating-ui-dom.
Three-stage architecture: 1. Node.js postinstall hook decodes a Python backdoor, writes it to ~/.local/share/pgmon/service.py, creates a systemd user service with Restart=always 2. Python backdoor polls the ICP canister every ~50 minutes; downloads and executes whatever URL the canister returns 3. deploy.js worm enumerates every package a stolen npm token can publish, bumps patch versions, preserves the original README, and publishes with –tag latest
A self-propagating variant (Wave 3) adds findNpmTokens() during postinstall — harvesting tokens from ~/.npmrc, environment variables, and npm config, then spawning the worm as a detached process. Every infected developer or CI pipeline becomes a propagation vector.
The worm appears to be entirely “vibe-coded” using an AI tool — no obfuscation, emoji-prefixed logging, self-explanatory variable names. Aikido Security reported that the attacker left a message in the source code addressing their researcher by name.
As of March 22, TeamPCP has also deployed Kubernetes wipers targeting infrastructure in Iran, as reported by Aikido Security.
Affected Versions
| Component | Vulnerable | Safe Version | Exposure Window (UTC) | Duration |
|---|---|---|---|---|
| trivy-action | All old tags 0.0.1–0.34.2 (no v prefix) | v0.35.0 (SHA: 57a97c7e). New v-prefixed tags (v0.0.1–v0.34.2) point to original commits. | Mar 19 ~17:43 to Mar 20 ~05:40 | ~12 hours |
| setup-trivy | All versions v0.1.0–v0.2.5 | v0.2.6 | Mar 19 ~17:43 to ~21:44 | ~4 hours |
| trivy binary | v0.69.4 | v0.69.2, v0.69.3 | Mar 19 18:22 to ~21:42 | ~3 hours |
| trivy Docker Hub | v0.69.4, v0.69.5, v0.69.6 | v0.69.3 or earlier; images by digest | v0.69.4: Mar 19; v0.69.5/6: Mar 22 ~16:00 | Ongoing |
| trivy GHCR | v0.69.4 | v0.69.3 | Mar 19 ~18:25 to removal | ~3 hours |
| trivy ECR | v0.69.4 | v0.69.3 | Mar 19 ~18:25 to removal | ~3 hours |
| Homebrew | Picked up v0.69.4 (source build, binary NOT backdoored) | Rolled back to v0.69.3 | N/A | Not backdoored |
| apt/rpm | Not affected | N/A | N/A | N/A |
| npm packages | 47+ packages (@emilgroup, @opengov, @teale.io, etc.) | Roll back to pre-Mar 20 versions | Mar 20 20:45 onwards | Ongoing |
| aquasec-com org | All 44 repos defaced and exposed | N/A — internal remediation required | Mar 22 20:31–20:32 | Ongoing |
Critical note on tag naming: Old version tags without a v prefix (0.0.1–0.34.2) should be considered compromised. New tags have been published with a v prefix (v0.0.1–v0.34.2) pointing to the original legitimate commits. Pin to the full SHA hash regardless.
Compromised credentials to assume exposed: – GITHUB_TOKEN (always available) – Any secret referenced via ${{ secrets.* }} in the workflow – Cloud provider credentials (AWS, GCP, Azure) – Docker registry tokens – SSH keys and Kubernetes tokens – npm publish tokens (treat as actively weaponised) – The Aqua Security GPG signing key (ID E9D0A3616276FA6C, in use since 2019, written to disk in cleartext during the release build — must be considered compromised)
Discovery and Disclosure Timeline of Trivy Supply Chain attack
| 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 27, 2026 | MegaGame10418 opens and closes PR #10252, triggering workflow exfiltration |
| February 28, 2026 | hackerbot-claw validates stolen aqua-bot PAT (🤖🦞 branches). Trivy repo privatised, releases deleted, malicious VS Code extension pushed |
| February 28, 06:08 UTC | Credential rotation begins (vuln-list-update failures observed through March 1) |
| March 1, 2026 | Aqua Security discloses incident in Discussion #10265, 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 tag via poisoned commit. v0.70.0 tag briefly created then deleted (botched first attempt) |
| March 19, 17:51 UTC | aqua-bot deletes v0.70.0 tag |
| March 19, 18:25 UTC | Poisoned release published — backdoored binaries pushed to Docker Hub, GHCR, ECR, GitHub Releases |
| March 19, ~18:45 UTC | 76 trivy-action tags and 7 setup-trivy tags force-pushed to malicious commits |
| March 19, ~19:15 UTC | First detection (Paul McCarty). Socket generates 182 threat feed entries. |
| March 19, 21:07 UTC | Trivy maintainer nikpivkin begins deleting compromised setup-trivy tags |
| March 19, 21:43 UTC | Clean setup-trivy v0.2.6 published |
| March 19, 23:05 UTC | Homebrew emergency downgrade PR filed by William Woodruff (Trail of Bits) |
| March 19, 23:13 UTC | Trivy maintainer knqyf263 deletes v0.69.4 tag |
| March 19, 23:56 UTC | bored-engineer confirms compromise, shares IOCs from deleted discussion |
| March 20, 00:01 UTC | Spam bots flood Discussion #10420 (47 accounts, 12-second window) |
| March 20, ~05:40 UTC | Malicious trivy-action artifacts removed |
| March 20, 20:45 UTC | CanisterWorm Wave 1 detected by Aikido — 28 @emilgroup packages compromised |
| March 20, 21:16 UTC | CanisterWorm Wave 3 — self-propagating variant with findNpmTokens() |
| March 22, 13:24 UTC | Argon-DevOps-Mgt creates/deletes ghost branch on trivy-plugin-aqua (token test) |
| March 22, ~16:00 UTC | Backdoored trivy Docker images v0.69.5 and v0.69.6 published to Docker Hub |
| March 22, 20:31 UTC | All 44 aquasec-com repos defaced in 2-minute automated blitz |
| March 22, 21:31 UTC | ICP canister made “Unavailable Due to Policy Violation” |
| March 23, 02:00 UTC | Aqua confirms “additional suspicious activity” on March 22, engages Sygnia, confirms ongoing attack |
| March 23 , 09:00 UTC | “no corresponding GitHub releases”, “latest tag pointed to v0.69.6″, “Socket confirms IOCs in binaries”. Added new March 22–23 entry for mirror caching persistence. |
Determine If You’re Affected by Trivy Supply Chain attack
Exposure check (do all of these)
- Search workflow files for Trivy references:
grep -r “aquasecurity/trivy-action” .github/workflows/
grep -r “aquasecurity/setup-trivy” .github/workflows/
- Check if any workflow used a version tag (not SHA) during any exposure window. Review the “Run Trivy” step of trivy-action and the “Setup environment” step of setup-trivy in your workflow run logs.
- Check for the backdoored binary. Search container registries, artifact caches, and CI build logs for trivy v0.69.4, v0.69.5, or v0.69.6.
docker images | grep trivy | grep -E “0.69.[456]”
- Check for exfiltration artifacts. Search your GitHub org for repositories named tpcp-docs:
gh repo list YOUR_ORG –json name -q ‘.[].name’ | grep tpcp-docs
- Check self-hosted runners and developer machines for persistence:
ls -la ~/.config/sysmon.py 2>/dev/null
ls -la ~/.config/systemd/user/pgmon.service 2>/dev/null
ls -la ~/.local/share/pgmon/service.py 2>/dev/null
ls -la /tmp/pglog /tmp/.pg_state 2>/dev/null
systemctl –user status pgmon.service 2>/dev/null
systemctl –user status sysmon 2>/dev/null
- Check network logs for C2 indicators:
scan.aquasecurtiy[.]org
45.148.10.212
plug-tab-protective-relay.trycloudflare.com
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io
championships-peoples-point-cassette.trycloudflare.com
investigation-launches-hearings-copying.trycloudflare.com
souls-entire-defined-routes.trycloudflare.com
- Check npm lockfiles for compromised packages from @emilgroup, @opengov, @teale.io/eslint-config, @airtm/uuid-base32, or @pypestream/floating-ui-dom with post-March 20 patch bumps.
- Run StepSecurity’s trivy-compromise-scanner for automated log auditing across repos or entire organisations: https://github.com/step-security/trivy-compromise-scanner
Which secrets were exposed?
Only secrets explicitly referenced in the workflow (via ${{ secrets.* }}) are loaded into Runner.Worker memory. GITHUB_TOKEN is always accessible. If your workflow uses secrets: inherit for reusable workflows, only secrets actually referenced in the called workflow are extractable — not all org/repo secrets.
That said, treat every credential accessible to the affected pipeline as compromised. Lateral movement is expected: attackers have been observed creating new workflows in new branches specifically to exfiltrate secrets from additional repositories after gaining initial access.
Temporary Protections
If you cannot complete full remediation immediately:
- Pin trivy-action to the safe SHA:
# SAFE — immutable commit reference
– uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
# DO NOT USE version tags — even v-prefixed tags, until you verify the SHA
- Pin setup-trivy to v0.2.6 (the only uncompromised version)
- Block all IOC domains and IPs at the network perimeter — see the full IOC table below
- Restrict outbound network access from CI/CD runners to known-good destinations. If using StepSecurity Harden-Runner, enable block mode to prevent connections to unknown domains.
- Enable GitHub’s required workflows feature to enforce SHA-pinned action references org-wide
- Monitor GitHub audit logs for repository creation events matching the tpcp-docs pattern
- Stop pulling trivy Docker images by tag — use image digests until the situation stabilises
How TeamPCP Exploits This — and What Comes Next
The credential harvest from this campaign gives TeamPCP an expanding attack surface:
Immediate exploitation (already observed): – Stolen npm tokens weaponised via CanisterWorm to infect 47+ downstream packages – Stolen Aqua credentials used to publish additional backdoored images (v0.69.5, v0.69.6) – Stolen service account tokens used to deface internal org (44 repos exposed) – Counter-IR tactics: deleting disclosure threads, flooding discussions with spam bots – Kubernetes wipers targeting infrastructure in Iran
Probable next steps (based on TeamPCP’s documented TTPs): – Stolen cloud credentials (AWS/GCP/Azure) used for cryptomining, data theft, or ransomware staging – Compromised SSH keys used for lateral movement into production environments – The GPG signing key (E9D0A3616276FA6C) could be used to sign malicious RPM packages that appear to come from Aqua Security – Stolen Docker Hub credentials could be used to publish further backdoored images under the official aquasec namespace – The ICP canister C2 can be re-armed at any time — even though the current one was taken down, the attacker can deploy a new canister. Decentralised C2 infrastructure is resilient to takedowns by design. – Any organisation whose secrets were exfiltrated from a CI/CD pipeline now faces secondary compromise risk: the attacker has their cloud keys, their registry tokens, their npm publish credentials
What makes this campaign structurally different: – The attacker weaponised a security tool — the very scanner organisations rely on to find vulnerabilities became the credential stealer – Mutable Git tags are a platform-level weakness, not a misconfiguration — GitHub does not notify anyone when a tag is force-pushed – The ICP canister dead-drop is a new C2 pattern that is resistant to traditional domain takedowns – The self-propagating CanisterWorm turns each compromised developer into an unwitting distribution vector – Three compromises in under a month with the attacker re-establishing access each time demonstrates that credential rotation is failing at Aqua
Update March 23 2026
Docker Hub bypass (no release pipeline), latest tag redirection, mirror caching extending attack shelf life, third-party auto-rebuild as silent propagation. Added to “structurally different”: Docker tags share Git tags’ mutability weakness, mirror caching extends shelf life.
Why it matters: These are active exploitation paths, not theoretical — the latest tag redirection and mirror caching are confirmed
Remediation Guidance
Immediate Actions
- Pin trivy-action to SHA 57a97c7e7821a5776cebc9bb87c984fa69cba8f1 — do not use any version tag
- Pin setup-trivy to v0.2.6
- Ensure trivy binary is v0.69.2 or v0.69.3 — remove v0.69.4, v0.69.5, v0.69.6 from all registries and caches
- Rotate ALL pipeline secrets if any workflow ran a compromised component during any exposure window. This includes: GitHub tokens, cloud credentials, SSH keys, Docker registry tokens, Kubernetes tokens, database passwords, npm publish tokens
- Block all IOC domains and IPs at network perimeter and in CI/CD runner egress rules
- Search for tpcp-docs repositories in your GitHub org — if found, assume full credential exfiltration
- Kill and remove persistence mechanisms on self-hosted runners and dev machines:
systemctl –user stop pgmon.service sysmon 2>/dev/null
systemctl –user disable pgmon.service sysmon 2>/dev/null
rm -f ~/.config/systemd/user/pgmon.service
rm -rf ~/.local/share/pgmon/
rm -f ~/.config/sysmon.py
rm -f /tmp/pglog /tmp/.pg_state
systemctl –user daemon-reload
- Audit npm dependencies for compromised @emilgroup, @opengov, @teale.io, @airtm, @pypestream packages — roll back to pre-March 20 versions
Long-Term Hardening
- Pin ALL GitHub Actions to full SHA hashes — automate with zizmor, pinact, StepSecurity secure-repo, or Renovate’s pinGitHubActionDigests
- Audit every pull_request_target workflow — if it checks out PR head code, it is a Pwn Request waiting to happen
- Minimise token permissions — replace org-scoped PATs with fine-grained tokens scoped to individual repos
- Enforce commit signing on release workflows — both imposter commits in this attack were unsigned
- Enable immutable releases on all repositories publishing GitHub Actions
- Restrict outbound network from CI/CD runners to allowlisted destinations
- Use StepSecurity Harden-Runner or equivalent for runtime anomaly detection on GitHub Actions runners
- Monitor for secondary compromise — attackers with stolen credentials are observed creating new workflows in new branches to exfiltrate additional secrets. This is not a one-time rotation.
- Added 23 March: Docker digest pinning (not tag), registry mirror audit/eviction policies, trusted publishing for downstream package maintainers
Detection Guidance
IOCs — Network
| Indicator | Type | Action |
|---|---|---|
| scan.aquasecurtiy[.]org | Exfiltration domain (typosquat) | Block at perimeter, hunt DNS logs |
| 45.148.10.212 | Exfiltration IP (TECHOFF SRV LIMITED, Amsterdam) | Block at firewall, hunt NetFlow |
| plug-tab-protective-relay.trycloudflare.com | Cloudflare Tunnel C2 | Block, search DNS logs |
| tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io | ICP canister C2 dead-drop | Block egress to icp0.io — standard domain takedowns do not apply |
| championships-peoples-point-cassette.trycloudflare.com | Secondary Cloudflare C2 | Block |
| investigation-launches-hearings-copying.trycloudflare.com | Secondary Cloudflare C2 | Block |
| souls-entire-defined-routes.trycloudflare.com | Secondary Cloudflare C2 | Block |
| recv.hackmoltrepeat.com | Initial C2 (February breach) | Block |
IOCs — File Hashes
| Hash (SHA256) | Description |
|---|---|
| 18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a | Malicious entrypoint.sh |
| 822dd269ec10459572dfaaefe163dae693c344249a0161953f0d5cdd110bd2a0 | Compromised trivy v0.69.4 Linux-64bit |
| 6328a34b26a63423b555a61f89a6a0525a534e9c88584c815d937910f1ddd538 | Compromised trivy v0.69.4 macOS-ARM64 |
| 0880819ef821cff918960a39c1c1aada55a5593c61c608ea9215da858a86e349 | Compromised trivy v0.69.4 Windows-64bit |
| 887e1f5b5b50162a60bd03b66269e0ae545d0aef0583c1c5b00972152ad7e073 | Compromised trivy v0.69.4 FreeBSD-64bit |
| 3350da5e45f99ec86eec5cb87efe84241d82a019822e4270facb818519778d12 | Poisoned v0.69.4.tar.gz |
| 3ca5fa62932273dd7eef3b6ec762625da42304ebb8f13e4be9fdd61545ca1773 | Known-good v0.69.3.tar.gz |
IOCs — Filesystem
| Path | Description |
|---|---|
| ~/.config/sysmon.py | Trivy binary dropper |
| ~/.config/systemd/user/pgmon.service | CanisterWorm persistence |
| ~/.local/share/pgmon/service.py | CanisterWorm backdoor |
| /tmp/pglog | CanisterWorm payload |
| /tmp/.pg_state | CanisterWorm state tracker |
IOCs — GitHub
| Indicator | Description |
|---|---|
| Repository named tpcp-docs | Fallback exfiltration via victim’s own GitHub account |
| Repo prefix tpcp-docs- | aquasec-com org defacement pattern |
| Description “TeamPCP Owns Aqua Security” | Defacement signature |
Forgery Detection Indicators
- Unsigned commits on releases that were previously GPG-signed by GitHub’s web-flow
- Impossible timestamps: commit dates from 2021-2024 with parent commits from March 2026
- Single-file modifications: each malicious trivy-action commit only modifies entrypoint.sh (tree is master HEAD)
- “0 commits to master since this release” on GitHub release pages for tags that should show hundreds
Phoenix Security Recommendations
The Trivy compromise is a case study in why vulnerability management cannot stop at scanning known CVEs. This attack had no CVE, no CVSS score, no NVD entry. The scanner itself was the attack vector. Traditional security tooling 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. When 10,000+ workflows reference a single action, you need automated discovery — not a grep.
Reachability analysis: Not every pipeline that references trivy-action actually executed during the exposure windows. Phoenix maps which instances ran compromised code versus which merely declared the action in a workflow file. This is the difference between rotating secrets for 10,000 repos and rotating secrets for the 45 that StepSecurity confirmed were actually affected.
Contextual deduplication: Organisations using Trivy across dozens of repositories get a single prioritised backlog rather than duplicate alerts per scanner per repo. One campaign. One response track.
Remediation campaigns: Create a campaign in Phoenix to track the full response — SHA migration, secret rotation per repo, IOC blocking confirmations, persistence removal on self-hosted runners — with ownership assigned per team.
Ownership attribution: In an organisation with hundreds of repositories using trivy-action, knowing who owns each pipeline is the difference between a coordinated response and weeks of email chains. Phoenix maps every affected workflow to the team that owns it.
Phoenix Security correlates compromised components with runtime workloads, identifies exposed pipelines via attack surface management, and assigns remediation ownership — converting a supply chain campaign into a measurable, owned response.
For more on CI/CD supply chain defence, see our analysis of the tj-actions compromise from 2025: https://phoenix.security/tj-actions-compromise/
References
- Aqua Security — “Update: Ongoing Investigation and Additional Activity” (March 23, 2026) —
- GitHub Security Advisory — GHSA-cxm3-wv7p-598c
- Wiz Research — “Trivy Compromised: Everything You Need to Know”
- Boost Security — “20 Days Later: Trivy Compromise, Act II”
- Socket Security — “Trivy Under Attack Again: Widespread GitHub Actions Tag Compromise”
- CrowdStrike — “From Scanner to Stealer: Inside the trivy-action Supply Chain Compromise.”
- StepSecurity — “Trivy Compromised a Second Time”
- Aikido Security — “TeamPCP deploys CanisterWorm on NPM following Trivy compromise.”
- OpenSourceMalware — “TeamPCP Defaces Aqua Security’s Internal GitHub Org — 44 Repos Exposed”
- Socket Security — “CanisterWorm: npm Publisher Compromise Deploys Backdoor.”
- aquasecurity/trivy-action Release v0.35.0
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.