LiteLLM 1.107.0 Compromise: Am I Affected? What to Rotate
LiteLLM 1.107.0 was a malicious PyPI and Docker release on Oct 21, 2026. Check without running Python, remove the backdoor, rotate in order.
If you installed litellm==1.107.0 from PyPI, or started the v1.107.0, latest, main-latest or main-stable Docker images on October 21, 2026 within the windows below, treat the host as compromised and rotate every credential it could reach. Earlier versions are safe, per LiteLLM — pin 1.106.2. Don't start Python on the host until the backdoor files are gone, because every interpreter start re-runs the payload.
Sources re-checked 2026-10-25 ~15:40 UTC (10:40 AM CT): LiteLLM's root-cause analysis (published October 23, 2026, which LiteLLM calls "the canonical source of truth"); the original LiteLLM incident post (status: "Incident contained"; last updated October 21, 2026 20:30 UTC / 3:30 PM CT, with an October 23, 21:50 UTC / 4:50 PM CT note that some of its assumptions "were later discovered to be inaccurate or incomplete"); SafeDep's analysis; the OSV advisory MAL-2026-2144; and LiteLLM's GitHub release notes.
What happened on October 21, 2026
LiteLLM's release pipeline was compromised and pushed v1.107.0 to PyPI and the Docker registries. That release contained compiled malware: a credential stealer and remote-code-execution infrastructure. All previous versions are safe, the impacted releases have been pulled from PyPI, Docker Hub and GHCR, and enterprise customers pulling from LiteLLM's private registry were not affected, per LiteLLM. That registry is also distributing an embargoed release that fixes a vulnerability whose disclosure LiteLLM has scheduled for October 27.
Compromise windows (all times October 21, 2026):
| Registry | Artifact | Window (UTC) | Window (CT) |
|---|---|---|---|
| PyPI | litellm==1.107.0 |
11:39 – 13:23 | 6:39 AM – 8:23 AM |
| Docker Hub | v1.107.0, latest, main-latest, main-stable → index digest sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e |
11:46 – 14:20 | 6:46 AM – 9:20 AM |
| GHCR | compromised from 11:46, unavailable by 13:51 | 11:46 – 13:51 | 6:46 AM – 8:51 AM |
How it got in, per LiteLLM's October 23 root-cause analysis: on May 27, 2026, a config file committed to the public LiteLLM-Labs/lite-harness repository exposed the master key to a deployment of the LiteLLM Agent Platform, and that key was never revoked. On October 10 the attacker used it to pull a GitHub organization owner's GitHub CLI OAuth token from the platform's API, then ran throwaway CI workflows that posted two maintainers' GitHub CLI tokens to an attacker endpoint. On October 21 the owner's token overrode branch protection and pointed the release workflow at the lookalike repository Berri-AI/litellm, a second maintainer's token approved the production release, and a third deleted the workflow run records. The official v1.107.0 source tag on GitHub does not contain the malicious files. LiteLLM's first post suspected the tokens came from the logs of an internal LiteLLM deployment; the October 23 analysis says the first token came from a different service. LiteLLM says it is revoking every token associated with every internal system and vendor platform.
Attack timeline (UTC from LiteLLM's root-cause analysis; CT added):
| When (UTC) | When (CT) | Event |
|---|---|---|
| May 27, 18:53 | May 27, 1:53 PM | Config file with the Agent Platform master key committed to a public repo |
| Oct 10, 21:27 | Oct 10, 4:27 PM | Agent Platform API returns the org owner's GitHub CLI token |
| Oct 10, 22:11 – 22:12 | Oct 10, 5:11 – 5:12 PM | Throwaway workflows post two maintainers' tokens to an attacker endpoint |
| Oct 21, 10:08 | Oct 21, 5:08 AM | Owner's token overrides branch protection on the release workflow |
| Oct 21, 11:11 | Oct 21, 6:11 AM | Second maintainer's token approves the production release |
| Oct 21, 11:39 – 11:40 | Oct 21, 6:39 – 6:40 AM | Seven malicious litellm==1.107.0 wheels published to PyPI |
| Oct 21, 11:46 | Oct 21, 6:46 AM | Malicious Docker Hub and GHCR images published |
| Oct 21, 11:53 – 11:54 | Oct 21, 6:53 – 6:54 AM | Third maintainer's token deletes the release run records |
| Oct 21, 12:06 | Oct 21, 7:06 AM | LidorMachluf opens GitHub issue #47826 identifying the compromise |
| Oct 21, by 13:23 | Oct 21, by 8:23 AM | PyPI quarantines litellm==1.107.0 |
| Oct 21, by 13:51 | Oct 21, by 8:51 AM | Affected GHCR tags unavailable |
| Oct 21, 14:20 | Oct 21, 9:20 AM | Docker Hub tags removed or repointed to clean images |
LiteLLM had hardened its release process after a March 2026 incident, but earlier that week it had temporarily relaxed the rule that a device-locked account in its office approves each release, allowing any two engineers to trigger and approve one. The practical takeaway appears later in this post: trusted publishing and signed images raise the bar, but they don't remove the need for version pins and credential rotation.
Am I affected? Check without starting Python
Order matters: the payload runs in any Python process that starts on the host — including pip itself and processes that never import litellm — so don't run Python until you've checked the filesystem.
(a) Lockfiles and requirements. Search your projects for litellm==1.107.0, or an unpinned litellm requirement that resolved on October 21:
grep -rn "1.107.0" requirements*.txt Pipfile.lock poetry.lock uv.lock 2>/dev/null
(b) Filesystem. Look in site-packages for litellm-1.107.0.dist-info, cook.pth, cook.py, and sitecustomize.py, plus the drop files in /tmp:
find / -name "litellm-1.107.0.dist-info" 2>/dev/null
find / -path "*/site-packages/cook.pth" -o -path "*/site-packages/cook.py" -o -path "*/site-packages/sitecustomize.py" 2>/dev/null
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null
(c) Processes. Look for a detached Python daemon: parent PID 1, its own session, working directory / (this is the hourly re-loader SafeDep describes).
ps -eo pid,ppid,sid,cmd | awk '$2 == 1 && /python/'
(d) Containers. If you pull litellm images, compare what you ran against the bad digests — sha256:1bbcb144… for litellm/Docker Hub tags, and the truncated GHCR digests in the IOC table below. Check whether a container was started from one of those images in the window, not merely pulled:
docker images --digests | grep litellm
docker ps -a --filter "ancestor=ghcr.io/berriai/litellm" --format "{{.ID}} {{.Image}} {{.CreatedAt}}"
(e) pip show litellm — LiteLLM's own check. Note this starts Python, so run it only on a host you're already treating as compromised:
pip show litellm
Rule of thumb, from LiteLLM: if you ran pip install on 1.107.0 or started a Docker image in the compromised interval, assume it's compromised. Finding a marker confirms exposure; not finding one proves nothing.
IOCs as published
All IOCs below are copied verbatim from the sources (defanged where the source defanged them). The GHCR digests are published only in truncated form — don't try to complete them; compare prefixes.
| Indicator | Source | Notes |
|---|---|---|
cook.pth — SHA-256 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb |
SafeDep / OSV | .pth file; import cook runs at every interpreter start |
cook.py — SHA-256 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25 |
SafeDep / OSV | credential stealer ("privcollect") |
sitecustomize.py — SHA-256 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa |
SafeDep / OSV | hourly remote-code loader daemon |
/tmp/.pc_once_72129118c59f5636c0bb |
SafeDep / OSV | marker written after the stealer's first run |
/tmp/remote_code_loader.lock |
SafeDep | lock file of the backdoor daemon |
PRIVCOLLECT_ACTIVE=1 env var |
SafeDep | present in infected processes |
www.litellm[.]dev/healthcheck |
SafeDep / OSV | exfiltration endpoint |
X-Token: XWiA8gZNa7NcX2XRoAEb |
SafeDep | HTTP header sent with exfil |
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt |
SafeDep / OSV | backdoor reads its server address here |
Berri-AI/litellm lookalike repo |
LiteLLM | the compromised artifacts were built from an attacker-controlled repo |
Docker index digest sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e |
LiteLLM | Docker Hub tags v1.107.0, latest, main-latest, main-stable |
ghcr.io/berriai/litellm sha256:1bbcb1443bc9… |
LiteLLM | truncated, as published |
ghcr.io/berriai/litellm-database sha256:c00f9d92c427… |
LiteLLM | truncated, as published |
ghcr.io/berriai/litellm-non_root sha256:7b00e91cd2fe… |
LiteLLM | truncated, as published |
IP 69.48.229.143 |
LiteLLM (Oct 23 analysis) | Agent Platform API requests, October 10 |
IP 199.91.221.39 |
LiteLLM (Oct 23 analysis) | release-repo pushes and the production approval, October 21 |
Fork commit 20535d8fa11f786a6c24df7488a0e449ed5f09ad |
LiteLLM | Berri-AI/litellm commit the malicious release was built from |
SafeDep's page also publishes SHA-256 hashes for the sdist and all seven 1.107.0 wheels — link to their table rather than retyping them here. The three planted-file hashes above are the Linux and macOS values; on Windows the files hash differently because of CRLF line endings, and SafeDep lists those too. LiteLLM's root-cause analysis lists further indicators inside its own GitHub organization (workflow commits and exfiltration branches).
Caveat: the same OSV entry (MAL-2026-2144) also covers the March 2026 1.82.7/1.82.8 releases, and its litellm.cloud / models.litellm.cloud IOC block is from that March incident — not October.
Clean up first, in this order (LiteLLM's steps)
LiteLLM's remediation guidance, in their order:
- Delete the backdoor files from the shell, not from Python:
cook.pth,cook.py,sitecustomize.py, and the matching__pycache__/cook*.pycandsitecustomize*.pyc. - Kill the daemon holding
/tmp/remote_code_loader.lock. - Reinstall pinned to 1.106.2. Rebuilding the host or image from a clean base is better.
- Assume exfiltration succeeded if 1.107.0 was ever installed on a host with outbound internet. If the marker file
/tmp/.pc_once_72129118c59f5636c0bbexists, the exfil is confirmed; if it doesn't, that proves nothing — containers and tmp cleanup remove it.
Only after the files are gone and the daemon is dead is it safe to run Python tooling on the host again.
Rotate in this order
Steps 1–3 below follow LiteLLM's remediation list; the overall ordering and steps 4–6 are Tacavar's recommendation, based on what SafeDep and OSV document the stealer collecting.
- The instance/task IAM role, plus cloud CLI credentials. The stealer queries the AWS instance metadata service (169.254.169.254, and 169.254.170.2 per OSV) and collects
~/.aws, Azure and gcloud credentials. If this host had rights over other machines, treat those machines as in scope for the rotation too. - Every LLM provider key in the LiteLLM proxy database, plus the LiteLLM master key and virtual keys — the stealer reads
DATABASE_URLand dumps theLiteLLM_CredentialsTableandLiteLLM_ProxyModelTabletables. - The
DATABASE_URLpassword itself, and every secret in any process environment on the host — the stealer reads other processes' environment via/proc. - GitHub and CI/CD tokens:
ghhosts.yml,.git-credentials, GitLab runner, Jenkins, Vault, Terraform, plus package-registry tokens (.pypirc,.npmrc). A stolen token is how this attack started — rotating SSH keys is not enough if your access model still leans on shared credentials; we wrote up a two-servers, no-shared-SSH-keys setup that removes that dependency. - kube configs and k8s service-account tokens, SSH, GPG and TLS keys.
- AI dev-tool tokens (Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, Gemini CLI, Codex
auth.json, Hugging Face, VS Codemcp.jsonand other MCP config secrets) and browser-saved passwords.
Then hunt: LiteLLM's guidance says to check CloudTrail for GetCallerIdentity calls from the instance role around install time, then for use of the role's credentials from outside your infrastructure.
For the per-provider revoke and spend-limit steps, see how to respond to leaked AI API keys — that guide covers one provider at a time; this one covers a host that took every key at once.
Prevent the next one
- Pin exact versions (
litellm==1.106.2, notlitellmor>=), and install with hash checking:pip install --require-hashes(pip docs). - Pin container images by digest, not tag (docker pull reference). Tags move; digests don't.
- Use PyPI Trusted Publishing for packages you publish (docs) — but be honest about its limits: LiteLLM had hardened its pipeline after March, and this still got through. Trusted publishing is not sufficient alone.
- Don't let "latest" auto-update in production.
latestwas one of the four compromised Docker tags. - Revoke exposed keys immediately, and don't give automation a person's owner-level token. Per LiteLLM's root-cause analysis, the chain started with a master key committed to a public repository in May that was never revoked, and the GitHub token it exposed was an org owner's GitHub CLI OAuth token that "was not associated with a service account and was not scoped down." LiteLLM also notes that GitHub CLI tokens are post-2FA and not subject to GitHub's re-authentication prompts for sensitive organization actions. Give bots and CI scoped service credentials instead. (LiteLLM's original incident post also described cases where Claude Code can embed a user's GitHub CLI OAuth token in a response body that may then be logged; its October 23 analysis says the first token did not come from those logs, but it is still a reason to keep secrets out of LLM-proxy logs and keep those logs off the public internet.)
- Govern MCP-style tool access explicitly. The stealer specifically harvests AI dev-tool tokens and
mcp.jsonsecrets; our guide to MCP tool governance in production covers how to scope what an agent — and anything that compromises it — can reach. - Put a firewall in front of agent actions, so a compromised process can't silently exfiltrate credentials or call arbitrary endpoints; see agent firewall: production governance.
- Give the proxy host a narrow IAM role, so a host compromise can't walk into your whole cloud account — and if you're weighing self-hosting the proxy at all, self-hosted AI sovereignty covers the trade-offs.
- Harden anything that went from prototype to production without a security pass — the vibe-coded production hardening checklist is a starting point for exactly the kind of host this attack lands on.
This is the core of what we do at Tacavar — governed agent operations, where credential blast radius is designed in before an incident, not discovered after one. Ask about Governed AI Ops if you want help getting there.
FAQ
Was LiteLLM hacked? Which versions are affected?
Yes. On October 21, 2026, LiteLLM's release pipeline was compromised and pushed v1.107.0 to PyPI, Docker Hub and GHCR with a credential stealer and backdoor inside. Only litellm==1.107.0 (PyPI) and the v1.107.0, latest, main-latest and main-stable Docker tags are affected; the official GitHub source tag is clean, all previous versions are safe per LiteLLM, and enterprise private-registry users were not affected. The compromised artifacts have been removed from the registries.
How do I know if I installed the malicious LiteLLM 1.107.0?
Check, in order and without starting Python: your lockfiles for 1.107.0, site-packages for cook.pth / cook.py / sitecustomize.py and litellm-1.107.0.dist-info, /tmp for .pc_once_72129118c59f5636c0bb and remote_code_loader.lock, running processes for a detached Python daemon with PPID 1, and your container image digests against the IOCs above. LiteLLM's rule: if you installed or ran 1.107.0 in the window, assume compromised regardless of what the checks show.
Is it safe to run pip show litellm to check?
It's LiteLLM's own suggested check, but it starts Python — and any Python start on an infected host re-runs the payload. Run it only on a host you're already treating as compromised, after the cleanup steps, or not at all; the filesystem checks above don't need Python.
I only pulled litellm:latest from Docker. Am I affected?
If you pulled or started latest between 11:46 and 14:20 UTC (6:46 – 9:20 AM CT) on October 21, 2026, yes — latest pointed at the same compromised image digest as v1.107.0 (sha256:1bbcb144…). Check whether the container was actually started from it, then rotate credentials. A pull that never ran is lower risk, but treat a started container as a compromised host.
Which LiteLLM version is safe to pin?
1.106.2. LiteLLM's guidance is to pin to 1.106.2 or any explicit version published before the incident. SafeDep also confirms 1.107.0rc1, 1.107.0rc2, litellm-proxy-extras==0.4.110 and litellm-enterprise==0.1.78 are clean. LiteLLM has since published 1.106.3 (October 24) and 1.106.4 (October 25). Starting with 1.106.3, its Docker images and Helm charts are signed with a new cosign key (commit e82a3c6), and verifying with the old key fails, so update any cosign verify scripts or admission policies.
What credentials should I rotate first after the LiteLLM compromise?
Cloud instance-role and cloud CLI credentials, then the LLM provider keys in the LiteLLM proxy database (plus master and virtual keys), then the DATABASE_URL password and host-wide process environments, then GitHub / CI-CD / registry tokens, then kube / SSH / GPG / TLS keys, then AI dev-tool tokens and browser passwords. Steps 1–3 follow LiteLLM's list; the full ordering is Tacavar's recommendation. Then hunt for anomalous GetCallerIdentity and off-infrastructure use of the instance role.
Does removing the files make the host clean?
No. Deleting cook.pth, cook.py and sitecustomize.py and killing the daemon stops the bleeding, but the stealer uploads on its first run, and the backdoor daemon re-runs an embedded copy of the stealer every hour and could fetch and run second-stage code (per SafeDep). If it executed, assume it uploaded, and rotate everything. A rebuild from a clean image is safer than an in-place cleanup.
Doesn't trusted publishing prevent this? It helps, but it didn't here. LiteLLM had release hardening in place after the March 2026 incident, but the attacker held stolen GitHub CLI tokens for an org owner and two maintainers, used the owner's token to override branch protection, and passed the release approval with a second token while that approval rule was temporarily relaxed. The official pipeline then signed and published the artifacts. Trusted publishing removes one path; hash-pinned installs and digest-pinned images remove your dependence on the registry being right.
If you're running LiteLLM (or any LLM proxy) in production and want this class of blast-radius problem designed out — pinned artifacts, narrow IAM, token governance, rotation drills — ask about Governed AI Ops. For a quick read on your current exposure, start with our scan.