When Your LLM Router Steals Your API Keys: The litellm .pth Injection
Source: simonwillison
Supply chain attacks against Python packages are not new, but the discovery of a credential-stealing .pth file inside litellm 1.82.8 is a sharper reminder than usual. LiteLLM is the library you reach for when you want a single interface across OpenAI, Anthropic, Azure, AWS Bedrock, Cohere, and a hundred other model providers. That means the environments where it runs are saturated with high-value API keys. An attacker who understands that fact and knows their Python internals can do a lot of damage with a small piece of code.
What a .pth File Actually Does
To understand why this attack is effective, you need to understand how Python’s site module works. When the interpreter starts, site.py scans every directory on sys.path for files with the .pth extension. Most lines in a .pth file are treated as directory paths to append to sys.path. But any line that begins with import is treated differently: it is executed directly, as a statement, before any user code runs.
This means a file like litellm_init.pth sitting in site-packages with content such as:
import litellm_init
…causes Python to import litellm_init on every single interpreter invocation in that environment. Not just when you import litellm. Not just when you run a script that touches LLM APIs. Every python call. Every subprocess. Every pip invocation. Every test runner. The code executes unconditionally and silently, before your script has had a chance to do anything.
The companion module, also installed by the malicious package, then does what credential stealers do: read environment variables and POST them to an external endpoint. In an environment configured for litellm, the variables in scope typically include OPENAI_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, COHERE_API_KEY, and whatever else the operator has configured for their model providers.
Why litellm Is a Particularly Good Target
Most credential-stealing supply chain attacks go after broad targets: packages with tens of millions of downloads per month, used across many domains. The attacker casts a wide net hoping to catch some AWS keys among the noise.
LiteLLM is more surgical. Its install base skews heavily toward teams actually running AI infrastructure. These environments have:
- Multiple high-value API keys, often with significant spending limits or attached to production workloads
- Deployment patterns that frequently use
.envfiles or environment variable injection, making secrets reliably available at process start - Automated pipelines, Kubernetes pods, and CI runners that re-execute Python constantly, giving a
.pthpayload many opportunities to fire - Operators who may be moving fast on AI infrastructure and reviewing package updates less carefully than they would core dependencies
A single stolen ANTHROPIC_API_KEY with a high rate limit can cost a victim thousands of dollars in model inference before they notice. A stolen AWS key in a production environment has an even higher blast radius. The targeting here is not accidental.
The .pth Vector Is Stealthier Than It Looks
The .pth file mechanism is effective partly because of how invisible it is. When a developer audits a suspicious package, they typically look at the Python files: __init__.py, modules, any scripts in bin/ or console_scripts. The .pth file does not show up in a cursory pip show or even in most IDE dependency explorers. It sits in site-packages looking like a path configuration artifact.
The execution also leaves a shallow footprint. There is no new process to observe, no unusual import visible in sys.modules after the fact (if the module cleans up after itself), and no modification to user code. If the exfiltration uses a common HTTPS endpoint with a plausible-looking domain, it may not even stand out in network logs.
Compared to other supply chain techniques like patching a widely-imported stdlib wrapper or injecting into __init__.py, the .pth approach has the advantage that it survives certain cleanup operations. If a developer does pip uninstall litellm but does not also verify the site-packages directory is clean, the .pth file and its companion module can persist.
You can check for unexpected .pth files in your environment with:
python -c "import site; print(site.getusersitepackages()); print(site.getsitepackages())"
Then inspect those directories for .pth files you did not intentionally install:
find /path/to/site-packages -name '*.pth' | xargs grep -l '^import'
Any .pth file with import lines that you cannot trace to a specific, trusted package is worth investigating immediately.
This Fits a Documented Pattern
The .pth injection technique has appeared in documented PyPI supply chain incidents before. The 2022 ctx package compromise used environment variable exfiltration with a similar payload structure. The SSCP family of attacks showed attackers using Python package publication as a delivery mechanism for persistent backdoors.
What has changed is the target profile. Earlier campaigns went after generic developer environments hoping to find cloud credentials. The litellm incident targets an audience that is, almost by definition, already holding keys to production LLM deployments. The economics of that shift are straightforward: the same attack infrastructure yields higher-value credentials per compromised machine.
The AI tooling ecosystem is also growing faster than its security posture is maturing. Teams stand up model routers, proxy servers, and orchestration pipelines in weeks, often pulling the latest versions of rapidly-iterating packages without pinning dependencies. LiteLLM itself ships multiple patch releases per week given the pace of the LLM API landscape. That update velocity is useful for staying current with new model providers, but it also means developers are conditioned to accept frequent version bumps as normal, which lowers the psychological barrier to installing a malicious release.
Version Pinning Is Not Enough
The instinct after an incident like this is to pin your dependency versions. That is necessary but not sufficient. A pinned litellm==1.82.7 protects you from the specific malicious version, but version pinning does not help if:
- The malicious version is the one in your lock file because you upgraded before the attack was discovered
- A transitive dependency was compromised instead
- The attacker targets a version you are already running by compromising the package maintainer account and pushing a new release to an existing version number (which PyPI does allow in some circumstances)
More robust mitigations layer additional controls:
Network egress filtering in production and CI environments prevents the exfiltration step even if malicious code executes. A .pth payload that cannot reach its C2 endpoint does nothing useful for the attacker.
Environment variable scoping limits which processes see which credentials. A model inference service does not need AWS_SECRET_ACCESS_KEY if it only calls OpenAI. Compartmentalizing credentials by service means a single compromised process cannot harvest everything.
Supply chain scanning tools like pip-audit, Dependabot, and Socket.dev catch known malicious packages and flag suspicious behaviors in new releases. Socket specifically analyzes package diffs for behaviors like network calls in install hooks, which can catch some of these attacks before a human reviews them.
Virtual environment isolation does not directly prevent .pth attacks (the files still execute within the venv), but it does prevent a compromised package in one project from affecting the system-wide Python installation.
The Broader Problem
The fundamental tension here is that the Python packaging ecosystem’s openness is one of its strengths. Anyone can publish to PyPI. Packages can be updated rapidly. Dependencies can be pulled automatically. This is why the ecosystem has grown to the scale it has.
Those same properties make it a reliable target for supply chain attacks. PyPI’s security team has improved response times for malicious package removal significantly in recent years, and tools like Sigstore/sigstore-python provide a path toward signed package provenance. But the gap between attack surface and defensive coverage remains wide, and AI tooling is squarely in the crosshairs.
For teams running litellm or similar infrastructure, the immediate action is to audit your site-packages for unexpected .pth files, rotate any API keys that may have been exposed in affected environments, and verify you are running a clean version. The longer-term action is to treat your LLM proxy infrastructure with the same security rigor you would apply to anything else sitting in front of production credentials, because that is exactly what it is.