LiteLLM 1.107.0 Compromise Explained: How a .pth File Ran on Every Python Start
A poisoned LiteLLM release on PyPI and Docker stole cloud and AI credentials without ever being imported. Here is the mechanism, the timeline, and the cleanup.
On this page 9 sections
The LiteLLM 1.107.0 compromise was a malicious PyPI and Docker release, published October 21, 2026, that stole cloud, developer, and AI credentials. It used a .pth file to run at every Python startup, so the library never had to be imported. Treat any environment that installed it as compromised and rotate its secrets.
Backtrace covers the tools engineers depend on in production, and LiteLLM sits in many AI stacks as a proxy to model providers. This explainer is for developers and platform engineers who pin Python dependencies. It covers how the attack worked, why a .pth file is such a quiet hook, and what a careful cleanup looks like.
What Exactly Happened to LiteLLM 1.107.0?
Someone published a poisoned build of LiteLLM to PyPI and to the project's container registry. The code was not in the upstream GitHub source. Socket's analysis shows the container build recorded a lookalike source owner, Berri-AI, instead of the real BerriAI organization.
The PyPI wheels went live at 11:39 UTC and Socket's scanner flagged them within about seven minutes, per Socket's LiteLLM write-up. Docker tags v1.107.0, latest, main-latest, and main-stable all pointed to the affected image by around 13:14 UTC. PyPI has since removed the release.
The entry point looks like stolen access rather than a code bug. The Hacker News reported that the malicious actions went through three engineers' GitHub accounts, apparently using stolen GitHub CLI OAuth tokens. One account triggered the release run and another approved it. LiteLLM has not finished tracing the original vector.
Why Does a .pth File Run Without an Import?
Python reads every .pth file in site-packages at startup. Most contain plain directory paths. A line that begins with import is executed as code, which makes the file a startup hook that needs no import of your library.
The attackers shipped three files in the package's purelib directory: cook.pth, cook.py, and sitecustomize.py. The .pth file holds a single line that imports cook. From then on, running python, pip, or a test runner in that environment can trigger the payload. This is why uninstalling later does not help for secrets that already left.
A .pth hook also dodges many of the checks teams rely on. Code review of your application will not show it. Import-time scanners will not see it. Only a scan of the installed wheel contents, or a diff of site-packages, exposes it.
What Does the Malware Steal and Where Does It Send It?
The first payload is a credential stealer with 16 collection modules. It reaches for cloud credentials across AWS, Azure, GCP, Kubernetes, Vault, and Terraform. It also takes Git and SSH keys, .env files, package registry configs, browser data, and environment variables.
AI tooling is a target as well. The stealer looks for auth files from tools such as Codex, Claude, Gemini, and Cursor. If a DATABASE_URL variable is set, it queries the LiteLLM proxy's saved credentials and model configuration tables, which often hold provider API keys.
Results are encrypted with AES-256-CBC, wrapped with an RSA-4096 key, packed into a tar archive, and posted to a litellm[.]dev endpoint named /healthcheck. The name is meant to look boring in egress logs.
The second payload is a backdoor. A background process reads a domain from a file in a GitHub repository, then downloads Python from that domain and runs it with no signature check. It retries about every hour. That makes it an open door, not just a one-time theft.
How Do You Check Whether You Installed It?
Search for the version first, then for the files. A version match alone is enough to treat the host as compromised, because the payload runs at startup. This list covers where to look.
- Search lockfiles, caches, build logs, and container layers for litellm==1.107.0.
- Look for cook.pth, cook.py, or sitecustomize.py in the site-packages directory of any affected environment.
- Check for the lock file /tmp/remote_code_loader.lock. An absent marker does not prove a host is clean.
- Compare running container digests against the published bad digests, including floating tags.
- Review egress logs for requests to the /healthcheck path and to raw.githubusercontent.com paths tied to the loader.
Use trusted tooling to inspect the machine. Avoid running the affected environment's own Python or pip first, since either one can re-trigger the payload.
| Time (UTC, Oct 21) | Event |
|---|---|
| 10:51 to 11:06 | Malicious container build runs from a lookalike source |
| 11:39 | Wheels published to PyPI |
| 11:46 | Socket's scanner flags the wheels |
| 13:14 | Floating Docker tags confirmed on the affected image |
What Should You Do After a Match?
Isolate first, preserve evidence, then rebuild and rotate. The order matters because rotating secrets on a host that is still running the backdoor just hands the attacker the new ones.
Block litellm==1.107.0 in your package index or lockfile policy. Isolate any host or container where Python ran after the install. Rebuild from a reviewed artifact, and pin container images by digest during recovery. Both Socket and Upwind recommend 1.106.2 as the safe version.
Then rotate everything the process could read, from a clean machine. Our guide to API key rotation best practices covers a safe order. If you handled a similar incident last month, the playbook in our Shai-Hulud response guide also applies: check builds, then rotate.
Finally, audit cloud, identity, repository, and registry logs for any use of the exposed credentials. Look for logins from new locations, new access keys, and unusual image pulls. One unrotated token is enough for the attacker to keep their foothold.
Why Are AI Gateway Packages Such Attractive Targets?
A LiteLLM proxy holds the keys to several model providers in one place. A single database table can contain provider API keys for OpenAI, Anthropic, Google, and others. That makes one compromised install far more valuable than a typical utility library.
These packages also run in odd places. A proxy often sits in a shared cluster, a CI job, and a developer laptop at once. Each of those locations has a different set of secrets nearby, so one poisoned release reaches cloud roles, deploy tokens, and personal credentials in a single sweep.
There is a pricing angle too. Stolen model-provider keys can be resold or burned through quickly, and the bill lands on the victim. After any exposure, check provider dashboards for spend spikes, and set hard usage limits on every key you issue. A limit turns a stolen key from an open tab into a capped annoyance.
Teams that treat the AI layer as plumbing tend to skip the controls they apply elsewhere. Run the proxy with its own service account, mount only the secrets it needs, and keep its database credentials away from the application containers that sit next to it. The same review you give a payments service belongs here as well.
How Can Teams Reduce Exposure to the Next One?
Pin versions and verify them. Lockfiles with hashes stop a new release from sliding into a build unnoticed. A delay window, where builds refuse packages newer than a day or two, would have avoided this release entirely, since PyPI removed it within hours.
Separate release credentials from dependency installation, and run builds with the narrowest tokens possible. The background in our supply chain attacks explainer lays out why build pipelines are such attractive targets. I would also keep long-lived cloud keys off developer laptops, because the stealer reads dotfiles first.
Finally, watch egress. A build container that talks only to your registry and your package mirror cannot post an encrypted archive to a random domain. Blocking unknown outbound traffic from CI turns many stealers into harmless noise.
What Does This Say About Release Pipelines?
The incident shows that a trusted publisher link is only as strong as the accounts behind it. When a release run can be triggered by one engineer and approved by another, stealing two sets of tokens is enough. LiteLLM revoked the tokens involved and removed trusted publishing links to its GitHub organization while it investigates.
Maintainers can take three concrete steps. Restrict builds to approved repository identities and revisions. Require a human approval from a different person on every release. Keep release credentials in a separate environment from anything that installs third-party dependencies.
Consumers have a matching job. Verify that the source recorded in a build's provenance matches the organization you expect, because a one-character lookalike owner is easy to miss. Add that check to any pipeline that pulls images from a public registry.
Conclusion: Treat the Version as the Verdict
The practical answer is blunt. If 1.107.0 ever landed in an environment, assume the credentials in reach were taken, remove and rebuild the environment, and rotate from a clean machine. Pin to 1.106.2 or later by digest.
The wider lesson is about trust in startup hooks. Backtrace's editors suggest adding a site-packages diff to your build checks, so the next surprise .pth file fails a pipeline instead of reaching production.