· 5 min read ·

When Your LLM Proxy Steals Your LLM Keys: The litellm Supply Chain Attack

Source: simonwillison

A malicious package slipped into litellm version 1.82.8, discovered and reported by Simon Willison. The payload was a file called litellm_init.pth bundled into the package distribution, designed to steal credentials from anyone who installed that version. The irony is sharp: litellm is a library whose entire purpose is to give you a single interface to every major LLM provider, which means its users tend to have API keys for OpenAI, Anthropic, Cohere, Mistral, and a dozen other services all sitting in their environment at once. If you wanted to target developers holding valuable AI credentials, poisoning litellm is an efficient way to do it.

The mechanism the attacker chose, the .pth file, deserves a close look, because it is one of the more underappreciated execution vectors in the Python ecosystem.

How .pth Files Execute Code

Python’s site module, which runs automatically when the interpreter starts, processes every .pth file it finds in site-packages. The documented purpose of .pth files is path manipulation: each line is supposed to be a directory path to append to sys.path, allowing packages to extend Python’s module search path without modifying the environment directly.

The dangerous edge case is that lines beginning with import are executed via exec() rather than treated as paths. This was originally intended to allow namespace packages and complex distributions to perform setup at interpreter startup. In practice, it means any .pth file installed into site-packages can run arbitrary Python code before your application begins, before any virtual environment guards kick in, before any user-level code has a chance to intervene.

A minimal malicious .pth file looks like this:

import litellm_init

If litellm_init.py exists in site-packages alongside it, Python will import and execute it the moment the interpreter starts. The name is chosen to look like legitimate initialization machinery for the package itself. Without knowing what to look for, a developer inspecting their site-packages directory would likely skim past it.

This is not a new technique. The .pth execution behavior has been documented in Python security discussions for years, and it has appeared in previous supply chain attacks. The NSPX30 implant and various academic red-team writeups have demonstrated persistence mechanisms using this exact approach. What makes it reappear repeatedly is that it works reliably, survives virtual environment recreation if the base interpreter is compromised, and is rarely audited.

The Target: LLM API Keys

The credential profile of a litellm user is unusually rich. The library supports over 100 LLM providers, and its common usage pattern involves setting environment variables for all the providers you might want to route to:

export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export COHERE_API_KEY=...
export AZURE_API_KEY=...
export HUGGINGFACE_API_KEY=...

A credential-stealing payload targeting litellm users would walk os.environ, filter for known API key patterns, and exfiltrate the results to an attacker-controlled endpoint. Modern LLM API keys are not just authentication tokens; they carry billing implications that can reach thousands of dollars per day if abused for training data generation or resale. OpenAI keys in particular have a secondary market for prompt injection attacks and model abuse at scale.

Beyond LLM keys, developers running litellm in production often have AWS credentials, database connection strings, and other infrastructure secrets in the same environment. The blast radius of a successful exfiltration extends well beyond the AI toolchain.

Supply Chain Pressure on Fast-Moving Packages

LiteLLM’s release cadence is aggressive. The project has published hundreds of releases across a relatively short lifespan, driven by the rapid pace of new model releases and provider API changes. That cadence creates pressure on both maintainers and the review processes that surround package publication.

PyPI does not perform code review before publication. The trust model is entirely based on account security: whoever controls the PyPI credentials for a package can publish whatever they want under that package name. This makes account compromise the most common vector for supply chain attacks on PyPI. The PyPI security incident timeline includes several high-profile cases where attacker-controlled releases were published under legitimate package names after account takeover.

The litellm_init.pth naming is worth noting as a social engineering detail. It mimics the internal conventions of a real package, making it plausible to someone doing a cursory audit. A less careful attacker might have named the file something obviously unrelated to litellm, which would have been caught faster.

Detection and Response

If you had litellm 1.82.8 installed, the immediate steps are:

  1. Rotate every API key that was present in your environment during any Python process that ran while that version was installed. This means not just the keys you used with litellm, but everything in os.environ at interpreter startup.

  2. Audit your site-packages directory for unexpected .pth files. On a clean installation you can list them with:

python -c "import site; print(site.getsitepackages())"
ls $(python -c "import site; print(site.getsitepackages()[0])")/*.pth
  1. Check for unexpected .py files that share names with .pth files in that directory.

  2. Review any outbound network connections made during Python startup if you have network logging available.

Upgrading to a clean version of litellm removes the malicious files, but the credentials that were present in your environment while the compromised version was active should be considered leaked regardless.

Auditing .pth Files Systematically

The broader lesson is that .pth files in site-packages deserve the same scrutiny as installed executables or conftest.py files. Most Python developers do not habitually inspect them.

You can audit all .pth files for executable content with a simple script:

import site
import os

for sitedir in site.getsitepackages() + [site.getusersitepackages()]:
    if not os.path.isdir(sitedir):
        continue
    for fname in os.listdir(sitedir):
        if not fname.endswith('.pth'):
            continue
        fpath = os.path.join(sitedir, fname)
        with open(fpath) as f:
            for i, line in enumerate(f, 1):
                line = line.strip()
                if line.startswith('import '):
                    print(f"{fpath}:{i}: {line}")

Any import line in a .pth file warrants inspection. Legitimate uses exist, mostly in editable installs and namespace packages, but they are rare and the module being imported should be clearly associated with the package that owns the .pth file.

Tools like pip-audit and safety check known vulnerability databases but do not inspect installed file contents for suspicious patterns. The gap between “version is known-bad” and “installed files are behaving maliciously” is where this class of attack lives. Filling that gap requires either hash-based verification of installed files against a trusted manifest, or runtime monitoring of outbound connections during interpreter startup.

The Broader Context

This incident is part of a pattern that has accelerated alongside the AI tooling boom. Packages with large, technically sophisticated user bases that accumulate high-value credentials are attractive targets. The socket.dev supply chain attack reports for 2025 and into 2026 show a measurable uptick in AI and ML tooling as targets, precisely because the credential value per compromised developer is higher than in most other ecosystems.

Python’s .pth execution semantics are unlikely to change given backward compatibility constraints, but there have been proposals to restrict the import execution behavior in .pth files or require explicit opt-in. Until something changes at the interpreter level, developers working with AI tooling have a concrete reason to treat their Python environments, and the credentials they expose to those environments, with the same care they would apply to production server access.

Was this interesting?