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 tagsv1.107.0,latest,main-latestandmain-stableon Docker Hub and GHCR. - Safe version: pin
1.106.2. The release candidates1.107.0rc1andrc2are 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.0git tag inBerriAI/litellmdoes 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 saysimport cook. Python processes.pthfiles 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 andghhosts. - Developer secrets: SSH and GPG keys, TLS private keys,
.pem/.key/.p12files 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_URLand dumps theLiteLLM_CredentialsTableandLiteLLM_ProxyModelTabletables. 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.0from 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
litellmDocker images taggedv1.107.0,latest,main-latestormain-stableduring 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:
- Don’t run
pip installfirst in the affected environment. Any Python process, pip included, triggers the stealer again. - Remove the implant: delete
cook.pth,cook.pyandsitecustomize.py(plus their compiled.pycfiles) fromsite-packages, and kill the background process holding/tmp/remote_code_loader.lock. - Rebuild the virtual environment or container image from a pinned
litellm==1.106.2, ideally by digest for Docker. - Assume exfiltration if the host had outbound internet access.
- 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_URLpassword, and every secret in the environment. We would add tokens for AI coding tools on any developer machine that ran it. - Check cloud logs. Wiz saw stolen AWS role credentials tested with
sts:GetCallerIdentity, thenbedrock: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.0and 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



