Supply Chain Attack via Python .pth Files — LiteLLM Case Study
How pip install alone can compromise an entire system
LiteLLM is a Python library unifying OpenAI, Anthropic, Google and other LLM providers. 95M monthly downloads. Hit by a supply chain attack in March 2026.
What Is a Supply Chain Attack?
Instead of attacking software directly, it targets libraries or build tools that software depends on. Plant malicious code in package registries like npm, PyPI, or RubyGems, and every user who installs it gets infected.
Direct attacks can be blocked by firewalls and auth. Supply chain attacks bypass existing security by disguising themselves as "normal package updates." The pip install developers run daily becomes the attack vector.
This Attack's Path
- Trivy (vulnerability scanner) v0.69.4-v0.69.6 compromised first
- PyPI credentials extracted from Trivy's CI/CD
- Stolen credentials took over LiteLLM maintainer accounts
- Malicious v1.82.7/v1.82.8 uploaded to PyPI
Code executes on install alone — no import needed.
What Are .pth Files?
Python auto-reads .pth files from site-packages/ on startup. Originally for adding package paths, but lines starting with import are executed as-is.
pip install → wheel extraction → .pth in site-packages → auto-executed on Python start.
4-Stage Payload
Stage 0 — .pth spawns subprocess
Stage 1 — RSA-4096 + AES-256-CBC encryption
Stage 2 — Credential harvest from 25+ locations: SSH keys, AWS/GCP/Azure tokens, K8s service account tokens, 11+ crypto wallets, shell history
Stage 3 — C2 polling every 50 minutes, arbitrary binary execution
K8s Cluster Takeover
Privileged pods on all nodes via operator: Exists tolerations + privileged: true. Host filesystem mounted at /host for persistent backdoors.
Impact: 19,262 dependent repos — including OpenHands, DSPy (Stanford NLP), Databricks AI.
Real-World Response — As a Trivy User
I was using trivy-action in CI/CD. Workflows actually ran during the compromise window (3/19-3/20).
Fortunately, trivy-action was pinned to a full-length commit SHA instead of a version tag. Using the SHA for v0.28.0 meant it never auto-updated to the compromised v0.69.4-v0.69.6.
For verification, I downloaded all GitHub Actions logs from 3/19-3/20 and confirmed the compromised version never executed. Even if it had, the workflow's env / secrets contained no sensitive keys (AWS credentials, PyPI tokens, etc.), so damage would have been zero.
If You Use GitHub Actions, Do This Now
Repository Settings → Actions → General → Actions permissions → Check "Require actions to be pinned to a full-length commit SHA". This makes workflows fail with an error when any action isn't SHA-pinned.
# ❌ Tag — tags can be overwritten by anyone
uses: aquasecurity/trivy-action@v0.28.0
# ✅ SHA pin — executes only this exact commit's code
uses: aquasecurity/trivy-action@a23fa65c5ff3e8d0c5f578c4b2a2b69e3b03be3b
Tags can be overwritten. SHAs can't. That difference decided the outcome here.
To bulk-convert existing workflow actions to SHA pins, pinact is handy. pinact run auto-converts all tags in .github/workflows/ files to SHAs.
How It Works
Trivy compromised → PyPI credentials extracted from CI/CD
Stolen credentials take over LiteLLM maintainer account
Malicious version with .pth file uploaded to PyPI
pip install places .pth in site-packages → auto-executes on Python start
4-stage payload: subprocess → encryption → credential harvest (25+) → C2 polling