LiteLLM Supply Chain Attack: The Hardened Pipeline Published the Malware
After March, LiteLLM moved every release onto one trusted pipeline. On October 21 the attacker used that pipeline, and every identity check along the way said yes.
The LiteLLM supply chain attack of October 21 is the second time this year that litellm on PyPI shipped a credential stealer. Each time, the release passed the checks that ask who published it, and none of them asked what was built. After March, LiteLLM fixed the first question. On Wednesday the attacker exploited the second.
What happened in the LiteLLM supply chain attack
Per LiteLLM's incident post, litellm==1.107.0 was live on PyPI from 11:39 to 13:23 UTC on October 21. All seven wheels were affected. The Docker tags v1.107.0, latest, main-latest and main-stable pointed at a compromised image from 11:46 UTC, until 13:51 on GHCR and 14:20 on Docker Hub. The wheels drop cook.pth, cook.py and sitecustomize.py into site-packages. Python runs them at every interpreter start, including a run of pip, even when nothing imports litellm. Among the things the stealer collects, the issue report lists the contents of LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable whenever DATABASE_URL is set. Those tables hold every upstream provider key the proxy stores.
The maintainers' account of the chain: an attacker held GitHub CLI OAuth tokens for three engineers, which LiteLLM believes came from the logs of an internal LiteLLM deployment exposed to the internet. One token belonged to an organization owner. The attacker used it to disable branch protection and force-push a change that let the private releaser accept a different source repository. Release permissions had been widened that Monday so two engineers could publish backports. The attacker then ran the official workflow against Berri-AI/litellm, one hyphen away from BerriAI/litellm. The pipeline built the artifacts, signed them and published them.
The first instance, and the fix it produced
In March, versions 1.82.7 and 1.82.8 carried a stealer for about 40 minutes. LiteLLM's March post says the attacker bypassed CI/CD and uploaded directly with credentials it believes came from the Trivy dependency in its scanning workflow. The fix was a new pipeline, and LiteLLM announced 1.83.0 as its first release on March 30.
That fix closed the March hole: an upload had to come from LiteLLM's own pipeline. October shows what it left open. An identity check tells you the pipeline ran. It does not tell you which source the pipeline was given.
What the artifacts show
Attestations stopped in April. We queried PyPI's integrity API for the first file of every litellm release uploaded since March 21. The 15 releases from 1.83.0 (March 31) through 1.83.14 (April 26) return a publish attestation. The publisher it names is GitHub repository BerriAI/project-releaser, workflow publish-litellm.yml. All 206 releases uploaded since, starting with 1.84.0.dev1 on April 29 and counting dev and release-candidate builds, return no provenance. That includes all eight files of 1.106.2, the version LiteLLM tells users to pin. The issue reporter wrote that litellm had never published attestations. PyPI's data shows it did, for four weeks.
An attestation would likely have passed anyway. The 1.83.x attestations identify the releaser repository and workflow, not the source the build was given. LiteLLM says the attacker ran its own release workflow, which signed and published the artifacts. If that is the publish workflow the 1.83.x attestations name, a check pinned to BerriAI/project-releaser would have accepted 1.107.0.
The record that told the truth. The reporter read the SLSA provenance that BuildKit attached to the 1.107.0 images. It named the source as https://github.com/Berri-AI/litellm at 20535d8f, where the releaser's earlier builds recorded BerriAI/litellm. It only helps someone who compares that field against the repository they expect.
Verified meant nothing. GitHub's API reports commit 783c43bc as verified, with reason valid. That is the commit that adds data = cook to the maturin section of pyproject.toml, the one line that bundles the payload into the wheel. Wiz notes the first nine attacker commits were made in GitHub's web editor, which signs them. The tag commit 66c8f5b1 has no such line, and its version field reads 1.108.0.
Your scanner may not know yet. OSV record MAL-2026-2144 lists 1.82.7, 1.82.8 and 1.107.0, updated at 22:01 UTC on October 21. On the morning of October 22, GitHub's advisory database returned zero advisories for litellm@1.107.0. Its reviewed advisory for the March malware, GHSA-5mg7-485q-xm76, still covers only 1.82.7 to 1.82.8.
Why teams keep walking into it
Every control in this chain answered an identity question correctly: a real maintainer token, a real organization owner, the real workflow, a verified commit. The attacker's work went into the one input that only branch protection guarded, the source repository.
An AI gateway raises the stakes. It is the routing layer we argue belongs in a production plan from day one, and the proxy keeps every provider key it routes to in one database. LiteLLM believes its own gateway was also where this attack began. LiteLLM says the tokens showed up in its own gateway's logs, in cases where Claude Code put a GitHub CLI token into a response body. LiteLLM has not finished confirming that, and we did not test it. Either way, a gateway that logs response bodies is storing secrets, and it needs the same handling as the key vault it already is.
Wiz reports the stolen credentials being used from about 16:20 UTC. Each AWS role was checked with sts:GetCallerIdentity, then bedrock:ListFoundationModels. Wiz reads that sequence as LLMjacking.
The cheapest way out
Run these on any host or image that resolved litellm between 11:39 and 14:20 UTC on October 21:
find / -path '*site-packages/cook.pth' 2>/dev/null
find / -path '*site-packages/sitecustomize.py' 2>/dev/null
ls -l /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock
curl -s https://api.osv.dev/v1/vulns/MAL-2026-2144 | jq -r '.affected[].versions[]'
gh api '/advisories?ecosystem=pip&affects=litellm@1.107.0' --jq length
curl -s https://pypi.org/integrity/litellm/1.106.2/litellm-1.106.2.tar.gz/provenance
A sitecustomize.py hit may be legitimate on some systems, so read it before deleting it. LiteLLM's guidance is not to run pip first. Delete the three files, kill the detached daemon, rebuild on 1.106.2, and if the host had outbound internet, assume the exfiltration succeeded. Rotate the instance role, every key in the proxy database, the master and virtual keys, and the DATABASE_URL password. A missing marker file proves nothing.
Then shorten what the next leak buys an attacker. GitHub CLI v2.103.0, released the same day, adds gh auth login --expiring: a leaked access token stops working within hours instead of lasting indefinitely. And if you consume container provenance, check that the source repository matches what you expect, not just that a signature verifies.
What would prove this post wrong: LiteLLM's final post-mortem showing that the PyPI upload came from some workflow other than BerriAI/project-releaser. In that case a pinned attestation check would have caught the release, and the argument here would only apply to the Docker images.
Sources
- LiteLLM — Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised
- BerriAI/litellm issue 47826 — litellm 1.107.0 compromised (report and maintainer status updates)
- BerriAI/litellm commit 783c43bc — adds data = cook to pyproject.toml
- PyPI integrity API — provenance for litellm 1.83.0 wheel
- PyPI JSON API — litellm release history
- OSV — MAL-2026-2144, Malicious code in litellm (PyPI)
- LiteLLM — Security Update: Suspected Supply Chain Incident (March 2026)
- Wiz — LiteLLM Compromised, Steals Credentials
- SafeDep — Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor
- GitHub Changelog — GitHub CLI: Opt in to expiring, auto-refreshing OAuth tokens
Related reading
- Dev Tools
Codex CLI Starts a Self-Updating Daemon by Default. The config.toml Switch Is Not in the Docs.
By Petru Popa · Read - Dev Tools
CodeQL Swift Scanning Has No Supported Setup on Xcode 27. Some Runs Still Pass.
By Petru Popa · Read - Dev Tools
GitHub Secret Scanning Got a New AI Model. Its Documented Blind Spots Predate It.
By Petru Popa · Read
Turn this into a plan for your team.
One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.