top of page

LiteLLM Was Poisoned Again, Through a Different Door. The Bad Release Was Gone Eight Hours Before the Advisory Named It.

Writer: Patrick Duggan
Patrick Duggan
56 minutes ago
6 min read

If you run LiteLLM, check today whether version 1.107.0 ever touched your machines. On October 21, someone used stolen developer tokens to run BerriAI's own release workflow and published a backdoored litellm 1.107.0 to PyPI and to Docker Hub and GitHub's container registry. The PyPI release was live from 11:39 to about 13:23 UTC. The Docker tags latest, main-latest and main-stable pointed at the infected build from 11:46 to 14:20 UTC. If you installed or pulled in those windows, assume your credentials are gone, and rotate them from a clean machine. The last clean release is 1.106.2.


This is the second time this year. In March it was versions 1.82.7 and 1.82.8. Same package, a different way in.





Who found it


Credit goes to two teams that did the primary research and published it the same day: StepSecurity and Datadog Security Labs. Everything below about how the attack worked comes from their write-ups. We did not discover any of it.



What the backdoor does


The release drops three files into Python's site-packages folder: cook.pth, cook.py and sitecustomize.py. A .pth file runs every time Python starts, so the malware runs even if your code never imports LiteLLM. Running pip or python again re-triggers it.


cook.py is a credential stealer. Both write-ups list the same kinds of targets: cloud credentials, SSH and Git keys, npm and PyPI tokens, browser passwords and cookies, AI coding tool sessions, and, if the LiteLLM proxy has a database configured, the upstream provider keys it manages. That last one matters for a gateway. LiteLLM exists to hold your API keys for many model providers in one place. The stolen bundle is encrypted and sent to www.litellm[.]dev/healthcheck.


sitecustomize.py is a backdoor that checks a GitHub-hosted file every hour for an address and runs code fetched from it. Datadog says the values it observed were test data, so no second stage was seen.


Uninstalling the package does not remove these files. Delete all three by hand.



How they got in


According to StepSecurity, citing BerriAI's post mortem, it started with an unexpected user account on an internally exposed LiteLLM deployment at BerriAI. The cause is not confirmed; a recently patched vulnerability is suspected. GitHub CLI login tokens for three developers ended up in that deployment's logs. Then, on October 21:


  • 10:08 UTC: branch protection rejected an unauthorized change to the private release workflow, and five seconds later an organization owner's token overrode it and pointed the release at a lookalike repository, Berri-AI/litellm, one hyphen away from the real BerriAI/litellm.

  • 11:11 UTC: a second developer's credential passed the production approval gate.

  • 11:39 to 11:40 UTC: seven wheels reached PyPI, alongside a tampered source archive.

  • 11:46 UTC: the infected Docker images went up.

  • 11:54 UTC: a third developer's credential deleted the staging and production run records.

Datadog's account agrees on the stolen tokens and the lookalike fork, and says it was not yet clear how the credentials were first compromised. Neither team names an actor. Datadog writes that no connection to the March compromise has been established.


The detail worth sitting with: after March, BerriAI moved releases to a private repository, adopted PyPI Trusted Publishing and signed its images, per StepSecurity. Those were the right moves. The attacker did not break them. They used the release workflow itself, with real tokens, so the result was a properly published, properly signed bad release.



The first time, in March


On March 24, versions 1.82.7 and 1.82.8 were pushed to PyPI. The chain ran through Trivy: LiteLLM's CI pipeline installed a poisoned version of the scanner, and that is how TeamPCP got publishing access. We mapped that chain on April 1 in One Actor, Three Supply Chains, and in August we wrote up the one word we got wrong when the "Trivy, not LiteLLM" story ran. By September the LiteLLM proxy had collected three entries in CISA's Known Exploited Vulnerabilities catalog, the third an MCP authentication bypass. If the suspected entry point in October does turn out to be a patched LiteLLM bug, the gateway was used to poison the gateway. That is not confirmed, and we are not claiming it.


So, twice in seven months: once through a build tool in CI, once through developer tokens and the release workflow. Different doors, same house.



What to do now


  1. Search for 1.107.0. Check lockfiles, requirements files, pip and uv caches, CI logs, virtual environments and container images for litellm 1.107.0. Wheel and archive hashes are listed below.

  2. Check what you pulled by tag. If anything pulled litellm/litellm or the berriai images on GHCR (litellm, litellm-database, litellm-non_root) by the latest, main-latest or main-stable tag between 11:46 and 14:20 UTC on October 21, treat it as infected, even though the tags now point at clean builds. A tag is a pointer; what you got is whatever it pointed at when you pulled.

  3. Clean up by hand. Delete cook.pth, cook.py and sitecustomize.py from site-packages, then rebuild the environment or image.

  4. Rotate from a clean machine. Cloud keys, SSH keys, Git and package-registry tokens, AI tool sessions, and every provider key your LiteLLM proxy held. Then check your providers' logs for use of those keys since October 21.

  5. Pin what you can't afford to re-check. Pin the Python package by hash and the image by digest, not by tag. Consider a waiting period before adopting new releases; some installers, such as uv, can ignore anything published after a date you choose. A two-hour window does not survive a one-day wait.


Indicators


From StepSecurity and Datadog. All 17 are now in our indicator index under the source manual-batch-litellm-1107-2026-10, credited to them, at confidence 90 (domains, URL, wheels) or 85 (source archive and payload files).


Network: litellm[.]dev, www.litellm[.]dev, and the exfiltration URL hxxp://www.litellm[.]dev/healthcheck. StepSecurity reports the domain was registered on October 13 at 00:45 UTC, eight days before the release.






What our tools would and would not have caught


This is the part we would rather not write.


The feed was late. Before today we held none of these indicators. They went in about a day after the two write-ups. That is distribution, not detection, and the credit belongs to the people who found them.


The package check could not have helped during the window. Our pre-install check is version-aware and reads OSV's malicious-package records. The record for LiteLLM, MAL-2026-2144, was created in March for 1.82.7 and 1.82.8. Its source repository shows 1.107.0 was added at 21:46 UTC on October 21, eight hours and 23 minutes after PyPI had already removed the release. So for the whole time the bad release was installable, a check that relies on that record had nothing to block it with. It names 1.107.0 now. That is useful for the next person cleaning up a cache or a lockfile. It is not prevention.


Nothing we run judges a container image. Our package check judges package names and versions. Our MCP server check looks at a server's dependencies. Neither takes an image tag or a digest. If your gateway runs from litellm:main-stable and you pulled it between 11:46 and 14:20 UTC, nothing we sell would have said a word. That is the next lane we need to build. We are not putting a date on it.


What would have worked here are the dull controls that need a person to decide once: pin by hash, pin images by digest, wait a day before taking a new release, and keep developer tokens out of logs. Automation can check everything else, and that is the split we keep coming back to: a person decides the irreversible things, and automation does the rest. A feed that arrives a day later and a check that waits on an advisory are both after the fact when the bad release lasts less than two hours.



Get the indicators


The 17 indicators above are in our free feed. A free key takes about 30 seconds and covers the feed and the package check.


We checked this post before publishing: about 45 checkable claims, 7 corrected or cut, mostly where our first draft said our tools would have caught more than they could. At least 5% of what remains is still wrong, and we don't know which.


If this was useful, or if it wasn't, rate it at the bottom of the page. It is the only direct signal we get.




Every indicator in this post is in the feed. Free.

1.58M+ IOCs, STIX 2.1 / TAXII, 88% novel vs ThreatFox, exploited-CVE leads ahead of CISA. No credit card — a free API key in 30 seconds, and you can audit every claim above against the live endpoints.



Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. If the box below does not load, the same question lives at https://analytics.dugganusa.com/nps.html?post=litellm-poisoned-again-through-a-different-door



Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page