Tech 4 min read

TeamPCP poisoned the LiteLLM PyPI package and embedded malware that steals more than 50 kinds of credentials

IkesanContents

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

VersionTriggerMalware locationExfiltration domain
1.82.7When import litellm.proxy runsEmbedded in proxy_server.pycheckmarx.zone/raw
1.82.8At Python startup (no import needed)litellm_init.pth + proxy_server.pymodels.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.

CategoryTargets
Systemhostname, whoami, uname -a, ip addr
Environment variablesprintenv (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 walletsBitcoin, Ethereum, Solana, and more
CI/CDterraform.tfvars, .gitlab-ci.yml, Jenkinsfile

Encryption and theft

  1. Write the collected data to a temporary file
  2. Generate a 32-byte AES-256 session key with openssl rand
  3. Encrypt the data with openssl enc -aes-256-cbc -pbkdf2
  4. Encrypt the session key with an attacker-owned 4096-bit RSA public key
  5. Bundle everything into tpcp.tar.gz and exfiltrate it with curl 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

ItemValue
Filelitellm_init.pth (presence in site-packages indicates compromise)
SHA256ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg
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 domainscan.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.