On this page
The LiteLLM 1.107.0 release on PyPI carries a credential stealer and a
remote code execution backdoor. All seven wheels (Linux, macOS, Windows) install them as three files in
site-packages. Both run when any Python process starts on the host, even one that never imports litellm. The
upstream GitHub source does not contain them.
Versions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean.
What the attack does #
Installing litellm==1.107.0 places three files in site-packages. Python loads two of them each time it starts.
cook.pthimportscook.py, a credential stealer. It collects SSH keys, cloud credentials, CI/CD secrets, AI tool tokens, browser passwords, and the LLM provider keys in a LiteLLM proxy database. It encrypts the data and sends it towww.litellm[.]dev.sitecustomize.pystarts a background daemon. Every hour, the daemon reads a server address from a GitHub file, runs the stealer again, and downloads and runs code from that server.
How the implant got into the release #
The pyproject.toml in the 1.107.0 source distribution (sdist) has one extra line in the maturin build section:
[tool.maturin]
...
python-source = "."
data = "cook" # <-- injected
python-packages = ["litellm_cli"] # legitimate (present upstream)
The maturin data = "cook" setting copies a cook/ folder into litellm-1.107.0.data/purelib/ in the wheel. pip
installs that folder into site-packages.
The sdist leaves out the cook/ folder, so this line is the only visible change. The upstream
pyproject.toml at tag v1.107.0 has no
data = "cook" line and no cook/ folder. Someone changed the build outside the public repository. SafeDep infers a
compromised release pipeline or stolen PyPI credentials.
The wheel RECORD lists the three added files:
litellm-1.107.0.data/purelib/cook.pth 12 bytes "import cook"
litellm-1.107.0.data/purelib/cook.py 22252 bytes exec(zlib.decompress(b64decode(...)))
litellm-1.107.0.data/purelib/sitecustomize.py 25509 bytes /backdoor + embedded stealer
Payload 1: the credential stealer in cook.py #
At startup, Python runs each line that starts with import in a .pth file in site-packages. cook.pth contains
import cook, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and
CI jobs.
cook.py wraps about 61 KB of Python in one layer of exec(zlib.decompress(base64.b64decode('...'))). The decoded
code starts with this configuration:
PC_URL = 'http://www.litellm.dev/healthcheck'
PC_TOKEN = 'XWiA8gZNa7NcX2XRoAEb' # sent as X-Token header
PC_PUBKEY = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvxRBQMVXDXVskKS47haU ...' # RSA-4096
PC_ONCE_PATH = '/tmp/.pc_once_72129118c59f5636c0bb'
PC_ASYNC = True # runs in a daemon thread
PC_MODULES = ['sysinfo','netinfo','browser_pw','browser_hist','ssh_keys',
'shell_hist','cloud_creds','dev_secrets','container_creds',
'cicd_secrets','ai_devtools','env_vars','db_litellm',
'cloud_metadata','gpg_keys','tls_privkey']
What it collects
| Module | Targets |
|---|---|
sysinfo ,netinfo |
Host name, user, operating system, IP addresses, network settings |
browser_pw |
Chromium saved passwords, decrypted with the profile master key (DPAPI on Windows). Firefox logins.json andkey4.db |
browser_hist |
Browser history |
ssh_keys |
~/.ssh/* |
shell_hist |
bash andzsh history, PowerShell PSReadLine,cmd history |
cloud_creds |
~/.aws/credentials andconfig , Azure profile and tokens,gcloud credentials,~/.kube/config ,~/.docker/config.json ,gh`` hosts.yml ,rclone.conf ,~/.netrc |
dev_secrets |
.pem ,.key ,.pfx ,.p12 ,.pk8 ,.ppk files under$HOME , Composerauth.json , FileZilla saved servers |
container_creds |
Kubernetes service account token |
cicd_secrets |
GitLab runner config.toml , JenkinsinitialAdminPassword andmaster.key ,.vault-token , Terraformcredentials.tfrc.json , Packer settings |
ai_devtools |
Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code mcp.json , Gemini CLI, Codexauth.json , Continue, Aider, Goose,opencode ,Qwen , Crush,Kiro , Zed, Hugging Face token |
env_vars |
Its own environment and the environment of other processes through /proc/<pid>/environ |
db_litellm |
Reads DATABASE_URL and dumps theLiteLLM_CredentialsTable andLiteLLM_ProxyModelTable tables, which hold the LLM provider API keys of a LiteLLM proxy |
cloud_metadata |
AWS instance metadata service (IMDSv2, then v1): instance identity and role credentials |
gpg_keys ,tls_privkey |
GPG keys, TLS private keys |
How it sends the data
- It encrypts the JSON results with a random AES-256-CBC key, and encrypts that key with RSA-OAEP and the embedded public key.
- It packs the result into a tar.gz (
hdr.json,data.bin,key.bin) and sends it in a POST request towww.litellm[.]dev/healthcheckwith the headerX-Token: XWiA8gZNa7NcX2XRoAEb. - After a successful upload, it writes
/tmp/.pc_once_72129118c59f5636c0bbso it collects only once per host. It also setsPRIVCOLLECT_ACTIVE=1to avoid a second run in the same process tree.
Payload 2: the backdoor in sitecustomize.py #
Python imports sitecustomize at startup, so this file also runs in every Python process.
GITHUB_RAW_URL = "https://raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt"
CODE_PATH = "/codepy"
LOOP_INTERVAL = 3600
LOCK_FILE = "/tmp/remote_code_.lock"
- It takes an exclusive lock on
/tmp/remote_code_.lock, so only one daemon runs per host. - It calls
fork()andsetsid(), sends standard input and output to/dev/null, and changes directory to/to detach from the parent process. - Every hour, it:
- reads
domain.txtfrom the attacker’s GitHub repository and takes the first line that looks like a domain, - runs its embedded copy of the stealer, which has no once-per-host marker,
- downloads
http://<domain>/codepyand runs it withexec(). - reads
The attacker can point the daemon at a new server by editing domain.txt. Each infected host runs whatever that server
returns as codepy.
Indicators of compromise #
Network
| Indicator | Purpose |
|---|---|
www.litellm[.]dev ,hxxp://www.litellm[.]dev/healthcheck |
Receives stolen data |
HTTP header X-Token: XWiA8gZNa7NcX2XRoAEb |
Sent with each upload |
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt |
Backdoor reads its server address here |
hxxp://<domain>/codepy |
Second-stage code download |
Requests to 169.254.169.254 from Python processes |
Theft of AWS instance credentials |
Host
| Indicator | Meaning |
|---|---|
cook.pth ,cook.py ,sitecustomize.py insite-packages |
Implant installed |
/tmp/.pc_once_72129118c59f5636c0bb |
Stealer already uploaded data |
/tmp/remote_code_.lock |
Backdoor daemon has run |
Environment variable PRIVCOLLECT_ACTIVE=1 |
Present in infected processes |
Detached Python daemon (parent PID 1, own session, working directory / ) |
Backdoor loop |
File hashes (SHA-256)
| File | SHA-256 |
|---|---|
litellm-1.107.0.tar.gz |
2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819 |
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-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-win_amd64.whl |
12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484 |
The planted files, from the wheel RECORD:
| File | SHA-256 (URL-safe base64) |
|---|---|
cook.pth |
fxkWMucyBwXRVLhSBPGuVGtUOsvX6IYSKbdvUEK5K9s |
cook.py |
ndCjkm-Wo364bPHuIxDZvpMTnMTr7z754u_pUxOQviU |
sitecustomize.py |
NL7wT-iKydCq4EdWMLFzhoHRW5TyM6yKpG6s9ZaDBfo |
- pypi
- python
- malware
- supply-chain
- litellm
Author
Kunal Singh
safedep.io
Share
The Latest from SafeDep blogs #
Follow for the latest updates and insights on open source security & engineering
Forged Commits Push a preinstall Dropper into hugo-paper and mevn-cli #
An attacker pushed forged commits to the default branches of hugo-paper and mevn-cli. Running npm install or bun install in either repository downloads and runs a second-stage script from...
ASOS App Hack: Attackers Used Push Notifications to Send a Ransom Note #
Attackers got into the platforms ASOS uses to message customers and pushed an extortion note to phones. Here is what is confirmed, what is only claimed, and what customers should do.
MALFEX Moves Its npm into a Native Windows DLL #
The MALFEX package imgdecode contains a native Windows . When loaded from a console, it attempts to download, decrypt, and inject shellcode into dllhost.exe.
tensorlake 0.5.144 npm Compromise Ships Mini Shai-Hulud #
[email protected] on npm ran a Mini Shai-Hulud worm at install. A maintainer account committed the payload through the GitHub web interface, and the official release workflow published it.
Ship Code. #
Not Malware. #
Start free with open source tools on your machine. Scale to a unified platform for your organization.