LiteLLM 1.107.0 Supply-Chain Attack: What It Stole and What to Rotate

LiteLLM 1.107.0 Supply-Chain Attack: What It Stole and What to Rotate

LiteLLM, the open-source AI gateway many teams put between their apps and OpenAI, Anthropic, Bedrock and other providers, shipped a poisoned release. Version 1.107.0 went out on PyPI and Docker with a credential stealer and a remote code loader built in. The artifacts were signed and published by LiteLLM’s own release pipeline, so the usual “is this the real package?” checks would have passed.

The files were up for about two hours on PyPI and a little longer on Docker. Wiz, citing ClickPy data, puts downloads of the bad version at about 49,000. Wiz has also seen stolen cloud credentials being tried about four and a half hours after they were taken.

The part builders will keep talking about is how it started. LiteLLM says the attacker got its engineers’ GitHub tokens out of the logs of an internal LiteLLM deployment, and that Claude Code had put those tokens into response bodies that the gateway logged.

It is the biggest AI developer story of the last day. LiteLLM posts on X passed 9,000 in under two days, and the main Hacker News thread drew close to 600 points and nearly 200 comments.

Key points

  • Bad version: litellm==1.107.0, all seven wheels (Linux, macOS, Windows), plus the Docker tags v1.107.0, latest, main-latest and main-stable on Docker Hub and GHCR.
  • Safe version: pin 1.106.2. The release candidates 1.107.0rc1 and rc2 are clean, and so are the companion packages that 1.107.0 pins.
  • Not affected: enterprise deployments that pull only from LiteLLM’s private registry.
  • What it does: runs every time Python starts on the host, even in processes that never import litellm. It steals SSH keys, cloud credentials, CI secrets, AI tool tokens, browser passwords and the provider API keys stored in a LiteLLM proxy database.
  • If you ran it: treat every secret that machine could reach as exposed. Rebuild, do not just upgrade.

What happened

LiteLLM’s incident report and the GitHub advisory line up on the main facts.

  • The source code was clean. The v1.107.0 git tag in BerriAI/litellm does not contain the malware. The release was built from a lookalike repository, Berri-AI/litellm (note the hyphen), that the attacker controlled.
  • The release pipeline was hijacked, not bypassed. With a stolen GitHub token belonging to an organization owner, the attacker turned off branch protection and pointed LiteLLM’s private release workflow at the lookalike repo. A second engineer’s token passed the production approval gate. A third account was used to delete the workflow logs. LiteLLM’s official pipeline then built, signed and published the artifacts.
  • It was quick. Per LiteLLM’s timeline (all UTC on the day), branch protection rejected the change at 10:08 and was overridden five seconds later. The production run started at 11:08, the wheels were uploaded at 11:39, the containers at 11:46, and the logs were deleted at 11:53.
  • It was caught in hours. LiteLLM says it became aware at 6:32 a.m. Pacific. PyPI quarantined the release by 13:23 UTC. LiteLLM says Docker Hub was serving the bad image until 14:20 UTC, and the GitHub advisory confirms that the floating tags no longer point at it.

The single visible change in the source tarball is one line in pyproject.toml: data = "cook" under the maturin build section. SafeDep found that this line tells the build tool to copy a cook/ folder into the wheel, and pip then drops that folder straight into site-packages.

How the malware works

Installing the bad version adds three files to site-packages:

  • cook.pth: a 12-byte file that just says import cook. Python processes .pth files at startup, so this line runs in every Python process on the machine. That includes your app, pip, IDE language servers and CI jobs.
  • cook.py: the stealer, internally called privcollect. It has 16 collection modules and a run-once marker at /tmp/.pc_once_72129118c59f5636c0bb.
  • sitecustomize.py: a loader that starts a background process on Linux and macOS. Every hour it reads a server address from a file in a GitHub repo, runs the stealer again, and downloads and runs whatever code that server returns. Wiz says the address file only ever held test values before GitHub took the repo down, so the second stage was never seen live.

What the stealer collects, per SafeDep’s decoded payload and Wiz’s analysis:

  • Cloud: AWS keys and config, plus role credentials from the EC2/ECS metadata service. Also GCP service account keys, Azure CLI tokens, ~/.kube/config, Docker config and gh hosts.
  • Developer secrets: SSH and GPG keys, TLS private keys, .pem/.key/.p12 files under your home folder, .netrc, Vault tokens, Terraform credentials, GitLab runner and Jenkins secrets, and Kubernetes service account tokens.
  • AI tools: config and tokens for Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, Gemini CLI, Codex, Continue, Aider, Kiro, Zed, the Hugging Face token and VS Code mcp.json, plus provider API keys in environment variables.
  • The gateway itself: it reads DATABASE_URL and dumps the LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable tables. Those tables hold the upstream provider keys a LiteLLM proxy routes with.
  • Everything else: browser passwords and history, shell history, and the environment variables of other processes on the host.

The data is encrypted (AES-256 with an RSA-4096-wrapped key) and sent to www.litellm[.]dev/healthcheck. That is an attacker-controlled lookalike domain, not LiteLLM infrastructure.

Wiz says the code shows strong signs of being written with an LLM: verbose names, blanket error handling, and Windows code paths that would fail silently. Wiz also says this is the first case it knows of in the wild that uses both a .pth file and sitecustomize.py at once.

The Claude Code and gateway logs angle

This is the part that matters beyond LiteLLM users.

In a follow-up to its incident report, LiteLLM said:

  • It had observed “specific situations” where Claude Code embeds a user’s GitHub CLI OAuth token into a response body. Where response bodies are logged, those tokens can end up in LiteLLM logs.
  • It found an unexpected, innocently named user account on an internal LiteLLM deployment that was exposed to the public internet.
  • It believes the attacker got into that deployment, took the GitHub tokens from its logs, and used them to take over the release workflow. LiteLLM suspects entry was through a recently patched vulnerability, but has not confirmed it.

What we could not verify:

  • We found no public response from Anthropic on the Claude Code claim.
  • LiteLLM has not said which Claude Code behavior or tool call surfaces the token.
  • One security vendor said on X that it scanned about 1,200 of its own Claude Code transcripts and found no tokens. That doesn’t disprove LiteLLM’s claim, but it means the “specific situations” are not yet well understood.

The lesson does not depend on who is at fault. If your gateway logs full requests and responses from coding agents, that log is a secrets store. It deserves the same access controls, retention limits and redaction you would give a password vault, and it should not sit on the open internet.

Who is affected

  • Anyone who installed litellm==1.107.0 from PyPI, including indirectly through another package or an unpinned requirements file. The exposure window was about 11:39 to 13:23 UTC on the day. An agent or CI job that installs the latest version on its own counts too.
  • Anyone who pulled litellm Docker images tagged v1.107.0, latest, main-latest or main-stable during the window. On Docker Hub that ran until about 14:20 UTC.
  • Not affected: pinned installs of 1.106.2 or earlier, the 1.107.0 release candidates, and enterprise users who pull only from LiteLLM’s private registry. LiteLLM notes that registry also carries an embargoed fix for a separate vulnerability, with public disclosure scheduled for later this month.

To check a machine quickly, run pip show litellm in each environment and search site-packages for cook.pth, cook.py and sitecustomize.py. The marker file /tmp/.pc_once_72129118c59f5636c0bb means the stealer ran and finished an upload.

What to do if you were exposed

LiteLLM’s remediation guidance, in order:

  1. Don’t run pip install first in the affected environment. Any Python process, pip included, triggers the stealer again.
  2. Remove the implant: delete cook.pth, cook.py and sitecustomize.py (plus their compiled .pyc files) from site-packages, and kill the background process holding /tmp/remote_code_loader.lock.
  3. Rebuild the virtual environment or container image from a pinned litellm==1.106.2, ideally by digest for Docker.
  4. Assume exfiltration if the host had outbound internet access.
  5. Rotate everything the host could reach: the cloud IAM role, every provider key stored in the proxy database, the LiteLLM master key and virtual keys, the DATABASE_URL password, and every secret in the environment. We would add tokens for AI coding tools on any developer machine that ran it.
  6. Check cloud logs. Wiz saw stolen AWS role credentials tested with sts:GetCallerIdentity, then bedrock:ListFoundationModels. That pattern points to LLMjacking: running up model bills on someone else’s account. In a few cases Wiz also saw IAM enumeration. Look for those calls in CloudTrail from unfamiliar IPs.

Why this matters

LiteLLM has been here before. A similar .pth stealer was pushed in versions 1.82.7 and 1.82.8 earlier this year. After that, the project moved to a private release repository, PyPI Trusted Publishers, immutable Docker tags and signed images.

Those controls did their job, and it still wasn’t enough. The attacker did not need to steal a PyPI token. They took over the pipeline the signatures come from, so the malware arrived signed and from the official source. Provenance tells you who built a package, not whether the build input was the real repo.

For teams running AI infrastructure, the takeaways are practical:

  • Gateways are high-value targets. A gateway holds every provider key you have, and often logs every prompt and response. One compromised proxy host can leak your whole model budget.
  • Pin and wait. Pin exact versions with hashes, and consider a cooldown of a day or two before adopting new releases of critical dependencies. Don’t let coding agents or CI install the latest version unpinned.
  • Treat agent output as sensitive. If agents can see a token, assume it can appear in their output, and redact before logging.
  • Guard release permissions. Org-owner tokens that can disable branch protection are the keys to your users’ machines. Use short-lived tokens, hardware-backed approvals and alerts on protection changes.

What to watch

  • LiteLLM’s confirmation of how the attacker got into its internal deployment, and the details of the vulnerability fix due for public disclosure.
  • Whether Anthropic confirms or disputes the Claude Code token behavior, and whether a fix follows.
  • More post-exploitation reports, especially LLMjacking on stolen Bedrock or provider keys.
  • Whether PyPI and Docker Hub move toward stricter checks on releases built from forks or lookalike repos.

Key Takeaways

  • litellm==1.107.0 and four Docker tags carried a stealer and backdoor that ran on every Python start, for about two hours on PyPI.
  • The release was built from a lookalike repo through LiteLLM’s real, signed pipeline, using stolen GitHub tokens.
  • LiteLLM traces those tokens to its own gateway logs, where it says Claude Code had embedded them in response bodies.
  • Safe pin is 1.106.2. If you ran 1.107.0, rebuild and rotate every reachable secret, starting with cloud roles and provider keys.

Image credit: LiteLLM

Sources

Leave a Comment

Your email address will not be published. Required fields are marked *