LiteLLM Compromised: What the LiteLLM Breach Teaches Us About AI Gateway Risk

The LiteLLM compromise put a credential stealer in v1.107.0. Timeline, IOCs, who is affected, and why the LiteLLM breach exposes AI gateway risk.

Krishanu

Krishanu

2026 State of Agentic AI Security

How ready is your organization to secure AI agents?

3 quick questions. See how you compare to other security leaders.

LiteLLM Breach
LiteLLM Breach

On October 21, 2026, attackers published a poisoned LiteLLM release, version 1.107.0, to PyPI, GHCR and Docker Hub. For about two hours, anyone who ran an unpinned pip install litellm or pulled the latest image got a credential stealer and an hourly remote code loader. The malicious version was downloaded roughly 49,000 times before PyPI quarantined it.

This is the second LiteLLM compromise of 2026. In March, a group tracked as TeamPCP pushed versions 1.82.7 and 1.82.8 with a stolen PyPI token. LiteLLM hardened its release process after that. The October LiteLLM breach went through every one of those new controls, and the artifacts came out signed, with valid provenance.

Below is the timeline, how the payload worked, and how to check and clean up your environment. We also look at what the incident says about AI gateways as a target, and why the entry point is the most uncomfortable part of the story.

TL;DR

  • What: litellm==1.107.0 (seven wheels) and Docker tags v1.107.0, latest, main-latest, main-stable carried a stealer called privcollect plus a backdoor loader.

  • When: October 21, 2026, 11:39 UTC to about 13:23 UTC on PyPI; images until 14:20 UTC.

  • How: Stolen GitHub CLI OAuth tokens from three engineers let the attacker rewrite the release workflow and build from a lookalike repo, Berri-AI/litellm.

  • Impact: Cloud keys, AI provider keys, SSH and GPG keys, browser data, AI coding tool tokens, and every upstream provider key stored in the LiteLLM proxy database.

  • Fix: Do not run pip first. Remove the payload files, kill the loader, rebuild on 1.106.2, and rotate everything the host could reach.

What happened: the LiteLLM breach timeline

The attacker went from a rejected workflow change to a published release in 91 minutes, then deleted the workflow logs 14 minutes later.

What happened: the LiteLLM breach timeline

The attacker started testing stolen AWS roles two hours after the last poisoned image came down.

Time (UTC)

Event

08:56 to 10:46

Attacker pushes 10 commits to the lookalike repo Berri-AI/litellm. Nine carry GitHub's verified signature.

09:48

Commit adds data = "cook" to the Maturin build config, so the payload ships inside the wheel.

10:08:29

Branch protection rejects an unauthorized change to the private release workflow.

10:08:34

Five seconds later, an org owner's stolen token overrides the protection and force-pushes the change.

10:46

Modified workflow starts a staging run against Berri-AI/litellm.

11:11

A second engineer's stolen credential approves the production gate.

11:39 to 11:40

Seven malicious litellm==1.107.0 wheels land on PyPI.

11:46

Malware scanners flag the release, about seven minutes after upload. Container images publish the same minute.

11:53

A third engineer's credentials delete the staging and production workflow records.

13:23

PyPI removes 1.107.0.

13:30

GitHub takes down the dead-drop repo.

14:20

Last poisoned Docker Hub tags removed or repointed.

16:20 to 17:01

Attacker tests stolen AWS instance role credentials, including Bedrock model listing.

The v1.107.0 git tag in the real repo was clean. Anyone who audited the source, or even the tag, saw nothing wrong. The only trace in the sdist was the extra data = "cook" line in pyproject.toml, which the tag did not have.

How the LiteLLM compromise worked

The payload ran every time Python started in an infected environment, whether or not anything imported LiteLLM. Three files were dropped into site-packages:

  • cook.pth contains one line, import cook. Python processes .pth files at interpreter startup, so the stealer runs before your code does. Running pip to fix the problem triggers it again.

  • cook.py is the stealer, which researchers call privcollect after the label in its output. It runs 16 collection modules in a background thread.

  • sitecustomize.py forks a detached daemon on Linux and macOS. Every hour it reads domain.txt from the GitHub repo letters9813/pythonclient-test, downloads Python from http://<domain>/codepy, and runs it with exec. There is no signature check. The pointer only ever held test values, so no second stage was observed.

How the LiteLLM compromise worked

Pairing .pth with sitecustomize.py appears to be the first documented case of this technique in the wild. Both are standard Python startup hooks, so the attacker got two independent footholds. cook.pth fires on every interpreter start on any OS, while sitecustomize.py gives Linux and macOS hosts a backdoor that keeps polling for new code. Delete one file and the other still runs.

Researchers also found signs the code was written with LLM help:

  • Verbose names: variables like encrypted_private_key_output.

  • Generic error handling: overly defensive try/except blocks that swallow failures instead of handling them.

  • Dead code: a module called exec_py that is defined but never called.

  • Untested edges: the run-once marker is written to /tmp/, which does not exist on Windows, and the loader's fcntl and fork calls crash there.

That mix is typical of code built by iterative prompting: it works on the happy path and breaks on platforms nobody tested.

What privcollect steals

Category

Targets

Cloud

AWS keys and instance or task role credentials, GCP service account tokens, Azure CLI cache and instance metadata, Kubernetes, Vault, Terraform

Developer secrets

SSH, GPG and TLS private keys, .env, .netrc, git, npm and PyPI configs, shell history

AI coding tools

~/.claude/.credentials.json, ~/.codex/auth.json, ~/.gemini/oauth_creds.json, ~/.cursor/mcp.json, plus Copilot, Windsurf, Kiro, Zed, Continue, OpenCode and Hugging Face

Browsers

Chromium and Firefox logins, cookies and history

Runtime

Every non-empty env var, and on Linux the environments of other readable processes

LiteLLM proxy

If DATABASE_URL is set, up to 500 rows each from LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable

The loot was encrypted with AES-256-CBC, the key wrapped with RSA-4096, and POSTed to www.litellm[.]dev/healthcheck. The lookalike domain was registered about a week before the release.

About 4.5 hours after exfiltration, the attacker began testing stolen AWS instance role credentials from 168.100.8[.]57 and 165.227.201[.]8. The first calls werests:GetCallerIdentity, then Bedrock and model listing. That pattern points to LLMjacking: reselling or burning a victim's model quota.

Why an AI gateway breach is worse than a normal package breach

Most supply chain stealers grab whatever happens to be on the box. This one shipped a module written for the LiteLLM proxy database. That tells you the attacker understood what an AI gateway is for.

The gateway is a keyring

Teams adopt LiteLLM, so applications never hold provider keys directly. The proxy stores the OpenAI, Anthropic, Azure, Bedrock, and Vertex credentials, then hands out virtual keys. That design is good for governance. It also means one compromised gateway host gives up every upstream key at once, plus the master key that mints new ones. privcollect read LiteLLM_CredentialsTable directly through Prisma, so there was no need to search the disk.

Gateway hosts also tend to run with cloud roles that can call Bedrock or Vertex, and with open egress to many AI endpoints. The attacker's first move with stolen AWS roles was to list Bedrock models. A gateway is close to the ideal LLM-jacking target.

The AI toolchain was the entry point and the target

LiteLLM's own account of the root cause should worry every AI engineering team. Its post says Claude Code can include a user's GitHub CLI OAuth token in response bodies, and those bodies can end up in LiteLLM logs when response logging is on. An internal LiteLLM deployment was reachable from the internet. The team suspects the attacker got in through a recently patched flaw, read the logs, and collected three engineers' GitHub tokens. The root cause is still unconfirmed.

If that holds, the chain runs like this. Tokens from an AI coding agent leak into AI gateway logs. Those tokens are used to poison the AI gateway. The poisoned gateway then steals AI coding agent credentials from every machine that installs it, ready to start the loop again somewhere else.

The AI toolchain was the entry point and the target

In many companies, prompt and response logs have become a store of secrets that nobody classifies. They hold whatever agents read, wrote, or echoed: tokens, .env contents, customer data, database URLs. Few teams scan them, restrict who can read them, or keep them off the internet. Treat LLM request and response logs as sensitive API payloads, because that is what they are.

Signing proved the wrong thing

After March, LiteLLM moved releases to a private repo, adopted PyPI Trusted Publishing, made Docker tags immutable, and started signing images. None of it helped. The attacker did not steal a publishing token this time. They edited the workflow that does the publishing, so the legitimate pipeline built, signed, and shipped the malware with valid provenance.

The provenance did record the truth. It said the source owner was Berri-AI, one hyphen away from BerriAI. Nobody checked. A signature tells you which pipeline built an artifact. It does not tell you that the pipeline was building from the right place. The weak points were controls one person could get around: branch protection that an owner token overrode in five seconds, and an approval gate that one stolen credential satisfied.

Repeat targets get hit again

LiteLLM had more than 100 million PyPI downloads in the last month and over 2,000 dependent packages. It sits in front of the most valuable keys in a modern stack. Researchers have not linked October to TeamPCP, but both attacks used the same .pth startup trick. Expect AI gateways, MCP servers, and agent frameworks to keep drawing this kind of attention. A clean history does not make the next release safe.

Who is affected by the LiteLLM incident?

You are exposed if any of these were installed or ran during the window. Enterprise customers on LiteLLM's private registry were not affected, per the official post.

Channel

Affected artifact

Window (UTC, Oct 21)

PyPI

Unpinned litellm resolving to 1.107.0 (seven wheels)

11:39 to 13:23

GHCR

litellm, litellm-database, litellm-non_root at v1.107.0, latest, main-latest, main-stable

11:46 to about 13:51

Docker Hub

litellm/litellm at the same tags

11:46 to 14:20

The exposure is wider than people who use LiteLLM directly. Any CI job, notebook or container that pulled LiteLLM as a transitive dependency during the window counts. So does any cache, mirror or base image built then.

How to check

  • Search lockfiles, SBOMs, and CI logs for litellm==1.107.0 or litellm-1.107.0.dist-info.

  • Check running and cached images for index digest sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e.

  • Look for cook.pth, cook.py and an unexpected sitecustomize.py in every site-packages. Compare hashes, because sitecustomize.py can be legitimate.

  • Look for /tmp/.pc_once_72129118c59f5636c0bb (exfil succeeded) and /tmp/remote_code_loader.lock (loader running). A missing marker proves nothing in containers.

  • Search egress logs for litellm[.]dev, /healthcheck, /codepy, the header X-Token: XWiA8gZNa7NcX2XRoAEb, and the string PRIVCOLLECT_ACTIVE.

  • In CloudTrail, look for GetCallerIdentity , and Bedrock calls from 168.100.8[.]57 or 165.227.201[.]8, or any instance role used from outside your network.

Do the inspection from a shell. Do not run python or pip on a suspect host, because that re-runs the payload.

What to do now

If 1.107.0 ran on a host with outbound internet access, assume the data left. Work through these in order.

  1. Isolate and preserve. Cut egress for the host or pod, snapshot it, and save logs before you change anything.

  2. Stop the loader. Kill the detached Python process holding /tmp/remote_code_loader.lock. Uninstalling the package does not stop it or remove the files.

  3. Remove the payload from a shell. Delete cook.pth, cook.py, the malicious sitecustomize.py, and matching __pycache__ files from every site-packages.

  4. Rebuild, don't repair. Recreate venvs and images from scratch, pinned to litellm==1.106.2 by hash or digest.

  5. Rotate from a clean machine. Cover everything the host could reach:

    • Cloud instance and task roles, access keys, service account tokens

    • Every provider key stored in the LiteLLM proxy database, plus master and virtual keys

    • The DATABASE_URL password

    • SSH, GPG and TLS keys, git, npm, PyPI and registry tokens

    • AI coding tool sessions (Claude Code, Codex, Gemini, Cursor, Copilot and others)

    • Browser sessions on any developer laptop that installed it

  6. Hunt for follow-on use. Review CloudTrail, provider usage dashboards, and billing for new regions, model calls or IAM changes since October 21.

  7. Check your own logs for tokens. If you log LLM response bodies, search them for GitHub, cloud, and provider tokens, and rotate what you find.

Lessons for AI supply chain and AI security

Cleanup closes this incident. These changes reduce the damage from the next one.

  • Treat AI gateways as tier-0 infrastructure. Give them the controls you give a secrets manager: no public exposure, strict egress allowlists, short-lived cloud roles scoped to the models they serve, and alerts on any new outbound destination. The exfil here went over plain HTTP to a domain a week old. An allowlist would have blocked it.

  • Keep long-lived keys out of the gateway when you can. Use workload identity for Bedrock, Vertex, and Azure OpenAI instead of static keys in the proxy database, so a database dump yields less.

  • Classify LLM logs as sensitive data. Turn off response body logging where you don't need it. Where you do, redact tokens before storage, restrict access, and scan the logs for secrets the same way you scan code.

  • Inventory every AI endpoint you run. The breach likely started at an internal LiteLLM deployment that nobody expected to be on the internet. Continuous discovery of AI gateways, MCP servers, and agent endpoints, with authentication and exposure testing, catches that before an attacker does.

  • Verify provenance, not just signatures. Check that the source repo in the attestation matches the one you expect. A one-character difference in the owner name was the whole attack.

  • Protect the workflow definition. Require two people to change release workflows. Use rules that org owners cannot bypass alone, and prefer short-lived, device-bound tokens over GitHub CLI OAuth tokens that do not expire on their own.

  • Scan the wheel, not the repo. The source and tag were clean. Scanners that inspected the built artifact flagged it within minutes. Add artifact scanning and a cooldown period on new versions of critical dependencies.

  • Pin by hash. Unpinned installs and floating tags like latest are how this release reached victims.

Secure your LiteLLM deployment

If you run LiteLLM in production, these two resources help you lock it down:

  • LiteLLM Security Checklist: a practical list of controls to harden your LiteLLM proxy and the keys it holds.

  • Akto + LiteLLM Solution Brief: how Akto adds a chained guardrail inside the LiteLLM proxy to catch prompt injection, semantic data leaks, and intent-level policy violations, with the YAML config to turn it on.


Follow us for more updates

The Largest Agentic AI Security Summit

The Secure, Governed AI Future.

October 27, 2026 | Virtual

Experience enterprise-grade Agentic Security solution