LiteLLM 1.107.0 compromised on PyPI and Docker: what to do
On this page
- Am I affected? The 2-minute check by install path
- What the malware does
- Clean up a host that ran litellm 1.107.0
- Credentials to rotate, by type
- Which LiteLLM version is safe
- The Docker and Kubernetes case
- How the attacker got in, and what to check in your own setup
- Not the March 2026 LiteLLM compromise
- Timeline in UTC and JST
- Tell the people who use your proxy
- Updates to this page
- Frequently asked questions
- Sources
As of October 24, 2026, 06:27 JST (October 23, 21:27 UTC). Every fact that can change carries the time it was checked, in JST (UTC+9). This page covers the LiteLLM compromise of October 21, 2026 only, not the one of March 2026.
litellm 1.107.0 on PyPI (October 21, 2026, 11:39–13:23 UTC) and LiteLLM's Docker tags v1.107.0, latest, main-latest, and main-stable (11:46–14:20 UTC) held a credential stealer. If you installed it then or ran an image pulled then, treat the host as compromised, rebuild on 1.106.2, and rotate every key it could reach.
Don't run pip install first: remove the implant from the shell before any Python starts, then reinstall pinned to litellm==1.106.2. LiteLLM's incident report says it plainly: "Any Python start in the affected environment runs cook.pth and sitecustomize.py again, and that includes running pip." The same files also start a loader that can fetch and run more code. In JST, the PyPI window is 20:39 to 22:23 and the Docker window is 20:46 to 23:20 on October 21.
As of 06:11–06:27 JST on October 24: PyPI no longer serves 1.107.0 and lists 1.106.2 as the latest release; latest, main-latest, and main-stable on Docker Hub and GHCR resolve to the 1.106.2 images. LiteLLM announced its completed investigation, dated October 23, in its GitHub issue at 21:15 UTC that day; it replaces its first account of how the attacker got in, and plans a new release. There is no CVE. GitHub's Advisory Database lists 1.107.0 in the malware advisory GHSA-92x9-889m-jgmw (updated October 22, 00:32 UTC), and the OSV database in MAL-2026-2144, the same records as the March versions; LiteLLM has published no advisory of its own.
Below: a 2-minute check for each way LiteLLM gets onto a machine, what the malware does, the cleanup steps in order, the credentials to rotate by type, which versions are clean, the Docker and Kubernetes details, and a timeline in UTC and JST.
Am I affected? The 2-minute check by install path
Two rules before you start. Every check below runs from the shell and starts no Python, so it can't set the payload off again. And a hit confirms exposure, but a miss doesn't clear you: LiteLLM's report says that not finding the files "does not prove the environment is clean".
| How LiteLLM got onto the machine | You're exposed if | Check (shell only) |
|---|---|---|
pip install litellm or litellm[proxy], with no pin or with -U, into a venv or a system Python | The install ran between 11:39 and 13:23 UTC on October 21 | Look for litellm-1.107.0.dist-info and cook.pth in site-packages (commands) |
A requirements.txt with litellm>=, ~=, or no version | An install from it ran in that window | The same, and your build or CI log for litellm-1.107.0 |
uv: uv add, uv lock --upgrade, uv sync, uv pip install, uvx | It resolved litellm in that window | The version line under name = "litellm" in uv.lock; for uvx, search ~/.cache/uv for cook.pth |
Poetry: poetry add, poetry update, poetry lock | It resolved litellm in that window | The version line under name = "litellm" in poetry.lock, then the venv's site-packages |
pipx: pipx install 'litellm[proxy]' or pipx upgrade | The install or upgrade ran in that window | Search your home directory for cook.pth |
| conda or mamba from conda-forge | Not through conda: conda-forge's newest litellm is 1.106.0, with no 1.107.0 (checked 05:39 JST). A pip install inside a conda env is the first row | Search $CONDA_PREFIX for cook.pth |
| A package that depends on litellm | Its version range allows 1.107.0 and the install ran in that window. In their newest PyPI releases (checked 05:39 JST), dspy asks for >=1.65.8, smolagents' litellm extra >=1.60.2, langchain-litellm >=1.101.0,<2.0.0, and crewai's litellm extra >=1.88.6,<2; aider-chat pins ==1.81.10 and openhands-ai ==1.84.1 | The first row's check |
| A lockfile, Dependabot, or Renovate change that moved litellm to 1.107.0 | Any machine installed from it: CI on the pull request, a teammate's laptop, a deploy. After 13:23 UTC only a cache could still serve the files | git log --all -S 1.107.0 over your lockfiles (commands) |
Docker: litellm/litellm, ghcr.io/berriai/litellm, litellm-database, or litellm-non_root at v1.107.0, latest, main-latest, or main-stable | A container started from an image pulled between 11:46 and 14:20 UTC. The GHCR tags were gone by 13:51 UTC | Compare docker images --digests with the bad digests |
Your own image built FROM one of those tags, or with an unpinned pip install litellm | It was built in that window. A RUN step on top of the bad image starts Python | List the image's files without starting it (commands) |
Kubernetes, including the litellm-helm chart | A pod started in the window from one of those tags, or from an image pulled then. The published chart runs the image tagged with its own version by default: chart 1.106.2 runs ghcr.io/berriai/litellm:1.106.2, the clean digest (checked 06:10 JST). A 1.107.0 chart and an unprefixed 1.107.0 image tag don't exist now; whether they existed in the window is not stated | helm history <release> for upgrades on October 21, pod image IDs with kubectl, and crictl images --digests on each node |
| A registry mirror or pull-through cache (Harbor, Artifactory, a Docker mirror) | It synced one of the four tags in the window. Your copy keeps the bad image after LiteLLM repointed the tag | Look up the tag's digest in your mirror |
| CI runners: GitHub Actions, GitLab CI, CircleCI, Buildkite | A job installed or ran LiteLLM in the window, or later restored a cache made then | The job's log for litellm-1.107.0; delete pip and uv caches saved that day |
| Platform builds: Railway, Render, Fly.io, Heroku, Vercel's Python runtime | A build in the window resolved litellm without a pin | The build log for litellm-1.107.0. That deploy ran the payload with the service's environment variables |
| LiteLLM Enterprise, from LiteLLM's private registry | Not affected, per LiteLLM: "Our private registry is not affected" | Nothing to check |
Built from the GitHub v1.107.0 source tag | LiteLLM: the official tag "does not contain the malicious files" | Nothing to check for the tag itself |
The pre-releases 1.107.0rc1 and 1.107.0rc2 | Clean, per Upwind's and SafeDep's analyses. A plain pip install doesn't pick pre-releases without --pre | Nothing to check |
The shell check, step by step
On Linux or macOS, search for the planted files and the dist-info folder. On a large disk, replace / with the places your Python environments live: your home directory, /usr, /opt, and your projects.
# 1. The planted files and the 1.107.0 install record
find / \( -path /proc -o -path /sys \) -prune -o \( -name cook.pth -o -name 'litellm-1.107.0.dist-info' \) -print 2>/dev/null
# sitecustomize.py has legitimate uses; the planted one is 25,509 bytes in site-packages
# (dist-packages for Debian and Ubuntu's system Python)
find / \( -path /proc -o -path /sys \) -prune -o \( -path '*site-packages*' -o -path '*dist-packages*' \) -name sitecustomize.py -size 25509c -print 2>/dev/null
# 2. The markers the payload leaves
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null
# 3. The background daemon: the process holding the lock (Linux and macOS)
# sudo, so lsof also shows processes of other users, such as a service account
sudo lsof /tmp/remote_code_loader.lock 2>/dev/null
If you have already run pip install litellm==1.106.2 over 1.107.0, the 1.107.0 dist-info folder may be gone. Steps 2 and 3 still apply, because the daemon and the markers live outside site-packages. On Linux, sudo fuser -v /tmp/remote_code_loader.lock also names the daemon. A container has its own /tmp, so these checks on the host don't see inside it: for a container, compare its image digest and rebuild it.
Check what your lockfiles have held, including in old commits and pull requests, in every folder of the repository:
grep -A1 '^name = "litellm"$' uv.lock poetry.lock 2>/dev/null
grep -n -i -E '^litellm(\[[^]]*\])? *==' requirements*.txt 2>/dev/null
git log --all --oneline -S '1.107.0' -- ':(glob)**/uv.lock' ':(glob)**/poetry.lock' ':(glob)**/Pipfile.lock' ':(glob)**/requirements*.txt'
On Windows, in PowerShell. Upwind reports that on Windows the daemon fails to start, but cook.py still runs; there, cook.pth is 13 bytes, because of the CRLF line ending.
Get-ChildItem -Path $env:USERPROFILE, $env:LOCALAPPDATA, $env:ProgramData, 'C:\Program Files', 'C:\Python*' -Recurse -Force -ErrorAction SilentlyContinue -Include cook.pth, 'litellm-1.107.0.dist-info'
For Docker, list local images with their digests and compare them with the digest table. To look inside an image you built without starting it, create a container and list its files; docker create doesn't run anything.
docker images --digests | grep -i litellm
docker ps -a | grep -i litellm
docker export "$(docker create your-image:tag)" | tar -t | grep -E 'cook\.pth|litellm-1\.107\.0'
For Kubernetes, list your Helm release's upgrades, the image IDs running pods report, and the images each node still holds:
helm history <release> -n <namespace> # an upgrade dated October 21, between 11:46 and 14:20 UTC?
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\t"}{range .status.containerStatuses[*]}{.imageID}{" "}{end}{"\n"}{end}' \
| grep -E '1bbcb1443bc9|c00f9d92c427|7b00e91cd2fe|bd1aeee419ea|56b74573eed6|24581ddc3076|827dd893c74c|645a67d81062|f75c8edae59b'
crictl images --digests | grep -i litellm # on each node
For GitHub Actions, list the runs of October 21 and search their logs:
gh run list --repo OWNER/REPO --created 2026-10-21 --limit 200
gh run view RUN_ID --repo OWNER/REPO --log | grep -m1 -E 'litellm-1\.107\.0|litellm==1\.107\.0'
What a result means
litellm-1.107.0.dist-info,cook.pth, or the 25,509-bytesitecustomize.pyin site-packages: the implant is installed. Go to the cleanup steps./tmp/.pc_once_72129118c59f5636c0bb: the stealer sent your data. Upwind and SafeDep both report that it writes this file only after a successful upload./tmp/remote_code_loader.lock: the hourly daemon ran. Ifsudo lsofnames a process, it is still running.- A hit under
/var/lib/dockeror/var/lib/containerdis a stored image or container layer. An image that never started is inert; use the Docker commands below to see whether a container ran from it. - Nothing found on a host that installed or ran LiteLLM in the window: LiteLLM's advice still applies. "If 1.107.0 was ever installed on a host with outbound internet, assume the exfil succeeded." Containers and
/tmpcleanup remove the marker.
What the malware does
Each of the seven 1.107.0 wheels (Linux, macOS, and Windows) put three extra files at the root of site-packages, per the analysis Upwind posted in LiteLLM's issue and SafeDep's write-up. Python runs two of them whenever it starts, so they run in every Python process that uses that environment, the venv or interpreter 1.107.0 was installed into, even one that never imports litellm: your app, pip, an IDE's language server, a CI job.
cook.pthholds one line,import cook. Python runs that line at startup, which loadscook.py.cook.pyis a stealer that calls itselfprivcollect. Per SafeDep's module table and Upwind's decode, it collects SSH keys; AWS, Azure, gcloud, Kubernetes, and Docker credentials; GitHub CLI and.git-credentialstokens;.npmrcand.pypirc;.envfiles and private keys; browser passwords and cookies; shell histories; the tokens and configs of AI coding tools (Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Copilot, Windsurf, Aider, Continue, Goose, opencode, Qwen, Crush, Kiro, Zed, VS Code'smcp.jsonfor MCP servers, Hugging Face, and, in Upwind's decode, the VS Code extensions Cline, Roo Code, Cody, and Claude Code, plus Amp, Factory, Trae, and Ollama); the environment variables of every process it can read through/proc/*/environ; and cloud credentials from the instance metadata service.- If
DATABASE_URLis set, it dumps the LiteLLM proxy'sLiteLLM_CredentialsTableandLiteLLM_ProxyModelTable, the tables that hold every upstream provider key the proxy stores. - It encrypts what it collects (AES-256-CBC, with the key wrapped in RSA-4096) and posts it to
http://www.litellm.dev/healthcheck. That domain is not LiteLLM's: the project's own islitellm.ai. sitecustomize.pystarts a detached daemon that holds/tmp/remote_code_loader.lock. Every hour it reads a file in the GitHub repositoryletters9813/pythonclient-test, and if the file holds a domain, runs the stealer again and downloads and executeshttp://<domain>/codepy. Upwind found the file empty since October 20, 09:15 UTC; SafeDep saw the account return 404 at 13:40 UTC on October 21, and Wiz says GitHub took the repository down. SafeDep adds that hosts whose daemon ran before then "could have received and run second-stage code", and that it could not recover a server address.
The stolen credentials were used. Wiz reports that between 16:20 and 17:01 UTC on October 21, about 4.5 hours after exfiltration, an attacker validated AWS instance-role credentials "harvested from compromised CI runners and Kubernetes nodes" with sts:GetCallerIdentity, followed by bedrock:ListFoundationModels and a ListModels call, "consistent with LLMjacking": using someone else's cloud account to run AI models. In a few environments it also listed IAM roles, users, and access keys. Wiz names two source IP addresses in its post. Wiz also puts downloads of 1.107.0 at about 49,000, from ClickPy data (Wiz's figure, updated October 22).
Indicators of compromise
| Type | Value | Source |
|---|---|---|
| Files in site-packages | cook.pth (12 bytes; 13 on Windows), cook.py (22,252 bytes), sitecustomize.py (25,509 bytes), as the wheels' records give them for Linux and macOS | Upwind, in LiteLLM's issue |
| SHA-256, Linux and macOS | cook.pth 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb; cook.py 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25; sitecustomize.py 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa | SafeDep, Wiz |
| SHA-256, Windows (CRLF line endings) | cook.pth 84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d; cook.py 03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea; sitecustomize.py b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465 | SafeDep, Wiz |
| Host markers | /tmp/.pc_once_72129118c59f5636c0bb, /tmp/remote_code_loader.lock, temp folders /tmp/.pc_sq_* and /tmp/.pc_e_*, the environment variable PRIVCOLLECT_ACTIVE=1 inside infected Python processes, a detached Python process in its own session with working directory / | Upwind, SafeDep |
| Network | POST http://www.litellm.dev/healthcheck with the header X-Token: XWiA8gZNa7NcX2XRoAEb; raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt; Python processes calling 169.254.169.254 | Upwind, SafeDep |
| Cloud logs | sts:GetCallerIdentity, then bedrock:ListFoundationModels, from outside your network | Wiz |
| Git | Commits from Berri-AI <ckdi82fkoos@proton.me> in the lookalike repository github.com/Berri-AI/litellm, and a last one, 20535d8fa11f786a6c24df7488a0e449ed5f09ad, from Berri-AI <Berri-AI@users.noreply.github.com> | LiteLLM, Upwind, Wiz |
Clean up a host that ran litellm 1.107.0
The order comes from LiteLLM's report, whose remediation steps credit the researcher who reported the incident. Steps 1 to 3 happen in the shell, before any Python starts.
1. Don't start Python in the affected environment
No pip install, no pip show, no python -c, no notebook kernel, and no editor opening the project, since a language server starts Python too. Every Python start runs cook.pth and sitecustomize.py again. LiteLLM's report suggests pip show litellm as a check, but the same report says any Python start re-runs the payload, so use the shell check instead.
2. Delete the planted files from every affected site-packages
Set SP to each site-packages folder the check found, then remove the three files and their compiled copies: rm -f "$SP/cook.pth" "$SP/cook.py" "$SP/sitecustomize.py" "$SP"/__pycache__/cook*.pyc "$SP"/__pycache__/sitecustomize*.pyc. On Windows, in PowerShell: Remove-Item "$SP\cook.pth", "$SP\cook.py", "$SP\sitecustomize.py", "$SP\__pycache__\cook*.pyc", "$SP\__pycache__\sitecustomize*.pyc" -Force -ErrorAction SilentlyContinue.
3. Kill the daemon
Find the process holding the lock with sudo lsof /tmp/remote_code_loader.lock (or sudo fuser -v /tmp/remote_code_loader.lock on Linux) and stop it with kill. Run the lsof line again: it should name no process.
4. Rebuild from scratch on 1.106.2
LiteLLM's report: "Reinstall pinned to litellm==1.106.2. Rebuilding images and venvs from scratch is better." Delete the venv and recreate it with litellm==1.106.2. For Docker, pin the 1.106.2 image by digest, such as litellm/litellm@sha256:7294b90f868c5c38c99b83021c7ba46f9b439e95f8e32964c925c7d2563a03f7. Clear the caches that could hand 1.107.0 back: uv cache clean litellm, the pip cache folder (~/.cache/pip on Linux, ~/Library/Caches/pip on macOS, %LOCALAPPDATA%\pip\Cache on Windows), and your CI caches (gh cache list, then gh cache delete).
5. Assume the data left the host
If the host had outbound internet, LiteLLM tells you to assume the exfiltration succeeded. The marker file confirms it; its absence proves nothing.
6. Rotate every credential the host could reach
LiteLLM names four in particular: "the instance/task IAM role", "every provider key stored in the LiteLLM proxy DB, plus the master and virtual keys", "the DATABASE_URL password", and "any secret that was in the environment of any process on the host". The rotation table covers the rest of what the stealer reads.
7. Search your cloud logs for use of the stolen keys
LiteLLM: "Check CloudTrail for GetCallerIdentity from the instance role around install time, then for use of the role's credentials from outside your infrastructure." For example, aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=GetCallerIdentity --start-time 2026-10-21T11:00:00Z, in each Region you use, us-east-1 included. Then look for ListFoundationModels and other Bedrock calls from addresses outside your network, the pattern Wiz saw.
Credentials to rotate, by type
The middle column is where the stealer looks, per SafeDep's module table and Upwind's decode. Rotate what the host could reach, starting with what costs money or ships code.
| Credential | Where the stealer reads it | What to do |
|---|---|---|
| AWS instance or ECS task role | The instance metadata service (IMDSv2, then v1) and ECS task credentials | Revoke the role's active sessions in IAM, then check CloudTrail (step 7). Wiz saw these used within hours |
| AWS, GCP, and Azure user credentials | ~/.aws/credentials, gcloud credentials, Azure CLI tokens, environment variables | Deactivate and replace access keys; delete and recreate service-account keys; sign out the CLIs and revoke their sessions |
| AI provider keys behind a LiteLLM proxy | LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable, through DATABASE_URL | Create new keys at each provider (OpenAI, Anthropic, Bedrock, Vertex, Azure OpenAI, and the rest), update the proxy, then revoke the old ones |
| LiteLLM master key and virtual keys | The proxy's environment and database | Replace the master key and reissue virtual keys. Virtual keys are what your users hold, so tell them |
| The proxy's database password | DATABASE_URL in the environment | Change the password and update DATABASE_URL |
| AI coding tool logins | Claude Code, Claude Desktop, Codex auth.json, Cursor, Copilot, Gemini CLI, Windsurf, Kiro, Zed, Hugging Face token | Log out and back in, and revoke API keys in each provider's console |
| API keys in other AI tools' config files | Aider (~/.aider.conf.yml), Continue (~/.continue config and .env), Goose, opencode, Qwen (~/.qwen/oauth_creds.json), Crush; in Upwind's decode also Cline, Roo Code, Cody, Amp, Factory, Trae, and Ollama | Replace every key those files hold, including the LiteLLM virtual key a client used to reach your proxy |
| MCP server tokens | VS Code's and Cursor's mcp.json, Windsurf's mcp_config.json, Kiro's ~/.kiro/settings/mcp.json, and the configs of the tools above | Revoke and reissue each token those files hold |
| GitHub | gh CLI's hosts.yml, .git-credentials | Revoke the GitHub CLI's authorization under Settings, Applications, Authorized OAuth Apps, and replace personal access tokens |
| npm, PyPI, Cargo, RubyGems, Maven, Gradle, Composer | .npmrc, .pypirc, and the other registry configs | Revoke the tokens, and check that no new versions of your own packages went out since October 21 |
| SSH and GPG keys | ~/.ssh/*, GPG private keys | Make new keys, then remove the old public keys from servers, GitHub, and GitLab |
| TLS private keys | /etc/ssl/private, Let's Encrypt folders, *.pem, *.key, *.pfx | Reissue the certificates and revoke the old ones |
| Kubernetes and Docker | The service-account token, ~/.kube/config, ~/.docker/config.json | Rotate the service account and kubeconfig credentials; revoke registry tokens |
| CI and infrastructure secrets | GitLab runner config, Jenkins keys, .vault-token, Terraform and Packer credentials, /run/secrets/* | Rotate each, starting with tokens that can deploy |
| Browser passwords and cookies, on a laptop | Chromium's saved passwords (decrypted) and cookies, Firefox logins.json | Change the passwords and sign out every session, starting with email, cloud consoles, and GitHub |
Everything else in an environment variable or a .env file | /proc/*/environ of every process it can read, and .env, .env.local, .env.production, .env.development in your home and working folders | Rotate it. On a shared host, that includes other services' secrets |
Which LiteLLM version is safe
On PyPI, 1.106.2. It is the latest release PyPI lists (checked 06:11 JST), uploaded on October 21 at 10:44 UTC, an hour before the bad release. pypi.org/pypi/litellm/1.107.0/json returns 404, and there is no 1.107.1 or later. LiteLLM's guidance: "pin your version to 1.106.2 or any explicit version published prior to now", and "All previous versions are safe." The March versions 1.82.7 and 1.82.8 are the exception; PyPI removed them too, but a private mirror may still hold them. Upwind checked every wheel of 1.103.6, 1.103.7, 1.104.4, 1.104.5, 1.105.2, 1.105.3, 1.106.0, 1.106.1, 1.106.2, 1.107.0rc1, and 1.107.0rc2 and found them clean.
On Docker, the 1.106.2 images, pinned by digest. As checked at 06:11 JST, v1.106.2, latest, main-latest, and main-stable all resolve to the same digest on Docker Hub and GHCR, per image; v1.107.0 returns "not found" on both.
| Image | 1.106.2 digest (clean) |
|---|---|
litellm/litellm, ghcr.io/berriai/litellm | sha256:7294b90f868c5c38c99b83021c7ba46f9b439e95f8e32964c925c7d2563a03f7 |
litellm/litellm-database, ghcr.io/berriai/litellm-database | sha256:35f2cec3a14bb297e08bfab56c1fc377432bc4071aaf71978fdff043a5d91e79 |
litellm/litellm-non_root, ghcr.io/berriai/litellm-non_root | sha256:07fabea33dfc2b127e865c03112dfc76936334b29028ec05cdd355af2385dc04 |
A new release is planned. LiteLLM's registries have been locked to new releases since October 21. Its post of October 23 says: "At this point, we are confident in our ability to safely issue a release by EOD today, providing our users with a fix for removing the eMudhra certificate authority from our trust store." PyPI still listed 1.106.2 as the latest at 06:27 JST on October 24. When the release comes, check it before you move to it: Upwind's report asked the maintainers to "Treat the next release as untrusted" until the release environment is audited, and LiteLLM says it will share its process changes "next week". For the eMudhra certificates themselves, see Chrome's eMudhra root removal on October 27: what to do.
One more date: October 27. LiteLLM says its private enterprise registry carries "an embargoed release that remediates a vulnerability whose disclosure date is scheduled for October 27." LiteLLM hasn't said whether 1.106.2 has that vulnerability. If your proxy is reachable from the internet, watch for the October 27 disclosure.
The Docker and Kubernetes case
LiteLLM's report lists these image indexes as compromised. The platform digests are Wiz's; LiteLLM gives only the first 12 characters for the -database and -non_root indexes.
| Image | Bad index digest (LiteLLM) | Bad platform digests (Wiz) |
|---|---|---|
ghcr.io/berriai/litellm and docker.io/litellm/litellm | sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e | amd64 bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5, arm64 56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf |
ghcr.io/berriai/litellm-database | sha256:c00f9d92c427… | amd64 24581ddc30767f16ecb69d2b0517556f2cd7dea56971adb42a3d1beb97d69523, arm64 827dd893c74cf621a860a66a3a447a5c732533f087589ce905573e285c0a239c |
ghcr.io/berriai/litellm-non_root | sha256:7b00e91cd2fe… | amd64 645a67d810628252d63df85dcc8824c58bcc77cf8383f4d3e6321f0ccc3ce9e4, arm64 f75c8edae59b72b1ebfd617295833f465c85bb2fab9c1d60f21b496db86cb8da |
LiteLLM says all affected Docker Hub tags "and image variants" were removed or repointed by 14:20 UTC, but it lists no Docker Hub digests for litellm/litellm-database and litellm/litellm-non_root. If you ran either with one of the four tags in the window, treat it like its GHCR twin.
- Pulling isn't running. The payload fires when a Python interpreter starts from the image:
docker run, aRUNstep in a build on top of it,docker execinto its container. A commenter's reproduction with a test image, posted in LiteLLM's issue on October 22, found that unpacking an image to local storage, the step a pull performs, set nothing off, whiledocker rundid. A host that only pulled or mirrored the image holds an inert copy; delete it. - Inside the image, the files sit in the app's venv: Upwind found them under
/app/.venv/lib/python3.13/site-packages/in the amd64ghcr.io/berriai/litellmimage. - A proxy with a database is the worst case. The
-databasevariant is built for a proxy withDATABASE_URLset, and the stealer dumps the provider keys from that database on first start. - The Docker Hub floating tags now point to 1.106.2. Docker Hub's API shows
latest,main-latest, andmain-stableon all three images pushed again between 17:50 and 17:52 UTC on October 21, to the 1.106.2 digests (checked 06:11 JST). - A signature check would have passed. LiteLLM signs its images, and its report says the hijacked workflow "signed and published the poisoned artifacts". Compare digests, not signatures, for this incident.
- Kubernetes nodes count too. The stealer reads instance-metadata credentials, and Wiz saw credentials taken from Kubernetes nodes in use. If a pod ran the bad image, rotate the node's instance role and the pod's service account, not only the proxy's keys.
- The published Helm chart pins the image to its own version by default. The chart at
oci://ghcr.io/berriai/litellm-helmfills an emptyimage.tagwith itsappVersion: chart 1.106.2 hasappVersion1.106.2 and runsghcr.io/berriai/litellm:1.106.2, which resolves to the clean digest (checked 06:10 JST). A 1.107.0 chart and an unprefixed1.107.0image tag both return "not found" now; LiteLLM's report doesn't say whether either existed in the window, so checkhelm history <release>for an upgrade on October 21. If you setimage.tagtolatestor another floating tag, the chart'spullPolicy: Always(in its values file on the main branch, read 05:38 JST) meant every pod start in the window pulled the bad image.
How the attacker got in, and what to check in your own setup
LiteLLM announced its completed investigation, dated October 23, in its GitHub issue on October 23 at 21:15 UTC (October 24, 06:15 JST), and calls it "the canonical source of truth where the status updates and the narrative below disagree". Its account, in order:
- May 27, 2026: a configuration file committed to the public repository
LiteLLM-Labs/lite-harnessheld the master key of a deployment of the LiteLLM Agent Platform, a tool LiteLLM uses to orchestrate agents. The commit was force-pushed over, but "key revocation did not occur." - October 10: the attacker used that key on the Agent Platform's API, which returned the configuration of every managed agent, including the GitHub CLI OAuth token of one of LiteLLM's two GitHub organization owners. That token "was not associated with a service account and was not scoped down."
- October 10, minutes later: with the owner's token, the attacker pushed throwaway branches to two repositories whose workflows posted the repositories' CI secrets to a webhook.site endpoint, then deleted the runs. Two of those secrets held the GitHub CLI tokens of two more maintainers.
- October 21: the owner's token overrode branch protection to point the release workflow at the lookalike
Berri-AI/litellm, the second maintainer's token approved the release, the third deleted the run records, and the owner's token force-pushed the change out of the visible history.
This replaces LiteLLM's first account. Its first post suspected that the tokens were taken from the logs of an internal, internet-facing LiteLLM deployment, and described Claude Code putting GitHub CLI tokens into logged response bodies. The October 23 post says: "Our first post suspected the tokens were taken from the logs of an internal LiteLLM deployment. The request logs show the first token came from a different service." The first post still carries the old passage (checked 06:25 JST).
What the chain says to check in your own setup:
- Keys that were ever public. Search your public repositories and their history for LiteLLM master keys and other API keys, and revoke any key that was ever committed, even if the commit was rewritten.
- Personal GitHub tokens in agents and CI. The stolen tokens were GitHub CLI OAuth tokens, which start with
gho_, per GitHub's documentation; personal access tokens start withghp_orgithub_pat_. Search agent configs, CI secrets, and logs for them, and replace them with tokens scoped to one job. LiteLLM notes that GitHub CLI tokens "are post-2fa and not subject to GitHub's 'super-user' 2fa reauth flows". - Your GitHub organization's audit log. Look for branch-protection overrides, deleted workflow runs, and workflow runs on branches nobody on your team made.
- CI secrets. If a workflow you didn't write ran in one of your repositories, rotate every secret that workflow could read.
Not the March 2026 LiteLLM compromise
Searches for "litellm compromised" also return the March 2026 incident. It is a different event, with different versions, and the Docker image was not affected then.
| March 24, 2026 | October 21, 2026 | |
|---|---|---|
| PyPI versions | 1.82.7 and 1.82.8 | 1.107.0 |
| Window, per LiteLLM | From 10:39 UTC "for about 40 minutes" | 11:39 to 13:23 UTC on PyPI; 11:46 to 14:20 UTC on Docker |
| Official Docker image | "not impacted" | v1.107.0, latest, main-latest, main-stable compromised |
| How it ran | litellm_init.pth (1.82.8) and code in proxy_server.py | cook.pth and sitecustomize.py |
| Where data went | models.litellm.cloud | www.litellm.dev |
| Origin, per LiteLLM | "the Trivy dependency used in our CI/CD security scanning workflow" | A LiteLLM Agent Platform master key committed to a public repository on May 27 and not revoked ("key revocation did not occur"); through it, an organization owner's GitHub CLI token on October 10, then two maintainers' tokens from CI secrets; a release built from the lookalike repository Berri-AI/litellm |
| OSV and GitHub records | MAL-2026-2144 and PYSEC-2026-2 in OSV; GHSA-5mg7-485q-xm76 and the malware advisory GHSA-92x9-889m-jgmw on GitHub | Added to MAL-2026-2144 on October 21 and to GHSA-92x9-889m-jgmw by October 22, 00:32 UTC |
Timeline in UTC and JST
Times are from LiteLLM's two posts and its maintainers' comments unless a row names another source.
| UTC | JST | Event |
|---|---|---|
| May 27, 18:53 | May 28, 03:53 | A config file with the LiteLLM Agent Platform's master key is committed to the public LiteLLM-Labs/lite-harness; per LiteLLM, "key revocation did not occur" |
| Oct 10, 21:13–21:28 | Oct 11, 06:13–06:28 | The attacker calls the Agent Platform's API with that key; at 21:27 it returns an organization owner's GitHub CLI token |
| Oct 10, 22:11–22:12 | Oct 11, 07:11–07:12 | Throwaway workflows in two LiteLLM repositories post CI secrets, two more maintainers' tokens among them, to webhook.site |
| Oct 13, 00:45 | Oct 13, 09:45 | litellm.dev registered (Upwind, from public registry data) |
| Oct 21, 08:56–10:46 | 17:56–19:46 | 10 commits in the lookalike Berri-AI/litellm add the payload (Upwind, Wiz) |
| Oct 21, 10:08 | 19:08 | The owner's stolen token overrides the branch protection on the private release workflow |
| Oct 21, 10:44 | 19:44 | litellm 1.106.2, the current clean release, uploaded to PyPI (PyPI) |
| Oct 21, 10:46 | 19:46 | The modified workflow starts a staging run from Berri-AI/litellm |
| Oct 21, 11:11 | 20:11 | The second maintainer's stolen token approves the production gate |
| Oct 21, 11:39–11:40 | 20:39–20:40 | Seven malicious 1.107.0 wheels uploaded to PyPI |
| Oct 21, 11:46 | 20:46 | The bad Docker images published under four tags |
| Oct 21, 11:53 | 20:53 | The third maintainer's stolen token deletes the workflow records |
| Oct 21, 12:06 | 21:06 | Upwind's report opens LiteLLM's GitHub issue #47826 |
| Oct 21, 12:09–12:24 | 21:09–21:24 | The owner's token reverts the workflow change and force-pushes it out of the visible history |
| Oct 21, 13:23 | 22:23 | PyPI has removed 1.107.0 |
| Oct 21, 13:51 | 22:51 | The GHCR tags unavailable; Docker Hub's still resolve to the bad image |
| Oct 21, 14:20 | 23:20 | All affected Docker Hub tags removed or repointed |
| Oct 21, 16:20–17:01 | Oct 22, 01:20–02:01 | Stolen AWS credentials validated and used to list Bedrock models (Wiz) |
| Oct 21, 17:50–17:52 | Oct 22, 02:50–02:52 | Docker Hub's latest, main-latest, and main-stable pushed to the 1.106.2 digests (Docker Hub API) |
| Oct 21, 18:30 | Oct 22, 03:30 | LiteLLM publishes its incident post |
| Oct 21, 21:22 | Oct 22, 06:22 | LiteLLM's first account of the attack chain, replaced on October 23 |
| Oct 22, 02:19 | Oct 22, 11:19 | Releases still paused; updates move from every 2 hours to "significant developments" |
| Oct 23, 21:15 | Oct 24, 06:15 | LiteLLM announces its completed investigation, dated October 23, in its issue: its "canonical source of truth", with a release planned "by EOD today" |
| Oct 27 | Oct 27 | Disclosure date of the vulnerability LiteLLM's embargoed enterprise release fixes |
Tell the people who use your proxy
If your LiteLLM proxy served your own users or customers, reissuing its virtual keys breaks every client that still holds an old one. Ask your coding agent to write one HTML page for them: which keys stopped working and from when, where to get a new key, and whether they need to rotate anything of their own.
Convika turns that file into a link: sign up free, drop the file on the upload screen, check the private preview, and publish (publish an HTML page). On the free plan the page is public: search engines and Convika's gallery may list it, so leave out key prefixes, hostnames, and anything internal.
Updates to this page
- October 24, 2026, 06:27 JST: LiteLLM's completed investigation, dated October 23 and announced at 21:15 UTC, withdraws its first post's suspicion that the tokens came from the logs of an internal LiteLLM deployment; the attacker started from an Agent Platform master key committed to a public repository on May 27. The section on how the attacker got in, the timeline, and the March comparison follow the October 23 account, and the version section adds the release LiteLLM plans.
- October 24, 2026, 06:15 JST: first version, with every changeable fact checked again between 06:10 and 06:15 JST.
This page changes when LiteLLM publishes the release it planned on October 23, when it shares its process changes, when it names the vulnerability behind its October 27 disclosure, when a CVE or a LiteLLM advisory is published, and when anyone reports new use of the stolen credentials. The same week, for developers: Node.js October 2026 security release: what to update.
Frequently asked questions
Is litellm 1.107.0 compromised?
Yes. All seven litellm 1.107.0 wheels on PyPI, live from 11:39 to 13:23 UTC on October 21, 2026, contained a credential stealer and a remote-code loader, per LiteLLM's incident report. PyPI has removed the release.
How do I check if I installed litellm 1.107.0 without running Python?
Search for cook.pth and a litellm-1.107.0.dist-info folder from the shell, with find, and look for /tmp/.pc_once_72129118c59f5636c0bb and /tmp/remote_code_loader.lock. Don't run pip show: any Python start in the affected environment runs the payload again.
Which LiteLLM version is safe to install?
1.106.2, which PyPI lists as the latest release (checked October 24, 06:11 JST). Pin it with litellm==1.106.2, and pin the Docker image by its 1.106.2 digest. LiteLLM says all versions before 1.107.0 are safe, March's 1.82.7 and 1.82.8 excepted.
Is the LiteLLM Docker latest tag safe now?
As checked on October 24 at 06:11 JST, latest, main-latest, and main-stable on Docker Hub and GHCR resolve to the 1.106.2 images. Between 11:46 and 14:20 UTC on October 21, they pointed to the compromised image. A copy pulled then, or synced by a mirror, is still the bad image.
Does docker pull alone run the malware?
No. The payload runs when Python starts inside the image, as with docker run, a RUN step in a build, or docker exec. A commenter's reproduction with a test image, posted in LiteLLM's issue, found that unpacking an image, as a pull does, set nothing off. Delete the pulled copy anyway.
What is cook.pth?
A 12-byte file (13 bytes on Windows) the bad wheels put in site-packages. It holds import cook, which Python runs at every start, so it loads cook.py, the credential stealer. Its SHA-256 on Linux and macOS is 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb.
Does uninstalling litellm remove the malware?
Not safely. pip uninstall starts Python, which runs the payload first, and the hourly daemon can keep running outside site-packages. Delete the files from the shell, kill the process holding /tmp/remote_code_loader.lock, and rebuild the environment.
Were LiteLLM Enterprise customers affected?
No, per LiteLLM: customers running the private enterprise release from its private registry were not affected.
Is this the same as the March 2026 LiteLLM compromise?
No. March affected 1.82.7 and 1.82.8 on PyPI, and LiteLLM said its official Docker image was not impacted. October affected 1.107.0 on PyPI and four Docker tags.
Is there a CVE for the LiteLLM 1.107.0 compromise?
No CVE as of October 24, 06:11 JST. GitHub's Advisory Database lists 1.107.0 in the malware advisory GHSA-92x9-889m-jgmw (updated October 22, 00:32 UTC), and the OSV database in the malware record MAL-2026-2144; both also cover 1.82.7 and 1.82.8. LiteLLM has published no advisory of its own for it.
How did the attacker publish LiteLLM 1.107.0?
Per LiteLLM's completed investigation of October 23: a LiteLLM Agent Platform master key, committed to a public repository on May 27 and not revoked ("key revocation did not occur"), let the attacker read an organization owner's GitHub CLI token on October 10. With it, the attacker took two maintainers' tokens from CI secrets, and on October 21 used the three tokens to point the release workflow at a lookalike repository, approve the release, and delete the records. LiteLLM's first post had suspected the logs of an internal LiteLLM deployment; the October 23 post replaces that.
Were the stolen credentials used?
Yes, per Wiz: between 16:20 and 17:01 UTC on October 21, an attacker validated stolen AWS instance-role credentials and used them to list Amazon Bedrock models. Check CloudTrail for GetCallerIdentity and Bedrock calls from outside your network.
Sources
Times are in JST (UTC+9), on October 24, 2026.
- LiteLLM: The attack chain behind the v1.107.0 LiteLLM release, published October 23, 2026, announced in issue #47826 at 21:15 UTC (read 06:25)
- LiteLLM: Oct 21, 2026 LiteLLM release v1.107.0 Compromised, published October 21, 2026, last updated October 21, 20:30 UTC (read 05:36, 06:11, and 06:25)
- LiteLLM issue #47826, opened October 21, 2026, 12:06 UTC by Upwind's researcher, with the maintainers' status comments to October 23, 21:15 UTC (read 05:36, 06:11, and 06:25), and Upwind's decoded payload in its comments of October 21, 12:57 UTC (read 06:10)
- PyPI JSON API: litellm and litellm 1.107.0 (read 05:36 and 06:11)
- Docker Hub: litellm/litellm tags, with litellm-database and litellm-non_root, through Docker Hub's tag API (read 05:36, 05:40, and 06:11)
- GHCR: berriai/litellm, litellm-database, and litellm-non_root, by registry HEAD requests (read 05:37 and 06:11)
- GHCR: berriai/litellm-helm, the published chart 1.106.2 and its appVersion, and the
litellm:1.106.2andlitellm:1.107.0image tags, by registry requests (read 06:10) - OSV: MAL-2026-2144, modified October 21, 2026, 22:01 UTC (read 05:37 and 06:11)
- GitHub Advisory Database: GHSA-92x9-889m-jgmw, malware, updated October 22, 2026, 00:32 UTC, and GHSA-5mg7-485q-xm76, updated March 27, 2026 (read 06:10)
- LiteLLM GitHub security advisories, the repository's own, newest October 1, 2026 (read 05:37 and 06:15)
- LiteLLM: Security Update, March 2026, last updated March 27, 2026, with a March 30 note (read 05:38)
- LiteLLM: helm/litellm-helm/values.yaml and Chart.yaml on main (read 05:38)
- SafeDep: Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor, October 21, 2026 (read 05:36 and 06:12)
- Wiz: LiteLLM Compromised, updated to October 22, 2026, 12:55 UTC (read 05:37)
- conda-forge: litellm, through the anaconda.org API (read 05:39)
- PyPI JSON for dspy, smolagents, langchain-litellm, crewai, aider-chat, and openhands-ai, newest releases (read 05:39)
- GitHub Docs: About authentication to GitHub, GitHub's token formats (read 05:40)
Related articles
- GitLab CVE-2026-110264: which version to upgrade to and howGitLab 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, and 18.11.13 fix the CVSS 10.0 RCE CVE-2026-110264. Upgrade paths by install type, Ruby, KEV status.
- Next.js security releases in 2026: which version fixes whatInstall Next.js 16.4.3, or 15.5.28 with sharp 0.35.5. All 10 security releases of 2026 with GHSA and CVE ids, and what each host has said, or hasn't.
- Railway security incident, October 2026: what to do nowRailway says to roll every environment variable after its October 2026 incident. Which keys to rotate, why deploys fail, and when each plan got builds back.