Skip to the page
Tecyfy

LiteLLM 1.107.0 compromised: how to check and clean up

On 21 October 2026 a poisoned LiteLLM 1.107.0 went out on PyPI, Docker Hub and GHCR for 2 to 2.5 hours and stole the keys of the machines that ran it. Who is affected, how to check without setting it off again, the clean-up in LiteLLM's order, which keys to rotate, and how the attackers got in.

Tecyfy · Published · 7 min read

A hook reaches out of a software box marked 1.107.0 and pulls out a ring of keys, showing the poisoned LiteLLM release stealing credentials

LiteLLM 1.107.0, published on 21 October 2026, was poisoned. Attackers ran LiteLLM's own release pipeline on code from a lookalike repository and shipped a version that steals every key on the machine that runs it. It was on PyPI from 11:39 to 13:23 UTC and behind the Docker tags latest, main-latest, main-stable and v1.107.0 until 14:20 UTC. If you installed LiteLLM without a pin, or started one of those images, in that window, treat the machine as compromised and rotate its keys. Version 1.106.2 and everything before it are safe.

LiteLLM is the gateway many teams use for routingModel RoutingSending each request to the cheapest model that handles it well, by rules, a classifier or a cascade, and checking with an eval that quality holds. calls to OpenAI, Anthropic, Google and the rest, so the machines that run it hold the keys to all of them. That is what the attackers came for. It is also LiteLLM's second poisoned release this year: the March incident hit 1.82.7 and 1.82.8 for about 40 minutes.

Who is affected by the LiteLLM compromise

You are affected if any of these ran on 21 October 2026, times in UTC:

Where it came fromWhat you asked forPoisoned between
PyPI, through pip, uv or Poetrylitellm, litellm>=…, or a package that asks for it without a pin11:39 and 13:23
Docker Hub berriai/litellmv1.107.0, latest, main-latest, main-stable11:46 and 14:20
GHCR litellm, litellm-database, litellm-non_rootthe same 4 tags11:46 and 13:51

Not affected: an exact older pin such as litellm==1.106.2, an older Docker tag or a pinned digest, a build from the official GitHub source, and LiteLLM's enterprise customers on its private registry.

2 traps catch people who think they were safe. Docker keeps the image it pulled, so a host that pulled latest at noon still runs the poisoned image under the same tag today, even though the tag now points somewhere clean. And LiteLLM often arrives inside something else. DSPy 3.4.1 asks for litellm>=1.65.8 and CrewAI's litellm extra for anything from 1.88.6 below 2, so a fresh install of either in the window could pull 1.107.0. Aider and OpenHands pin an exact older version and could not. A notebook kernel or a CI job that ran pip install dspy at 12:00 counts.

Set where yours came from and when:

Try itWas your LiteLLM install poisoned?
12:05UTC

Dashed lines: 10:08 branch protection overridden; 11:53 workflow records deleted.

Treat as compromised

Check from a shell

# Search files first: pip and python start the payload again
find / \( -name 'litellm-1.107.0.dist-info' -o -name 'cook.pth' \
  -o -name 'sitecustomize.py' \) 2>/dev/null
ls -la /tmp/.pc_once_* /tmp/remote_code_loader.lock 2>/dev/null
  1. 1.Do not run pip or python yet. Each start runs cook.pth and sitecustomize.py again.
  2. 2.From the shell, delete cook.pth, cook.py, sitecustomize.py and their __pycache__ .pyc files from every site-packages.
  3. 3.Kill the background Python process that holds /tmp/remote_code_loader.lock.
  4. 4.Rebuild the environment from scratch with litellm==1.106.2, or reinstall pinned to it.
  5. 5.Assume the keys left the host if it had outbound internet, and rotate them: cloud roles, every provider key in the proxy database, the master and virtual keys, the DATABASE_URL password and every secret in any process's environment.
  6. 6.Look in CloudTrail for GetCallerIdentity from the role around install time, then for the role used from outside your network.

Treat this machine as compromised. PyPI served the poisoned release from 11:39 to 13:23 UTC, and anything that ran it may have sent its keys away. Follow the steps below in order.

Times are from LiteLLM's incident post, last updated 20:30 UTC on 21 October 2026. The checker runs in your browser. Finding none of the files does not prove a machine is clean: containers and /tmp cleanup remove them.

How to check without setting it off again

The malware hides in a .pth file. Python runs .pth files every time it starts, before your code and before pip, so pip show litellm in an infected environment runs the stealer once more. LiteLLM lists pip show as a check, and it is fine on a machine you have already cleaned or know is clean. On a suspect one, look at the files from the shell first:

find / \( -name 'litellm-1.107.0.dist-info' -o -name 'cook.pth' \
  -o -name 'cook.py' -o -name 'sitecustomize.py' \) 2>/dev/null
ls -la /tmp/.pc_once_* /tmp/remote_code_loader.lock 2>/dev/null

cook.pth and litellm-1.107.0.dist-info mean the bad release was installed. sitecustomize.py can be legitimate in some Python builds, so check that it sits next to cook.pth. /tmp/.pc_once_72129118c59f5636c0bb is written only after the stolen data was sent, so finding it confirms the theft. Not finding it proves nothing: a container restart or a /tmp clean-up removes it.

For containers, compare digests:

docker images --digests | grep -i -E 'berriai|litellm'

A berriai/litellm image with sha256:1bbcb1443bc9… is the bad one. On GHCR, litellm-database was sha256:c00f9d92c427… and litellm-non_root was sha256:7b00e91cd2fe…. In Kubernetes, the image ID of each pod's container status shows the digest it actually runs.

What the LiteLLM 1.107.0 malware steals

The researchers who reported it in GitHub issue 47826 list a thorough haul:

  • Cloud access: the instance or task role's credentials from the metadata service, checked on the spot with AWS STS GetCallerIdentity, plus cloud, Kubernetes and Docker config files.
  • Developer keys: SSH keys, GitHub CLI tokens, .env files and the environment of every running process.
  • AI tools: saved credentials of Claude, Codex, Gemini, Cursor and Copilot, and Hugging Face tokens.
  • The gateway itself: with DATABASE_URL set, as it is on a LiteLLM proxy, it dumps the tables that hold every provider key the proxy routes to.
  • Browser passwords, on desktops.

It sends all of it, encrypted, to www.litellm.dev, a lookalike domain registered 8 days before the attack. A second file, sitecustomize.py, starts a background process that checks a file on GitHub every hour and runs whatever code it finds there. That file was empty when reported, which means a machine that ran 1.107.0 was waiting for orders. On Windows the background part fails but the theft still runs.

How to clean up, in LiteLLM's order

Follow these steps in order. LiteLLM's steps come from LidorMachluf, who helped find the attack. Rebuilding beats repairing: a container or virtual environment you throw away cannot hide anything you missed.

  1. Do not run pip or python yet. Every start runs the malware again.
  2. Delete the planted files from the shell: cook.pth, cook.py, sitecustomize.py and their __pycache__/cook*.pyc and sitecustomize*.pyc, from every site-packages folder on the machine, including other virtual environments and conda envs.
  3. Kill the background process that holds /tmp/remote_code_loader.lock.
  4. Reinstall pinned to litellm==1.106.2, or better, rebuild the environment or image from scratch with that pin.
  5. Assume the keys are gone if the machine had outbound internet access, and rotate everything it could reach: the instance or task role, every provider key stored in the proxy database, the proxy's master key and virtual keys, the DATABASE_URL password, and every secret in any process's environment.
  6. Read your cloud logs. In CloudTrail, look for GetCallerIdentity from the machine's role around the install time, then for that role's credentials used from outside your network.

Step 5 is the long one. A LiteLLM proxy with 10 providers means 10 keys to replace at the makers' consoles, plus the virtual keys you handed to every team. Spend limits on each provider key cap what a stolen key costs while you work through the list, and a list of every machine key and its owner turns a day of hunting into an hour.

How attackers got into LiteLLM's release pipeline

This is LiteLLM's own account, posted the evening of the attack and marked as still under investigation.

After the March incident, LiteLLM hardened its releases: a private release repository, PyPI Trusted Publishing, immutable Docker tags and signed images. The attacker got past all 4 without breaking any of them. On the Monday before, LiteLLM had widened release permissions so 2 engineers could publish daily fixes. The attacker held 3 developers' GitHub CLI tokens. One belonged to an organisation owner, and with it the attacker switched off branch protection, forced in a change that pointed the release workflow at Berri-AI/litellm instead of BerriAI/litellm, and ran it. A second account approved the release. A third deleted the workflow's records 14 minutes after the upload.

Where did the tokens come from? LiteLLM found an unexpected user account on its own LiteLLM deployment, which was reachable from the internet, and believes the attacker read the tokens out of its logs. It also says it has seen cases where Claude Code puts a user's GitHub CLI token into a response body, and a gateway that logs response bodies then stores the token. That is LiteLLM's finding. How the attacker got into the deployment is not confirmed; LiteLLM suspects a recently patched flaw.

The order matters more than the names. Signing and Trusted Publishing prove which workflow published a file. They do not prove what source the workflow built, and here the real workflow built the attacker's code, so the signatures were valid.

What to change after the LiteLLM attack

Pin exact versions, with a lockfile. Every affected install asked for "the newest". uv lock, pip-compile --generate-hashes or poetry.lock turn a fresh install into a repeat of a known one, and a Docker image pinned by digest cannot change under you.

Let new releases age. Both LiteLLM attacks were caught within hours. uv's exclude-newer setting accepts a duration such as 7 days, so a resolve ignores anything uploaded in the last week. Pair it with a manual override for urgent security fixes.

Don't log what you can't protect. A gateway's logs hold whatever flows through it. If you log prompts and responses for debugging, set LiteLLM's turn_off_message_logging: True under litellm_settings in production so messages and responses stay out of your logging tools, keep the logs out of the internet's reach, and treat the log store like a secrets store.

Give the gateway its own small keys. One provider key per gateway, with a spend limit, and a cloud role that can do nothing else. The proxy is a single place that holds everything, and this attack went for exactly that.

Watch for the next one. Security incidents in the AI tools you run now appear on What changed, next to model and price changes, with the steps to take.

Questions

Is LiteLLM safe to use after the 1.107.0 compromise?
Yes, on a version published before 1.107.0. LiteLLM says every earlier version is safe and asks users to pin litellm==1.106.2 or another exact earlier version. The poisoned 1.107.0 was removed from PyPI by 13:23 UTC on 21 October 2026 and from Docker Hub and GHCR by 14:20 UTC. The official 1.107.0 source tag on GitHub never held the malicious files.
Which LiteLLM versions and Docker tags were compromised?
litellm==1.107.0 on PyPI (all 7 wheels, 11:39 to 13:23 UTC on 21 October 2026), and the Docker tags v1.107.0, latest, main-latest and main-stable on Docker Hub (11:46 to 14:20 UTC) and on GHCR (11:46 to 13:51 UTC) for the litellm, litellm-database and litellm-non_root images. Enterprise customers on LiteLLM's private registry were not affected.
How do I check if I installed the compromised LiteLLM?
Search the file system for litellm-1.107.0.dist-info, cook.pth and sitecustomize.py, and look for /tmp/.pc_once_* and /tmp/remote_code_loader.lock. For containers, compare image digests with sha256:1bbcb1443bc9 on Docker Hub. Search files before running pip or python: every Python start in an infected environment runs the malware again. Finding nothing does not prove the machine is clean.
What does the LiteLLM 1.107.0 malware steal?
According to the researchers who found it, it collects cloud credentials and instance-role tokens, SSH keys, Kubernetes, Docker and GitHub CLI credentials, .env files, browser passwords, the saved keys of AI tools such as Claude, Codex, Gemini, Cursor and Copilot, and the environment of every process. With DATABASE_URL set it also dumps the LiteLLM proxy's stored provider keys and model table. It then sends them, encrypted, to litellm.dev, a lookalike domain.
Which keys should I rotate after the LiteLLM compromise?
Everything the host could reach: the instance or task role, every provider key stored in the LiteLLM proxy database, the proxy's master and virtual keys, the DATABASE_URL password and every secret in the environment of any process on the host. LiteLLM says to assume the theft succeeded on any host with outbound internet access.
Are DSPy and CrewAI users affected by the LiteLLM compromise?
Only if pip or uv resolved litellm to 1.107.0 during the window. DSPy 3.4.1 requires litellm>=1.65.8, and CrewAI's litellm extra allows any 1.x from 1.88.6, so an install with no lockfile between 11:39 and 13:23 UTC could have pulled it. Aider and OpenHands pin an exact older litellm, so their installs could not.
How did attackers compromise LiteLLM's release?
LiteLLM says the attacker held 3 developers' GitHub CLI tokens, taken from the logs of LiteLLM's own internet-facing LiteLLM deployment. One belonged to an organisation owner and was used to switch off branch protection and change the release workflow to build from a lookalike repository, Berri-AI/litellm. A second account approved the production release and a third deleted the workflow records.