On October 21, 2026, LiteLLM 1.107.0 reached PyPI and the official Docker images. The v1.107.0 tag on GitHub did not contain the malicious files. The wheels for that same version did. They installed a credential stealer and a remote code loader that ran whenever Python started in the environment.

The lesson is not that open source is untrustworthy. It is that source code and the artifact you install are different objects. Reviewing a tag does not tell you what a wheel will execute, and a valid signature does not prove that the build looked at the right repository.

What was published, and for how long

LiteLLM's incident report gives a usefully specific timeline. All times are UTC.

Artifact Affected range Exposure window
PyPI litellm==1.107.0 All seven wheels 11:39:51–13:23
Docker Hub v1.107.0, latest, main-latest, main-stable 11:46:56–14:20
GHCR The same tags on litellm, litellm-database, and litellm-non_root 11:46:56–13:51

The affected Docker Hub image index was sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e. LiteLLM says any container that executed that digest should be treated as compromised.

There is an important exception. The GitHub source tag and the source distribution did not contain the payload. The sdist's pyproject.toml had one added line, data = "cook", but the cook/ directory itself was not included. A review of the source tarball therefore produced a clean-looking result. The wheel is what users actually ran.

How the wheel and the source diverged

LiteLLM builds platform wheels with maturin, including a Rust bridge. Maturin's data option copies a local directory into the wheel's .data/ tree. The attacker used that setting to place three files at the top level of site-packages.

File Role
cook.pth A 12-byte import cook. Python's site initialization executes it.
cook.py A credential stealer that encrypts collected data and sends it out
sitecustomize.py Starts a background loader that periodically fetches remote code

Both .pth files and sitecustomize.py are processed automatically when a Python interpreter starts. The code does not need to import litellm. pip, pytest, an IDE language server, and a Jupyter kernel all trigger the same startup path if they share the environment. "Our application never imports LiteLLM" is not a defense.

Independent analyses add detail. SafeDep found that cook.py collected SSH keys, cloud credentials, CI/CD secrets, browser passwords, AI coding-tool tokens, and provider keys from a LiteLLM proxy database. StepSecurity reported that the bundle was encrypted with AES-256-CBC, wrapped with an RSA-4096 key, and posted over plain HTTP to www.litellm[.]dev/healthcheck, a lookalike of LiteLLM's real domain.

The loader was designed to read a server address from a GitHub dead drop and download code every hour. At analysis time, the dead drop was empty and the GitHub account had been deleted. Researchers did not find evidence that the second stage actually ran. That does not make the host safe: infected machines keep polling the same location, so a later re-registration of that account could restore the channel.

On Windows, the loader fails silently because it depends on fork. The stealer still runs. A platform-specific loader failure is not evidence of a clean host.

How the release pipeline was subverted

According to LiteLLM, the attacker first reached an internal LiteLLM deployment exposed to the public internet. The deployment's logs contained GitHub CLI OAuth tokens for three developers. One token belonged to a GitHub organization owner. The attacker used it to disable branch protection, force a change to the release workflow, and point the build at a lookalike repository, Berri-AI/litellm instead of BerriAI/litellm. The official pipeline then built, signed, and published the malicious artifacts.

The uncomfortable part is that the signature was valid. Signatures and SLSA provenance answer a narrow question: which pipeline produced this artifact? They do not automatically prove that the pipeline read from the repository you expected. StepSecurity found that the v1.107.0 image provenance named the hyphenated lookalike repository. That is a crucial distinction for anyone who treats "signed image" as a complete supply-chain control.

LiteLLM's report also says that Claude Code could embed a user's GitHub CLI OAuth token in a response body, and that logging response bodies put those tokens in LiteLLM's logs. That is LiteLLM's account, not an independently confirmed Anthropic statement. It should be separated from the part of the chain that is supported by the release records: an organization-owner token changed the workflow definition. The initial access route is still under investigation.

Check before you start Python

The official report suggests checking with pip show litellm. The same report warns that every Python start, including pip, runs cook.pth and sitecustomize.py again. That is a real tension. If compromise is plausible, isolate the host and inspect files and lockfiles with shell tools before invoking Python.

The commands below are unexecuted examples for this article. Adapt paths and permissions, and keep them read-only where possible.

# 1) Look for startup hooks added to site-packages
find / -type f \( \
  -path '*/site-packages/cook.pth' -o \
  -path '*/site-packages/cook.py' -o \
  -path '*/site-packages/sitecustomize.py' \
\) 2>/dev/null

# 2) Search lockfiles and CI configuration for the exact version
grep -RIn --include='requirements*.txt' --include='poetry.lock' \
  --include='uv.lock' --include='Pipfile.lock' --include='pyproject.toml' \
  'litellm.*1\.107\.0' . 2>/dev/null

# 3) Inspect the image digest of a running container
docker image inspect --format '{{.RepoTags}} {{.RepoDigests}}' litellm:latest 2>/dev/null

A sitecustomize.py file can be legitimate; some Python distributions ship one. Inspect it before deleting it. Absence of the files is not proof of cleanliness either, because containers and temporary directories may have been rebuilt or cleaned.

If exposure is confirmed, order matters.

  1. Isolate the host or container from the network.
  2. Without starting Python, remove cook.pth, cook.py, sitecustomize.py, and the matching __pycache__ files from every affected site-packages.
  3. Find and stop the detached Python process holding /tmp/remote_code_loader.lock.
  4. Reinstall with litellm==1.106.2, or rebuild the image and virtual environment from a verified base.
  5. Rotate every credential the host could reach.

Do not begin with pip uninstall litellm. That also starts Python and runs the startup hook again. The first task is removing the code that executes on interpreter startup, not removing the package name from metadata.

Credentials to rotate

A LiteLLM proxy concentrates many provider keys in one process. A single compromised developer laptop or gateway can therefore expose a wide set of credentials.

Scope Examples
Cloud AWS instance/task role, GCP service account, Azure credentials
LiteLLM Master key, virtual keys, DATABASE_URL password, provider keys
Developer tools GitHub CLI, SSH/GPG keys, package registry tokens
AI tools Claude Code, Codex, Gemini CLI, Cursor, and similar sessions
CI/CD Vault tokens, GitLab Runner, Jenkins, Terraform and Packer credentials

LiteLLM recommends checking CloudTrail for GetCallerIdentity from the instance role around install time, followed by use of that role's credentials from outside your infrastructure. Treat that as an investigation lead, not proof of what data was taken.

Controls that address the actual failure

"Turn on package signing" is not enough. The attacker used a signed pipeline. Defenses have to cover the artifact, the build definition, credentials, and egress separately.

Control What it prevents What it does not solve
Pin versions and hashes A new release entering automatically An already-pinned malicious version
Pin images by digest A floating tag changing underneath you A malicious digest that you pin
Check provenance source Artifacts built from a lookalike repository A compromised build definition
Short-lived release tokens Long-term abuse of a stolen token A stolen owner token that can rewrite the workflow
Separate approval for workflow changes One owner token rewriting the release path Approvers who reuse the same credential
Egress allowlists Known exfiltration domains Unknown domains and DNS bypasses
Install cooldown Immediate adoption of a bad release Mirrors that sync instantly

The important distinction is between "this artifact was signed" and "this artifact came from the source and workflow we intended." If you verify provenance in CI, check the source repository and the protected branch or workflow identity, not just the presence of an attestation.

What this incident does not establish

Separate confirmed facts from open questions. The malicious artifacts, exposure windows, file names, exfiltration path, and build source are documented by LiteLLM and independent analysts. The following are not settled.

  • The exact vulnerability used for initial access to LiteLLM's internal deployment
  • Whether the Claude Code token-exposure explanation generalizes beyond LiteLLM's logs
  • Whether stolen data was used, and at what scale
  • Whether any host actually received and ran second-stage code
  • Which credentials were exposed in each affected organization

That uncertainty argues for assuming exposure when the install history matches, not for waiting until data misuse is proven.

Conclusion: the artifact is the trust boundary

The LiteLLM incident is not an argument against open source. It is an argument for treating the tag, the sdist, the wheel, the container image, the signature, and the provenance as answers to different questions. A clean source tree does not make a wheel safe. A valid signature does not prove the build read the right repository.

Three actions matter now. Search lockfiles for litellm==1.107.0, check the digests of running containers, and rotate credentials for any environment that could have executed the affected artifact. Then revisit who can change your release workflow. That is the control the attacker actually used.

Further reading

References