npx wardlatch@latest --incident litellm-2026-10
Wardlatch · LiteLLM · Check and fix · Free
litellm 1.107.0Did the LiteLLM stealer reach you?
Only if you installed litellm==1.107.0 from PyPI, or pulled LiteLLM’s Docker images tagged v1.107.0, latest, main-latest or main-stable, during about two hours on 2026-10-21. LiteLLM’s release pipeline was hijacked, and those releases carried a credential stealer that runs every time Python starts in that environment. Earlier versions are not affected, LiteLLM says. Check without starting Python, and if you find it, assume the theft succeeded.
- PyPI
litellm==1.107.0, all seven wheels, 11:39 to 13:23litellm==1.106.2 - Docker Hub
v1.107.0,latest,main-latest,main-stable, 11:46 to 14:20v1.106.2 by digest - GitHub’s registrythe same tags of
litellm,litellm-databaseandlitellm-non_root, 11:46 to 13:51v1.106.2 by digest
v1.107.0 source tag on GitHub and its private enterprise registry were not affected.Look for it without starting Python.
Don’t check with pip show, pip list or python -c: starting Python in an affected environment, pip included, runs the stealer again. Both checks below only read files. Run Wardlatch from your home folder (after cd ~); it needs Node.js 18.17 or later.
It never starts Python for this check. In Python’s site-packages folders (system, Homebrew, user, pyenv, conda, pipx, uv and Poetry environments, and the virtual environments under your project folders) it looks for the release’s install record, litellm-1.107.0.dist-info, and the files the wheels drop: cook.pth and cook.py by name, sitecustomize.py by its SHA-256. It looks in the temp folders for the markers the stealer leaves when it runs, and in Docker’s and Podman’s local image lists for the compromised images, by digest and by tag. It pulls and runs nothing, and sends nothing, apart from asking your container engine for its image list: the engine your current docker context names, which can be another machine.
By hand, start with the files. This searches your home folder and the system’s install folders, and prints the path of anything it finds; it can take a few minutes. It passes the paths through cat -v, run as command cat so an alias or function named cat isn’t run: a control character in a folder name shows in cat -v notation, such as ^[ or M-BM-^[, and isn’t acted on, and a name with accents or other non-ASCII letters shows as M- sequences, é as M-CM-). It hides error messages, so any folder it can’t read is skipped without a word: on a Mac, that includes the folders macOS protects (Desktop, Documents, Downloads and the Trash, ~/.Trash) unless your terminal app has Full Disk Access or you allow it when macOS asks. Search a skipped folder on its own if a Python environment could be there.
find -H "${HOME%/}" /usr /opt /srv /var/www /app /Library/Frameworks /Library/Python "${HOME%/}/Library/Python" "${HOME%/}/Library/Caches/pypoetry" "${HOME%/}/Library/Application Support/hatch" "${HOME%/}/Library/Application Support/pipx" "${HOME%/}/Library/Application Support/pdm" "${HOME%/}/Library/Application Support/pypoetry" \( -path "${HOME%/}/Library" -o -name node_modules -o -name .git -o -path '*/.local/share/containers' -o -path '*/.local/share/flatpak' -o -path '*/.local/share/docker' \) -prune -o \( -name 'litellm-1.107.0.dist-info' -o -path '*-packages/cook.pth' -o -path '*-packages/cook.py' -o -path '*-packages/sitecustomize.py' \) -print 2>/dev/null | LC_ALL=C command cat -v
Then the markers in the temp folders, and the images Docker has (for Podman, put podman in place of docker). The Docker engine must be running: if docker says it failed to connect or cannot connect, no image was checked, so start it and run the line again. The docker line asks whichever engine DOCKER_HOST or your current docker context names, which can be another machine; Wardlatch doesn’t follow a DOCKER_HOST that names another machine or a socket another user owns, nor DOCKER_CONTEXT. To ask this machine’s engine only, put -H unix:///var/run/docker.sock after docker, or your engine’s own socket, such as -H "unix://$HOME/.docker/run/docker.sock" for Docker Desktop.
command ls -a /tmp "${TMPDIR:-/tmp}" | grep -E '^(\.pc_once_72129118c59f5636c0bb|remote_code_loader\.lock|\.pc_sq_[a-z0-9_]{8}|\.pc_e_[a-z0-9_]{8})$'docker images --digests --no-trunc --format '{{.Repository}}:{{.Tag}}@{{.Digest}}' | grep -i litellm
- Exposed
- A
litellm-1.107.0.dist-infofolder, or acook.pthorcook.pyin a site-packages folder: that environment had 1.107.0. Any line from the temp-folder check: the stealer ran on this machine. An image whose digest or name is in the list below. - sitecustomize.py
- It has legitimate uses. It is the stealer’s if it sits beside a
cook.pthor the 1.107.0 record, or ifshasum -a 256 '/path/to/site-packages/sitecustomize.py'(on Linux,sha256sumworks the same way) prints a value that starts34bef04fe88ac9d0(the full value is in Wardlatch’s incident file). - Nothing
- No lines from any of them. LiteLLM notes that not finding these files doesn’t prove an environment is clean: if you installed or pulled LiteLLM in the window, follow section C anyway.
- Compromised image digests (Wardlatch’s incident file has three in full; LiteLLM published the other two only as these prefixes): any digest that starts
sha256:1bbcb1443bc9,sha256:bd1aeee419ea,sha256:56b74573eed6,sha256:c00f9d92c427orsha256:7b00e91cd2fe. - Tags that only ever named the compromised image:
ghcr.io/berriai/litellm:v1.107.0,ghcr.io/berriai/litellm-database:v1.107.0,ghcr.io/berriai/litellm-non_root:v1.107.0andlitellm/litellm:v1.107.0.
What to install instead.
Every version before 1.107.0 is unaffected, LiteLLM says. Its incident report says to pin litellm==1.106.2, or another explicit version published before the incident, and to pull Docker images by an explicit earlier version’s digest rather than by latest.
LiteLLM’s root-cause analysis of 2026-10-23 says it expects to issue a further release, without naming its number. Until then, keep the explicit pin.
Clean up from the shell, rebuild, rotate.
LiteLLM’s steps, in its order.
- Don’t start Python in that environment, not even
pip install: every Python start runs the stealer again. - From the shell, delete
cook.pth,cook.pyand the stealer’ssitecustomize.py, and any__pycache__/cook*.pycandsitecustomize*.pyc, from every affected site-packages folder. - Stop the detached python process that holds
/tmp/remote_code_loader.lock.lsof /tmp/remote_code_loader.lockshows its process ID. - Rebuild the environment or image from scratch, pinned to
litellm==1.106.2. - Assume the theft succeeded if 1.107.0 ran on a machine with internet access, and rotate every secret the machine could reach: cloud credentials and instance or task roles, every provider key stored in the LiteLLM proxy database, the master and virtual keys, the
DATABASE_URLpassword, and any secret in the environment of any process on that machine. - Docker: stop and remove the containers from a compromised image, delete the image, and pull an explicit earlier version by digest (LiteLLM: v1.106.2). Treat every secret those containers could reach as stolen.
- Look for use of the stolen credentials: LiteLLM suggests checking your cloud’s audit log (CloudTrail on AWS) for identity checks from the machine’s role around the install time, and for those credentials being used from outside your infrastructure.
What the check doesn’t see.
- Places it doesn’t know. Wardlatch looks in the usual site-packages folders, in the virtual environments under the project folders it searches (
~/Development,~/code,~/src,~/projectsand the like, or the folders you give with--paths), and in those up to three folders below your home folder, in folders it can open. Under the folders it searches, it finds environments that have apyvenv.cfgfile; a conda environment made with-pin a project folder isn’t found. An environment elsewhere isn’t found unless you give the folder that holds it with--paths: for a virtual environment at/opt/litellm,--paths /opt, not the environment’s own folder.--pathsreplaces the project folders, so name them too, such as--paths ~/Development --paths /opt. - Images by tag. Wardlatch flags an image by its digest, or by a tag that only ever named the compromised image. An image pulled as
latest,main-latestormain-stablein the window is flagged by its digest only. - Your own temp folders. On a Mac, Wardlatch looks for the stealer’s markers only in the temp folders it can open, not in other accounts’: run it as the account that ran LiteLLM.
- This machine only. Running containers’ own files, other machines, CI runners and remote Docker hosts aren’t checked. Run it where LiteLLM was installed.
- Finding nothing isn’t proof, LiteLLM says. If you installed or pulled it in the window, rotate the secrets anyway.
Update, 2026-11-02 (0.1.12). Apple’s own Python folders, such as /Library/Python/3.x/site-packages where sudo pip3 installs, are searched now, and a folder the run couldn’t reach is UNKNOWN rather than missing. If an earlier version said SAFE for this check, run it again. The changelog has every change.
This page describes Wardlatch 0.1.9 and its incident file, litellm-2026-10.json.
Sources and credits.
- LiteLLM, incident report: LiteLLM v1.107.0 compromise, 2026-10-21: the affected wheels, images, tags and times; earlier versions not affected; the files it drops; remediation in order; pin 1.106.2.
- LiteLLM, root-cause analysis, 2026-10-23: the windows on PyPI, Docker Hub and GitHub’s registry, and the corrections to the earlier report.
- Wardlatch 0.1.9, litellm-2026-10.json and README: the SHA-256 values, temp markers, digests and tags it checks.
Credits · LiteLLM credits LidorMachluf, who spotted the malicious release within 20 minutes of publication and wrote up the remediation (issue 47826); Karlo Zanki and Socket’s Threat Research team, for their investigation and coordinated disclosure; and Asritha Bodepudi and Evan Sultanik of Trail of Bits, for help with the response. LiteLLM’s report links no profile for the three, so the links are to their organisations. All sources read 2026-10-25.
Debrief.
The questions this page answers, every answer open.
Which LiteLLM versions carried the credential stealer?
Only litellm 1.107.0: all seven wheels on PyPI, from 11:39 to 13:23 UTC on 2026-10-21, and the Docker images tagged v1.107.0, latest, main-latest and main-stable from 11:46 UTC until 13:51 on GitHub’s registry and 14:20 on Docker Hub. Earlier versions are not affected, LiteLLM says (LiteLLM’s report).
Can I check with pip show litellm?
No. Starting Python in an affected environment, pip included, runs the stealer again. Check with commands that only read files: the check has them, and Wardlatch never starts Python for this.
What files does the LiteLLM 1.107.0 stealer leave?
In Python’s site-packages: the release record litellm-1.107.0.dist-info and the files the wheels drop, cook.pth, cook.py and a sitecustomize.py, plus their .pyc files. In the temp folder: remote_code_loader.lock and a few hidden marker files. LiteLLM notes that not finding them doesn’t prove an environment is clean (LiteLLM’s report).
Which LiteLLM version is safe to install?
LiteLLM says to pin litellm==1.106.2, or another explicit version published before the incident, and to pull Docker images by an explicit earlier version’s digest.
I had LiteLLM 1.107.0. What should I rotate?
Everything the machine could reach, LiteLLM says (its incident report): cloud credentials and instance or task roles, every provider key stored in the LiteLLM proxy database, the master and virtual keys, the DATABASE_URL password, and any secret in the environment of any process on the machine. Assume the theft succeeded if it ran with internet access.
Does Wardlatch check for the LiteLLM stealer?
Yes. npx wardlatch@latest --incident litellm-2026-10 looks in the usual site-packages folders and the virtual environments under your project folders for the release record and the dropped files, in the temp folders for the stealer’s markers, and at Docker’s and Podman’s local images by digest and tag. It never starts Python and never pulls or runs an image. It finds a virtual environment by its pyvenv.cfg file, so a conda environment made with -p in a project folder isn’t found; for a virtual environment outside those folders, give the folder that holds it with --paths: for one at /opt/litellm, --paths /opt, not the environment’s own folder. --paths replaces the project folders, so name them too, such as --paths ~/Development --paths /opt.