Most supply chain attacks go after a library you depend on. TeamPCP went after the tools you use to check that those libraries are safe. Between February and May 2026, this group quietly took over the build pipelines of Trivy, Checkmarx, and LiteLLM, three names that sit inside the security and AI stacks of tens of thousands of engineering teams, and turned each one into a credential stealer that fed the next breach.
The result is one of the most instructive supply chain campaigns of the year. It is anchored by a single critical vulnerability, CVE-2026-33634, it cascaded across five ecosystems, and it ended with the attackers open-sourcing their worm and running a contest to see who could do the most damage with it. This is who TeamPCP is, exactly what they did, the flaws they abused, and the concrete steps that stop them.
Who is TeamPCP
TeamPCP is a financially motivated, cloud-native threat group that Google's Threat Intelligence Group tracks as UNC6780. They also operate under the names PCPcat, ShellForce, and DeadCatx3. The group has been active since at least September 2025 and built its reputation on Docker API and Kubernetes exploitation, cryptomining, ransomware partnerships, and self-propagating worms.
What makes them notable is focus. Rather than chasing a single high-value package, TeamPCP targets the shared infrastructure of software delivery: CI/CD pipelines, publishing tokens, container registries, and the GitHub Actions that almost every modern repository runs on autopilot. Compromise one of those, and you inherit the trust that every downstream user has placed in it.
The campaign at a glance
| Date (2026) | Event |
|---|---|
| Late Feb | TeamPCP breaches Aqua Security and steals credentials; rotation afterward is incomplete |
| Mar 19 | Attackers rewrite mutable Git tags in Trivy's GitHub Actions, planting a credential stealer |
| Mar 20 | CanisterWorm begins spreading across npm using stolen publish tokens |
| Mar 23 | Checkmarx KICS GitHub Action tags and OpenVSX extensions are poisoned |
| Mar 24, 10:39 UTC | Malicious LiteLLM 1.82.7 and 1.82.8 land on PyPI, live for about 40 minutes |
| Mar 27 | Telnyx Python SDK 4.87.1 and 4.87.2 backdoored using WAV steganography |
| Apr | Checkmarx confirms data was stolen; samples appear on a dark web forum |
| May 12 | TeamPCP open-sources its worm on GitHub and announces a BreachForums contest |
The same campaign, drawn on a timeline, shows how tightly the waves were sequenced. Each compromise fed the next within hours, not weeks.
Rendering diagram
How one breach became many
The campaign is a textbook example of credential chaining, where each compromise produces the keys to the next. The diagram below traces the full workflow, from the original Aqua Security breach to five poisoned ecosystems.
Rendering diagram
Stage 1: Trivy. TeamPCP started from a foothold left by an earlier breach of Aqua Security, where credential rotation was incomplete. On March 19 they abused a misconfigured pull_request_target GitHub Actions workflow to exfiltrate the aqua-bot service account token, then force-pushed malicious code over the existing version tags of aquasecurity/trivy-action (75 of 76 tags) and aquasecurity/setup-trivy (all 7). Poisoned container images followed at aquasec/trivy versions 0.69.4, 0.69.5, and 0.69.6. Every pipeline that ran a Trivy scan now executed an infostealer that swept SSH keys, cloud tokens, and npm and PyPI credentials straight out of the runner. This is the flaw captured in CVE-2026-33634, rated 9.4 on CVSS v4.0 and added to the CISA Known Exploited Vulnerabilities catalog with a remediation deadline of April 9, 2026.
Stage 2: npm. With harvested npm publish tokens, TeamPCP deployed a worm it calls CanisterWorm. It pushed malicious patch updates to packages from compromised maintainer accounts and injected postinstall hooks into package.json that pulled additional auth tokens out of every victim's .npmrc. Each new token meant more packages it could poison. Researchers counted the worm across more than 60 npm packages and over 140 malicious artifacts.
Stage 3: Checkmarx. Using GitHub tokens stolen in the Trivy wave, the group force-pushed to all 35 tags of Checkmarx/kics-github-action, poisoned checkmarx/ast-github-action, and published trojanized VS Code extensions to the OpenVSX marketplace. Checkmarx later confirmed that internal data was stolen and leaked.
Stage 4: LiteLLM. The same credential theft yielded LiteLLM's PyPI publishing token. On March 24 the attackers uploaded litellm 1.82.7 and 1.82.8 directly to PyPI, bypassing the project's normal CI/CD. The packages were live for roughly 40 minutes before PyPI quarantined them, which on a package with millions of daily installs is more than enough.
Stage 5: Telnyx. Three days later the group backdoored the Telnyx Python SDK (4.87.1 and 4.87.2), this time hiding the second-stage payload inside WAV audio files using steganography, a technique designed to slip past scanners that inspect scripts but not media.
The signature flaw: mutable tags and CI/CD trust
The technical heart of this campaign is not a memory bug or a clever exploit. It is a design weakness almost every team shares: trusting mutable references in automation.
When your workflow says uses: aquasecurity/trivy-action@v0.69, that tag is a pointer, not a fixed identity. Whoever controls the repository can move it to point at new code, and your pipeline will happily run whatever it now points to, with full access to the secrets in your runner environment. TeamPCP did exactly that. They did not need to trick you into installing anything new. They changed what your existing, pinned-looking dependency resolved to.
LiteLLM's payload showed how durable this can be. Version 1.82.8 shipped a file named litellm_init.pth into Python's site-packages. A .pth file with an executable line runs on every Python interpreter startup, with no import required, which means pip, your IDE, and language servers all trigger it. Because the file was correctly listed in the wheel's RECORD, it passed pip's hash verification. Hash checks confirm a file matches what the registry advertised. They cannot tell you the advertised content is malicious.
What the malware actually did
Once running, the payload followed a consistent three-stage pattern across packages. Here is what that looked like from the moment a single build runner pulled a poisoned package.
Rendering diagram
Harvest. It collected environment variables, SSH private keys, .env files, AWS, GCP, and Azure credentials, Docker config.json, Kubernetes kubeconfig files, shell history, database passwords, CI/CD secrets, and even cryptocurrency wallet files. On cloud hosts it reached the instance metadata service (IMDSv2) to pull secrets from AWS Secrets Manager and SSM Parameter Store.
Exfiltrate. Data was encrypted with an AES-256 session key, which was itself wrapped with a hardcoded RSA-4096 public key, bundled into tpcp.tar.gz, and POSTed to attacker infrastructure such as models.litellm[.]cloud. The request carried the header X-Filename: tpcp.tar.gz, a reliable signature of the group's tooling.
Persist and spread. It wrote a backdoor to ~/.config/sysmon/sysmon.py, installed a sysmon.service systemd user unit, and polled https://checkmarx[.]zone/raw every five minutes for follow-on payloads. Where it found Kubernetes service account tokens, it created privileged node-setup-* pods that mounted the host filesystem to compromise entire clusters.
The destructive turn
In late March, researchers observed CanisterWorm gaining a wiper. The Kubernetes payload included geolocation logic: hosts whose timezone and locale matched Iran received an Alpine container named kamikaze that deleted every top-level directory on the host filesystem and forced a reboot. Every other host received a persistent backdoor instead. A campaign that began as quiet credential theft had added a politically targeted, destructive capability, and it did so on infrastructure that most teams never inspect.
CanisterWorm's command and control also used the Internet Computer Protocol (ICP), running its C2 logic inside a decentralized canister. That makes it a tamper-proof dead drop, far harder to take down than a normal domain or server.
What they are doing now
The campaign has not wound down. Across the npm and PyPI ecosystems, the group's self-propagating worm, which they brand as part of a "Mini Shai-Hulud" effort, published more than 400 malicious package versions across 172 distinct packages in a single five-hour burst. Those packages had a combined download history exceeding 518 million installs before compromise.
On May 12, 2026, TeamPCP published the worm's source code on GitHub under an MIT license with the message "Shai-Hulud: Open Sourcing The Carnage," and announced a cash contest on BreachForums for the largest supply chain attack built with it. In other words, the playbook described in this post is now public and being actively encouraged. Expect copycats.
The blast radius
One stolen aqua-bot token did not compromise one project. It branched. The tree below shows how a single root credential fanned out into every downstream ecosystem and victim class.
Rendering diagram
Everything the campaign affected
| Type | Compromised assets |
|---|---|
| GitHub Actions | aquasecurity/trivy-action, aquasecurity/setup-trivy, Checkmarx/kics-github-action, Checkmarx/ast-github-action |
| Container images | aquasec/trivy, ghcr.io/aquasecurity/trivy, public.ecr.aws/aquasecurity/trivy (0.69.4 to 0.69.6) |
| PyPI | litellm (1.82.7, 1.82.8), Telnyx Python SDK (4.87.1, 4.87.2) |
| npm | 60+ packages, 140+ malicious artifacts via CanisterWorm |
| OpenVSX (VS Code) | checkmarx.ast-results, checkmarx.cx-dev-assist |
| Data impact | 300+ GB and roughly 500,000 credentials exfiltrated; 16+ organizations leaked publicly |
Indicators of compromise
| Category | Indicator |
|---|---|
| C2 domains | scan.aquasecurtiy[.]org, checkmarx[.]zone, models.litellm[.]cloud, tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io |
| C2 IPs | 83.142.209[.]11, 83.142.209[.]203, 45.148.10[.]212, 46.151.182[.]203 |
| Persistence files | ~/.config/sysmon/sysmon.py, ~/.config/systemd/user/sysmon.service, litellm_init.pth, /tmp/pglog, /tmp/.pg_state |
| Dropper artifacts | kamikaze.sh, kube.py, tpcp.tar.gz, request header X-Filename: tpcp.tar.gz |
| Kubernetes | pods named node-setup-*, containers named kamikaze or provisioner in kube-system |
A few commands to check your own systems:
# Was a malicious LiteLLM installed?
pip show litellm | grep Version # rotate everything if 1.82.7 or 1.82.8
# Look for the persistence backdoor
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null && echo "BACKDOOR FOUND"
# Find suspicious .pth files in site-packages
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
-name "*.pth" -exec grep -l "base64\|subprocess\|exec" {} \;
# Detect Kubernetes lateral movement
kubectl get pods -A | grep "node-setup-"
How to stop TeamPCP
The good news is that this entire class of attack has well-understood countermeasures. None of them are exotic.
If you may be affected, treat it as a full compromise. The stealer self-deletes and the persistence is quiet, so removing the package is not enough. Rotate every credential that touched an affected machine: SSH keys, AWS, GCP, and Azure credentials, npm and PyPI tokens, GitHub tokens, Docker registry logins, Kubernetes service account tokens, database passwords, and any API keys in .env files. Then hunt for the IOCs above.
Pin to immutable references, not tags. This is the single highest-leverage fix. Reference GitHub Actions and dependencies by full commit SHA or content hash, never by a movable tag like @v1 or @latest. A pinned digest cannot be silently repointed.
Lock down installs.
- Use
npm ciand committed lockfiles instead ofnpm install, so builds resolve from the lockfile rather than from version ranges. - Run installs with
--ignore-scriptsin CI to block thepostinstallhooks that CanisterWorm relied on. - Use pip's
--require-hashesmode for critical Python dependencies, and pin tool versions rather than pulling unpinned releases fromapt.
Kill long-lived secrets in CI/CD. Replace static publishing tokens and personal access tokens with short-lived OIDC token exchange, so there is no durable credential for a stealer to harvest. Audit every pull_request_target workflow, since that trigger runs with write access and was the original entry point here.
Watch the runtime, not just the manifest. Hash verification proved insufficient because the malicious content was advertised honestly. Detection has to include behavior: outbound connections to unknown hosts during a build, secret access with a Python-urllib user agent, unexpected .pth files, new systemd user units, and pod creation in kube-system.
How Breachline helps
This campaign is exactly the kind of layered, behavioral problem that point-in-time scanning misses. Nebula's supply chain analysis cross-references your dependency tree against known malicious package and version lists, flags phantom dependencies that appear in a manifest but are never imported, and audits for unpinned references that are vulnerable to tag rewriting. Just as important, Nebula tests the application and infrastructure around your pipeline for the SSRF, credential exposure, and injection flaws that turn a single stolen token into full compromise. The lesson of TeamPCP is that trust is the attack surface, and trust has to be verified continuously, not once a quarter.
Takeaways
- TeamPCP (UNC6780) weaponizes the CI/CD tools you already trust, chaining one stolen credential into the next across npm, PyPI, Docker Hub, OpenVSX, and GitHub.
- The root flaw is mutable references and long-lived secrets in automation. Pin to digests, use OIDC, and run installs with scripts disabled.
- The worm is now open source and being promoted, so this technique will spread. Assume it, and verify your supply chain continuously.
Sources
- Unit 42, Palo Alto Networks: Weaponizing the Protectors: TeamPCP's Multi-Stage Supply Chain Attack
- Datadog Security Labs: LiteLLM and Telnyx compromised on PyPI, tracing the TeamPCP campaign
- Snyk: How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM
- LiteLLM: Security Update, Suspected Supply Chain Incident
- The Hacker News: Trivy Hack Spreads Infostealer via Docker, Triggers Worm and Kubernetes Wiper
- BleepingComputer: TeamPCP deploys Iran-targeted wiper in Kubernetes attacks
- NVD: CVE-2026-33634
- Security Boulevard: Mini Shai-Hulud FAQ on the TeamPCP npm and PyPI campaign
- SecurityWeek: Checkmarx Confirms Data Stolen in Supply Chain Attack