Supply Chain PyPI Active Exploit

LiteLLM 1.107.0 shipped a credential vacuum that ran on every python

October 23, 2026·8 min read·CVEasy Threat Research
A green PyPI package tile and a mint Docker whale on the left both feed a gold funnel labelled with a hook glyph into a central site-packages box holding three stacked file tiles labelled by small ink glyphs, three thin ink arrows rise from the site-packages box into a running python interpreter on the right, a dashed rust exfil arc arcs out of the interpreter to a lookalike server tile at the frame edge, and a gold hourly loop circles down to a small ink file glyph pinned by a flag

At 11:39 UTC on October 21, 2026, the BerriAI release pipeline built LiteLLM 1.107.0, uploaded the wheels to PyPI, and tagged four Docker images on Docker Hub and GHCR. For roughly the next 100 minutes, every pip install litellm==1.107.0, every docker pull berriai/litellm:latest, and every CI job pinned to main-stable or main-latest installed a credential stealer and a second stage loader. PyPI quarantined the version at 13:20 UTC. The Docker Hub tags were pulled by roughly 14:20 UTC per the Datadog Security Labs timeline. By the time most teams saw the headline, the wheel had already been sitting on their runners for about two hours.

The affected artifacts were litellm 1.107.0 on PyPI and the v1.107.0, latest, main-latest, and main-stable tags on both Docker Hub and GHCR. The Chainguard writeup confirms the git tag of the same name does not contain the malicious code. The payload lived only in the prebuilt wheels and in the container images that bundled them. The last clean release called out by Datadog and Chainguard is 1.106.2.

Three files that ran before anyone imported litellm

The malicious wheel added three files to Python's site-packages, as peers of the litellm/ directory rather than inside it. The three files are cook.pth, cook.py, and sitecustomize.py. The placement is the whole trick.

A .pth file is a line oriented configuration file that CPython's site module reads at interpreter startup, and any line beginning with import is executed before user code runs. The cook.pth file contained an import cook line. The companion sitecustomize.py is auto imported by the same site module on every interpreter start, provided it exists anywhere on sys.path. Together they guarantee the payload runs the instant a Python process begins. Nobody needs to import litellm. Running pip, pytest, ansible, a Jupyter kernel, or a container ENTRYPOINT written in Python is enough.

cook.py is the credential stealer. sitecustomize.py is an hourly beacon that fetches a domain name from raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt, then downloads and executes whatever Python code sits at that domain. The dead drop file only ever held test content before the GitHub account was taken down, so no second stage was observed in the wild, but the loader was armed to pull anything the operator decided to drop. Local artifacts are a lockfile at /tmp/remote_code_loader.lock (so only one copy runs at a time) and a run once marker at /tmp/.pc_once_72129118c59f5636c0bb. Those two paths are the fastest tell on a host.

Removing the litellm/ directory does not remove the three files, because they sit beside the package, not inside it. Running pip uninstall litellm on the compromised interpreter re executes cook.pth and sitecustomize.py before the uninstall completes. The LiteLLM security advisory is explicit that any Python start in the environment re triggers the payload, which includes pip itself.

Why the attack ran everywhere: The payload is wired into the site bootstrap, not into the litellm package. Any interpreter that touches the affected site-packages runs it, whether or not the running program ever uses LiteLLM.

Why the wheel diverged from the tag

The source is clean. The wheels are not. That gap exists because the build was published through LiteLLM's legitimate release workflow by someone holding three engineers' stolen GitHub CLI OAuth tokens, per the reporting on BerriAI's own statement. The release ran against a lookalike repository named Berri-AI/litellm, which differs from the real BerriAI/litellm by a single hyphen. The build provenance recorded the lookalike as the source of truth. The attestation looked fine to a human skimming the summary. The commit it attested, and the three files it signed into the wheel, were not from the real history.

That is the shape of the attack class. Signed provenance ties an artifact to a repository and a commit. It does not tell you whether the repository is the one you meant. When the pipeline, the signing key, the registry account, and the GitHub organization all accept a lookalike fork as a legitimate upstream, every downstream verification check reports green on code that was never in the real upstream history. SLSA level three attestations do not catch this on their own; the identity of the upstream repository is the piece that has to be audited by hand.

What came out of the pipe

cook.py targets categories, not single files. From hosts that ran it, Datadog and the Socket writeup enumerate the collection scope:

  • Cloud. AWS, GCP, and Azure credentials, including values pulled from the IMDS endpoint on cloud instances; Kubernetes kubeconfig; Docker config; HashiCorp Vault tokens; Terraform state secrets.
  • Developer surface. SSH, GPG, and TLS private keys; .git-credentials; .npmrc; .pypirc; .env files; shell history.
  • AI providers. Local session files and API keys for Claude, Codex, Gemini, Cursor, and GitHub Copilot.
  • Browsers. Saved passwords and cookies.
  • LiteLLM proxy. On hosts configured as the LiteLLM proxy, the backing database tables that store upstream provider credentials. That is the fattest prize of the lot.

Everything collected is encrypted with AES 256 under an RSA 4096 session key and POSTed to www.litellm[.]dev/healthcheck, with an X-Token header set to XWiA8gZNa7NcX2XRoAEb. The exfil host is a lookalike of the official litellm.ai, which is why the request looks ordinary in a flow log.

Where a CVSS queue dropped it to the floor

There is no CVE for this event. There will not be one. CVE identifiers describe a flaw in a product; this is an unauthorized release of a legitimate product, published through the real pipeline with signed attestations. Scanner feeds have nothing to match on because nothing in the installed package metadata is wrong. The version string is 1.107.0. The wheel hashes resolve. The signature verifies. The SBOM looks correct.

A triage queue sorted by CVSS would show this at the bottom, below every CVSS 7 and higher advisory, because the queue is literally empty of entries that cover it. The dashboards all report green while every Python interpreter on affected hosts continues to obey cook.pth and keeps the hourly beacon alive.

How TRIS scored it

TRIS, the Threat and Risk Intelligence Scoring engine inside CVEasy AI, walks each finding through separate layers instead of collapsing to a single number. That shape matters here because the CVE and CVSS layers both contribute zero on this event, yet the finding belongs at the top of the queue.

Threat. Confirmed exploitation in the registry itself. Datadog, Chainguard, and the StepSecurity analysis independently reproduced the wheel content, the import time execution, and the exfil endpoint. A proven live payload with observed command and control is top band on this layer, not a theoretical proof of concept.

Reach. LiteLLM is a widely deployed AI gateway with a proxy mode that often holds the organization's root AI provider credentials in a database. On a proxy host, the blast radius is the full set of downstream LLM keys and the spend attached to them. On a developer laptop that happened to pip install litellm into a venv, the blast radius is whatever that laptop had in ~/.aws/credentials and shell history. The two numbers are not comparable, and any score that returns one value for both is useless.

Exposure. The score pivots on whether this interpreter actually ran during the window. CVEasy AI joins the inventory against /tmp/remote_code_loader.lock, /tmp/.pc_once_72129118c59f5636c0bb, the install logs for litellm==1.107.0, and container digests that match the four Docker tags during 11:39 to roughly 14:20 UTC on October 21, 2026. A host with none of those drops to the lowest band on this finding. A host with any one of them lands in the ACT (act now) band regardless of what else is on its triage queue this week.

Impact. The payload harvests long lived cloud and AI credentials. Those credentials often drive automation on other hosts, so the rotation surface is wider than the compromise surface. TRIS multiplies the rotation surface into the impact tier, which usually pushes a proxy host from high to top band.

Same package, same date, two different pieces of work: a sandboxed dev container that never pulled the bad digest scores a flat low, and the exposed proxy scores ACT with a rotation plan attached. The finding without a CVE still lands with a specific action list rather than a yellow banner.

What to do today

Treat any host that executed Python during the window as fully compromised. The window is 11:39 UTC to roughly 14:20 UTC on October 21, 2026, for direct PyPI installs, and until Docker Hub quarantine for pulls that landed later from cached mirrors. Fast checks, in order:

  1. Grep installation logs and build caches for a litellm-1.107.0 wheel. If any CI job installed that pin, that runner is in scope.
  2. Enumerate every site-packages on the host and look for cook.pth, cook.py, and sitecustomize.py. Finding one confirms exposure; absence does not prove the host is clean, because a later clean install may have deleted the file while leaving credentials already gone.
  3. Check for /tmp/remote_code_loader.lock and /tmp/.pc_once_72129118c59f5636c0bb. Either marker confirms the loader actually ran.
  4. In egress logs, hunt POST requests to www.litellm[.]dev/healthcheck and GET requests to raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt.

Rotate everything the stealer touches. Cloud provider keys, including role credentials fetched via IMDS on affected cloud instances. All AI provider API keys. SSH keys. .git-credentials. Any Vault token the host held. On a LiteLLM proxy host, rotate every upstream provider key stored in the proxy database and invalidate every downstream virtual key the proxy issued.

Rebuild, do not upgrade in place. Revert production LiteLLM installs to 1.106.2 on fresh interpreters and rebuild container images from clean base digests. A naive pip install --upgrade litellm on a compromised interpreter re executes cook.pth before pip even finishes resolving. The only safe path is a clean host, a clean venv, and a clean image.

The loader was armed, not fired. The dead drop file never carried a real second stage before the GitHub account was pulled, but the hourly beacon from sitecustomize.py was reaching out from compromised hosts for the whole window. Any host that still has an outbound network path and still has that file on disk is still beaconing.

How CVEasy AI surfaces this

CVEasy AI, the number one local-first CTEM (Continuous Threat Exposure Management) platform, ingests Python registry events, container digest changes, and researcher reports as first class inputs beside the NVD feed. TRIS joins the registry event to your installed package inventory and your container runtime inventory, scores the finding against the four layers above, and lands the ACT item in your queue in minutes.

Three decisions it answers without a CVE: which hosts actually ran this interpreter during the window; which cloud and AI keys those hosts could reach; and which downstream services depend on the keys that need rotation first. The join runs on your hardware. Package inventories, container digests, and shell history on each host stay local to that host. TRIS pulls the join into the queue and writes the rotation plan without sending any of it to a third party.

The attack that never gets a CVE is the one most likely to be running right now in your pipeline. Build the queue so a finding without a number can still reach the top of it.

Sources: Datadog Security Labs, Chainguard Unchained, Socket, LiteLLM security advisory, StepSecurity, The Hacker News, Wiz.