TeamPCP poisoned the LiteLLM PyPI package and embedded malware that steals more than 50 kinds of credentials
Contents
On March 24, 2026, malicious code was embedded in LiteLLM’s PyPI packages 1.82.7 and 1.82.8 and remained public for about 46 minutes. The bug that exposed the malware was a fork bomb caused by a mistake in the malware itself.
This attack was part of TeamPCP’s multi-stage campaign that began with a supply-chain compromise of Trivy. It is phase 9 in a chain that also included the Trivy-based campaign (trivy-canisterworm-npm-supply-chain-icp-c2) and the Open VSX extension compromise (GlassWorm).
Attack flow
graph TD
A[Trivy CI/CD vulnerability<br/>2026-02-27] -->|aqua-bot PAT stolen| B[Poisoned Trivy binary<br/>v0.69.4 - 2026-03-19]
B -->|LiteLLM CircleCI installed the latest package via apt| C[PYPI_PUBLISH_PASSWORD stolen<br/>2026-03-23]
C -->|Uploaded with the krrishdholakia account| D[litellm 1.82.7 - 2026-03-24T10:39 UTC]
D --> E[litellm 1.82.8 - 2026-03-24T10:52 UTC]
E -->|Discovered through the fork bomb| F[PyPI quarantine - 2026-03-24T11:25 UTC]
F --> G[PyPA advisory PYSEC-2026-2]
LiteLLM’s CircleCI installed Trivy from apt without pinning its version. The poisoned Trivy binary (v0.69.4 and later) stole every secret during CI execution, and TeamPCP used the stolen PyPI token to upload the malicious package directly. There was no GitHub tag corresponding to v1.82.7 or v1.82.8.
How the two versions differ
| Version | Trigger | Malware location | Exfiltration domain |
|---|---|---|---|
| 1.82.7 | When import litellm.proxy runs | Embedded in proxy_server.py | checkmarx.zone/raw |
| 1.82.8 | At Python startup (no import needed) | litellm_init.pth + proxy_server.py | models.litellm.cloud |
1.82.8 is especially dangerous because it uses a .pth file. Python automatically executes .pth files placed in site-packages when the interpreter starts. That means the malware runs simply by starting Python, even if the package is never imported or used directly.
The contents of litellm_init.pth are a short launcher that spawns a Python subprocess and executes a double-base64-encoded payload.
Malware behavior
Collection
The malware gathers credentials from more than 50 locations.
| Category | Targets |
|---|---|
| System | hostname, whoami, uname -a, ip addr |
| Environment variables | printenv (all API keys, secrets, and tokens) |
| SSH | ~/.ssh/id_rsa, id_ed25519, id_ecdsa, authorized_keys, known_hosts, config |
| Git | ~/.gitconfig, ~/.git-credentials |
| AWS | ~/.aws/credentials, ~/.aws/config, IMDS tokens and temporary credentials |
| Kubernetes | ~/.kube/config, /etc/kubernetes/admin.conf, service account tokens |
| GCP | ~/.config/gcloud/application_default_credentials.json |
| Azure | ~/.azure/ |
| Docker | ~/.docker/config.json, /kaniko/.docker/config.json |
| Shell history | ~/.bash_history, ~/.zsh_history, .mysql_history |
| Crypto wallets | Bitcoin, Ethereum, Solana, and more |
| CI/CD | terraform.tfvars, .gitlab-ci.yml, Jenkinsfile |
Encryption and theft
- Write the collected data to a temporary file
- Generate a 32-byte AES-256 session key with
openssl rand - Encrypt the data with
openssl enc -aes-256-cbc -pbkdf2 - Encrypt the session key with an attacker-owned 4096-bit RSA public key
- Bundle everything into
tpcp.tar.gzand exfiltrate it withcurl POST
How the fork bomb exposed it
The .pth file is inherited by Python child processes as well. The malware used subprocess.Popen to launch Python, so the payload executed again in the child process and the number of processes exploded recursively.
Callum McMahon at FutureSearch noticed this when he updated his MCP server with uvx and LiteLLM 1.82.8 was pulled in. His machine became unresponsive at 100% CPU with about 11,000 processes. He worked with Claude AI to identify the cause and reported it to PyPI Security and the LiteLLM maintainers.
The time from public release to quarantine was only about 46 minutes.
Response and mitigations
If litellm_init.pth exists in site-packages, the machine is compromised and needs immediate review.
Docker image users are not affected because their versions are pinned.
LiteLLM (BerriAI) responded by:
- Removing the poisoned versions from PyPI
- Deleting all maintainer accounts and creating new ones
- Rotating all GitHub, Docker, and PyPI secrets
- Pinning Trivy to the safe CI version
v0.35.0 - Working with Google Mandiant on the investigation
Indicators of compromise
| Item | Value |
|---|---|
| File | litellm_init.pth (presence in site-packages indicates compromise) |
| SHA256 | ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg |
| Exfiltration domain (1.82.8) | models.litellm.cloud (registered on 2026-03-23) |
| Exfiltration domain (1.82.7) | checkmarx.zone/raw (IP: 83.142.209.11) |
| C2 domain | scan.aquasecurtiy.org (a lookalike domain), IP: 45.148.10.212 |
| Telegram | @Persy_PCP, @teampcp |
PyPA has issued official advisory PYSEC-2026-2. TeamPCP also claimed to vxunderground that it stole 54GB of data.
CI/CD lessons
This attack worked because LiteLLM’s CI installed Trivy without version pinning.
Use OIDC for PyPI publishing. Static PyPI tokens are long-lived and broad-scoped, so once stolen they can be abused for a long time. OIDC-based short-lived tokens are issued and revoked per CI run, which limits the blast radius.
Pin your tooling versions. External tools such as Trivy and Docker Buildx should be pinned in CI so that surprise upgrades do not leak secrets.