Commissioned, Curated and Published by Russ. Researched and written with AI.
What’s New This Week
The TeamPCP supply chain campaign – responsible for the Trivy GitHub Actions and Docker Hub compromises earlier in March 2026 – has pivoted to PyPI. LiteLLM versions 1.82.7 and 1.82.8 contain a malicious .pth file that abuses a Python interpreter mechanism to execute a credential stealer on every Python startup, before any user code runs. The payload collects SSH keys, cloud credentials, Kubernetes tokens, database passwords, shell history, and crypto wallets, then exfiltrates everything encrypted to an attacker-controlled domain designed to look like LiteLLM’s official infrastructure. If you have either version installed anywhere – including CI/CD runners – assume full credential compromise and rotate everything.
Changelog
| Date | Summary |
|---|---|
| 24 Mar 2026 | Initial writeup covering the .pth mechanism, full payload analysis, and remediation steps. |
The incident
On March 24, 2026, LiteLLM versions 1.82.7 and 1.82.8 on PyPI were found to contain a malicious .pth file: litellm_init.pth, 34,628 bytes. The file is listed openly in the wheel’s own RECORD manifest:
litellm_init.pth,sha256=ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg,34628
GitGuardian confirmed this is the same TeamPCP infostealer used in the Trivy campaign. The Trivy timeline: a CI compromise in late February 2026 stole a Personal Access Token. Partial remediation on March 1 left credentials insufficiently rotated. On March 19, the attacker reused still-valid credentials to weaponise 75 of 77 trivy-action tags. On March 24, the campaign pivoted to PyPI.
This is the same payload, the same encryption scheme, the same exfiltration infrastructure – deployed via a completely different ecosystem.
Why .pth files are an ideal delivery mechanism
Python’s .pth mechanism is a site-packages feature most developers never think about. Any file with a .pth extension placed in site-packages/ is processed automatically by the Python interpreter at startup. No import statement. No entry point. No __init__.py. The interpreter just runs it – every time, for every Python process on the system.
This means the attack does not require import litellm anywhere in your codebase. It does not require your code to do anything at all. It executes before your application code starts. It executes in CI/CD runners the moment Python initialises. It executes in containers, virtualenvs, any environment where the malicious LiteLLM wheel was installed.
The .pth file survives pip uninstall of packages that don’t clean up properly. It survives virtual environment recreation if the base installation is affected. It is persistent by design, because the .pth mechanism was designed for legitimate path manipulation – a perfectly reasonable feature that becomes a clean rootkit delivery path.
What the payload does
The payload in litellm_init.pth is double base64-encoded. When decoded, it executes a two-stage infostealer.
Stage 1 – Collection
The malware builds a complete picture of the compromised environment:
System identity
- Hostname, full
uname -aoutput, network interface configuration (ip addr), routing table (ip route) - All environment variables via
printenv
Credentials and keys
- SSH keys:
~/.ssh/id_rsa,id_ed25519,id_ecdsa,authorized_keys,known_hosts,config - Git credentials:
~/.gitconfig,~/.git-credentials - AWS:
~/.aws/credentials,~/.aws/config, IMDS token and security credentials from the instance metadata service - Kubernetes:
~/.kube/config,/etc/kubernetes/*.conf, service account tokens - GCP:
~/.config/gcloud/application_default_credentials.json - Azure: the full
~/.azure/directory - Docker:
~/.docker/config.jsonand/kaniko/.docker/config.json(explicitly targeting Kaniko CI builds) - Package managers:
~/.npmrc,~/.vault-token,~/.netrc
History and context
- Shell history:
~/.bash_history,~/.zsh_history
Crypto wallets
~/.bitcoin/,~/.ethereum/keystore/,~/.config/solana/and other wallet paths
Infrastructure secrets
- SSL/TLS private keys:
/etc/ssl/private/, Let’s Encrypt.pemand.keyfiles - CI/CD configuration:
terraform.tfvars,.gitlab-ci.yml,.travis.yml,Jenkinsfile,.drone.yml - Database credentials: PostgreSQL, MySQL, Redis, and LDAP configuration files
This is not a targeted collection. This is a sweep of every credential path that matters in a modern engineering environment. A single affected developer machine hands over everything needed to impersonate that engineer across every system they touch.
Stage 2 – Encryption and exfiltration
The payload does not transmit in the clear:
- Generates a random 32-byte AES-256 session key via
openssl rand - Encrypts the collected data with AES-256-CBC and PBKDF2 key derivation
- Encrypts that session key with a hardcoded 4096-bit RSA public key using OAEP padding
- Packs everything into
tpcp.tar.gz - Exfiltrates via
curl POSTtohttps://models.litellm.cloud/
That last point deserves attention. The exfiltration domain is litellm.cloud – not litellm.ai, which is LiteLLM’s official domain. The subdomain models.litellm.cloud is designed to look like a legitimate LiteLLM API endpoint. Network monitoring tools that look for obvious red flags will see HTTPS traffic to what appears to be the vendor’s own infrastructure.
The RSA encryption means the stolen data cannot be decrypted without the attacker’s private key. There is no way to recover or examine what was taken by analysing network traffic.
Scope and exposure
LiteLLM is a widely used Python library for normalising calls across different LLM providers – OpenAI, Anthropic, Azure, Bedrock, Vertex AI, and others. It is common in AI application development, internal tooling, and CI/CD workflows that interact with language model APIs. The library has broad adoption precisely in the kinds of environments that hold high-value cloud credentials.
Affected versions: 1.82.7 and 1.82.8 on PyPI.
Any environment that installed either version – developer workstation, CI/CD runner, container, serverless function – should be treated as compromised. The .pth mechanism runs before user code, so the exposure begins at install time, not at first use.
What to do
Step 1: Check your version
pip show litellm
If the output shows Version: 1.82.7 or Version: 1.82.8, you are affected.
Step 2: Assume full credential compromise
If either version is installed, do not wait for forensic confirmation. The payload runs silently at Python startup. There is no log entry, no error, no indication it ran. Treat the machine as fully compromised and rotate everything:
- SSH keys (all of them in
~/.ssh/) - AWS, GCP, and Azure access keys and service account credentials
- Kubernetes service account tokens
- API keys for any service
- Database passwords
- CI/CD secrets
- npm publish tokens
- Any credential stored in the paths listed above
Step 3: Remove the affected version
pip uninstall litellm
Then upgrade to a clean version:
pip install litellm --upgrade
Verify the installed version is not 1.82.7 or 1.82.8.
Step 4: Treat any affected CI/CD runner as fully compromised
If a runner installed either version during a pipeline execution, that runner had its Python startup context hijacked. Treat it the same as a developer machine. Rotate all secrets the runner had access to.
Step 5: Audit .pth files in site-packages
find $(python -c "import site; print(' '.join(site.getsitepackages()))") -name "*.pth" 2>/dev/null
Inspect any .pth file you do not recognise. Legitimate .pth files are short path entries, not kilobytes of encoded content.
Step 6: Pin future installs to hashes
pip install litellm==X.X.X --require-hashes
Hash pinning ensures that what you install matches what you audited. It does not help after a compromise, but it prevents this class of attack going forward. Use pip-compile with --generate-hashes to maintain a verified lockfile.
The pattern
The Trivy compromise (GitHub Actions, Docker Hub, npm), and now LiteLLM (PyPI). Same infostealer. Same encryption scheme. Same exfiltration infrastructure design. Different ecosystems, different attack surfaces, same team.
What links these targets is the same logic as the Trivy attack: compromise tools that sit in privileged positions within the development and deployment pipeline. Security teams adopted Trivy because it was a trusted scanner. AI teams adopted LiteLLM because it is a trusted LLM abstraction layer. Both are deeply integrated into the workflows they serve. Both run with access to the credentials those workflows use.
The payload’s collection list is telling. It is not a speculative sweep – it is a precise map of everything a Python developer working with cloud infrastructure, Kubernetes, and AI APIs is likely to have on their machine or in their CI/CD environment. Someone put significant time into building this target list.
The .pth delivery mechanism is particularly well-chosen for this attack. It bypasses import-time scanning, executes before application code, and is obscure enough that most developers have never audited their site-packages for .pth files they did not install intentionally. The mechanism itself is legitimate and necessary; it is not a vulnerability in Python. It is just a feature that can be abused.
If you are running LiteLLM, check your version now.
Sources
- GitHub issue report: BerriAI/litellm#24512
- GitGuardian analysis: Trivy’s March supply chain attack shows where secret exposure hurts most