Yaamlabs
Supply chain

LiteLLM 1.107.0: a credential stealer in the release

A poisoned LiteLLM 1.107.0 reached PyPI and Docker on October 21. How the startup hook works, how to check your hosts, and what to rotate.

By Yaali. October 24, 2026, 6 min read, Supply chain, Threat intel.

Cover illustration of a glowing package crate on a conveyor belt with a small mechanical spider trailing threads to cloud and key shapes, with the Yaamlabs logo and the text: LiteLLM 1.107.0 shipped a credential stealer, 1.106.2 last safe release

On October 21, 2026 at 11:39 UTC, LiteLLM 1.107.0 appeared on PyPI with a credential stealer and a remote code loader built into all seven of its wheels. LiteLLM is an open source Python library and proxy that puts one API in front of OpenAI, Anthropic, Bedrock and other model providers, and it sees about 23 million downloads a week. PyPI quarantined the release around 13:20 UTC. The Docker images tagged v1.107.0, latest, main-latest and main-stable on GitHub Container Registry and Docker Hub served the same payload from 11:46 until as late as 14:20 UTC.

If any machine, CI (continuous integration) runner or container installed 1.107.0 or pulled one of those tags in that window, treat every secret it could read as stolen. That includes the provider keys stored in a LiteLLM proxy database. This is LiteLLM's second compromise this year: in March, releases 1.82.7 and 1.82.8 were also trojanized.

Timeline of October 21: lookalike repository commits, release pipeline abuse with three stolen GitHub tokens, PyPI and Docker publication, quarantine, and attacker use of stolen AWS credentials

How it works

The application code in 1.107.0 is unchanged. The attack sits in packaging: a single line, data = "cook", added to the Maturin build configuration in pyproject.toml, tells the build to copy a cook/ directory into the wheel. When pip installs that wheel, three files land at the top level of site-packages: cook.pth, cook.py and sitecustomize.py. The source distribution (sdist) carried the tampered build setting but not the payload files, so the danger is in the wheels and the images.

Both files abuse Python's normal startup. Every time the interpreter starts, the site module reads each .pth file in site-packages and executes any line beginning with import. The whole of cook.pth is import cook. Python also imports a module named sitecustomize automatically if one exists. So the malware runs in every Python process in that environment, including pip itself, whether or not anything imports litellm.

cook.py decodes a Base64 and zlib-compressed collector its authors call privcollect and runs it in a background thread. It sets PRIVCOLLECT_ACTIVE=1 to avoid running twice and works through 16 modules: AWS, Azure and Google Cloud credentials files and instance metadata, Kubernetes and Docker config, Git, npm and PyPI credentials, .env files, SSH, GPG and TLS private keys, browser password and cookie databases, shell history, every non-empty environment variable, and the credential files of AI coding tools such as Claude, Codex, Gemini and Cursor. If DATABASE_URL is set, it queries the proxy's LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable, up to 500 rows each, which is where a LiteLLM proxy keeps its upstream provider keys.

The results are encrypted with a random AES-256-CBC key, the key is wrapped with an embedded RSA-4096 public key, and the bundle is sent by plain HTTP POST to www.litellm[.]dev/healthcheck, a lookalike domain. After a successful upload it writes /tmp/.pc_once_72129118c59f5636c0bb so it does not collect again.

sitecustomize.py is the second stage. It takes the lock file /tmp/remote_code_loader.lock, forks a detached process, and once an hour reads domain.txt from the GitHub repository letters9813/pythonclient-test. If the file names a host, the loader downloads http://<host>/codepy and runs it with exec, with no signature or hash check. Wiz and Socket report that the file only ever held test values, so no second-stage code was seen, and GitHub has since removed the account. A loader already running on a host will still keep polling until the process is killed.

How the attacker got a signed release

The builds came from Berri-AI/litellm, a lookalike repository one hyphen away from the real BerriAI/litellm, where ten attacker commits were pushed between 08:56 and 10:46 UTC. The attacker then ran LiteLLM's own private release workflow against that source using the GitHub accounts of three LiteLLM engineers, apparently through stolen GitHub CLI OAuth tokens. One account changed the release workflow, overriding branch protection, and triggered the run. A second passed the production approval gate, and a third deleted the workflow run records, which left investigators with fewer logs.

Because the real pipeline built the artifacts, they carried valid build provenance. The provenance did name Berri-AI as the source owner, which is how researchers spotted the switch. LiteLLM says an unexpected user account turned up in an internal LiteLLM deployment that was exposed to the internet, and that the tokens were probably taken from that deployment's logs. It believes they got there because Claude Code had included a GitHub CLI token in response bodies that the deployment logged. How the attacker got into that deployment is not confirmed; LiteLLM suspects a recently patched vulnerability.

What attackers did with the data

Wiz saw the stolen keys being used. About four and a half hours after the first stolen data was uploaded, the attacker validated AWS instance role credentials taken from CI runners and Kubernetes nodes in several environments. Each key was tested with sts:GetCallerIdentity, then bedrock:ListFoundationModels, which fits LLMjacking: running paid AI models on a victim's cloud bill. In some environments the attacker also listed IAM roles, users and access keys and read CloudTrail settings.

What to do

Checklist for LiteLLM 1.107.0: find exposure, inspect without running Python, clean up, rotate credentials, and hunt in cloud logs

  1. Find exposure. Search lockfiles and requirements for litellm==1.107.0, look for litellm-1.107.0.dist-info on disk, and check running containers and Kubernetes pods for the affected image digests. Any deployment on latest, main-latest or main-stable that pulled between 11:46 and 14:20 UTC on October 21 counts.
  2. Inspect without starting Python. Every python or pip run in an affected environment re-runs the collector. Use find / -name cook.pth -path "*site-packages*", ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock and lsof on the lock file to spot a live loader. A missing marker does not prove the host is clean, and sitecustomize.py exists legitimately in some environments, so compare hashes against the vendor lists.
  3. Clean up. Uninstalling litellm does not remove the three startup files or stop a running loader. Delete cook.pth, cook.py and sitecustomize.py from site-packages and kill the detached process, or better, rebuild the environment or image from scratch. Pin to 1.106.2, the release Wiz, Datadog, Endor Labs and StepSecurity name as last safe (OX Security says 1.106.0), and pin container images by digest.
  4. Rotate from a clean machine. Cloud keys and instance role sessions, every model provider key stored in the proxy database, the proxy's database password, GitHub, npm and PyPI tokens, SSH and GPG keys, and any AI tool logins on developer laptops.
  5. Hunt. Search egress logs for litellm.dev, the letters9813/pythonclient-test path and any URL ending in /codepy. In CloudTrail, look for GetCallerIdentity followed by bedrock:ListFoundationModels from unfamiliar addresses after 11:39 UTC on October 21.

The wider lesson for release pipelines

Approval gates and signed provenance held up only as well as the tokens behind them, and here three developer tokens were enough to change, approve and then erase a release. Where you build packages yourself, check that the provenance source repository matches the one you expect before publishing, keep long-lived CLI tokens off servers, and never log full response bodies. Where you consume them, a short cooling-off period before new releases reach production would have kept this one out entirely, since it was pulled in under two hours.

We look at exactly this path, from developer tokens to release workflow, in our red team and code review work, and security operations can run the CloudTrail and egress hunts above for you. Open the chat and Yaali, our AI agent, will pass your question to an engineer.


Sources: Socket, Wiz, Datadog Security Labs, Help Net Security, StepSecurity, Endor Labs, Wiz on the March compromise.

Read next

Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.