---
title: "Trivy Supply Chain Compromise: What Happened, What Was Stolen, and How to Respond"
description: "A consolidated technical reference for the TeamPCP supply chain attack against Aqua Security's Trivy scanner. Covers the full attack chain from AI-assisted initial breach through credential theft, GitHub Actions tag poisoning, a self-propagating npm worm, and Kubernetes escape payloads. Includes IOCs, detection queries, and remediation steps."
url: "https://safedep.io/blog/trivy-teampcp-supply-chain-compromise"
author: "SafeDep Team"
published: "2026-03-23"
---

# Trivy Supply Chain Compromise: What Happened, What Was Stolen, and How to Respond

## TL;DR

Between February 27 and March 22, 2026, the threat actor TeamPCP compromised Aqua Security's Trivy vulnerability scanner, its GitHub Actions, and over 60 npm packages in a multi-phase open source software supply chain attack. This post consolidates the timeline, techniques, IOCs, and remediation guidance from multiple vendor reports into a single reference.

**If you used Trivy, trivy-action, or setup-trivy between March 19-22, 2026, assume the following impact:**

- All secrets accessible to the workflow (GitHub tokens, cloud credentials, SSH keys, Docker registry tokens) were exfiltrated, encrypted with RSA-4096, and sent to attacker infrastructure
- CI/CD runner memory was scraped for every secret marked `isSecret:true` in GitHub Actions
- Developer machines that ran Trivy v0.69.4 outside CI have a persistent systemd backdoor polling for arbitrary payloads
- npm tokens stolen from CI runners were used to publish malicious versions of 64+ packages within 60 seconds
- Kubernetes clusters accessible from compromised runners may have attacker DaemonSets with host-level access

## How it Started: An AI Bot and a Misconfigured Workflow

On February 27, 2026, an autonomous GitHub bot called `hackerbot-claw` [opened PR #10252](https://awesomeagents.ai/news/hackerbot-claw-trivy-github-actions-compromise/) against the Trivy repository. The PR was immediately closed, but that didn't matter. Trivy's "API Diff Check" workflow used `pull_request_target`, which runs with repository-level secrets while executing code from the attacker's fork. This is the well-documented ["Pwn Request" pattern](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).

The bot [exfiltrated a Personal Access Token](https://blog.hunterstrategy.net/hackerbot-claw-github-actions-exploitation-campaign/) to `recv.hackmoltrepeat[.]com`. By February 28, the attacker had full repository control: they privatized the repo, deleted all 178 GitHub releases (v0.27.0 through v0.69.1), and published a malicious VS Code extension to Open VSIX.

Aqua Security responded and [rotated credentials](https://github.com/aquasecurity/trivy/discussions/10425). But the rotation wasn't atomic. The attacker observed or intercepted the refreshed tokens, maintaining access through the `aqua-bot` service account.

## The Second Compromise: v0.69.4 and Tag Poisoning

On March 19 at 17:43 UTC, `aqua-bot` [pushed a malicious v0.69.4 tag](https://www.stepsecurity.io/blog/trivy-compromised-a-second-time---malicious-v0-69-4-release) to Trivy, triggering the automated release pipeline. The malicious binary was distributed across GitHub Releases, GHCR, Docker Hub, ECR Public, deb/rpm repositories, and `get.trivy.dev` before being removed around 21:36 UTC, a roughly 3-hour exposure window.

The malicious Trivy binary [ran the legitimate scanner alongside the payload](https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack), masking the intrusion from the user's perspective. On developer machines (detected by `GITHUB_ACTIONS != "true"`), it installed a persistent backdoor as a systemd user service at `~/.config/systemd/user/sysmon.py` that polled an ICP canister for arbitrary payloads every five minutes.

### Tag Poisoning

The bigger blast radius came from GitHub Actions. Within hours, `aqua-bot` [force-pushed 75 of 76 `trivy-action` tags](https://socket.dev/blog/trivy-under-attack-again-github-actions-compromise) (0.0.1 through 0.34.2) and all 7 `setup-trivy` tags to malicious commits. Over 10,000 GitHub workflows referenced `trivy-action`. Any workflow using `aquasecurity/trivy-action@v0.XX.X` silently resolved to the attacker's code.

The technique was [crafted to avoid detection](https://socket.dev/blog/trivy-under-attack-again-github-actions-compromise): the attacker started from master HEAD, replaced only `entrypoint.sh`, cloned the original commit's metadata (author, email, timestamps, message), and force-pushed the tag. The spoofed commits even impersonated well-known developers: "rauchg" (Guillermo Rauch, Vercel CEO) for the `actions/checkout` injection, and "DmitriyLewen" (Aqua maintainer) for the Trivy commit.

Detection signals: missing GPG signatures on commits that were originally web-signed, impossible chronology (commits dated 2021 with parents from March 2026), and only one file changed per commit where originals touched multiple files.

### What the Credential Stealer Did

The malicious `entrypoint.sh` contained a three-stage payload in 204 lines:

1. **Collection**: On GitHub-hosted runners, the malware escalated to root via passwordless `sudo`, located `Runner.Worker` processes, and [scraped their memory via `/proc/<pid>/mem`](https://www.stepsecurity.io/blog/trivy-compromised-a-second-time---malicious-v0-69-4-release), searching for the JSON pattern `{"value":"<secret>","isSecret":true}` where Actions stores masked secrets. It also swept 50+ filesystem paths for SSH keys, AWS/GCP/Azure credentials, Kubernetes tokens, Docker configs, and cryptocurrency wallets.

2. **Encryption**: Harvested data was encrypted using AES-256-CBC with RSA-4096 hybrid encryption (OAEP padding), bundled as `tpcp.tar.gz`. Only the attacker's private key can decrypt.

3. **Exfiltration**: Primary channel was HTTPS POST to `scan.aquasecurtiy[.]org` (typosquat of aquasecurity.org, resolving to `45.148.10.212`). Fallback: if `INPUT_GITHUB_PAT` was available, the malware [created a public `tpcp-docs` repository](https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack) on the victim's GitHub account and uploaded the encrypted bundle as a release asset.

The legitimate Trivy scan ran after the malware (lines 106-204), so users saw normal scan output.

## CanisterWorm: npm Goes Self-Propagating

Less than 24 hours after the Trivy compromise, [stolen npm tokens from CI runners were weaponized](https://www.aikido.dev/blog/teampcp-deploys-worm-npm-trivy-compromise). On March 20 at 20:45 UTC, TeamPCP deployed CanisterWorm, a self-propagating npm worm that represents the first observed use of Internet Computer Protocol (ICP) canisters as malware C2 infrastructure.

The worm's propagation logic:

- Harvest npm tokens from `~/.npmrc`, project `.npmrc`, environment variables
- Authenticate via `/-/whoami`, enumerate publishable packages via maintainer search
- Bump patch version, preserve the original README, publish with `--access public --tag latest`
- Restore original files in a finally block

28 `@emilgroup` packages were compromised in under 60 seconds. The worm then spread to `@opengov` (16 packages), `@teale.io/eslint-config`, `@airtm/uuid-base32`, and `@pypestream/floating-ui-dom`. By March 21, [Socket reported 135 malicious artifacts across 64+ unique packages](https://socket.dev/blog/canisterworm-npm-publisher-compromise-deploys-backdoor-across-29-packages).

The payload architecture was a three-stage chain: a Node.js postinstall loader decoded a base64 Python script, wrote it to `~/.local/share/pgmon/service.py`, and installed it as a systemd user service (`pgmon.service`) masquerading as PostgreSQL monitoring. The Python backdoor slept 5 minutes (sandbox evasion), then polled the ICP canister `tdtqy-oyaaa-aaaae-af2dq-cai` every 50 minutes for a URL pointing to the current payload. A kill switch: if the URL contained "youtube.com", the implant went dormant.

## Rapid Payload Iteration: kamikaze.sh

On March 22, TeamPCP [rotated through six payload versions in approximately five hours](https://ramimac.me/trivy-teampcp/) via the ICP canister, each one picked up by every infected machine on its next poll:

| Time (UTC) | Version | Capability                                                                              |
| ---------- | ------- | --------------------------------------------------------------------------------------- |
| ~11:45     | v1      | Monolithic K8s DaemonSet with host escape, systemd persistence, **Iran-targeted wiper** |
| 12:45      | v2      | Modular loader fetching `kube.py`, C2 rotated to Cloudflare tunnel                      |
| 13:00      | v3      | Pure SSH/Docker worm (scans local /24 for ports 22, 2375)                               |
| 13:25      | v3.1    | Module split: `kube.py` (broken K8s) + `prop.py` (SSH/Docker worm)                      |
| 14:56      | v3.2    | Working ICP backdoor, kubectl re-enabled, new Cloudflare tunnel                         |
| 16:15      | v3.3    | **WAV steganography**: payloads hidden in audio files (`bg_kube.wav`, `bg_prop.wav`)    |

The Iran-targeted wiper ([discovered by ramimac](https://ramimac.me/trivy-teampcp/)) deployed a separate DaemonSet that detected `Asia/Tehran` timezone or `fa_IR` locale and executed `rm -rf / --no-preserve-root`. This is the only destructive, non-theft component in the campaign.

By 16:00 UTC, TeamPCP also pushed malicious Docker Hub images (`v0.69.5`, `v0.69.6`) directly, bypassing the GitHub release pipeline. At 20:31-20:32 UTC, [44 internal Aqua repositories in the `aquasec-com` organization were defaced](https://ramimac.me/trivy-teampcp/). The ICP canister was denylisted at 21:31 UTC.

## IOCs for Security Teams

### Network Indicators

| Indicator                                                     | Role                                        |
| ------------------------------------------------------------- | ------------------------------------------- |
| `scan.aquasecurtiy[.]org` / `45.148.10.212`                   | Primary C2 (TECHOFF SRV LIMITED, Amsterdam) |
| `tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io`                   | ICP canister dead drop (denylisted 03/22)   |
| `plug-tab-protective-relay.trycloudflare[.]com`               | Cloudflare tunnel (credential exfil)        |
| `investigation-launches-hearings-copying.trycloudflare[.]com` | Cloudflare tunnel (kamikaze v2)             |
| `championships-peoples-point-cassette.trycloudflare[.]com`    | Cloudflare tunnel (v3/v3.1)                 |
| `create-sensitivity-grad-sequence.trycloudflare[.]com`        | Cloudflare tunnel (v3.2/v3.3)               |
| `recv.hackmoltrepeat[.]com`                                   | Phase 1 PAT exfiltration                    |

### Affected Artifacts

| Component                          | Affected Versions                                    | Exposure  |
| ---------------------------------- | ---------------------------------------------------- | --------- |
| Trivy binary                       | v0.69.4 (all registries), Docker Hub v0.69.5/v0.69.6 | ~3 hours  |
| `trivy-action`                     | Tags 0.0.1-0.34.2 (75 tags)                          | ~12 hours |
| `setup-trivy`                      | Tags v0.2.0-v0.2.5                                   | ~4 hours  |
| npm (@emilgroup, @opengov, others) | 135+ artifacts, 64+ packages                         | Varies    |

### Malicious Container Digests (v0.69.4)

Per [Aqua's advisory](https://github.com/aquasecurity/trivy/discussions/10425):

| Architecture | Digest                                                                    |
| ------------ | ------------------------------------------------------------------------- |
| Generic      | `sha256:27f446230c60bbf0b70e008db798bd4f33b7826f9f76f756606f5417100beef3` |
| amd64        | `sha256:12c702212dee1cbec9471e9261501a3335963321fe76e60e5a715b5acd3c40a2` |
| arm64        | `sha256:2d7cee41048988eec27615412e7c6e2e21046f2b5faa888c24e11ca6764058ed` |

### Filesystem Indicators

| Path                                   | Component                        |
| -------------------------------------- | -------------------------------- |
| `~/.config/systemd/user/sysmon.py`     | Trivy developer machine backdoor |
| `~/.config/systemd/user/pgmon.service` | CanisterWorm persistence         |
| `~/.local/share/pgmon/service.py`      | CanisterWorm Python backdoor     |
| `/tmp/pglog`                           | Payload staging                  |
| `/tmp/.pg_state`                       | State tracking                   |

### Hunt Queries

- **GitHub**: Search your organizations for repositories named `tpcp-docs` or matching `tpcp-docs-*`. Their existence indicates successful credential exfiltration via the [dead drop fallback](https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack).
- **GitHub Actions**: Review workflow runs from March 19-20 referencing `aquasecurity/trivy-action` or `aquasecurity/setup-trivy`. Check the "Run Trivy" and "Setup environment" steps for anomalous output.
- **Kubernetes**: Search for DaemonSets named `host-provisioner-std` or `host-provisioner-iran` in `kube-system` namespace.
- **systemd**: Check for user services matching `sysmon`, `pgmon`, `pgmonitor`, or `internal-monitor`.
- **npm**: Audit package versions in `@emilgroup`, `@opengov`, `@teale.io`, `@airtm`, `@pypestream` scopes against expected versions.

## What to Do Now

**If you used Trivy between March 19-22, 2026:**

1. Pin to safe versions: v0.69.3 or earlier for Trivy, v0.35.0 for trivy-action, v0.2.6 for setup-trivy. [Aqua's advisory](https://github.com/aquasecurity/trivy/discussions/10425) has the full version matrix.
2. Rotate every secret accessible to affected workflows: GitHub tokens, cloud provider credentials, Docker registry tokens, SSH keys.
3. Block the C2 domains and IP at your network perimeter.
4. Verify container integrity using cosign signatures against known-good digests.

**For long-term hardening:**

- Pin all GitHub Actions to full commit SHAs, not version tags. Tags can be force-pushed; commit SHAs cannot.
- Use [StepSecurity Harden-Runner](https://www.stepsecurity.io/blog/trivy-compromised-a-second-time---malicious-v0-69-4-release) or equivalent tools to monitor and restrict outbound network traffic from CI runners.
- Audit `pull_request_target` workflows to ensure they never checkout untrusted fork code with elevated permissions.
- Treat credential rotation as an atomic operation: revoke first, re-issue second, with no window where old and new credentials overlap.

## Independently Verifiable Evidence

Several claims in this post can be verified directly against GitHub's public APIs using the `gh` CLI. We ran these queries on March 23, 2026. You can reproduce them.

### The release gap is real

178 releases (v0.27.0 through v0.69.1) are missing from Phase 1:

```bash
$ gh api repos/aquasecurity/trivy/releases --paginate \
    --jq '.[].tag_name' | sort -V | tail -20
# ...
v0.25.4
v0.26.0
v0.69.2    # ← gap: 43 minor versions missing
v0.69.3
```

The malicious v0.69.4 tag has been deleted:

```bash
$ gh api repos/aquasecurity/trivy/git/ref/tags/v0.69.4
# {"message":"Not Found","status":"404"}
```

### The imposter commit exists and is unsigned

Commit [`1885610c`](https://github.com/aquasecurity/trivy/commit/1885610c6a34811c8296416ae69f568002ef11ec) is still in Trivy's object store. It claims to be authored by "DmitriyLewen" (a real Aqua maintainer) but carries no signature. Legitimate DmitriyLewen commits go through GitHub web merges and are GPG-signed by GitHub.

```bash
$ gh api repos/aquasecurity/trivy/git/commits/1885610c6a34811c8296416ae69f568002ef11ec \
    --jq '{author: .author.name, email: .author.email, message: .message,
           verified: .verification.verified, reason: .verification.reason}'
{
  "author": "DmitriyLewen",
  "email": "91113035+DmitriyLewen@users.noreply.github.com",
  "message": "fix(ci): Use correct checkout pinning",
  "verified": false,
  "reason": "unsigned"
}
```

The same pattern holds for the `setup-trivy` malicious commit, which spoofed "Tomochika Hara" (thara):

```bash
$ gh api repos/aquasecurity/setup-trivy/git/commits/8afa9b9f9183b4e00c46e2b82d34047e3c177bd0 \
    --jq '{author: .author.name, verified: .verification.verified, reason: .verification.reason}'
{
  "author": "Tomochika Hara",
  "verified": false,
  "reason": "unsigned"
}
```

### setup-trivy: only one tag survives

Tags v0.2.0 through v0.2.5 were not restored after remediation. Only the clean v0.2.6 remains, matching [Aqua's advisory](https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23):

```bash
$ gh api repos/aquasecurity/setup-trivy/tags --jq '.[].name'
v0.2.6
```

The setup-trivy events API captures the incident response in real-time:

```bash
$ gh api repos/aquasecurity/setup-trivy/events \
    --jq '[.[] | {type, actor: .actor.login, created_at,
           ref: .payload.ref, release: .payload.release.tag_name}]'
# ...
# 21:07 UTC - nikpivkin deletes v0.2.5 tag (incident response begins)
# 21:34 UTC - itaysk adds emergency collaborator
# 21:43 UTC - simar7 publishes clean v0.2.6 release
```

### trivy-action tags are all v-prefixed now

The original unprefixed tags (0.0.1 through 0.34.2) were deleted during remediation and could not be re-created because GitHub's immutable releases feature locked the attacker's force-pushed versions. 74 tags exist now, all v-prefixed:

```bash
$ gh api repos/aquasecurity/trivy-action/tags --paginate \
    --jq '.[].name' | wc -l
74

$ gh api repos/aquasecurity/trivy-action/tags --paginate \
    --jq '.[].name' | sort -V | tail -5
v0.33.0
v0.33.1
v0.34.0
v0.35.0    # ← only safe tag (pointed to master HEAD during attack)
```

### The dead drop exfiltration worked

The [Wiz analysis](https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack) described a fallback where the malware creates `tpcp-docs` repos on victim GitHub accounts. These repos are publicly searchable:

```bash
$ gh search repos "tpcp-docs" --json fullName,createdAt
[
  {"createdAt": "2026-03-22T18:30:12Z", "fullName": "kaufmann-digital/tpcp-docs"},
  {"createdAt": "2026-03-22T21:59:30Z", "fullName": "BEUMERGroupBot/tpcp-docs"},
  {"createdAt": "2026-03-22T23:08:47Z", "fullName": "cloud-team-si-it/tpcp-docs"},
  {"createdAt": "2026-03-22T23:25:10Z", "fullName": "dhoppe/tpcp-docs"}
]
```

All created on March 22, 2026. If you find a `tpcp-docs` repo in your org, assume all secrets from that workflow run are compromised.

### The discussion spam is visible

[Discussion #10420](https://github.com/aquasecurity/trivy/discussions/10420) has 772 comments. The API shows identical bot messages at the same timestamp and TeamPCP taunts:

```bash
$ gh api "repos/aquasecurity/trivy/discussions/10420" \
    --jq '{title, comments}'
{"title": "Why did this discussion about the Trivy incident get removed/closed",
 "comments": 772}

$ gh api "repos/aquasecurity/trivy/discussions/10420/comments?per_page=5" \
    --jq '[.[] | {created_at, body: .body[0:60]}]'
[
  {"created_at": "2026-03-19T23:56:52Z", "body": "To be explicit, it wasn't just discussion on the previous..."},
  {"created_at": "2026-03-20T00:01:16Z", "body": "sugma and ligma, teampcp owns you"},
  {"created_at": "2026-03-20T00:01:17Z", "body": "sugma and ligma, teampcp owns you"},
  {"created_at": "2026-03-20T00:08:33Z", "body": "this solved my issue, thanks"},
  {"created_at": "2026-03-20T00:08:33Z", "body": "thanks for the detailed explanation"}
]
```

Identical messages at identical timestamps (00:08:33 UTC) from coordinated bot accounts.

### tfsec and traceeshark confirm lateral movement

Both repos show `pushed_at` timestamps on March 19, consistent with the `aqua-bot` injection [documented by Wiz](https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack):

```bash
$ gh api orgs/aquasecurity/repos --paginate \
    --jq '.[] | select(.name | test("tfsec$|traceeshark")) | {name, pushed_at}'
{"name": "tfsec",       "pushed_at": "2026-03-19T23:03:55Z"}
{"name": "traceeshark", "pushed_at": "2026-03-19T22:56:37Z"}
```

## The Bigger Picture

This campaign is a benchmark for what supply chain attacks look like now. TeamPCP (also tracked as [PCPcat, ShellForce, DeadCatx3](https://flare.io/learn/resources/blog/teampcp-cloud-native-ransomware)) combined AI-assisted reconnaissance, CI/CD credential theft, self-propagating cross-ecosystem worms, decentralized C2 infrastructure, and destructive payloads into a single operation. They iterated through six payload versions in five hours, faster than most organizations can convene an incident response team.

The uncomfortable lesson: a single misconfigured GitHub Actions workflow led to a chain that compromised a security vendor's binary distribution, 75 GitHub Action tags used by 10,000+ workflows, 64+ npm packages, Docker Hub images, and internal infrastructure. The blast radius of CI/CD credential compromise is not linear. It compounds.
