Skip to content

The TeamPCP Chain Attack: From Trivy to Five Ecosystems in Five Days

This page is part of a series on the Trivy supply chain compromise of March 2026. See also: the main article, supply chain risks in Azure DevOps, and detecting the compromise in Azure DevOps.

It is tempting to read the Trivy compromise as an isolated incident against Aqua Security. That is the wrong way to read it. Trivy was the starting point of what several incident analyses describe as a chain attack, in which stolen credentials from one ecosystem appear to have enabled access to the next within hours. This appendix documents the chain in chronological order. Where causality is confirmed by sources, that is noted. Where it is analytical inference, that is marked too.

The actor behind the campaign is referred to in most analyses as TeamPCP, based on self-identification in payload strings and shared technical signatures, including a common RSA-4096 public key. That attribution has not been confirmed by any government intelligence service and should be treated as indicative.

The mechanics of the chain

Before the timeline, it is worth understanding why chain attacks work so effectively in CI/CD environments. A tool like Trivy runs by design with access to the pipeline's secrets. That is not a bug, it is a precondition for the tool to function. The same logic applies to LiteLLM, which centralises API keys to every LLM provider an organisation uses. And to Checkmarx KICS, which analyses infrastructure-as-code and for that reason often has access to Terraform variables and state files.

TeamPCP's targets all ran with broad access in CI/CD environments, and the credentials stored in those environments were directly usable as a pivot to the next target. Whether the chain was deliberately planned from the start or whether each hop was taken opportunistically based on whatever was stolen in the previous phase is not established.

The timeline

Late February 2026: The initial breach at Aqua Security

A vulnerable pull_request_target workflow in the Trivy repository was exploited via the well-documented "Pwn Request" pattern. The workflow executed with repository-level secrets even when code came from an external fork, and a Personal Access Token belonging to the aqua-bot service account was stolen. The exact actor handle and exfiltration domain associated with the initial intrusion appear with varying levels of detail across different analyses and are not consistently confirmed by primary sources.

By February 28, the attacker had privileged access to the repository. The Trivy repository was temporarily privatised, a number of releases were deleted, and a malicious VS Code extension was published to Open VSX. Aqua Security disclosed the incident and initiated credential rotation, but as they acknowledged in their incident discussion, the rotation was not atomic. The attacker retained access via the aqua-bot service account.

March 19: Trivy: day one

~17:43 UTCThe aqua-bot account force-pushes nearly all version tags in aquasecurity/trivy-action and all tags in aquasecurity/setup-trivy to malicious commits containing a built-in credential stealer. Simultaneously, the release pipeline is triggered and publishes a tampered Trivy binary as v0.69.4 to GitHub Releases, Docker Hub, GHCR and AWS ECR.

Pipelines run as normal. Scans return results. But underneath, SSH keys, cloud credentials, npm tokens, PyPI publishing tokens, Kubernetes tokens and Docker registry credentials are being collected from every organisation whose CI pipeline runs Trivy without SHA pinning.

Several analyses suggest that credentials stolen during this phase were later used to reach LiteLLM's release pipeline, in part because LiteLLM used Trivy in its CI without version pinning. The exact causal link has not been publicly confirmed by Aqua Security or BerriAI.

The same evening, 44 repositories in Aqua Security's aquasec-com organisation are renamed with tpcp-docs prefixes via a stolen service account token.

March 20: CanisterWorm deployed to npm

Using stolen npm tokens from Trivy victims, CanisterWorm was deployed. The worm automated the compromise: given a single stolen npm token, it enumerated all packages the token had publish access to and released malicious versions across the entire scope. The exact number of affected packages varies across analyses, but multiple independent sources report dozens of packages and hundreds of artefacts.

CanisterWorm used an ICP canister (Internet Computer Protocol blockchain) as a dead-drop resolver for its C2 infrastructure. This means the C2 address cannot be taken down via traditional domain seizure, since it resides on a decentralised blockchain. Aikido Security was among the first to document this specific abuse of ICP for C2 purposes.

March 22: Docker Hub, Kubernetes wiper and WAV steganography

TeamPCP pushed additional malicious Docker Hub images, v0.69.5, v0.69.6 and latest, using separately compromised Docker Hub credentials not covered by Aqua Security's remediation. This extended the Docker Hub exposure by roughly another ten hours.

The same day, a new CanisterWorm variant with destructive capabilities was observed: a Kubernetes wiper designed to delete a cluster and its nodes. Some analyses report that the variant included geofencing logic tied to Iranian timezone and Farsi as the system language, but this is not consistently confirmed in primary sources and should be treated as uncertain. The variant also introduced WAV steganography as a delivery method, hiding malicious payloads as valid audio frames in .wav files (hangup.wav for Windows, ringtone.wav for Linux/macOS), a technique reused five days later against Telnyx.

March 23: Checkmarx

By this point, CI/CD access obtained through compromised Trivy environments had reportedly yielded credentials for additional organisations' release infrastructure. kics-github-action and ast-github-action from Checkmarx were compromised, along with two OpenVSX extensions: ast-results 2.53.0 and cx-dev-assist 1.7.0. The attacker hijacked 35 tags between approximately 12:58 and 16:50 UTC using a new C2 domain, checkmarx[.]zone, impersonating the Checkmarx brand. Malicious code was removed approximately three hours later.

Checkmarx KICS is an IaC analysis tool that frequently has access to Terraform variables and state files. Organisations that ran the compromised version should expect IaC secrets to have been exposed, not only GitHub Actions environment secrets.

March 24: LiteLLM on PyPI

Malicious versions litellm 1.82.7 and litellm 1.82.8 were published to PyPI. Several analyses, including from SANS Institute and ReversingLabs, suggest that PyPI publishing tokens were stolen from LiteLLM's CI pipeline, which used Trivy without version pinning, and that those tokens were then used to publish the malicious releases. Neither Aqua Security nor BerriAI have publicly confirmed the exact mechanism, but the causal connection via CI/CD access is assessed as well-grounded by multiple independent researchers.

LiteLLM is a universal proxy layer for LLM provider integrations with approximately 95 million monthly downloads on PyPI. It centralises API keys for OpenAI, Anthropic, AWS Bedrock, Google Vertex AI and similar services, meaning a LiteLLM compromise could in practice expose all LLM credentials an organisation manages.

Version 1.82.8 was the more destructive of the two: it contained a .pth file that the Python interpreter loads automatically at startup, without the package even needing to be explicitly imported. This means every python, pip or pytest command on an affected system triggers the credential stealer. PyPI quarantined the packages within approximately three hours, but with 3.6 million daily downloads, the exposure window was significant.

March 27: Telnyx on PyPI via WAV steganography

telnyx 4.87.1 and telnyx 4.87.2 were compromised. The Telnyx token had been stolen via CanisterWorm: some CI environment with publishing rights to the Telnyx npm scope had installed a compromised npm package whose post-install hook captured the token and delivered it to the attacker.

The delivery method was WAV steganography: the malicious payload was hidden as valid audio frames in .wav files (hangup.wav for Windows, ringtone.wav for Linux/macOS). The technique was first introduced by TeamPCP in the Kubernetes wiper on March 22 and was now standardised as part of the group's toolkit. The RSA-4096 public key in the Telnyx payload is identical to the one in the LiteLLM payload, providing strong technical attribution that both originate from the same actor.

The C2 server for the Telnyx compromise was 83[.]142.209.203:8080. The Windows variant dropped msbuild.exe into the Startup folder. The Linux variant implemented the same credential sweep as in earlier stages of the campaign.

The chain in summary

Most incident analyses describe the attack as a credential-reuse pattern in which each phase was enabled by access established in the previous one:

pull_request_target exploit → stolen aqua-bot PAT → Trivy binary and GitHub Actions compromised → stolen npm tokens → CanisterWorm spreads through npm ecosystem → stolen PyPI tokens (likely via Trivy-exposed CI environments) → LiteLLM compromised → Telnyx npm token stolen via CanisterWorm → Telnyx PyPI compromised.

In parallel: credentials from Trivy-exposed CI environments used against Checkmarx release infrastructure.

Whether the chain was deliberately planned from the start or whether each hop was taken opportunistically based on what was stolen in the previous phase is not established. What is established is that the same technical signatures, RSA-4096 key, encryption scheme, exfiltration pattern, recur throughout the campaign, pointing to a single actor.

Affected components, ecosystems and exposure windows

Component Ecosystem Compromised versions Exposure window
aquasecurity/trivy-action GitHub Actions v0.0.1–v0.34.2 (nearly all tags) ~Mar 19 17:43 UTC – Mar 20 ~05:40 UTC
aquasecurity/setup-trivy GitHub Actions v0.1.0–v0.2.5 (all tags) ~Mar 19 17:43 UTC – ~21:44 UTC
Trivy binary GitHub/Docker/ECR v0.69.4 ~Mar 19 18:22 UTC – ~21:42 UTC
aquasec/trivy (Docker Hub) Docker Hub v0.69.4, v0.69.5, v0.69.6, latest Mar 19 18:24 UTC – Mar 23 01:36 UTC
CanisterWorm npm Dozens of packages (exact figures vary) Mar 20 – ongoing at time of writing
checkmarx/kics-github-action GitHub Actions Compromised tags ~Mar 23 12:58–16:50 UTC
litellm PyPI 1.82.7, 1.82.8 Mar 24 ~10:39–~14:35 UTC
telnyx PyPI/npm 4.87.1, 4.87.2 Mar 27

IOCs for the full campaign

The indicators below are compiled from published incident analyses. Trivy-related IOCs (C2 domain, IP, Cloudflare Tunnel, ICP canister, persistence files and Docker digests) have strong support across multiple independent sources. IOCs for the Checkmarx, LiteLLM and Telnyx phases have less breadth of confirmation and should be verified against primary sources before use in blocking rules.

Type Value Connection Confidence
Domain (C2, Trivy) scan[.]aquasecurtiy[.]org Typosquat, primary exfil High: multiple sources
IP (Trivy C2) 45.148.10.212 TECHOFF SRV LIMITED, Amsterdam High: multiple sources
Cloudflare Tunnel plug-tab-protective-relay.trycloudflare.com Fallback exfil, Trivy High: Snyk/StepSecurity
ICP canister tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io C2, CanisterWorm/persistence High: Aikido/Datadog
Domain (C2, Checkmarx) checkmarx[.]zone Exfil, Checkmarx phase Medium: Datadog/ramimac
Domain (C2, LiteLLM) models[.]litellm[.]cloud Exfil, LiteLLM phase Medium: Datadog
IP (Telnyx C2) 83[.]142.209.203:8080 Exfil, Telnyx phase Medium: CyberSecNews
GitHub repo (exfil) tpcp-docs Created in victim org on successful exfil High: Aqua advisory
File (persistence, Linux) ~/.config/systemd/user/sysmon.py Trivy binary, developer machines High: CrowdStrike/Snyk
File (persistence, Windows) %APPDATA%\...\Startup\msbuild.exe Telnyx payload Medium: CyberSecNews
File (persistence, Python) litellm_init.pth LiteLLM 1.82.8, auto-executes at Python start High: Datadog
Systemd service pgmon.service CanisterWorm, later variants Medium: MrCloudBook
Docker image digest sha256:27f446230c60bbf0b70e008db798bd4f33b7826f9f76f756606f5417100beef3 Docker Hub v0.69.4 High: Docker blog
Docker image digest sha256:425cd3e1a2846ac73944e891250377d2b03653e6f028833e30fc00c1abbc6d33 Docker Hub v0.69.6 High: Docker blog

Sources: Datadog Security Labs, SANS Institute, Kaspersky, ReversingLabs, StepSecurity, SafeDep, MrCloudBook, CyberSecurityNews, The Hacker News, ramimac.me/teampcp, Aikido Security