Skip to main content
New2026 CISO Guide to Software Trust87% of public container images ship with high or critical CVEs.Get the guide
CleanStart

LiteLLM 1.107.0 Compromise: What Happened, Who Is Affected, and How to Clean Up

6 min read
Contents

On October 21, a malicious litellm 1.107.0 reached PyPI and LiteLLM's official Docker images. LiteLLM says its own release pipeline was compromised. The release carried compiled malware that steals credentials and loads remote code.

Here is the detail that should worry every security team. LiteLLM says the official v1.107.0 source tag does not contain the malicious files. Anyone reviewing the code that morning saw a clean project.

The LiteLLM 1.107.0 attack, hour by hour

The attacker used developer credentials that LiteLLM believes were stolen GitHub CLI OAuth tokens. Times are UTC on October 21 and published analyses of the release.

Time (UTC)

Event

Oct 13, 00:45

Lookalike domain litellm.dev registered, a week ahead

08:56 to 10:46

Attacker commits pushed to Berri-AI/litellm, an attacker-controlled repository

10:46

A modified workflow runs a staging build from that repository

11:11

A second developer credential passes the production approval gate

11:39 to 11:40

Seven malicious wheels uploaded to PyPI

11:46

Docker tags v1.107.0, latest, main-latest and main-stable start serving the malicious image

12:06

Compromise reported in issue #47826

13:23

PyPI removes 1.107.0

14:20

Docker tags pointed back at clean sources

That is about 1 hour 44 minutes on PyPI and 2 hours 34 minutes on Docker. The attacker staged infrastructure a week early and passed the project's own approval gate. Every step went through LiteLLM's real release path.

What the LiteLLM 1.107.0 payload does

The malicious code shipped as compiled files inside all seven wheels. The source distribution carried a tampered pyproject.toml but no payload files.

  • Runs at every Python start. Startup hooks named cook.pth and sitecustomize.py launch the payload whenever Python starts. Nothing has to import LiteLLM.
  • Takes the keys. It collects LLM provider API keys, cloud credentials, environment variables and the credentials stored in a LiteLLM proxy's own database.
  • Sends them out. Stolen data goes to litellm[.]dev, a lookalike domain.
  • Leaves a door open. . A background loader checks a GitHub dead drop every hour for remote code to run.

The second time this year

In March, litellm 1.82.7 and 1.82.8 hit PyPI through a stolen publishing token, using a .pth startup hook of the same kind. PyPI measured that window at 2 hours 32 minutes. Researchers say October used different tooling and infrastructure.

The lesson repeats anyway. The code you review and the code you install are two different files, and only the second one runs.

Why reviewing the code missed it

Code review, pull requests and repository scans all read source. The v1.107.0 tag holds no payload, so every source check passed.

Provenance had the same blind spot. This build came out of LiteLLM's own pipeline, approved by a real developer credential. Signed provenance tells you who built a package, and on October 21 the who looked right.

Identity, integrity and intent

Before a package enters a build, three questions decide whether it deserves trust. Each one catches something the other two miss.

Identity: is the publisher who it claims to be?

Identity asks whether today's release matches the history that earned the package its reputation. Signals include account changes, unusual workflows and a release that breaks the usual pattern.

October is the hard case for identity. The release used real credentials and passed a real approval gate, so identity alone could not settle it.

Integrity: does the artifact match its source?

Integrity compares the shipped artifact with the source it claims to come from. It also watches for new files, new dependencies or a jump in size.

October fails this check clearly. The wheels carried startup hooks and compiled files that the v1.107.0 tag does not contain. Integrity needs no understanding of the payload, because it is a comparison. A mismatch is a mismatch.

Intent: does the code do what the package is for?

Intent compares what the code does with what the package says it does. A library that routes LLM requests has no reason to install hooks that run at every Python start, read cloud credentials and call out to an unfamiliar domain.

No single check is enough. In October, identity looked normal because the attacker used real credentials. Integrity and intent both raised a flag, and two independent warnings on the same release are a strong signal.

How CleanStart checks what the registry ships

CleanStart's Tricorder system treats every new version as untrusted until checks on the shipped files pass. It works in four steps:

  1. Fetch. A collector watches each registry's feed and downloads the actual package.
  2. Read every file that runs. For PyPI that means .py files, setup.py, .pth files, sitecustomize.py and entry points. If the ecosystem's tools will execute a file, Tricorder reads it.
  3. Parse for behavior. Each source file becomes a syntax tree in a common form, so one rule covers the same trick in any supported language.
  4. Score agreement. Independent checks raise findings, and a correlator scores them higher when they agree on the same release.

Here is what those checks see in 1.107.0.

Question

How CleanStart checks it

What it shows for 1.107.0

Identity

Compares the release with the package's own publishing history

No clear signal. The release came through LiteLLM's real pipeline, with real credentials and a real approval

Integrity

Compares the files in each wheel with the source tag the release claims to come from

Startup hooks (cook.pth, sitecustomize.py) and compiled files that the v1.107.0 tag does not contain

Intent

Reads every file Python will run, including .pth files and sitecustomize.py, and asks whether the behavior fits the package's purpose

Startup hooks that launch compiled code at every Python start, in a package whose job is routing LLM requests

Identity coming up empty is the reason to ask three questions. Integrity and intent both flag this release, and the correlator scores that agreement above either finding alone. A release scored that way goes to an analyst before it can be promoted into the verified catalog.

Two habits that would have helped

Pin by digest. On October 21, the Docker tags latest, main-latest and main-stable pointed at the malicious image for 2 hours 34 minutes. Anyone who pulled by tag got the bad build. A digest points to one exact image and can't be moved, so builds pinned by digest kept running the version they already had.

Wait until a release is checked. Fast detection only helps if it finishes before you install. PyPI removed 1.107.0 in under two hours, and unpinned pipelines grabbed it in that time. A safer rule is simple: automation installs a new release only after something has inspected it.

A release published minutes ago hasn't been checked by anyone yet, so its safety is unknown, and unknown should mean wait. CleanStart calls this state not yet assessed and holds those versions out of its catalog until the checks finish.

What to do this week

  1. Look for 1.107.0. Search images, CI caches and laptops for litellm 1.107.0, cook.pth and unexpected sitecustomize.py files. Check egress logs for litellm[.]dev.
  2. Rotate if it ran. LiteLLM advises treating every secret the host could read as stolen, including provider keys stored in the proxy database.
  3. Pull by digest. Replace latest and main-stable references with image digests.
  4. Add a cooldown. Stop automation from installing releases younger than a few days. In uv, as shown in PyPI's guidance:
[tool.uv]exclude-newer = "P3D"  # 3 days as an ISO 8601 duration; needs uv 0.9.17 or later
CleanStart Security

CleanStart Security

The CleanStart team brings you the latest news, insights, and quick takes from the world of cybersecurity and software supply chain security.

Frequently Asked Questions

Only litellm==1.107.0 on PyPI and the Docker images served under v1.107.0, latest, main-latest and main-stable on October 21, 2026. LiteLLM states all earlier versions are safe.

No. The official v1.107.0 tag is clean. The malicious wheels and images were built from the attacker-controlled repository Berri-AI/litellm.

You are if a container started from one of the four affected tags between 11:46 and 14:20 UTC on October 21.

No. March affected versions 1.82.7 and 1.82.8 through a stolen publishing token. Researchers say October used different tooling and infrastructure, though both hid a payload in a Python startup hook.

No. CleanStart ships litellm 1.105.1 and never shipped 1.107.0.

Related Blogs

See All
Php2shell attack
Supply Chain Attack
4 min read

PHP2Shell: What You Need to Know About Two Critical PHP Vulnerabilities

PHP2Shell (CVE-2026-109618, CVE-2026-109619) enables unauthenticated RCE in PHP. See fixed versions for 8.2 to 8.5 and how to patch and verify your runtime.

Read more
Gitlab
Supply Chain Attack
7 min read

AI Infrastructure Is Software Infrastructure. Secure It Accordingly.

GitLab's critical AI Gateway vulnerability highlights the need for stronger isolation, least privilege, dependency management, and verified software foundations.

Read more
Typosquatting banner
Application Security
7 min read

What Is Typosquatting? Attacks, Examples, and AI Risks

What is typosquatting, and how is AI increasing the risk? Learn how malicious packages work, the threats they pose, and how to prevent typosquatting

Read more