FALCONINTERNET

LiteLLM 1.107.0 on PyPI and Docker stole credentials for two hours

Security
LiteLLM 1.107.0 on PyPI and Docker stole credentials for two hours

On October 21, 2026, attackers published a malicious build of LiteLLM, the open source Python library and proxy that many companies use to route requests to OpenAI, Anthropic, Google and other model providers. Version 1.107.0 reached PyPI at 11:39 UTC and stayed up for about two hours. The same code shipped in its Docker images. Treat any machine that installed it as compromised, and rotate every secret that machine could read.

LiteLLM often arrives as a dependency of something else. Chainguard counts more than 100 million PyPI downloads in the past month and more than 2,000 packages that depend on it. Wiz puts downloads of the bad version at about 49,000. This is the project's second compromise this year: in March, versions 1.82.7 and 1.82.8 were published with a stolen PyPI token.

Release window and affected artifacts

  • PyPI: all seven litellm==1.107.0 wheels (Linux, macOS and Windows), uploaded between 11:39:51 and 11:40:13 UTC and quarantined around 13:20 UTC.
  • Docker Hub: docker.io/litellm/litellm tags v1.107.0, latest, main-latest and main-stable pointed at the bad image from 11:46 UTC until 14:20 UTC, per LiteLLM's incident report.
  • GHCR: the same tags on ghcr.io/berriai/litellm, litellm-database and litellm-non_root, removed by 13:51 UTC.

The infected image index digest is sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e. Version 1.106.2 is the last clean release. The 1.107.0rc1 and rc2 pre-releases and the v1.107.0 source tag on GitHub are clean. A container that auto-updated to latest during the window got the malware without anyone choosing a version.

How the attackers got publishing access

LiteLLM's report says the attacker held GitHub CLI OAuth tokens for three developers, one of them a GitHub organization owner. The company believes the tokens were taken from the logs of an internal LiteLLM deployment that was reachable from the internet and had an unexplained user account on it, possibly through a recently patched vulnerability. That entry point is not confirmed.

At 10:08 UTC the owner's token overrode branch protection on the private release workflow. The modified workflow built from Berri-AI/litellm, a lookalike of the real BerriAI/litellm repository that differs by one hyphen. A second developer's token approved the production run, and a third deleted the workflow run records about 14 minutes after the upload. The official pipeline then signed and published the packages, so their build provenance looked normal apart from naming the fork as the source.

The payload runs on every Python start

The wheels drop three files into site-packages: cook.pth, cook.py and sitecustomize.py. Python processes .pth files and sitecustomize.py at interpreter startup, so every Python program in that environment runs the malware, including pip and scripts that never import litellm. Uninstalling the package leaves the files in place.

cook.py is a credential stealer with 16 collection modules. StepSecurity, Datadog and Wiz report that it collects cloud credentials (including instance metadata roles), SSH, GPG and TLS keys, Git, npm and PyPI tokens, .env files, shell history, browser passwords and cookies, the environment of every running process, and tokens for AI coding tools. If DATABASE_URL is set, it also dumps the LiteLLM proxy tables that hold upstream provider API keys. The data is encrypted and sent by plain HTTP POST to www.litellm.dev/healthcheck.

sitecustomize.py starts a detached daemon on Linux and macOS that reads a hostname from a file on GitHub every hour, then downloads and executes /codepy from that host. The file held only test values at release, and the GitHub account behind it has been deleted, but infected hosts keep polling. Anyone who re-registers that username could serve code to them. Starting around 16:20 UTC the same day, Wiz saw the attacker test stolen AWS instance role credentials with sts:GetCallerIdentity and Bedrock model listing calls.

Checking servers, laptops and CI runners

Check with shell tools, not Python. LiteLLM's guidance suggests pip show litellm, but running pip in an infected environment fires the payload again. These commands check directly:

# payload files and package metadata in any Python environment
find / -name cook.pth -path "*site-packages*" 2>/dev/null
find / -type d -name "litellm-1.107.0.dist-info" 2>/dev/null

# run-once marker and loader lock
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null
lsof /tmp/remote_code_loader.lock 2>/dev/null

# pinned or locked versions in your repositories
grep -rnE "litellm[=<>~! ]*1\.107\.0" --include="requirements*.txt" --include="*.lock" --include=pyproject.toml .

# container images on Docker hosts
docker images --digests | grep -E "berriai/litellm|litellm/litellm"

The marker file is written only after a successful upload, so its presence confirms data left the machine. Its absence proves nothing. Search DNS and firewall logs for litellm.dev, raw.githubusercontent.com/letters9813/ and any request path ending in /codepy. Include developer laptops and CI runners; any unpinned install during the window got 1.107.0.

Cleanup and credential rotation

  • Kill the process holding /tmp/remote_code_loader.lock before running anything else in that environment.
  • Delete cook.pth, cook.py, sitecustomize.py and their __pycache__ entries, then rebuild the virtual environment or container from scratch instead of cleaning it in place. Pin litellm==1.106.2, and pin images by digest instead of tag.
  • Purge 1.107.0 from internal package mirrors and image registries, and block litellm.dev at DNS and egress.
  • From a clean machine, rotate everything the host could read: cloud access keys and instance or task roles, SSH and GPG keys, Git and registry tokens, CI secrets, and AI tool sessions. On LiteLLM proxy hosts, also rotate every provider key stored in the proxy database, the master key, the salt key, virtual keys and the DATABASE_URL password.
  • Review AWS CloudTrail, or your cloud's equivalent, for GetCallerIdentity and Bedrock calls from those roles coming from addresses you do not own. Check model provider bills for usage you did not generate.

A request to /codepy in any log means attacker-supplied code ran on that host, so rebuild it rather than rotating keys alone. LiteLLM has said it will publish details on October 27 of an embargoed vulnerability fixed in its enterprise release. Until that advisory is out, keep any self-hosted LiteLLM proxy off the public internet, behind a VPN or an IP allowlist.

Need this handled instead of explained?

We do this for a living — talk to an engineer about your setup.