Eslam Hamed
Technical Lead & AI Solutions Architect
Building enterprise AI systems, cloud platforms, and production-ready software
LiteLLM Compromised Twice: Why a Valid Signature Did Not Help, and How to Protect Your AI Gateway
On October 21, a malicious LiteLLM 1.107.0 reached PyPI, Docker Hub and GHCR. Nobody uploaded it with stolen registry credentials. LiteLLM's own release pipeline built it from a lookalike fork, Berri-AI/litellm, signed it and published it. The wheels were live on PyPI for about 1.7 hours, and the Docker Hub images for about 2.6.
It was the second time in seven months. In March, versions 1.82.7 and 1.82.8 shipped a credential stealer to PyPI. LiteLLM is the open-source gateway many teams put in front of every model provider they use, so it holds the keys for every provider behind it. That makes this more than another package incident.
I read LiteLLM's incident posts, its March townhall and the independent analyses from Socket and Datadog. Facts below come from LiteLLM unless I say otherwise, and I mark my own arithmetic. I have not analysed the malware myself.
Two LiteLLM compromises, one target
| March 24 | October 21 | |
|---|---|---|
| Versions | 1.82.7 and 1.82.8 | 1.107.0 |
| Channels | PyPI only | PyPI, Docker Hub and GHCR |
| How it ran | In 1.82.8, a .pth file on any Python start | cook.pth and sitecustomize.py on any Python start |
| Way in | A compromised Trivy scanner in CI, with static release credentials in reach | Stolen GitHub tokens running the real release pipeline |
| Live for | About 40 minutes | 1.7 hours on PyPI, 2.1 on GHCR, 2.6 on Docker Hub |
| Release signing | Not in place until v1.83.0 | Signed by LiteLLM's own pipeline |
Sources: the March GitHub issue, LiteLLM's townhall and its October incident post. The October window lengths are my arithmetic from LiteLLM's timestamps.
Why the AI gateway is the target
A gateway routes requests to model providers, so it needs each provider's key, and it ends up holding all of them, plus its own master and virtual keys, a database password and usually a cloud role. LiteLLM's advice to affected users reads like that inventory: rotate the instance or task IAM role, every provider key stored in the proxy database, the master and virtual keys, the database password, and any secret in the environment of any process on the host.
The payload was written for that inventory. Datadog found that the October stealer collects cloud credentials, including from instance metadata, along with SSH keys, Git and PyPI tokens, LLM provider keys and sessions for AI coding tools. If a database is configured, it queries the tables where the proxy stores upstream provider credentials. LiteLLM says the attacker's activity in its own systems focused on upstream inference API tokens. Datadog and Socket both put LiteLLM at about 23 million weekly downloads.
I have written before that a secret that never enters the model's context cannot be repeated by the model; it can still be misused through a tool that holds it. A gateway is the tool that holds all of them. The same logic made OpenClaw's gateway token worth stealing earlier this year.
How the October release was built
LiteLLM's root-cause analysis, published on October 23, sets out the chain.
- May 27. A configuration file containing the master key of the LiteLLM Agent Platform, a tool LiteLLM uses to orchestrate agents, was committed to a public repository. The commit was force-pushed over. The key was never revoked.
- October 10. The attacker used that key on the platform's API. One response returned every managed agent's configuration, including a GitHub CLI token belonging to one of LiteLLM's two organisation owners. It was a personal token rather than a service account, and it was not scoped down.
- The same evening. Throwaway branches ran workflows that posted repository secrets to an attacker-controlled endpoint, and the runs were deleted. Two of those secrets were the GitHub CLI tokens of two more maintainers.
- October 21. Branch protection rejected the attacker's change to the release workflow, and the owner's token overrode it. The workflow built from the fork, a second maintainer's token approved the production gate, and a third deleted the run records.
That gate normally needed a special account locked to a device in LiteLLM's office. Earlier that week it had been relaxed so that any two engineers could ship daily backports, and LiteLLM notes that the owner's token could have removed the restriction anyway.
One correction matters. LiteLLM's first post suspected the tokens came from the logs of an internal LiteLLM deployment. The root-cause analysis traced the first token to the Agent Platform instead, and LiteLLM marked its first post as inaccurate or incomplete in places. A first incident report is a draft.
Why the signature did not help
After March, LiteLLM did what the usual guidance says. Its CI/CD v2, announced on March 30, isolated security scans and tests, moved releases into a separate repository, published to PyPI through Trusted Publishing so that no long-lived token was involved, made Docker release tags immutable, and signed images with cosign. The March townhall named the cases signing was meant to cover: stolen registry credentials, tampered artifacts and mutated tags.
October avoided all three. The attacker needed no registry credential, because the real pipeline published. Nothing was tampered with after the build, because the build itself took the wrong source. A cosign check confirms that an image came from LiteLLM's pipeline, and this one did. A signature tells you who built an artifact. It does not tell you what it was built from.
That second question had an answer on day one. Socket examined the provenance attached to the images on October 21 and found the release workflow's input source_owner set to Berri-AI, with the source recorded as github.com/Berri-AI/litellm. The provenance for 1.106.2 records BerriAI/litellm. Datadog described the provenance as having faithfully recorded the malicious fork. The evidence was one hyphen long, and no check looked at it.
Floating tags did the rest. Socket reported that latest, main-latest and main-stable on GHCR resolved to the same image index as v1.107.0, so a deployment that pulled latest received the stealer without asking for it. The researcher who reported the release advised pinning 1.106.2 by digest.
Other release failures this year point the same way. In March, a Claude Code release shipped its source map to npm through a genuine pipeline. In May, an attacker published 84 malicious versions of 42 TanStack packages through a pull_request_target workflow, a poisoned Actions cache and an OIDC token read from runner memory, and TanStack says no npm token was stolen. A short-lived token shrinks what can be stolen. It does not stop a hijacked pipeline from using it.
What I would take from my own release work
On Auto-RAG and MerlinX, the platforms I work on at DXWand, releases ship in gated waves. UAT runs the identical image artifacts, verified by digest rather than rebuilt look-alikes, and nothing promotes until post-deploy assertions pass and a soak holds against the baseline error rate. I also brought 35 Azure DevOps projects and about 224 repositories under one standard: an estate-wide branch-policy audit, then mandatory review gates on tier-one repositories, protected tags, a hardened deploy-render gate, break-glass access, and a scheduled drift audit.
LiteLLM had several of the same controls, including review gates, protected tags and a gated release environment, and its branch protection did reject the first push. What failed were the exceptions: an owner token that could override protection, and a release gate relaxed for a week of backports. Break-glass access and drift audits exist for those two cases. The digest rule carries over to an image you did not build: a digest you pinned before October 21 never pointed at 1.107.0.
An AI gateway supply-chain checklist
| Control | Would it have kept 1.107.0 out? |
|---|---|
| Pin images by digest and packages by hash | Yes, for any pin set before October 21 |
| Adopt no release younger than a day | Yes, every channel was clean within 2.6 hours |
| Check the provenance's source repository | Yes for the images, whose provenance named Berri-AI/litellm |
| Verify the cosign signature | No, LiteLLM's pipeline signed it |
| Egress allowlist for the gateway | Not the install, but likely the theft |
| Short-lived, scoped credentials | Not the install, but less to steal |
Source: my assessment, from the LiteLLM, Socket and Datadog accounts above.
If you run a gateway:
- Pin and wait. Deploy images by
sha256digest, neverlatest, install Python packages with hash checking, and give new releases a cooling-off period. LiteLLM's own March plan added a cooldown before upgrading its dependencies. - Allowlist egress. A gateway needs your providers, its database and your telemetry. The stealers posted to lookalike domains,
models.litellm[.]cloudin March andwww.litellm[.]devin October. Admitting all of GitHub would still have let the October backdoor reach its dead drop onraw.githubusercontent.com. - Shrink the credentials. Keep provider keys in a secret manager, give the gateway's cloud role and database user only what routing needs, block instance metadata if it is unused, and issue virtual keys per team with budgets.
- Never log tokens. If your gateway stores request or response bodies, scrub authorisation headers and token patterns first.
If you publish one:
- Lock the source. A release workflow should build one repository, with no free-form source input. Datadog's advice is the same.
- Make overrides loud. Approval for the release environment should need a device-bound credential, and any branch-protection override should page someone.
- Keep personal tokens out of CI. Use short-lived, scoped tokens per job, as I argued after PocketOS.
- Revoke on the day. A force-push hides a leaked key. Only rotation makes it worthless.
If you ran LiteLLM 1.107.0
LiteLLM's guidance, in brief: do not start with pip, because any Python start reruns the payload, pip included. Delete cook.pth, cook.py, sitecustomize.py and their cached .pyc files from every affected site-packages, kill the daemon holding /tmp/remote_code_loader.lock, rebuild pinned to 1.106.2, and rotate everything the host could reach. Treat any container that ran image index sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e as compromised. Finding the files confirms exposure; not finding them proves nothing.
In March, a LiteLLM release could not say where it came from. In October, it said so, down to the hyphen, and nobody asked.
Sources
- LiteLLM, Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised, October 21, 2026, updated October 23
- LiteLLM, The attack chain behind the v1.107.0 LiteLLM release, October 23, 2026
- Socket, LiteLLM PyPI Package and Docker Images Compromised to Steal Cloud and AI Credentials, October 21, 2026
- Datadog Security Labs, LiteLLM supply chain compromise: What you need to know, October 21, 2026
- GitHub issue 47826, litellm 1.107.0 compromised, October 21, 2026
- GitHub issue 24518, litellm 1.82.7 and 1.82.8 compromised, March 24, 2026
- LiteLLM, Security Townhall Updates, March 27, 2026
- LiteLLM, Announcing CI/CD v2, March 30, 2026
- TanStack, Postmortem: npm supply-chain compromise, May 11, 2026, updated May 15
Written by Eslam Hamed, a Technical Lead and AI Solutions Architect working on enterprise RAG, agentic AI, and release engineering for AI platforms.
Putting agents near real systems and data? I help teams design the controls before something goes wrong. See how we can work together.