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

  1. The litellm.dev domain was registered.[2]

  2. First attacker commit to Berri-AI/litellm, adding cook.pth.[2]

  3. A privileged developer credential overrode branch protection and applied a workflow commit.[1]

  4. The modified workflow started a staging run from Berri-AI/litellm.[1]

  5. Seven malicious litellm 1.107.0 wheels were uploaded to PyPI.[1]

  6. Container publication jobs ran and the Docker image included malicious content.[1]

  7. The unauthorized workflow revision was removed and the release workflow restored.[1]

  8. PyPI had removed litellm 1.107.0.[1]

  9. Affected GHCR tags were unavailable; Docker Hub tags still resolved to the compromised image.[1]

  10. 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

  1. Run pip show litellm, and search lockfiles, requirements, caches, CI build logs and container layers for litellm 1.107.0.
  2. 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.
  3. Check /tmp for the marker file /tmp/.pc_once_72129118c59f5636c0bb and for /tmp/remote_code_loader.lock. Absence of the marker proves nothing.
  4. Check which image digests your deployments or Kubernetes pods ran on 21 October.
  5. 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

  1. Do not start pip first in an affected environment; each Python start reruns the payload. Isolate affected hosts and preserve evidence.
  2. Kill the detached Python daemon holding /tmp/remote_code_loader.lock.
  3. Delete cook.pth, cook.py, sitecustomize.py and matching pycache files from affected site-packages.
  4. Rebuild environments and images from scratch and pin litellm==1.106.2, or the v1.106.2 image by digest.
  5. 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.
  6. 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.

EcosystemPackageVersions
PyPIlitellm1.107.0
Dockerghcr.io/berriai/litellmv1.107.0latestmain-latestmain-stable1.107.0
Dockerghcr.io/berriai/litellm-databasev1.107.0latestmain-latestmain-stable
Dockerghcr.io/berriai/litellm-non_rootv1.107.0latestmain-latestmain-stable
Dockerdocker.io/litellm/litellmv1.107.0latestmain-latestmain-stable
Dockerlitellm/litellm1.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.

TypeIndicatorNoteCopy
File path/tmp/remote_code_loader.lockLock file held by detached python daemon[1]
Otherlitellm-1.107.0.dist-infoPackage metadata directory indicating exposure[1]
OtherBerri-AI/litellmAttacker-controlled lookalike source repository[1]
OtherLidorMachlufCredited with helping detect the incident[1]
Domainwww[.]litellm[.]devExfiltration endpoint (POST http://www.litellm.dev/healthcheck)[2]
URLhxxps://raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txtDead-drop file read by sitecustomize.py for C2 domain[2]
HTTP headerX-Token: XWiA8gZNa7NcX2XRoAEbToken header sent with exfil POST[2]
File pathsite-packages/cook.pthPayload file installed by wheel[2]
File path/tmp/.pc_once_72129118c59f5636c0bbMarker written after successful exfil POST[2]
GitHub accountletters9813Dead-drop GitHub account[2]
Emailckdi82fkoos[@]proton[.]meCommit author email for attacker commits[2]
SHA-2567598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86dMalicious litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl[2]
SHA-2566ea3d8ef6cf73a72d8df24f663b7a31ce67494eeb9b0d707b93484e66675d2dfDecoded stealer cook.py variant[2]
SHA-25620a59e5a285799e7cf7fa16cfba1c1414f1e84a8459e14207cfd4e998c2cd24bDecoded stealer sitecustomize variant[2]
Domainlitellm[.]devAttacker-controlled exfil (lookalike)[3]
URLhxxp://www[.]litellm[.]dev/healthcheckExfiltration endpoint[5]
OtherPRIVCOLLECT_ACTIVE=1Environment variable indicator[3]
SHA-25684dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7dcook.pth persistence hook - Windows[3]
SHA-2567f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdbcook.pth persistence hook - Linux/MacOS[3]
SHA-25603ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897eacook.py credential stealer payload (22,252 bytes) - Windows[3]
SHA-2569dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25cook.py credential stealer payload (22,252 bytes) - Linux/MacOS[3]
SHA-256b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465sitecustomize.py loader with hourly backdoor loop (25,509 bytes) - Windows[3]
SHA-25634bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fasitecustomize.py loader with hourly backdoor loop (25,509 bytes) - Linux/MacOS[3]
IP address168[.]100[.]8[.]57Attacker IP used to validate stolen AWS credentials[3]
IP address165[.]227[.]201[.]8Attacker IP used to validate stolen AWS credentials[3]
File pathcook/purelib/cook.pthStartup payload file in the source revision and wheel[5]
File pathlitellm-1.107.0.data/purelib/cook.pthStartup hook file in the wheels[5]
File pathlitellm-1.107.0.data/purelib/cook.pyCollector wrapper in the wheels[5]
File pathlitellm-1.107.0.data/purelib/sitecustomize.pyBackground loader in the wheels[5]
Otherletters9813/pythonclient-testGitHub repository hosting the loader pointer[5]
URLhxxps://github[.]com/Berri-AI/litellmLookalike source repository recorded in provenance[5]
Other20535d8fa11f786a6c24df7488a0e449ed5f09adRecorded source revision[5]
Other783c43bc455d2e05d3cfaf194c2f325cfeab1433Commit adding the Maturin data directory[5]
SHA-2560aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1aWheel litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl[5]
SHA-256668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6Wheel litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl[5]
SHA-256462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7Wheel litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl[5]
SHA-256c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054Wheel litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl[5]
SHA-2561bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00Wheel litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl[5]
SHA-25612d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484Wheel litellm-1.107.0-cp310-abi3-win_amd64.whl[5]
SHA-2561bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339eContainer image index[5]
SHA-256bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5Linux amd64 manifest[5]
SHA-25656b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdfLinux arm64 manifest[5]
GitHub accountBerri-AIlookalike source repository owner[6]
User agentMozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0)exfiltration user agent[6]
SHA-2562befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819litellm-1.107.0.tar.gz sdist hash[6]
OtherXWiA8gZNa7NcX2XRoAEbexfiltration token value (X-Token)[6]
OtherCommit 783c43bc455d2e05d3cfaf194c2f325cfeab1433Adds the Maturin data setting and payload[7]
OtherCommit 20535d8fa11f786a6c24df7488a0e449ed5f09adAdds COPY cook ./cook to Dockerfiles; recorded build revision[7]
Other9cf97e935aeaf791b9c8db224a2bb3ee156134ceUnauthorized workflow commit applied to release workflow[1]
File pathsite-packages/cook.pyStealer payload[2]
File pathsite-packages/sitecustomize.pyRemote-code loader[2]
Othera898fd08ad220fed52488906eb45d196f332b972ad5d85214e7726545ecc4218RSA-4096 public-key fingerprint, SHA-256 of DER SubjectPublicKeyInfo[2]

Sources

  1. Datadog Security Labs · LiteLLM supply chain compromise: What you need to know

Changelog

  1. First published.
  2. 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.