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 endpoint
  • models.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:39litellm 1.82.7 published — payload in proxy_server.py
10:52litellm 1.82.8 published — .pth file for broader execution
11:48FutureSearch researcher discovers the compromise
12:36Hacker News thread goes live (reaches 324 points)
12:44Attackers flood GitHub issue #24512 with 88 bot comments from 73 accounts
15:09LiteLLM maintainer rotates all credentials
15:27Malicious 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:

CategoryWhat It Steals
SystemHostname, env vars, running processes, network routes, /var/log/auth.log
SSH/GitPrivate keys, .git-credentials, shell history
CloudAWS credentials + config, GCP service accounts, Azure principals
Cloud APIsAWS Secrets Manager, SSM Parameter Store, IMDS (with IMDSv2 signing)
ContainersDocker config.json, K8s kubeconfig, service account tokens, all namespace secrets
CI/CDJenkins configs, Travis CI, Terraform state
TokensSlack/Discord webhook URLs, API keys from .env files
CryptoBitcoin, 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:

  1. Generate 32-byte AES-256 session key via openssl rand
  2. Encrypt data with AES-256-CBC (PBKDF2 key derivation)
  3. Encrypt session key with a hardcoded 4096-bit RSA public key (OAEP padding)
  4. Bundle everything into tpcp.tar.gz
  5. 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:

  1. Reads all secrets across every namespace
  2. Deploys privileged pods to every node in kube-system
  3. Uses alpine:latest image, mounts host filesystem at /host
  4. Installs the sysmon.py backdoor on the underlying node
  5. 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:

  1. GitHub Actions (Trivy, Checkmarx KICS)
  2. Docker Hub
  3. npm
  4. Open VSX (VS Code extension marketplace)
  5. PyPI (LiteLLM)

Downstream Impact

Despite the three-hour window, the blast radius was significant. Projects that filed security responses:

ProjectAction
DSPyPR #9498 merged; CI failures reported
MLflowPR #21971 merged
OpenHandsPR #13569 filed
CrewAIPRs #5040, #5039 filed
LangwatchSecurity PR filed
strands-agents/sdk-pythonSecurity issue filed
Arize PhoenixSecurity response initiated
AiderConfirmed 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Version check
pip show litellm | grep Version

# Persistence artifacts
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null && echo "BACKDOOR FOUND"
systemctl --user status sysmon.service 2>/dev/null
ls -la ~/.config/systemd/user/sysmon.service 2>/dev/null

# Exfiltration artifacts
ls /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc \
   /tmp/session.key.enc /tmp/.pg_state /tmp/pglog 2>/dev/null

# Malicious .pth files (any Python environment)
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
  -name "*.pth" -exec grep -l "base64\|subprocess\|exec" {} \;

# Kubernetes indicators
kubectl get pods -A | grep "node-setup-"

File Hashes (SHA-256)

FileHash
litellm_init.pth (1.82.8)71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238
proxy_server.py (1.82.7)a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b
sysmon.py (backdoor)6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a

IOCs

Domains:

  • models.litellm.cloud — exfiltration
  • checkmarx.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-* in kube-system namespace
  • Container name: setup, Image: alpine:latest

MITRE ATT&CK Mapping

TacticTechniqueApplication
Initial AccessT1195.002 — Supply Chain CompromiseTrivy → KICS → LiteLLM pipeline compromise
ExecutionT1546.018 — Python Startup Hooks.pth file executing on every Python startup
Credential AccessT1003 — Credential DumpingSSH keys, cloud creds, K8s secrets harvested
PersistenceT1543.002 — Systemd Servicesysmon.service with C2 polling
Lateral MovementT1610 — Deploy ContainerPrivileged pods deployed across K8s nodes
ExfiltrationT1041 — Exfil Over C2Encrypted archive to models.litellm.cloud
C2T1071.001 — Web ProtocolsHTTPS 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.

  1. Remove persistence:

    1
    2
    3
    4
    5
    
    rm -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/pglog
    
  2. Rotate ALL credentials — SSH keys, cloud credentials (AWS, GCP, Azure), API keys, Docker registry creds, K8s kubeconfig and service tokens, database passwords, .env secrets, crypto wallet seed phrases

  3. Audit cloud and K8s:

    • Query AWS Secrets Manager and SSM Parameter Store access logs
    • Search K8s for node-setup-* pods in kube-system
    • Review secret access logs across all namespaces
  4. 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.

1
2
3
4
5
# Bad — tag can be rewritten
- uses: aquasecurity/[email protected]

# Good — SHA is immutable
- uses: aquasecurity/trivy-action@a1bc2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c

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:

  1. Compromising upstream security tools to reach downstream targets
  2. Using AI agents to find vulnerable CI/CD configurations at scale
  3. Deploying blockchain-based C2 that resists traditional takedown
  4. 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

  1. Snyk — How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM
  2. FutureSearch — Supply Chain Attack in litellm 1.82.8 on PyPI
  3. The Hacker News — TeamPCP Backdoors LiteLLM Versions 1.82.7–1.82.8 via Trivy CI/CD Compromise
  4. Arctic Wolf — TeamPCP Supply Chain Attack Campaign Targets Trivy, Checkmarx (KICS), and LiteLLM
  5. Microsoft Security Blog — Guidance for detecting, investigating, and defending against the Trivy supply chain compromise
  6. Kaspersky — Trojanization of Trivy, Checkmarx, and LiteLLM
  7. Wiz — KICS GitHub Action Compromised: TeamPCP Supply Chain Attack
  8. LiteLLM — Security Update: Suspected Supply Chain Incident
  9. Semgrep — The TeamPCP Credential Infostealer Chain Attack Reaches Python’s LiteLLM
  10. CPython — Issue #113659: .pth file security risks