LiteLLM release 1.107.0 contained a credential stealer and a remote-code loader. The malicious files were published to PyPI and to LiteLLM’s Docker tags on October 21, 2026. The official source tag was clean, but the published artifacts were built from a lookalike repository. Pin to 1.106.2 or any version released before the incident, rebuild every image and virtual environment that installed the bad release, and rotate every key the host could read, starting with the provider keys stored in the proxy.
What happened
LiteLLM’s incident report gives this sequence for October 21, 2026, in UTC:
| Time | Event |
|---|---|
| 10:08 | A credential tied to a privileged developer account overrode branch protections and applied a modified release workflow. The report says the attacker then force-pushed a change after a merge attempt was blocked. |
| 11:11 | A credential tied to a second developer account satisfied the production approval gate. |
| 11:39 to 13:23 | Seven malicious wheels were live on PyPI. |
| 11:46 to 14:20 | Docker Hub tags v1.107.0, latest, main-latest and main-stable pointed to the compromised image. On GitHub Container Registry the same tags were affected until 13:51. |
| 20:30 | Last status update in the report: incident contained, investigation in progress. |
The release was built from Berri-AI/litellm, a lookalike of the official BerriAI/litellm repository. The sequence matters for your own pipeline. A privileged credential bypassed review controls, and a second credential satisfied the approval gate. Both were developer accounts, so the attacker held credentials that looked legitimate at every step. The report says the attacker held three sets of developer GitHub CLI credentials, and the timeline shows two used at the gates, so review all three accounts.
What the payload does
The malicious build added three files to the Python site-packages directory: cook.pth, cook.py and sitecustomize.py. Python executes .pth files and sitecustomize.py each time an interpreter starts in that environment. The payload therefore runs whenever Python starts in that environment, including when pip runs, even if nothing imports LiteLLM.
LiteLLM’s report and the analyses from Hot Open Lab, Cycode and Corgea describe the following behavior:
- Steals credentials and loads remote-code execution tools, according to LiteLLM’s report.
- If
DATABASE_URLis set, dumps LiteLLM’s own credential and model tables, according to Hot Open Lab. The credential table holds the upstream provider keys the proxy uses. Cycode reports that the query pulls up to 500 of them. - Runs a loop from
sitecustomize.pythat checks a dead drop and would run a second-stage Python script from it. Cycode reports that researchers found the dead drop empty when they looked. A detached Python process holds/tmp/remote_code_loader.lock.
Removing the package does not undo what already ran. Treat any host that executed 1.107.0 as compromised until you have checked it.
Who is exposed
You are exposed if any of the following happened during the windows above:
- A laptop, server, CI runner or Kubernetes pod resolved an unpinned
litellmrequirement and installed 1.107.0 between 11:39 and 13:23 UTC. - A host or container pulled
latest,main-latest,main-stableorv1.107.0from Docker Hub between 11:46 and 14:20 UTC, or from GitHub Container Registry between 11:46 and 13:51 UTC. - A proxy restarted or scaled during the window and pulled a floating tag, even if nobody chose 1.107.0 on purpose.
LiteLLM states that all versions before 1.107.0 are safe. Its public statement also says enterprise deployments that pull only from its private registry are unaffected. Confirm the source and digest of your own images before you rely on that.
Triage without starting Python
Do not run pip show litellm, python -c or any other Python command on a host you suspect. Starting an interpreter re-runs the payload. Check with tools that do not execute code.
- Search for the dropped files. On Linux, run
find / \( -name 'cook.pth' -o -name 'cook.py' -o -name 'sitecustomize.py' -o -name 'cook*.pyc' -o -name 'sitecustomize*.pyc' \) 2>/dev/null. Search for the compiled.pycfiles too, because a.pycfile can run without its source. Then inspectsitecustomize.pyin each site-packages directory. Some environments ship one legitimately, so compare it with a known-good copy from before October 21. - Check for the lock file and daemon. Look for
/tmp/remote_code_loader.lockand for a detached Python process holding it. - Check image digests, not tags. Compare the digests of running and cached images against a pre-incident digest you recorded, or against a freshly rebuilt image. A tag such as
latesttells you nothing once it has been repointed. - Search build records. Search requirements files, lockfiles and CI logs for
litellm==1.107.0, and for unpinnedlitellminstalls that ran on October 21. - Review cloud audit logs. LiteLLM recommends checking CloudTrail for
GetCallerIdentitycalls from the instance role around install time. Separately, review for use of those credentials from outside your infrastructure. Equivalent logs exist on other clouds.
Keep the output of each check. It becomes the evidence file for your incident record.
Remediation order
- Stop before installing anything. Do not run
pip install -Uinside an infected environment. The install itself starts Python and re-runs the payload. - Remove the payload. Delete
cook.pth,cook.py,sitecustomize.pyand any matching__pycache__entries from every affected site-packages directory. Stop the detached process that holds the lock file. - Rebuild instead of patching in place. Rebuild images and virtual environments from scratch, pinned to
litellm==1.106.2or another explicit pre-incident version. For containers, rebuild from a pre-incident base and pin the rebuilt image by digest. - Rotate what the host could reach. LiteLLM’s list covers the instance or task IAM role, every provider key in the proxy database, the master and virtual keys, the
DATABASE_URLpassword, and any secrets in the environment of running processes. Beyond that list, rotate any other secret the host held, such as SSH keys, CI and registry tokens, and Kubernetes service account tokens. That is our recommendation, not part of LiteLLM’s list. - Assume exfiltration if the host had outbound access. LiteLLM says a marker file confirms exfiltration, but its absence proves nothing. As of its October 21 update, the report does not name the marker file, so do not rely on a clean marker check. Rotation is the control that matters.
If a live service still depends on a critical key, rotate that key first so a working credential is not left exposed for the length of the rebuild. Otherwise, follow the order above.
Controls that limit the next incident
The attack passed through a release workflow, a credential and a floating tag. Each control below closes one of those paths. They apply to any AI gateway you run, not only LiteLLM.
| Control | Why it matters here |
|---|---|
| Pin dependencies with hashes | An unpinned install picks up a new release the moment it is published. |
| Pin images by digest | The latest tag moved to the compromised image during the window. |
| Protect release workflows | The report says a modified workflow was applied by overriding branch protections. |
| Require reviewers, not just credentials, for release approval | The approval gate was satisfied by a second developer credential. An attacker holding two credentials can clear a gate that counts credentials, not people. |
| Keep tokens out of logs | LiteLLM’s report believes developer GitHub CLI tokens reached internal deployment logs. Treat that as a hypothesis until it is confirmed. |
| Restrict outbound traffic from proxy hosts | The stealer sends data out. A default-deny egress rule limits what it can reach. |
| Reference provider keys from a secrets manager | If your setup allows it, the proxy database should hold references, not raw keys. One database dump then yields less. |
What is still unknown
- Initial access. The report says it currently suspects a recently patched vulnerability in the internal LiteLLM deployment. It has not confirmed how the attacker got in.
- Token theft. The report says it believes the attacker obtained developer GitHub CLI tokens from logs of the internal deployment. That is a belief in the report, not a confirmed finding. The report also says Claude Code can embed a user’s GitHub CLI OAuth token in a response body that may appear in those logs. If your deployment uses Claude Code, check whether its output reached your logs.
- Second-stage code. Researchers found the dead drop empty when they looked. An empty dead drop today does not rule out a later payload.
A clean result from any single check is not proof of safety while the investigation is open. Recheck when LiteLLM publishes its indicators.
Why a vulnerability scanner will not flag this
A vulnerability scanner matches installed packages against known CVEs. Hot Open Lab’s analysis states that there is no CVE for this incident yet: “It is a supply-chain compromise, not a code bug.” A scanner can help you find where LiteLLM is installed, but detection here depends on file, digest and log checks, and on the rotation list above.
Evidence to keep
Keep these records for the incident file. They show what you checked and when.
- The list of hosts, containers and CI runners that could have resolved or pulled the bad release, with the check result for each.
- The output of the file, digest and build-record searches, dated and tied to the host.
- The rotation log: which keys were rotated, when, and which service depended on each one.
- The CloudTrail review, with the query used and its result.
Questions buyers ask
Did we run this if we never imported LiteLLM?
Possibly, yes. The .pth file runs on every Python start in an environment where the package was installed, so an import is not required. Check the environment, not your code.
We pull a floating Docker tag. Are we exposed? You are exposed if a pull or restart happened from Docker Hub between 11:46 and 14:20 UTC on October 21, or from GitHub Container Registry between 11:46 and 13:51 UTC. Restarting a container from an image that already ran the payload does not clean it. Rebuild from a digest pinned to a pre-incident version.
Is the private registry safe? LiteLLM says enterprise deployments that pull only from its private registry are unaffected. Confirm your own source and digest before you rely on that statement.
Does a vulnerability scan catch this? No. There is no CVE for this incident yet. Use the file, digest and log checks above.
Do we have to notify anyone? Counsel decides. The answer depends on whether the host could reach personal or regulated data and whether that access is confirmed. Some notification clocks start at discovery, so raise it early.
For the wider program, see the AI security assessment. To see how this differs from a scan, read penetration test vs vulnerability scan. For another supply chain risk in agent tooling, read MCP supply chain security.
Key facts
- LiteLLM's incident report, dated October 21, 2026, says all seven malicious litellm==1.107.0 wheels were live on PyPI from 11:39 to 13:23 UTC, and that Docker Hub tags v1.107.0, latest, main-latest and main-stable pointed to a compromised image from 11:46 to 14:20 UTC, while the same tags on GitHub Container Registry were affected until 13:51 UTC (LiteLLM, October 2026).
- LiteLLM's report says the official v1.107.0 source tag did not contain the malicious files, and that the published artifacts were built from an attacker-controlled repository, Berri-AI/litellm (LiteLLM, October 2026).
- LiteLLM recommends pinning to litellm==1.106.2 or another explicit version published before the incident, rebuilding from scratch, and rotating every credential the affected host could reach (LiteLLM, October 2026).