Version 1.107.0 of the litellm Python package carried a credential stealer and a remote-access trojan. It reached PyPI and the official LiteLLM Docker images on 21 October 2026. LiteLLM reports that the bad files were live for about two hours.
LiteLLM confirms the attack in its incident post. Aikido, Wiz and StepSecurity describe the release. PyPI quarantined it, and LiteLLM removed or repointed the Docker tags. LiteLLM tells users to pin 1.106.2. The malware starts with every Python process in the environment, even one that never imports LiteLLM.
What happened
A poisoned LiteLLM release went out on 21 October 2026. LiteLLM reports that the seven 1.107.0 wheels reached PyPI between 11:39:51 and 11:40:13 UTC. The Docker images followed at 11:46:56 UTC. Wiz counts about 49,000 downloads of the bad version, from ClickPy data.
LiteLLM published the full attack chain on 23 October 2026. In May 2026, the master key for an internal service, the LiteLLM Agent Platform, went into a public repository and stayed valid. On 10 October, the attacker used that key to get the GitHub token of an organization owner. CI workflows pushed with that token then sent out the tokens of two more maintainers. On 21 October, the three tokens let the attacker point the release at a lookalike repository, Berri-AI/litellm. The same tokens approved the release and deleted the run records.
LiteLLM's first post said that the tokens came from the logs of an internal deployment. Wiz and StepSecurity repeat that account, and the attack-chain post corrects it. The official v1.107.0 tag on GitHub does not hold the malicious files. StepSecurity notes that the bad artifacts carried valid provenance, because BerriAI's own pipeline built and signed them.
Aikido reports that a data = "cook" line under [tool.maturin] in pyproject.toml put a cook folder into each wheel. The wheels put three files at the root of site-packages: cook.pth, cook.py and sitecustomize.py. Aikido describes cook.pth as a 12-byte file that holds only import cook. Python reads .pth files at every start, so cook.py runs every time Python starts.
The file cook.py is the infostealer. Aikido reports that it decompresses and runs a 61,000-byte payload. Wiz reports that it writes a marker file, /tmp/.pc_once_72129118c59f5636c0bb, and that this write fails on Windows. Aikido reports that the marker stops the stealer from running more than once per machine. StepSecurity adds that on Windows the stealer still runs, and likely runs again at every Python start.
Aikido lists what the stealer collects:
- System details, saved browser passwords and history, SSH private keys and shell history.
- AWS, Azure and GCP credentials, from files and from the live instance metadata endpoints.
- Docker, Docker Swarm, Kubernetes, GitLab runner, Jenkins and HashiCorp Vault secrets.
- Config files for AI developer tools such as Claude Code, Cursor, GitHub Copilot and Codex CLI.
- Git, npm, PyPI and pip credentials, plus GPG and TLS private keys.
- Environment variables. On Linux, this includes every other running process.
- All rows of
LiteLLM_CredentialsTable, which holds the stored provider API keys, andLiteLLM_ProxyModelTable.
Wiz reports that the stealer encrypts the data with AES-256-CBC and wraps the key with RSA-4096. It sends the data by HTTP POST to http://www[.]litellm[.]dev/healthcheck, a lookalike domain that Wiz reports is attacker controlled.
The file sitecustomize.py is the remote-access trojan. Aikido reports that it starts a background daemon with a lock at /tmp/remote_code_loader.lock. Every hour, the daemon reads a C2 domain from domain.txt in the GitHub repository letters9813/pythonclient-test. It then runs a Python script from http://<domain>/codepy with the full privileges of the process.
Wiz reports that the trojan runs only on Linux and macOS, and that domain.txt never held more than test values. StepSecurity lists those values as localhost:18909 and test.test.com:18909. GitHub took the repository down on 21 October.
Wiz saw stolen AWS instance role credentials in use between 16:20 and 17:01 UTC that day. The calls included sts:GetCallerIdentity, bedrock:ListFoundationModels and, in a few environments, IAM listing calls.
An earlier LiteLLM compromise, in March 2026, has its own page. None of the sources names a group behind 1.107.0.
Affected versions
Only litellm 1.107.0 is malicious, on PyPI and in the official Docker images.
litellm1.107.0 on PyPI: all seven compiled wheels carry the payload, for macOS, Linux and Windows. StepSecurity reports that the source tarball was altered but holds no payload.- LiteLLM gives the PyPI window as 11:39:51 to 13:23 UTC on 21 October 2026. Wiz dates the quarantine to 13:20 UTC. Aikido gives the install window as 11:39 to 12:47 UTC.
ghcr.io/berriai/litellm,litellm-databaseandlitellm-non_rootat the tagsv1.107.0,latest,main-latestandmain-stable. LiteLLM reports that these tags were unavailable by 13:51 UTC.docker.io/litellm/litellmat the same four tags, from 11:46:56 to 14:20 UTC. By 14:20 UTC, LiteLLM removedv1.107.0and pointed the other tags at clean images. Aikido names the Docker Hub tag as1.107.0.- Not affected, per StepSecurity: 1.107.0rc1, 1.107.0rc2 and 1.103.x through 1.106.x.
Indicators of compromise
Aikido, Wiz, StepSecurity and LiteLLM publish these indicators. Network addresses are defanged.
- Files at the root of site-packages:
cook.pth,cook.py,sitecustomize.py, plus matching__pycache__/cook*.pycandsitecustomize*.pyc - Stealer marker:
/tmp/.pc_once_72129118c59f5636c0bb. Aikido states that any/tmp/.pc_once_*file means the stealer ran. LiteLLM states that a missing marker proves nothing. - Temporary folders
.pc_sq_*and.pc_e_*, per StepSecurity - Environment variable:
PRIVCOLLECT_ACTIVE=1 - Trojan lock:
/tmp/remote_code_loader.lock. Aikido states that this file means the C2 daemon ran or still runs. - Exfiltration endpoint:
http://www[.]litellm[.]dev/healthcheck - C2 domain lookup:
https://raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain[.]txt - Second stage:
http://<domain>/codepy - HTTP header:
X-Token: XWiA8gZNa7NcX2XRoAEb - User-Agent:
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0) - Source of the AWS checks that Wiz saw:
168.100.8[.]57and165.227.201[.]8 - Lookalike source repository:
github[.]com/Berri-AI/litellm, commit authorckdi82fkoos@proton[.]me
SHA256 hashes of the three injected files, from Wiz and StepSecurity:
| File | Platform | SHA256 |
|---|---|---|
cook.pth | Linux, macOS | 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb |
cook.pth | Windows | 84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d |
cook.py | Linux, macOS | 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25 |
cook.py | Windows | 03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea |
sitecustomize.py | Linux, macOS | 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa |
sitecustomize.py | Windows | b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465 |
SHA256 hashes of the seven 1.107.0 wheels, from Aikido, Wiz and StepSecurity:
| Wheel | SHA256 |
|---|---|
litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl | 7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d |
litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl | 0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a |
litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl | 462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7 |
litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl | 668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl | 1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl | c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054 |
litellm-1.107.0-cp310-abi3-win_amd64.whl | 12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484 |
Docker image digests. LiteLLM and StepSecurity give the multi-platform index. Wiz gives the per-platform digests, and reports that docker.io/litellm/litellm carries the same digests as ghcr.io/berriai/litellm:v1.107.0.
| Image | Platform | Digest |
|---|---|---|
ghcr.io/berriai/litellm:v1.107.0 | index | sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e |
ghcr.io/berriai/litellm:v1.107.0 | linux/amd64 | sha256:bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5 |
ghcr.io/berriai/litellm:v1.107.0 | linux/arm64 | sha256:56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf |
ghcr.io/berriai/litellm-database:v1.107.0 | linux/amd64 | sha256:24581ddc30767f16ecb69d2b0517556f2cd7dea56971adb42a3d1beb97d69523 |
ghcr.io/berriai/litellm-database:v1.107.0 | linux/arm64 | sha256:827dd893c74cf621a860a66a3a447a5c732533f087589ce905573e285c0a239c |
ghcr.io/berriai/litellm-non_root:v1.107.0 | linux/amd64 | sha256:645a67d810628252d63df85dcc8824c58bcc77cf8383f4d3e6321f0ccc3ce9e4 |
ghcr.io/berriai/litellm-non_root:v1.107.0 | linux/arm64 | sha256:f75c8edae59b72b1ebfd617295833f465c85bb2fab9c1d60f21b496db86cb8da |
How to check
Use commands that only list files. Python runs cook.py at every start, and pip is a Python program. Do not run pip show in an environment that can hold 1.107.0.
On Linux or macOS, check for the stealer marker and the trojan lock. The second command shows any process that still holds the lock:
ls -la /tmp/.pc_once_* /tmp/remote_code_loader.lock lsof /tmp/remote_code_loader.lock
A missing marker file does not prove that the machine is clean. Search every Python environment for the bad version and the injected files:
find / -type d -name "litellm-1.107.0.dist-info" 2>/dev/null find / -path "*site-packages*" \( -name cook.pth -o -name cook.py \) 2>/dev/null
On Windows, run this in PowerShell:
Get-ChildItem -Path C:\ -Recurse -Force -Include cook.pth,cook.py,litellm-1.107.0.dist-info -ErrorAction SilentlyContinue
If you keep wheels in a cache, mirror or artifact store, hash them and compare the result with the wheel table:
find / -name "litellm-1.107.0-*.whl" -exec shasum -a 256 {} \; 2>/dev/nullFor Docker, list the LiteLLM images on the host with their digests:
docker images --digests | grep -i litellm
Any image tagged v1.107.0 or 1.107.0 is affected. Compare every other LiteLLM digest with the Docker table. A local listing can show the index digest or a per-platform digest.
Search proxy, DNS and firewall logs for litellm[.]dev, letters9813/pythonclient-test and any path that ends in /codepy. StepSecurity states that a /codepy request means attacker code ran on that host. In AWS CloudTrail, look for GetCallerIdentity calls from instance or task roles near the install time, and for calls from the two Wiz addresses.
What to do now
- If any check finds 1.107.0, the
cookfiles or the/tmpmarkers, treat the machine as compromised. If 1.107.0 ran on a host with internet access, LiteLLM tells users to assume that data left the host. - Do not run
pipin an affected environment. LiteLLM states that each Python start runs the malicious files again. - Stop the Python daemon that holds
/tmp/remote_code_loader.lock, the LiteLLM processes and any containers that run the affected images. - Delete
cook.pth,cook.py, the injectedsitecustomize.pyand the matching__pycache__files from every affected site-packages folder. Some environments hold their ownsitecustomize.py, so read that file before you delete it. - Rebuild virtual environments and images from scratch with
litellm==1.106.2, the version that LiteLLM names. Pin Docker images by digest, and purge cachedv1.107.0images from hosts and mirrors. - Rotate every secret that the host had access to. On a LiteLLM proxy host, start with the provider keys, the master key, the salt key, the virtual keys and the
DATABASE_URLpassword. - Then rotate cloud keys, instance roles, SSH and GPG keys, and Kubernetes and Vault tokens. Also rotate CI/CD secrets, Git and registry tokens, AI tool tokens and saved browser passwords.
- Block
litellm[.]dev, and alert on requests toraw[.]githubusercontent[.]com/letters9813/.
What Vigilance would show (rebuilt from public reports)
Vigilance compares the version you trust with the new one and names the file that gained a new power. The block below is rebuilt from the public reports in the words Vigilance prints. It is not a captured scan, because the malicious release is not redistributed.
files scanned: 67 HEADS UP 1 file can now do things the old version could not. The rest changed and gained nothing. CHANGED cook.py It now downloads from the internet, reads saved passwords and access keys and runs other programs. It did not before.
Frequently asked questions
Is LiteLLM safe to use now?
Version 1.107.0 is malicious. PyPI quarantined it on 21 October 2026, and LiteLLM cleaned the bad Docker tags the same day. LiteLLM tells users to pin 1.106.2. Pin Docker images by digest.
What does the LiteLLM 1.107.0 malware steal?
Aikido reports that it takes browser passwords, SSH keys, cloud credentials, Docker and Kubernetes secrets, CI/CD secrets, AI tool configs and environment variables. It also reads the API keys stored in the LiteLLM database.
Was the LiteLLM Docker image compromised?
Yes. The official images on GHCR and Docker Hub carried the bad build at v1.107.0, latest, main-latest and main-stable. LiteLLM reports that the tags pointed at it from 11:46:56 UTC to 14:20 UTC at the latest on 21 October 2026.
How did attackers publish LiteLLM 1.107.0?
LiteLLM reports that the attacker used the GitHub tokens of three maintainers to build the release from a lookalike repository, Berri-AI/litellm.
Is uninstalling LiteLLM 1.107.0 enough?
No. Aikido reports that the stealer sends data out on its first run. The trojan also lets the attacker run more code. Rotate every exposed secret, and rebuild the environment or container from scratch.
Sources
- docs.litellm.ai/blog/security-incident-oct-21-2026
- docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain
- aikido.dev/blog/litellm-compromised-infostealer-rat
- wiz.io/blog/litellm-compromised-steals-credentials
- stepsecurity.io/blog/litellm-1-107-0-compromised-pypi-backdoored-wheels-credential-stealer