TL;DR
On March 24, 2026, two backdoored versions of LiteLLM — a Python package with 3.4 million daily downloads — were published to PyPI by threat actor TeamPCP. The attack chain started with compromised security scanners (Trivy, Checkmarx KICS) in LiteLLM’s CI/CD pipeline, which were used to steal the maintainer’s PyPI publishing credentials.
The malicious versions (1.82.7 and 1.82.8) contained a three-stage payload: credential harvesting (SSH keys, cloud creds, K8s secrets, crypto wallets), encrypted exfiltration to attacker infrastructure, and persistent backdoor with Kubernetes lateral movement that deployed privileged pods across every node.
The malware was live for approximately three hours before quarantine. The accidental discovery? A fork bomb bug in the attacker’s own malware that crashed a researcher’s machine.
The Attack Chain
This wasn’t a smash-and-grab. TeamPCP executed a multi-phase campaign across five days, compromising three separate tools before reaching their final target.
Phase 1: Trivy Compromise (March 19)
It started with Trivy, Aqua Security’s open-source vulnerability scanner used across thousands of CI/CD pipelines.
TeamPCP exploited a misconfigured pull_request_target workflow in the Trivy GitHub repository — a well-documented attack vector that grants forked PR code access to the target repository’s secrets. An attacker using the handle MegaGame10418 submitted a pull request that exfiltrated aqua-bot’s credentials.
With those credentials, TeamPCP:
- Force-pushed malicious binaries to Trivy v0.69.4
- Overwrote 76 of 77 version tags in
aquasecurity/trivy-action - Overwrote all 7 tags in
aquasecurity/setup-trivy - Injected a credential-stealing payload into every CI/CD pipeline pinned to those tags
Any repository using Trivy GitHub Actions with tag-based pinning was now executing attacker-controlled code in their pipeline — with access to all repository secrets and environment variables.
Phase 2: Checkmarx KICS (March 23)
Four days later, TeamPCP leveraged previously stolen CI/CD secrets to compromise Checkmarx’s KICS (Keeping Infrastructure as Code Secure) GitHub Actions. Between 12:58–16:50 UTC on March 23, 35 tags were hijacked across ast-github-action and kics-github-action.
The same day, two C2 domains were registered:
checkmarx.zone— C2 polling endpointmodels.litellm.cloud— data exfiltration endpoint
Both registered through Spaceship, Inc. with provider DEMENIN B.V. — matching TeamPCP’s established infrastructure pattern.
Phase 3: LiteLLM on PyPI (March 24)
The endgame. Using PyPI publishing credentials stolen from LiteLLM’s CI/CD pipeline (which used the now-compromised Trivy action), TeamPCP published two malicious versions directly to PyPI:
| Time (UTC) | Event |
|---|---|
| 10:39 | litellm 1.82.7 published — payload in proxy_server.py |
| 10:52 | litellm 1.82.8 published — .pth file for broader execution |
| 11:48 | FutureSearch researcher discovers the compromise |
| 12:36 | Hacker News thread goes live (reaches 324 points) |
| 12:44 | Attackers flood GitHub issue #24512 with 88 bot comments from 73 accounts |
| 15:09 | LiteLLM maintainer rotates all credentials |
| 15:27 | Malicious versions deleted, PyPI unquarantined |
No GitHub tags were created for these versions. They were uploaded directly to PyPI using stolen credentials — bypassing the normal release process entirely.
How It Was Found: The Fork Bomb Bug
The discovery was accidental — and ironic.
Callum McMahon, a researcher at FutureSearch, was testing a Cursor MCP plugin that pulled in LiteLLM as a transitive dependency. His machine became completely unresponsive — RAM exhaustion within seconds of Python starting.
He traced the issue to a file called litellm_init.pth — a 34,628-byte double base64-encoded payload sitting in site-packages.
Here’s the thing: .pth files execute on every Python interpreter startup. The payload spawned child processes via subprocess.Popen. Those child processes started their own Python interpreters. Which triggered the .pth file again. Which spawned more child processes. Exponential fork bomb.
The fork bomb was a bug in the malware. The attackers didn’t account for .pth files re-triggering in child processes. Their credential stealer worked fine — it just also happened to crash every machine it infected, which is how it got caught in three hours instead of three weeks.
The Two Delivery Mechanisms
Version 1.82.7: Source Injection
The payload was embedded as base64-encoded content in litellm/proxy/proxy_server.py. This executes when you import litellm.proxy — the standard pathway for running LiteLLM’s proxy server mode.
SHA-256: a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b
Version 1.82.8: .pth File Injection
A malicious litellm_init.pth was added to the wheel root. Python processes this file on every interpreter startup — not just when you import LiteLLM, but when you run pip, when your IDE starts a language server, when any tool spawns a Python subprocess.
The .pth file was correctly declared in the wheel’s RECORD file with a valid hash. It would pass pip install --require-hashes. There’s nothing suspicious about it from pip’s perspective — hash verification confirms integrity, not intent.
SHA-256: 71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238
The Three-Stage Payload
Stage 1: Credential Harvesting
The malware systematically sweeps the host for anything valuable:
| Category | What It Steals |
|---|---|
| System | Hostname, env vars, running processes, network routes, /var/log/auth.log |
| SSH/Git | Private keys, .git-credentials, shell history |
| Cloud | AWS credentials + config, GCP service accounts, Azure principals |
| Cloud APIs | AWS Secrets Manager, SSM Parameter Store, IMDS (with IMDSv2 signing) |
| Containers | Docker config.json, K8s kubeconfig, service account tokens, all namespace secrets |
| CI/CD | Jenkins configs, Travis CI, Terraform state |
| Tokens | Slack/Discord webhook URLs, API keys from .env files |
| Crypto | Bitcoin, Ethereum, Solana, Cardano, Monero wallets, seed phrases, Ledger configs |
The IMDS harvesting with IMDSv2 token signing is worth noting — this isn’t script kiddie work. They’re handling the X-aws-ec2-metadata-token header correctly, which means they can steal instance role credentials from EC2 instances running the hardened metadata service.
Stage 2: Encrypted Exfiltration
Collected data gets hybrid-encrypted before exfiltration:
- Generate 32-byte AES-256 session key via
openssl rand - Encrypt data with AES-256-CBC (PBKDF2 key derivation)
- Encrypt session key with a hardcoded 4096-bit RSA public key (OAEP padding)
- Bundle everything into
tpcp.tar.gz - POST to
https://models.litellm.cloud/via curl
The identical RSA public key appears across Trivy, KICS, and LiteLLM payloads — the strongest technical attribution link tying all three compromises to TeamPCP.
Stage 3: Persistence + Lateral Movement
Local persistence:
~/.config/sysmon/sysmon.py # backdoor script
~/.config/systemd/user/sysmon.service # systemd service
The service is described as “System Telemetry Service” — blending in with legitimate monitoring. It polls https://checkmarx.zone/raw every 5 minutes for next-stage payloads. At the time of analysis, the endpoint was returning YouTube URLs — a placeholder until the operator switches to real payloads.
Kubernetes lateral movement:
If a K8s service account token is found at the standard mount path, the malware:
- Reads all secrets across every namespace
- Deploys privileged pods to every node in
kube-system - Uses
alpine:latestimage, mounts host filesystem at/host - Installs the
sysmon.pybackdoor on the underlying node - Pod naming:
node-setup-{node_name}(truncated to 35 chars)
This is particularly devastating for LiteLLM deployments. LiteLLM is often run as a centralized AI gateway in Kubernetes, sitting behind multiple LLM provider credentials. Compromising that pod gives you access to an unusually broad set of API keys — OpenAI, Anthropic, Azure OpenAI, Bedrock, Vertex AI — all in one place.
Who Is TeamPCP?
Aliases: PCPcat, Persy_PCP, ShellForce, DeadCatx3 Active since: December 2025 Communication: @Persy_PCP and @teampcp on Telegram Campaign phase: LiteLLM was Phase 09 of an ongoing operation
Infrastructure Fingerprints
TeamPCP’s operational consistency makes attribution straightforward:
- Same RSA key pair across all operations
- Same bundle naming:
tpcp.tar.gz - Same staging repos:
tpcp-docs-prefixed GitHub repositories as dead-drop C2 - Same registrar: Spaceship, Inc. with DEMENIN B.V. hosting
- Same ecosystem targeting pattern: tools with elevated pipeline access
Advanced Capabilities
Two capabilities stand out:
CanisterWorm — Uses the Internet Computer Protocol (ICP) blockchain as a C2 channel. Since ICP canisters are decentralized smart contracts, this C2 infrastructure is resistant to domain takedowns and hosting provider intervention. Traditional incident response playbooks don’t work against blockchain-based C2.
Hackerbot-claw — An AI agent (“openclaw”) used for automated attack targeting. This is one of the earliest documented operational uses of AI agents in supply chain campaigns — the attacker is using AI to find vulnerable CI/CD configurations at scale.
Ecosystems Compromised
LiteLLM marks the fifth ecosystem TeamPCP has hit:
- GitHub Actions (Trivy, Checkmarx KICS)
- Docker Hub
- npm
- Open VSX (VS Code extension marketplace)
- PyPI (LiteLLM)
Downstream Impact
Despite the three-hour window, the blast radius was significant. Projects that filed security responses:
| Project | Action |
|---|---|
| DSPy | PR #9498 merged; CI failures reported |
| MLflow | PR #21971 merged |
| OpenHands | PR #13569 filed |
| CrewAI | PRs #5040, #5039 filed |
| Langwatch | Security PR filed |
| strands-agents/sdk-python | Security issue filed |
| Arize Phoenix | Security response initiated |
| Aider | Confirmed safe — pinned to litellm==1.82.3 |
The Aider case is instructive: pinning to exact versions saved them. Every other project was pulling the latest version.
Detection
Check If You’re Affected
| |
File Hashes (SHA-256)
| File | Hash |
|---|---|
litellm_init.pth (1.82.8) | 71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238 |
proxy_server.py (1.82.7) | a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b |
sysmon.py (backdoor) | 6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a |
IOCs
Domains:
models.litellm.cloud— exfiltrationcheckmarx.zone— C2 polling
Filesystem paths:
~/.config/sysmon/sysmon.py~/.config/systemd/user/sysmon.service/tmp/tpcp.tar.gz,/tmp/session.key,/tmp/payload.enc/tmp/.pg_state,/tmp/pglog
Kubernetes indicators:
- Pods matching
node-setup-*inkube-systemnamespace - Container name:
setup, Image:alpine:latest
MITRE ATT&CK Mapping
| Tactic | Technique | Application |
|---|---|---|
| Initial Access | T1195.002 — Supply Chain Compromise | Trivy → KICS → LiteLLM pipeline compromise |
| Execution | T1546.018 — Python Startup Hooks | .pth file executing on every Python startup |
| Credential Access | T1003 — Credential Dumping | SSH keys, cloud creds, K8s secrets harvested |
| Persistence | T1543.002 — Systemd Service | sysmon.service with C2 polling |
| Lateral Movement | T1610 — Deploy Container | Privileged pods deployed across K8s nodes |
| Exfiltration | T1041 — Exfil Over C2 | Encrypted archive to models.litellm.cloud |
| C2 | T1071.001 — Web Protocols | HTTPS polling to checkmarx.zone/raw |
Remediation
If You Never Installed 1.82.7 or 1.82.8
Pin to a safe version:
litellm<=1.82.6
If You Installed 1.82.7 or 1.82.8
Treat the system as compromised regardless of whether you think the payload executed. The .pth file triggers on every Python startup — if Python ran even once after installation, the payload fired.
Remove persistence:
1 2 3 4 5rm -f ~/.config/sysmon/sysmon.py /root/.config/sysmon/sysmon.py systemctl --user disable sysmon.service rm -f ~/.config/systemd/user/sysmon.service rm -f /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc \ /tmp/session.key.enc /tmp/.pg_state /tmp/pglogRotate ALL credentials — SSH keys, cloud credentials (AWS, GCP, Azure), API keys, Docker registry creds, K8s kubeconfig and service tokens, database passwords,
.envsecrets, crypto wallet seed phrasesAudit cloud and K8s:
- Query AWS Secrets Manager and SSM Parameter Store access logs
- Search K8s for
node-setup-*pods inkube-system - Review secret access logs across all namespaces
Fresh install — rebuild on a clean environment, don’t upgrade in-place
Why This Matters
Security Scanners Are High-Value Targets
TeamPCP’s target selection is deliberate: Trivy scans containers, KICS scans infrastructure-as-code, LiteLLM routes AI model traffic. All three tools require elevated access to credentials, configs, and environment variables by design. Compromising a security scanner is particularly insidious — it’s the tool you trust to tell you when something is wrong.
Hash Verification Doesn’t Help Here
The malicious .pth file was correctly declared in the wheel’s RECORD file. It would pass pip install --require-hashes. Hash verification confirms that the file hasn’t been tampered with after publication — but when the publisher’s credentials are stolen, the attacker is the publisher.
No widely-deployed pip plugin currently inspects .pth file contents for patterns like subprocess, base64, or exec. There’s an open CPython issue (#113659) discussing .pth file security risks, but no shipped mitigation yet.
Pin Your Dependencies
Aider was safe because they pinned litellm==1.82.3. Every project using litellm>=1.82 or pulling latest got hit. This applies everywhere — your requirements.txt, your CI/CD pipeline tools, your GitHub Actions.
For GitHub Actions specifically: pin to commit SHAs, not tags. Tags can be force-pushed (as TeamPCP demonstrated with 76 Trivy tags). Commit SHAs are immutable.
| |
The Bigger Picture
TeamPCP represents a maturation in supply chain attack methodology. They’re not just publishing typosquatted packages and hoping someone installs them. They’re:
- Compromising upstream security tools to reach downstream targets
- Using AI agents to find vulnerable CI/CD configurations at scale
- Deploying blockchain-based C2 that resists traditional takedown
- Flooding disclosure channels with bot accounts to delay response
Nine phases of operations across five ecosystems since December 2025. The sophistication curve is going up, not down.
If you’re running LiteLLM, check your version. If you’re running Trivy or KICS in CI/CD, verify your action pinning. And if you’re managing a centralized AI gateway with credentials for multiple LLM providers, consider what happens when that single point of trust gets compromised.
References
- Snyk — How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM
- FutureSearch — Supply Chain Attack in litellm 1.82.8 on PyPI
- The Hacker News — TeamPCP Backdoors LiteLLM Versions 1.82.7–1.82.8 via Trivy CI/CD Compromise
- Arctic Wolf — TeamPCP Supply Chain Attack Campaign Targets Trivy, Checkmarx (KICS), and LiteLLM
- Microsoft Security Blog — Guidance for detecting, investigating, and defending against the Trivy supply chain compromise
- Kaspersky — Trojanization of Trivy, Checkmarx, and LiteLLM
- Wiz — KICS GitHub Action Compromised: TeamPCP Supply Chain Attack
- LiteLLM — Security Update: Suspected Supply Chain Incident
- Semgrep — The TeamPCP Credential Infostealer Chain Attack Reaches Python’s LiteLLM
- CPython — Issue #113659: .pth file security risks
