Brief · Both
LiteLLM 1.107.0 Was Trojanized: Check, Rotate, Rebuild
On Oct 21, someone ran LiteLLM's own release pipeline against a lookalike repo (Berri-AI/litellm, note the hyphen) and shipped a poisoned 1.107.0. Per LiteLLM's incident post, all seven PyPI wheels were live from 7:39 to 9:23 AM ET. The Docker Hub tags v1.107.0, latest, main-latest and main-stable pointed at the bad image from 7:46 to 10:20 AM ET, and the same tags on GHCR until no later than 9:51 AM ET. LiteLLM gives those times in UTC; I converted them. The bad artifacts are gone, and latest now points at 1.106.2. If you installed or started it in that window, removing it upstream doesn't help you.
What it did: the wheels drop cook.pth, cook.py and sitecustomize.py into site-packages, so the payload runs on every Python start, even if nothing imports litellm, and pip counts. cook.py is a credential stealer. It grabs cloud credentials (including instance-role creds from the metadata service), SSH and GPG keys, GitHub CLI and package-registry tokens, AI tool configs, browser passwords, the environment of every process it can read, and, if DATABASE_URL is set, every provider key in the proxy's LiteLLM_CredentialsTable. It encrypts all of it and POSTs it to www.litellm[.]dev, which isn't LiteLLM's domain. sitecustomize.py starts an hourly loader that fetches a domain from a GitHub dead drop and runs whatever Python that domain serves. The reporters and JFrog both found the dead drop empty, so no second stage has been seen. One commit could change that.
The stolen credentials are already in use. Wiz saw them tested about 4.5 hours after exfiltration, from 12:20 to 1:01 PM ET. AWS instance-role credentials from CI runners and Kubernetes nodes were checked with GetCallerIdentity, then bedrock:ListFoundationModels, which Wiz calls "consistent with LLMjacking." Wiz puts downloads of the bad version at about 49,000.
How it got in, according to LiteLLM: the attacker had GitHub CLI OAuth tokens for three LiteLLM engineers, one of them an org owner. The attacker used that token to disable branch protection and point the release workflow at the lookalike repo. As for where the tokens came from, LiteLLM says it has "observed specific situations in which Claude Code will embed a user's GitHub CLI OAuth token into a response body, and in situations where response bodies are logged, these tokens may appear in LiteLLM logs." It says its internal LiteLLM deployment was exposed to the internet and had an "unexpected, innocuously named user account," and it believes the attacker got in there and pulled the tokens from the logs. LiteLLM hasn't said what those situations are. It hasn't confirmed how the attacker reached the deployment either; it "suspect[s]" a recently patched vulnerability. It also says it's still confirming parts of this account. Treat it as LiteLLM's working theory, not a settled root cause.
My take: whichever tool put the token there, a gateway that logs full response bodies is a credential store nobody meant to build. Put it on the internet and it's somebody else's credential store.
What to do:
- Check whether you got it. It takes minutes, and only installs or pulls in that window are affected.
- Python: on a host you suspect, don't start with
pip show, because pip runs the payload too. Use the shell:find / -path '*site-packages*' \( -name cook.pth -o -name 'litellm-1.107.0.dist-info' \) 2>/dev/null. LiteLLM says finding one confirms exposure, but not finding one doesn't prove you're clean. - Lockfiles and CI:
grep -rnE 'litellm(-|==)1\.107\.0' .across repos, lockfiles and saved CI logs for Oct 21. Unpinned installs and anything that pulls in litellm as a dependency count. (uv.lock and poetry.lock put name and version on separate lines; check those by hand.) - Docker:
docker images --digests | grep -i litellm. The bad index digest issha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e, and the platform manifests startbd1aeee4(amd64) and56b74573(arm64). On Kubernetes, checkkubectl get pods -A -o jsonpath='{..imageID}'. Check registry mirrors and CI caches too, since they can still hold the bad digest after upstream deleted it.
- Python: on a host you suspect, don't start with
- If it ran, treat the host as compromised. The stealer doesn't need a C2 server, and Wiz has seen the stolen credentials used.
- Isolate it and snapshot it first, then follow LiteLLM's cleanup: from the shell, delete
cook.pth,cook.py,sitecustomize.pyand their__pycache__copies, and kill the detached Python daemon holding/tmp/remote_code_loader.lock. - Rotate everything the host could reach, from a clean machine. Per LiteLLM, that means the instance or task IAM role, every provider key in the proxy DB, the master and virtual keys, the
DATABASE_URLpassword and any secret in any process's environment. Add GitHub, npm and PyPI tokens, SSH keys and AI tool logins if it was a dev box or CI runner. Rebuilding the host doesn't revoke credentials that were already copied. - Rebuild from scratch on
litellm==1.106.2, the version LiteLLM says to pin, or on thev1.106.2image pinned by digest, not by tag. LiteLLM calls a full rebuild better than cleaning in place.
- Isolate it and snapshot it first, then follow LiteLLM's cleanup: from the shell, delete
- Hunt (incident response). The order JFrog gives is isolate, preserve, rotate, rebuild.
- Preserve before you wipe: the host snapshot, proxy and DNS logs, CloudTrail, and your GitHub org audit log. Note when each host installed or started 1.107.0.
- Hunt for egress to
litellm[.]dev(HTTP headerX-Token: XWiA8gZNa7NcX2XRoAEb) and fetches ofraw.githubusercontent.com/letters9813/pythonclient-test. On hosts, look for/tmp/.pc_once_72129118c59f5636c0bb,/tmp/remote_code_loader.lockandPRIVCOLLECT_ACTIVE=1. File hashes are in Wiz's IOC table. - In CloudTrail, a
GetCallerIdentityfrom your own host around install time is the stealer checking its loot. After that, look for the role's credentials used from outside your network, especially from 168.100.8[.]57 or 165.227.201[.]8 (Wiz), or for BedrockListFoundationModels, IAMListRoles/ListUsers/ListAccessKeysandDescribeTrails. - In the GitHub audit log, look for token use from unfamiliar IPs, branch-protection changes and workflow runs nobody started.
- Fix your own gateway logging, even if you never touched 1.107.0. This is the part that applies to everyone.
- Don't store full request and response bodies. In LiteLLM, leave
store_prompts_in_spend_logsoff (it's the default), setturn_off_message_logging: trueunderlitellm_settingsso messages and responses don't reach your logging callbacks, and don't turn onlog_raw_request_response. - If you've been logging bodies, search those logs for tokens (
gho_,ghp_,sk-and similar), rotate what you find, and then purge the logs. - Keep the proxy and its admin UI off the internet. LiteLLM says a recently patched vulnerability is its suspected way in, and it has an embargoed fix due for disclosure Oct 27. It hasn't said whether the two are related, so stay current.
- Don't store full request and response bodies. In LiteLLM, leave
Tl;dr: if 1.107.0 never ran on your machines, fix your logging and go back to work. If it did, it's an incident: preserve, rotate, rebuild, in that order.
Sources
- LiteLLM: Oct 21, 2026 release v1.107.0 compromised (incident post, updated Oct 21, 2026)
- LiteLLM GitHub issue #47826: litellm 1.107.0 compromised
- Wiz: LiteLLM compromised, steals credentials (Oct 21, 2026)
- JFrog Security Research: LiteLLM hijacked again (Oct 21, 2026)
- PyPI: litellm
- Docker Hub: litellm/litellm tags
- LiteLLM docs: Logging (redact messages and responses)
- LiteLLM docs: config settings (store_prompts_in_spend_logs, turn_off_message_logging)