What happened
On 21 October 2026, LiteLLM's release pipeline was used to publish litellm 1.107.0 to PyPI and to its Docker images with compiled malware: a credential stealer and a remote-code loader. LiteLLM says all earlier versions are safe and the affected releases have been pulled.
The official v1.107.0 source tag on GitHub does not contain the malicious files. The artifacts were built from a lookalike repository, Berri-AI/litellm, which differs from the real BerriAI/litellm by one hyphen. All seven wheels add cook.pth, cook.py and sitecustomize.py to site-packages, so the malware runs whenever Python starts, even if litellm is never imported.
The stealer collects credentials, environment variables and cloud metadata, encrypts them and sends them to litellm.dev. The loader polls a GitHub file hourly for a domain and runs code from it, but the file was empty, so that stage was dormant and no second stage was observed. Wiz reports that the attacker validated stolen AWS instance role credentials, and SecurityWeek says Wiz saw stolen AWS credentials used to query Amazon Bedrock models about 4.5 hours after exfiltration, suggesting LLMjacking.
LiteLLM says the attacker used a GitHub CLI OAuth token, obtained through an internal LiteLLM deployment exposed to the internet, to compromise the release workflow. Other sources say tokens from three engineers' accounts were used. SecurityWeek reports the tokens were likely leaked by Claude Code placing them in response bodies that may have been logged. LiteLLM says investigators have not confirmed how the attacker entered the internal deployment, and the investigation is ongoing.
PyPI removed 1.107.0 by 13:23 UTC on 21 October. Affected GHCR tags were unavailable by 13:51 UTC, and by 14:20 UTC all affected Docker Hub tags had been removed or repointed to verified clean artifacts. According to Aikido, GitHub took down the attacker's dead-drop repository.
Background
Processes running LiteLLM typically hold provider API keys, cloud identities and database connection strings, which makes them an attractive target for credential theft. Datadog notes an earlier 2026 LiteLLM incident affected versions 1.82.7 and 1.82.8, and that no connection between the two has been established.
StepSecurity says that because the malicious artifacts were built and published by the legitimate pipeline with valid provenance, the hardening measures introduced in March did not prevent this compromise.
How it works
Per Socket, Endor Labs and Aikido, a maturin data setting added to pyproject.toml (data = "cook") bundled a directory absent from the tagged source into the wheels, and a COPY line in the Dockerfiles did the same for images. The tag's pyproject.toml lacks the setting, so reviewing the tag alone would miss it. The sdist contains the setting but not the cook directory, so it has no payload.
According to the project's account, an attacker-used credential overrode branch protection to modify the private release workflow, pointing it at Berri-AI/litellm. The workflow ran staging, then production, with a second developer credential satisfying the approval gate. A third developer's credentials were then used to delete workflow records, limiting available logs. Build provenance recorded the lookalike repository as the source.
At runtime, cook.pth launches cook.py on every interpreter start. The stealer gathers host information, browser passwords, SSH keys, cloud and developer credentials, AI tool configs, environment variables and, when DATABASE_URL is set, LiteLLM proxy provider keys. The stealer protects what it collects with AES-256-CBC encryption, locks the key using RSA-4096 OAEP-SHA256, and sends the package over unencrypted HTTP to litellm.dev. The marker file is only written after a successful HTTP 200, so the stealer reruns on each Python start while exfiltration fails.
sitecustomize.py starts a background daemon that checks a GitHub file hourly for a domain, then fetches /codepy from it and executes the response with no signature or hash check. Deleting the litellm folder does not remove the dropped files; running python or pip triggers the malware again.
Timeline
The litellm.dev domain was registered.[2]
First attacker commit to Berri-AI/litellm, adding cook.pth.[2]
A privileged developer credential overrode branch protection and applied a workflow commit.[1]
The modified workflow started a staging run from Berri-AI/litellm.[1]
Seven malicious litellm 1.107.0 wheels were uploaded to PyPI.[1]
Container publication jobs ran and the Docker image included malicious content.[1]
The unauthorized workflow revision was removed and the release workflow restored.[1]
PyPI had removed litellm 1.107.0.[1]
Affected GHCR tags were unavailable; Docker Hub tags still resolved to the compromised image.[1]
All affected Docker Hub tags were removed or repointed to clean artifacts.[1]
Who is affected
- Anyone who installed litellm 1.107.0 from PyPI, including unpinned installs and open-ended constraints that resolved to it. Any Python process in that environment runs the malware.
- Anyone who ran the Docker images tagged v1.107.0, latest, main-latest or main-stable in the window, on GHCR or Docker Hub.
- LiteLLM proxy hosts with DATABASE_URL set should assume stored provider credentials were taken.
- Not affected, per LiteLLM: enterprise customers on the private registry, and earlier versions.
Impact
Wiz cites about 49,000 downloads of the malicious version per ClickPy, and SecurityWeek says the packages were online for roughly three hours. Datadog puts availability at about two hours. Socket's scanner flagged the seven wheels seven minutes after publication.
The PyPI exposure window runs from about 11:39 to 13:23 UTC and the Docker window from about 11:46 to 14:20 UTC. Unpinned installs and deployments following floating tags such as latest or main-stable could receive the bad release without choosing it. Enterprise customers using the private registry were not affected, according to LiteLLM.
How to check
- Run pip show litellm, and search lockfiles, requirements, caches, CI build logs and container layers for litellm 1.107.0.
- Search site-packages for cook.pth, cook.py, sitecustomize.py and litellm-1.107.0.dist-info. Finding one confirms exposure; finding none does not prove the environment is clean. Prefer checking on disk without running Python.
- Check /tmp for the marker file /tmp/.pc_once_72129118c59f5636c0bb and for /tmp/remote_code_loader.lock. Absence of the marker proves nothing.
- Check which image digests your deployments or Kubernetes pods ran on 21 October.
- Search egress logs for litellm.dev, including POSTs to /healthcheck and requests ending in /codepy, and check CloudTrail for GetCallerIdentity from the instance role around install time.
What to do
- Do not start pip first in an affected environment; each Python start reruns the payload. Isolate affected hosts and preserve evidence.
- Kill the detached Python daemon holding /tmp/remote_code_loader.lock.
- Delete cook.pth, cook.py, sitecustomize.py and matching pycache files from affected site-packages.
- Rebuild environments and images from scratch and pin litellm==1.106.2, or the v1.106.2 image by digest.
- Assume exfiltration succeeded and, from a clean machine, rotate everything the host could reach: IAM roles, provider keys, master and virtual keys, the DATABASE_URL password and environment secrets.
- Block litellm.dev at DNS or egress and block litellm==1.107.0 in your package proxy.
Affected packages
The package versions named in this report.
| Ecosystem | Package | Versions |
|---|---|---|
| PyPI | litellm | 1.107.0 |
| Docker | ghcr.io/berriai/litellm | v1.107.0latestmain-latestmain-stable1.107.0 |
| Docker | ghcr.io/berriai/litellm-database | v1.107.0latestmain-latestmain-stable |
| Docker | ghcr.io/berriai/litellm-non_root | v1.107.0latestmain-latestmain-stable |
| Docker | docker.io/litellm/litellm | v1.107.0latestmain-latestmain-stable |
| Docker | litellm/litellm | 1.107.0 |
Indicators of compromise
Shown defanged so they can't be clicked. Copy copies the real value. Every indicator appears in the cited source.
| Type | Indicator | Note | Copy |
|---|---|---|---|
| File path | /tmp/remote_code_loader.lock | Lock file held by detached python daemon[1] | |
| Other | litellm-1.107.0.dist-info | Package metadata directory indicating exposure[1] | |
| Other | Berri-AI/litellm | Attacker-controlled lookalike source repository[1] | |
| Other | LidorMachluf | Credited with helping detect the incident[1] | |
| Domain | www[.]litellm[.]dev | Exfiltration endpoint (POST http://www.litellm.dev/healthcheck)[2] | |
| URL | hxxps://raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt | Dead-drop file read by sitecustomize.py for C2 domain[2] | |
| HTTP header | X-Token: XWiA8gZNa7NcX2XRoAEb | Token header sent with exfil POST[2] | |
| File path | site-packages/cook.pth | Payload file installed by wheel[2] | |
| File path | /tmp/.pc_once_72129118c59f5636c0bb | Marker written after successful exfil POST[2] | |
| GitHub account | letters9813 | Dead-drop GitHub account[2] | |
| ckdi82fkoos[@]proton[.]me | Commit author email for attacker commits[2] | ||
| SHA-256 | 7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d | Malicious litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl[2] | |
| SHA-256 | 6ea3d8ef6cf73a72d8df24f663b7a31ce67494eeb9b0d707b93484e66675d2df | Decoded stealer cook.py variant[2] | |
| SHA-256 | 20a59e5a285799e7cf7fa16cfba1c1414f1e84a8459e14207cfd4e998c2cd24b | Decoded stealer sitecustomize variant[2] | |
| Domain | litellm[.]dev | Attacker-controlled exfil (lookalike)[3] | |
| URL | hxxp://www[.]litellm[.]dev/healthcheck | Exfiltration endpoint[5] | |
| Other | PRIVCOLLECT_ACTIVE=1 | Environment variable indicator[3] | |
| SHA-256 | 84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d | cook.pth persistence hook - Windows[3] | |
| SHA-256 | 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb | cook.pth persistence hook - Linux/MacOS[3] | |
| SHA-256 | 03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea | cook.py credential stealer payload (22,252 bytes) - Windows[3] | |
| SHA-256 | 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25 | cook.py credential stealer payload (22,252 bytes) - Linux/MacOS[3] | |
| SHA-256 | b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465 | sitecustomize.py loader with hourly backdoor loop (25,509 bytes) - Windows[3] | |
| SHA-256 | 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa | sitecustomize.py loader with hourly backdoor loop (25,509 bytes) - Linux/MacOS[3] | |
| IP address | 168[.]100[.]8[.]57 | Attacker IP used to validate stolen AWS credentials[3] | |
| IP address | 165[.]227[.]201[.]8 | Attacker IP used to validate stolen AWS credentials[3] | |
| File path | cook/purelib/cook.pth | Startup payload file in the source revision and wheel[5] | |
| File path | litellm-1.107.0.data/purelib/cook.pth | Startup hook file in the wheels[5] | |
| File path | litellm-1.107.0.data/purelib/cook.py | Collector wrapper in the wheels[5] | |
| File path | litellm-1.107.0.data/purelib/sitecustomize.py | Background loader in the wheels[5] | |
| Other | letters9813/pythonclient-test | GitHub repository hosting the loader pointer[5] | |
| URL | hxxps://github[.]com/Berri-AI/litellm | Lookalike source repository recorded in provenance[5] | |
| Other | 20535d8fa11f786a6c24df7488a0e449ed5f09ad | Recorded source revision[5] | |
| Other | 783c43bc455d2e05d3cfaf194c2f325cfeab1433 | Commit adding the Maturin data directory[5] | |
| SHA-256 | 0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a | Wheel litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl[5] | |
| SHA-256 | 668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6 | Wheel litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl[5] | |
| SHA-256 | 462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7 | Wheel litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl[5] | |
| SHA-256 | c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054 | Wheel litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl[5] | |
| SHA-256 | 1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00 | Wheel litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl[5] | |
| SHA-256 | 12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484 | Wheel litellm-1.107.0-cp310-abi3-win_amd64.whl[5] | |
| SHA-256 | 1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e | Container image index[5] | |
| SHA-256 | bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5 | Linux amd64 manifest[5] | |
| SHA-256 | 56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf | Linux arm64 manifest[5] | |
| GitHub account | Berri-AI | lookalike source repository owner[6] | |
| User agent | Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0) | exfiltration user agent[6] | |
| SHA-256 | 2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819 | litellm-1.107.0.tar.gz sdist hash[6] | |
| Other | XWiA8gZNa7NcX2XRoAEb | exfiltration token value (X-Token)[6] | |
| Other | Commit 783c43bc455d2e05d3cfaf194c2f325cfeab1433 | Adds the Maturin data setting and payload[7] | |
| Other | Commit 20535d8fa11f786a6c24df7488a0e449ed5f09ad | Adds COPY cook ./cook to Dockerfiles; recorded build revision[7] | |
| Other | 9cf97e935aeaf791b9c8db224a2bb3ee156134ce | Unauthorized workflow commit applied to release workflow[1] | |
| File path | site-packages/cook.py | Stealer payload[2] | |
| File path | site-packages/sitecustomize.py | Remote-code loader[2] | |
| Other | a898fd08ad220fed52488906eb45d196f332b972ad5d85214e7726545ecc4218 | RSA-4096 public-key fingerprint, SHA-256 of DER SubjectPublicKeyInfo[2] |
Sources
- Datadog Security Labs · LiteLLM supply chain compromise: What you need to know
- SecurityWeek · LiteLLM Compromised in Fresh Supply Chain Attack
Changelog
- First published.
- Added the unconfirmed initial access, SecurityWeek's Claude Code token-leak report, Datadog and Socket details, extra timeline entries and checks; removed the unsupported claim about a patched… Added 4 sources, 1 package, 13 indicators.