§1What happened
For about two hours on 21 October 2026, if you ran pip install litellm or pulled the latest LiteLLM Docker image, you installed a credential stealer. The compromised release, version 1.107.0 of a library that sees roughly 23 million downloads a week, shipped three extra files into your Python environment: a tiny cook.pth, a cook.py that did the stealing, and a sitecustomize.py that phoned a dead drop every hour for fresh code to run. PyPI and the LiteLLM team pulled it within the window, and the upstream source on GitHub was never affected. But two hours of a package that popular is a lot of installs.
The stealer swept the host for SSH keys, cloud credentials, CI and CD secrets, tokens for AI developer tools, browser passwords, and any LLM provider keys sitting in a LiteLLM proxy database. It encrypted the haul and posted it to a lookalike domain. LiteLLM's own incident report is unusually candid about how it got in, and that part matters more than the payload.
§2The detail that should change what you check
The stealer was a .pth file. If you have not met one, a .pth in a site-packages directory is a startup hook: Python reads it and executes any line beginning with import every single time the interpreter starts. Not when you import the package. When the interpreter starts, for any reason, running any script.
A package you installed once and never imported can still own the whole host.
Sit with the blast radius. The usual mental model of a bad dependency is "it runs if my code imports it", so you reassure yourself that litellm was only in one experiment, one container, one throwaway venv. That reasoning does not hold here. Once the file is on disk in that environment, every python invocation that touches it, a cron job, a health check, a build step, a shell that happens to activate the venv, fires the stealer. The compromise is not scoped to the library. It is scoped to the host.
§3How it got in, and why it is the familiar story
The payload is loud; the entry is the lesson. LiteLLM's report says the attacker held the GitHub credentials of three developers, and believes they were lifted from the logs of an internal LiteLLM deployment that was exposed to the internet. With an organisation owner's token the attacker walked past branch protection, pointed the private release workflow at a lookalike repository, and shipped a poisoned build through the real release pipeline.
No zero-day. No clever exploit. A secret ended up in a log, the log ended up reachable from the internet, and the secret was a key to the release machinery. We have written this sentence before about other people's incidents, and the reason we keep writing it is that it keeps being true: the credential you forgot was in a log is the credential that gets used. The Hacker News and Wiz both land on the same root cause.
§4We run litellm, so we checked ourselves
This is close to home for us, because we are not above it: we had litellm on a box. An autonomous fleet installs a lot of Python, and a tool this common turns up in venvs you forgot you made. So the honest move was not to write a think-piece, it was to scan. We built a small read-only detector and ran it across the fleet, matching the published indicators: the cook.pth and cook.py artefacts, a rogue sitecustomize.py sitting in a site-packages root rather than inside a legitimate package, the attacker's /tmp marker files, any install pinned to 1.107.0, and any requirements or lockfile pinning it.
The result across the hosts we could reach was clean. We do carry litellm, an 1.81.x fork on one box and 1.83.14 in a benchmark venv on another, and neither is the poisoned 1.107.0, and neither showed any of the artefacts. One personal machine was asleep and is queued for the same scan on wake. "Clean" from a first-pass scanner is not a guarantee, but it is the difference between knowing and assuming, and knowing took one command.
§5The detector
Here is the scan, in full. It makes no network calls, only reads the disk, and exits non-zero if it finds anything. Run it on every host that has ever installed or pulled litellm, not just the ones you think run it in anger, because the .pth does not care which script starts the interpreter.
#!/bin/sh
# litellm-ioc-scan.sh - read-only detector for the litellm 1.107.0 compromise.
# Exit 0 = clean, 1 = suspect. Matches the published IoCs.
status=0
for sp in $(find / -maxdepth 9 -type d -name site-packages 2>/dev/null); do
for f in cook.pth cook.py sitecustomize.py; do
[ -e "$sp/$f" ] && { echo "SUSPECT: $sp/$f"; status=1; }
done
done
for f in $(find / -name cook.pth -o -name cook.py 2>/dev/null); do
echo "SUSPECT: $f"; status=1; done
for f in /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock; do
[ -e "$f" ] && { echo "SUSPECT: $f"; status=1; }
done
for di in $(find / -type d -name 'litellm-*.dist-info' 2>/dev/null); do
case "$di" in *litellm-1.107.0.dist-info) echo "SUSPECT: installed $di"; status=1;;
*) echo "info: installed $(basename "$di")";; esac
done
[ "$status" -eq 0 ] && echo "CLEAN - no IoCs, no 1.107.0." || echo "SUSPECT - isolate the host, rotate EVERY reachable credential, remove the files."
exit "$status"
A sitecustomize.py inside a package such as opentelemetry or debugpy is legitimate; the malicious one sits directly in a site-packages root next to cook.pth, which is why the script distinguishes the two. If it prints anything but CLEAN, assume the host's secrets are gone: pull it off the network, rotate every credential it could reach, SSH keys, cloud, CI, provider API keys, browser logins, then remove the files and reinstall from a known-good version. You cannot un-leak a key by deleting the file that stole it.
§6How we run it
Two habits took the edge off this one before it happened, and both are boring on purpose. We do not expose internal services to the internet, and our agents never read a raw secret to use it: a key is checked and used through a wrapper that keeps the value out of logs and transcripts, so the exact failure that started the LiteLLM breach, a usable credential sitting in a reachable log, is the one we spend the most effort designing out. The lesson of this incident is not "audit your dependencies", though you should. It is that a supply-chain payload runs with the full authority of the environment it lands in, and the only real limit on the damage is how few live secrets that environment can see. Pin your dependencies. Keep your secrets out of logs. And when the next one lands, have a one-command answer to "are we hit", because the two hours you spend writing it after the fact are the two hours the stealer was already live.
