A leaked key let an attacker ship a poisoned litellm 1.107.0 to PyPI, GHCR and Docker Hub for about two hours. It stole cloud, AI-provider and SSH credentials.
On 21 October 2026 an attacker published a backdoored litellm==1.107.0 to PyPI and pushed matching container images to GHCR and Docker Hub under the v1.107.0, latest, main-latest and main-stable tags. Anything that installed that wheel or pulled those tags ran a credential stealer. The last safe release is 1.106.2. No CVE has been issued; this is a compromise of the release pipeline, not a bug in LiteLLM's code. It is separate from the LiteLLM vulnerabilities ThreatFrontier has covered before.
Wiz reports about 49,000 downloads of 1.107.0 (ClickPy figure). The download count includes mirrors and CI jobs, so it overstates the number of distinct victims, but anyone whose environment fetched the package in the window should treat the host as compromised.
Exposure window
The window below is from LiteLLM's own incident post. Wiz's times differ by a few minutes and are noted.
| Channel | Live (UTC, 21 Oct) | Gone |
|---|---|---|
| PyPI, all seven wheels | 11:39:51 | by 13:23 (Wiz: quarantine at 13:20) |
| GHCR tags | 11:46:56 | unavailable by 13:51 (Wiz: about 13:53) |
| Docker Hub tags | 11:46:56 | removed or repointed at 14:20 |
On LiteLLM's times, PyPI served the release for 1 h 44 min, GHCR for up to 2 h 05 min and Docker Hub for up to 2 h 34 min, so the Docker Hub tags outlived PyPI by 57 min. The first stolen-key test Wiz saw came 2 h after the last tag was removed. At 13:23 UTC, the Upwind researcher who first reported the incident was still seeing latest serve the infected image.
How long each channel served the poisoned release on 21 October (UTC), and when stolen AWS keys were first tested. Times from LiteLLM's incident post and Wiz.
What the malware does
The official v1.107.0 git tag does not contain the payload. LiteLLM says the artifacts were built from an attacker-controlled lookalike repository, Berri-AI/litellm (one hyphen, versus BerriAI/litellm). A data = "cook" line in pyproject.toml pulled three extra files into each wheel, and they install into the site-packages root:
cook.pth: importscookon every start of that Python interpreter, even when litellm is never imported.cook.py: a stealer that calls itselfprivcollect, runs once in a background thread, and writes/tmp/.pc_once_72129118c59f5636c0bb.sitecustomize.py: on Linux and macOS, forks a detached daemon holding/tmp/remote_code_loader.lock. Each hour it readsdomain.txtfrom the GitHub repoletters9813/pythonclient-test. If a domain is present it fetcheshttp://<domain>/codepyand runs the response.
Wiz says this pairing of .pth and sitecustomize.py is apparently the first documented use in the wild. The second stage was dormant when researchers looked, so no remote-code payload has been observed. The operator could re-arm it by editing one file, though GitHub took the repo down around 13:30 UTC, according to Wiz.
The stealer collects, per the Upwind decode and Wiz:
- Browser passwords and cookies (Chromium family and Firefox; Wiz also lists Safari).
- SSH and GPG keys, shell history,
.envfiles,.netrc,.npmrc,.pypircand Git credentials. - AWS, GCP and Azure credentials, including instance and task role credentials from the cloud metadata services.
- Credentials and config for AI tools and providers.
- The environment variables of every process it can read.
- If
DATABASE_URLis set, the LiteLLM proxy'sLiteLLM_CredentialsTableandLiteLLM_ProxyModelTable, which hold every upstream provider key the proxy stores.
Data is encrypted with AES-256-CBC under an RSA-4096-wrapped key and POSTed to http://www.litellm.dev/healthcheck. litellm.dev was registered on 13 October and is not the project's domain (litellm.ai). Wiz saw stolen AWS credentials validated roughly 4.5 hours after exfiltration, between 16:20 and 17:01 UTC, from 168.100.8[.]57 and 165.227.201[.]8. Each was checked with sts:GetCallerIdentity, then used to list Bedrock foundation models, a pattern consistent with LLM-resource hijacking. Wiz saw limited IAM enumeration in a few environments. Neither Wiz nor LiteLLM names a threat actor.
How the attacker got in
LiteLLM's root-cause analysis, published 23 October, traces a long chain, and corrects an earlier theory that tokens had leaked from an internal deployment's logs:
- A master key for LiteLLM's experimental Agent Platform was committed to a public repository on 27 May and never revoked.
- On 10 October the attacker used it to read the Agent Platform API, which returned a GitHub CLI token belonging to an organization owner.
- With owner access, the attacker ran CI workflows that posted repository secrets to an external webhook, yielding two maintainers' GitHub CLI tokens.
- On 21 October the attacker changed the private release workflow to build from the lookalike fork. Branch protection rejected the push, and the owner token overrode it.
- A release policy relaxed shortly before allowed any two engineers to trigger and approve a release. The attacker used one maintainer token to start the run and another to approve it, then deleted the run records and reverted
main.
LiteLLM's earlier status posts had described the tokens as stolen GitHub CLI OAuth tokens from three engineers' accounts. Its enterprise customers, who pull from a private registry, were not affected.
What defenders should do
LiteLLM and the original reporter give the same order of operations. Do not start with pip install: any Python start in the affected environment, pip included, re-runs the malware.
- Find exposure: look for
litellm-1.107.0.dist-info,cook.pth,cook.pyandsitecustomize.pyin every site-packages, and check image pull history for the four tags in the window. Absence of the marker file proves nothing, because containers and temp cleanup remove it. - From the shell, delete
cook.pth,cook.py,sitecustomize.pyand their__pycache__entries, and kill the detached Python process holding/tmp/remote_code_loader.lock. - Reinstall pinned to
litellm==1.106.2, or better, rebuild images and virtual environments from scratch. Pin container images by digest, not tag. - If 1.107.0 ever ran on a host with outbound internet, assume exfiltration succeeded. Rotate the instance or task role, every provider key in the proxy database, the proxy master and virtual keys, the
DATABASE_URLpassword, and any secret in the environment of any process on the host. Rotate browser-stored and SSH credentials for developer machines. - Check CloudTrail for
GetCallerIdentityfrom the instance role around install time, then for use of its credentials from outside your infrastructure. Blocklitellm.devand search egress logs for/healthcheckPOSTs.
LiteLLM has paused ordinary releases and locked its public registries while it rebuilds the release process. It also says an embargoed fix for an unrelated vulnerability is due for disclosure on 27 October.
Open questions
The timeline above is LiteLLM's. The incident page and RCA contain small internal inconsistencies (the RCA says detection came within 20 minutes of publication; its own timeline implies about 26), and the RCA says earlier status updates contained assumptions later found inaccurate, so expect further revision.