2026-11-01 18:25 UTC
DANGMUAAI & Developer Tools, Decoded
BackInfrastructure

LiteLLM 1.107.0 Attack: 49,000 Downloads, What to Check

A poisoned LiteLLM release ran on install, not on import, and stole every cloud and provider key it could reach. Here is the self-check and the fix.

DangMua EditorialNov 01, 20267 min read
LiteLLM 1.107.0 Attack: 49,000 Downloads, What to Check

A poisoned release of LiteLLM sat on PyPI and Docker Hub for about two hours on 21 October 2026, long enough for roughly 49,000 downloads.

It stole whatever keys the machine could reach, and it ran even if your code never imported LiteLLM — the detail that makes this one worth reading past the headline.

Two hours, 49,000 downloads

LiteLLM is the open-source gateway many teams put between their apps and OpenAI, Anthropic or Google. Per a technical write-up of the incident, you are at risk if any environment installed litellm==1.107.0 from PyPI on 21 October roughly between 11:39 and 13:23 UTC, or ran ghcr.io/berriai/litellm or the Docker Hub image on tags v1.107.0, latest, main-latest or main-stable pulled between 11:46 and 14:20 UTC. Pinning 1.106.2 or earlier kept you out entirely, and LiteLLM says its private enterprise registry was not touched.

The chain started long before the release. Following LiteLLM's canonical record of 23 October: a master key for an experimental Agent Platform was committed to a public repository on 27 May 2026, and the commit was overwritten but the key was never revoked. On 10 October the attacker used that key to make the Agent Platform return a GitHub organisation owner's CLI token, then ran workflows that posted CI secrets to webhook.site and deleted the runs. On 21 October that token overrode branch protection, a build ran from a lookalike repository — Berri-AI/litellm, where the real one is BerriAI/litellm — and a maintainer's stolen token approved the production release gate.

Seven malicious wheels went up over 22 seconds. A community member reported the compromise at 12:06, PyPI quarantined the version by 13:23, and the last Docker Hub tags were removed or repointed at 14:20 — sources differ on clean-up times, so if you pull from Docker Hub, use the later one. The stolen data was used fast: Wiz reportedly saw AWS instance-role credentials taken from victims being tested from two IP addresses between 16:20 and 17:01 UTC the same day.

Why installing was the same as running

Most malicious packages need to be imported. This one did not. The wheel dropped three files into site-packages, outside the litellm folder: cook.pth containing the single line import cook, cook.py (the credential stealer), and sitecustomize.py (a backdoor loader).

Python reads every .pth file in site-packages at startup, executes any line beginning with import, and imports any module called sitecustomize. Python's own documentation says those lines "are always run at Python startup ... regardless of whether a particular module is actually going to be used." They are deprecated. They still run.

Three consequences follow, and each breaks a habit most teams rely on:

  • Any Python process ran the stealer — tests, a Jupyter kernel, a cron job. Microsoft reportedly saw a Windows case where installing an unrelated dependency triggered it.
  • pip uninstall litellm does not remove it. The files sit outside the package folder.
  • Checking with Python can re-trigger it. Running python or pip in a suspect environment starts the hooks again.

The published source stayed clean throughout: the official v1.107.0 tag never contained the payload. Endor Labs traced it to one added line in pyproject.toml, data = "cook", which copied a cook/ folder absent from the public source into the wheel.

What a gateway hands over

Microsoft, Datadog and Wiz agree on what the stealer collected: AWS, Google Cloud and Azure credentials including instance-role credentials from metadata services; Kubernetes, Docker, Vault and Terraform secrets; SSH, GPG and TLS keys; Git, npm and PyPI tokens; .env files; shell history; environment variables; keys and sessions for AI coding assistants, IDEs and MCP configs; browser passwords and cookies. It also read up to 500 rows per query from LiteLLM's own credentials and proxy-model tables, where upstream provider keys live.

That last item is the point. A gateway exists to hold every provider key so your apps do not have to, so one compromised proxy hands over OpenAI, Anthropic, Google and Bedrock keys at once, plus its database password and cloud role. In August 2026, Microsoft told defenders to "treat AI gateways as Tier-0 secrets stores" — the most sensitive tier.

The other end of the same supply chain: packages that never existed

The gateway is one attack surface. The dependency names your coding agent suggests are another, and a study published this week put numbers on it.

A developer had three small open-weight models — gemma4:e4b, qwen3:4b-instruct and phi4-mini:3.8b, run locally through Ollama — write 1,199 short Python scripts across 200 coding tasks, then checked every pip install they asked for against PyPI. 212 of 1,410 install suggestions (15.0%) named packages that nobody has ever published. The rate split sharply by task — 1.7% on everyday work against 30.0% on obscure ones — and by model, 8.1% for gemma4 against 20.7% for qwen3.

The finding that matters for security is reproducibility. The author wiped everything and reran the study: 161 of the 162 ghost names returned, and not one new ghost appeared. As the write-up puts it, a random hallucination is noise, a reproducible one is a target. It also reports that 56.5% of ghost names are the task's own words glued together — so anyone who can guess your prompt can guess the name your agent will try to install.

A negative result in the same study travels further than the detector the author built on top of it: asking a model whether a package exists is a bad check. One local model said 143 of 271 real packages did not exist, and a hosted model said 60 of 161 ghosts did.

Controls that would actually have helped

LiteLLM had already moved to trusted publishing, immutable Docker tags and signed images after an earlier incident in March 2026. The attacker did not break those controls — they drove the real pipeline with real credentials, and the provenance honestly recorded the lookalike fork as the source. The write-up's assessment of what would have stopped it:

ControlWould it have helped?
Hash-pinned lock files (uv.lock, poetry.lock, --require-hashes)Yes, if you were pinned to 1.106.2
Delayed adoption (uv's exclude-newer = "7 days", pip's --uploaded-prior-to P7D)Yes — both poisoned releases were pulled within hours
Image digests instead of tagsYes — floating tags were repointed at the bad image
Checking provenance, not just signaturesYes — it named the lookalike fork
Egress allowlists on gateway and build hostsYes — the exfil post would have failed
Scoped, short-lived tokens over personal CLI loginsPartly — the chain started with a personal owner token

What to do this week

If you run LiteLLM, inspect suspect environments from a clean machine rather than by running Python inside them: search lock files for 1.107.0, search every site-packages for cook.pth, cook.py and sitecustomize.py, and check images by digest. If 1.107.0 ran on a host with outbound internet access, LiteLLM's rule is to assume the theft succeeded — isolate, delete the hook files, rebuild rather than repair, and rotate from a clean machine, including instance roles, every key in the proxy database, and the browser and AI-tool sessions that Microsoft notes a rotation does not always end.

If you do not run LiteLLM, the transferable control is the cheapest one on the table: delayed adoption. Both LiteLLM compromises this year were caught the same day, so a seven-day wait would have skipped both — provided you keep a fast path open for genuine security fixes. Pair it with hash-pinned lock files and a rule that your coding agent never installs a package name no human has seen.

More from DangMua