{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-21-compromised-litellm-1-107-0-on-pypi-installs-a-credential/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "• Oct 21, 2026 • 6 min read\n\nThe [LiteLLM 1.107.0](https://pypi.org/project/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. PyPI has since removed the release.\n\nVersions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean. The companion packages that 1.107.0 pins, `litellm-proxy-extras==0.4.110` and `litellm-enterprise==0.1.78`, are also 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 tokens, 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, runs 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\npyproject.toml (litellm-1.107.0.tar.gz)\n\nTOML 6 lines\n\nThe `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`.\n\nThe sdist leaves out the `cook/` folder, so this line is the only visible change. No public commit in [BerriAI/litellm](https://github.com/BerriAI/litellm) contains `data = \"cook\"` or a `cook/` folder.\n\nThe wheel `RECORD` lists the three added files:\n\nlitellm-1.107.0.dist-info/RECORD\n\nTEXT 4 lines\n\n### The release does not match its tag\n\nThe tag `v1.107.0` points to commit [`66c8f5b`](https://github.com/BerriAI/litellm/commit/66c8f5b1a9cefaa7c55b758f86f4ab552f2e859e) (06:59 UTC). Its `pyproject.toml` says `version = \"1.108.0\"`. SafeDep compared every sdist file with the git trees by blob hash. The sdist matches a later `main` commit, [`a8113b0`](https://github.com/BerriAI/litellm/commit/a8113b0dd721804c752a6f1952367fb642205224) (08:50 UTC), plus five edited files and the `cook/` folder.\n\nFour of the edited files are `Cargo.toml` files that gain a `readme = \"README.md\"` line. The legitimate 1.106.2 and rc2 releases have the same edit. The fifth is `pyproject.toml`, compared here with `a8113b0`:\n\npyproject.toml (a8113b0 → litellm-1.107.0.tar.gz)\n\nDIFF 11 lines\n\nThe edit sets the version back to 1.107.0 and pins the companion packages to the rc2 values. As a result, a dependency diff against rc2 shows nothing unusual. The legitimate 1.106.2 sdist matches its tag commit [`49943cd`](https://github.com/BerriAI/litellm/commit/49943cd52ea46aacf8db2e5cb2b890d580fe9bb4) except for the `Cargo.toml` `readme` lines.\n\n### How the wheels were built\n\nThe malicious wheels match the output of the normal BerriAI release pipeline:\n\n- The `WHEEL` file names `maturin` 1.9.4, the same version as in the legitimate releases. The implant files have correct `RECORD` entries, which shows no edits after the build.\n- Zip timestamps show separate native builds: macOS at 11:20, Linux at 11:21, and Windows at 11:37 UTC.\n- In the Windows wheel, all 2,804 `.py` files have CRLF line endings, including the three implant files. The Linux wheel has none. A git checkout on Windows with line-ending conversion produces this pattern.\n\nNo public GitHub Actions run built or published the release. SafeDep checked all 539 runs between 09:30 and 12:30 UTC. At 11:50 UTC, about 10 minutes after the PyPI upload, `litellm-dispatcher[bot]` published the GitHub release `v1.107.0`. The same delay occurred for 1.106.2. PyPI has no provenance attestations for 1.107.0, 1.106.2, or rc2.\n\nSafeDep assesses, with moderate to high confidence, that BerriAI’s private release pipeline built these wheels from an attacker-modified git source. The attacker could have used a compromised maintainer or bot credential, a private branch or mirror, or the reference the pipeline builds from. A stolen PyPI token used from the attacker’s own machines fits the evidence less well. Only BerriAI’s pipeline logs can confirm the cause.\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 `import cook`, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and CI jobs.\n\n`cook.py` wraps about 61 KB of Python in one layer of `exec(zlib.decompress(base64.b64decode('...')))`. The decoded code starts with this configuration:\n\ncook.py (decoded)\n\nPYTHON 10 lines\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 also 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\nsitecustomize.py\n\nPYTHON 5 lines\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 detach 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()`.\n\nThe attacker can point the daemon at a new server by editing `domain.txt`. Each infected host runs whatever that server returns as `codepy`.\n\nAt 13:40 UTC, the GitHub account `letters9813` and its repository returned 404, about two hours after the release. The backdoor now finds no server address and does nothing. Hosts that ran the daemon before the deletion could have received and run second-stage code. SafeDep could not recover the server address.\n\n## Timeline\n\nAll times are UTC on 2026-10-21.\n\n| Time           | Event                                                           |\n| -------------- | --------------------------------------------------------------- |\n| 06:59          | Tag `v1.107.0` commit `66c8f5b` created                         |\n| 08:50          | Commit `a8113b0` on `main`, the base of the release source      |\n| 10:44          | Legitimate 1.106.2 uploaded to PyPI                             |\n| 11:20 to 11:37 | Malicious 1.107.0 wheels built                                  |\n| 11:39 to 11:40 | 1.107.0 uploaded to PyPI (all 8 files)                          |\n| 11:50          | `litellm-dispatcher[bot]` publishes GitHub release `v1.107.0`   |\n| 13:40          | Observed: 1.107.0 gone from PyPI, `letters9813` account deleted |\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 (account deleted) |\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 have different hashes on Windows because of the CRLF line endings:\n\n| File               | Linux and macOS                                                    | Windows                                                            |\n| ------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |\n| `cook.pth`         | `7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb` | `84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d` |\n| `cook.py`          | `9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25` | `03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea` |\n| `sitecustomize.py` | `34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa` | `b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465` |\n\n- pypi\n- python\n- malware\n- supply-chain\n- litellm\n\n### Author\n\n![Kunal Singh](https://safedep.io/images/authors/kunal-singh.jpg)\n\n## The Latest from SafeDep blogs\n\nFollow for the latest updates and insights on open source security & engineering\n\n![Background](https://safedep.io/illustrations/bottom.png)\n\n![SafeDep Logo](https://safedep.io/_astro/logo.BdYG7kdO_Z2m03dG.svg)\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.\n",
  "authors": [
    "abhisek"
  ],
  "captured_at": "2026-10-21T19:14:42.600331207+00:00",
  "category": "news",
  "content_html": "<p>• Oct 21, 2026 • 6 min read</p>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://pypi.org/project/litellm/1.107.0/\">LiteLLM 1.107.0</a> 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 <code>site-packages</code>. Both run when any Python process starts on the host, even one that never imports <code>litellm</code>. The upstream GitHub source does not contain them. PyPI has since removed the release.</p>\n<p>Versions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean. The companion packages that 1.107.0 pins, <code>litellm-proxy-extras==0.4.110</code> and <code>litellm-enterprise==0.1.78</code>, are also clean.</p>\n<h2>What the attack does</h2>\n<p>Installing <code>litellm==1.107.0</code> places three files in <code>site-packages</code>. Python loads two of them each time it starts.</p>\n<ol>\n<li><code>cook.pth</code> imports <code>cook.py</code>, 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 to <code>www.litellm[.]dev</code>.</li>\n<li><code>sitecustomize.py</code> starts 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.</li>\n</ol>\n<h2>How the implant got into the release</h2>\n<p>The <code>pyproject.toml</code> in the 1.107.0 source distribution (sdist) has one extra line in the <code>maturin</code> build section:</p>\n<p>pyproject.toml (litellm-1.107.0.tar.gz)</p>\n<p>TOML 6 lines</p>\n<p>The <code>maturin</code> <code>data = &quot;cook&quot;</code> setting copies a <code>cook/</code> folder into <code>litellm-1.107.0.data/purelib/</code> in the wheel. pip installs that folder into <code>site-packages</code>.</p>\n<p>The sdist leaves out the <code>cook/</code> folder, so this line is the only visible change. No public commit in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/BerriAI/litellm\">BerriAI/litellm</a> contains <code>data = &quot;cook&quot;</code> or a <code>cook/</code> folder.</p>\n<p>The wheel <code>RECORD</code> lists the three added files:</p>\n<p>litellm-1.107.0.dist-info/RECORD</p>\n<p>TEXT 4 lines</p>\n<h3>The release does not match its tag</h3>\n<p>The tag <code>v1.107.0</code> points to commit <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/BerriAI/litellm/commit/66c8f5b1a9cefaa7c55b758f86f4ab552f2e859e\"><code>66c8f5b</code></a> (06:59 UTC). Its <code>pyproject.toml</code> says <code>version = &quot;1.108.0&quot;</code>. SafeDep compared every sdist file with the git trees by blob hash. The sdist matches a later <code>main</code> commit, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/BerriAI/litellm/commit/a8113b0dd721804c752a6f1952367fb642205224\"><code>a8113b0</code></a> (08:50 UTC), plus five edited files and the <code>cook/</code> folder.</p>\n<p>Four of the edited files are <code>Cargo.toml</code> files that gain a <code>readme = &quot;README.md&quot;</code> line. The legitimate 1.106.2 and rc2 releases have the same edit. The fifth is <code>pyproject.toml</code>, compared here with <code>a8113b0</code>:</p>\n<p>pyproject.toml (a8113b0 → litellm-1.107.0.tar.gz)</p>\n<p>DIFF 11 lines</p>\n<p>The edit sets the version back to 1.107.0 and pins the companion packages to the rc2 values. As a result, a dependency diff against rc2 shows nothing unusual. The legitimate 1.106.2 sdist matches its tag commit <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/BerriAI/litellm/commit/49943cd52ea46aacf8db2e5cb2b890d580fe9bb4\"><code>49943cd</code></a> except for the <code>Cargo.toml</code> <code>readme</code> lines.</p>\n<h3>How the wheels were built</h3>\n<p>The malicious wheels match the output of the normal BerriAI release pipeline:</p>\n<ul>\n<li>The <code>WHEEL</code> file names <code>maturin</code> 1.9.4, the same version as in the legitimate releases. The implant files have correct <code>RECORD</code> entries, which shows no edits after the build.</li>\n<li>Zip timestamps show separate native builds: macOS at 11:20, Linux at 11:21, and Windows at 11:37 UTC.</li>\n<li>In the Windows wheel, all 2,804 <code>.py</code> files have CRLF line endings, including the three implant files. The Linux wheel has none. A git checkout on Windows with line-ending conversion produces this pattern.</li>\n</ul>\n<p>No public GitHub Actions run built or published the release. SafeDep checked all 539 runs between 09:30 and 12:30 UTC. At 11:50 UTC, about 10 minutes after the PyPI upload, <code>litellm-dispatcher[bot]</code> published the GitHub release <code>v1.107.0</code>. The same delay occurred for 1.106.2. PyPI has no provenance attestations for 1.107.0, 1.106.2, or rc2.</p>\n<p>SafeDep assesses, with moderate to high confidence, that BerriAI’s private release pipeline built these wheels from an attacker-modified git source. The attacker could have used a compromised maintainer or bot credential, a private branch or mirror, or the reference the pipeline builds from. A stolen PyPI token used from the attacker’s own machines fits the evidence less well. Only BerriAI’s pipeline logs can confirm the cause.</p>\n<h2>Payload 1: the credential stealer in cook.py</h2>\n<p>At startup, Python runs each line that starts with <code>import</code> in a <code>.pth</code> file in <code>site-packages</code>. <code>cook.pth</code> contains <code>import cook</code>, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and CI jobs.</p>\n<p><code>cook.py</code> wraps about 61 KB of Python in one layer of <code>exec(zlib.decompress(base64.b64decode('...')))</code>. The decoded code starts with this configuration:</p>\n<p>cook.py (decoded)</p>\n<p>PYTHON 10 lines</p>\n<h3>What it collects</h3>\n<table>\n<thead>\n<tr>\n<th>Module</th>\n<th>Targets</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>sysinfo</code>, <code>netinfo</code></td>\n<td>Host name, user, operating system, IP addresses, network settings</td>\n</tr>\n<tr>\n<td><code>browser_pw</code></td>\n<td>Chromium saved passwords, decrypted with the profile master key (DPAPI on Windows). Firefox <code>logins.json</code> and <code>key4.db</code></td>\n</tr>\n<tr>\n<td><code>browser_hist</code></td>\n<td>Browser history</td>\n</tr>\n<tr>\n<td><code>ssh_keys</code></td>\n<td><code>~/.ssh/*</code></td>\n</tr>\n<tr>\n<td><code>shell_hist</code></td>\n<td><code>bash</code> and <code>zsh</code> history, PowerShell PSReadLine, <code>cmd</code> history</td>\n</tr>\n<tr>\n<td><code>cloud_creds</code></td>\n<td><code>~/.aws/credentials</code> and <code>config</code>, Azure profile and tokens, <code>gcloud</code> credentials, <code>~/.kube/config</code>, <code>~/.docker/config.json</code>, <code>gh</code> <code>hosts.yml</code>, <code>rclone.conf</code>, <code>~/.netrc</code></td>\n</tr>\n<tr>\n<td><code>dev_secrets</code></td>\n<td><code>.pem</code>, <code>.key</code>, <code>.pfx</code>, <code>.p12</code>, <code>.pk8</code>, <code>.ppk</code> files under <code>$HOME</code>, Composer <code>auth.json</code>, FileZilla saved servers</td>\n</tr>\n<tr>\n<td><code>container_creds</code></td>\n<td>Kubernetes service account token</td>\n</tr>\n<tr>\n<td><code>cicd_secrets</code></td>\n<td>GitLab runner <code>config.toml</code>, Jenkins <code>initialAdminPassword</code> and <code>master.key</code>, <code>.vault-token</code>, Terraform <code>credentials.tfrc.json</code>, Packer settings</td>\n</tr>\n<tr>\n<td><code>ai_devtools</code></td>\n<td>Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code <code>mcp.json</code>, Gemini CLI, Codex <code>auth.json</code>, Continue, Aider, Goose, <code>opencode</code>, <code>Qwen</code>, Crush, <code>Kiro</code>, Zed, Hugging Face token</td>\n</tr>\n<tr>\n<td><code>env_vars</code></td>\n<td>Its own environment and the environment of other processes through <code>/proc/&lt;pid&gt;/environ</code></td>\n</tr>\n<tr>\n<td><code>db_litellm</code></td>\n<td>Reads <code>DATABASE_URL</code> and dumps the <code>LiteLLM_CredentialsTable</code> and <code>LiteLLM_ProxyModelTable</code> tables, which hold the LLM provider API keys of a LiteLLM proxy</td>\n</tr>\n<tr>\n<td><code>cloud_metadata</code></td>\n<td>AWS instance metadata service (IMDSv2, then v1): instance identity and role credentials</td>\n</tr>\n<tr>\n<td><code>gpg_keys</code>, <code>tls_privkey</code></td>\n<td>GPG keys, TLS private keys</td>\n</tr>\n</tbody>\n</table>\n<h3>How it sends the data</h3>\n<ol>\n<li>It encrypts the JSON results with a random AES-256-CBC key, and encrypts that key with RSA-OAEP and the embedded public key.</li>\n<li>It packs the result into a tar.gz (<code>hdr.json</code>, <code>data.bin</code>, <code>key.bin</code>) and sends it in a POST request to <code>www.litellm[.]dev/healthcheck</code> with the header <code>X-Token: XWiA8gZNa7NcX2XRoAEb</code>.</li>\n<li>After a successful upload, it writes <code>/tmp/.pc_once_72129118c59f5636c0bb</code> so it collects only once per host. It also sets <code>PRIVCOLLECT_ACTIVE=1</code> to avoid a second run in the same process tree.</li>\n</ol>\n<h2>Payload 2: the backdoor in sitecustomize.py</h2>\n<p>Python imports <code>sitecustomize</code> at startup, so this file also runs in every Python process.</p>\n<p>sitecustomize.py</p>\n<p>PYTHON 5 lines</p>\n<ol>\n<li>It takes an exclusive lock on <code>/tmp/remote_code_loader.lock</code>, so only one daemon runs per host.</li>\n<li>It calls <code>fork()</code> and <code>setsid()</code>, sends standard input and output to <code>/dev/null</code>, and changes directory to <code>/</code> to detach from the parent process.</li>\n<li>Every hour, it:\n<ol>\n<li>reads <code>domain.txt</code> from the attacker’s GitHub repository and takes the first line that looks like a domain,</li>\n<li>runs its embedded copy of the stealer, which has no once-per-host marker,</li>\n<li>downloads <code>http://&lt;domain&gt;/codepy</code> and runs it with <code>exec()</code>.</li>\n</ol>\n</li>\n</ol>\n<p>The attacker can point the daemon at a new server by editing <code>domain.txt</code>. Each infected host runs whatever that server returns as <code>codepy</code>.</p>\n<p>At 13:40 UTC, the GitHub account <code>letters9813</code> and its repository returned 404, about two hours after the release. The backdoor now finds no server address and does nothing. Hosts that ran the daemon before the deletion could have received and run second-stage code. SafeDep could not recover the server address.</p>\n<h2>Timeline</h2>\n<p>All times are UTC on 2026-10-21.</p>\n<table>\n<thead>\n<tr>\n<th>Time</th>\n<th>Event</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>06:59</td>\n<td>Tag <code>v1.107.0</code> commit <code>66c8f5b</code> created</td>\n</tr>\n<tr>\n<td>08:50</td>\n<td>Commit <code>a8113b0</code> on <code>main</code>, the base of the release source</td>\n</tr>\n<tr>\n<td>10:44</td>\n<td>Legitimate 1.106.2 uploaded to PyPI</td>\n</tr>\n<tr>\n<td>11:20 to 11:37</td>\n<td>Malicious 1.107.0 wheels built</td>\n</tr>\n<tr>\n<td>11:39 to 11:40</td>\n<td>1.107.0 uploaded to PyPI (all 8 files)</td>\n</tr>\n<tr>\n<td>11:50</td>\n<td><code>litellm-dispatcher[bot]</code> publishes GitHub release <code>v1.107.0</code></td>\n</tr>\n<tr>\n<td>13:40</td>\n<td>Observed: 1.107.0 gone from PyPI, <code>letters9813</code> account deleted</td>\n</tr>\n</tbody>\n</table>\n<h2>Indicators of compromise</h2>\n<h3>Network</h3>\n<table>\n<thead>\n<tr>\n<th>Indicator</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>www.litellm[.]dev</code>, <code>hxxp://www.litellm[.]dev/healthcheck</code></td>\n<td>Receives stolen data</td>\n</tr>\n<tr>\n<td>HTTP header <code>X-Token: XWiA8gZNa7NcX2XRoAEb</code></td>\n<td>Sent with each upload</td>\n</tr>\n<tr>\n<td><code>raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt</code></td>\n<td>Backdoor reads its server address here (account deleted)</td>\n</tr>\n<tr>\n<td><code>hxxp://&lt;domain&gt;/codepy</code></td>\n<td>Second-stage code download</td>\n</tr>\n<tr>\n<td>Requests to <code>169.254.169.254</code> from Python processes</td>\n<td>Theft of AWS instance credentials</td>\n</tr>\n</tbody>\n</table>\n<h3>Host</h3>\n<table>\n<thead>\n<tr>\n<th>Indicator</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>cook.pth</code>, <code>cook.py</code>, <code>sitecustomize.py</code> in <code>site-packages</code></td>\n<td>Implant installed</td>\n</tr>\n<tr>\n<td><code>/tmp/.pc_once_72129118c59f5636c0bb</code></td>\n<td>Stealer already uploaded data</td>\n</tr>\n<tr>\n<td><code>/tmp/remote_code_loader.lock</code></td>\n<td>Backdoor daemon has run</td>\n</tr>\n<tr>\n<td>Environment variable <code>PRIVCOLLECT_ACTIVE=1</code></td>\n<td>Present in infected processes</td>\n</tr>\n<tr>\n<td>Detached Python daemon (parent PID 1, own session, working directory <code>/</code>)</td>\n<td>Backdoor loop</td>\n</tr>\n</tbody>\n</table>\n<h3>File hashes (SHA-256)</h3>\n<table>\n<thead>\n<tr>\n<th>File</th>\n<th>SHA-256</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>litellm-1.107.0.tar.gz</code></td>\n<td><code>2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl</code></td>\n<td><code>462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl</code></td>\n<td><code>668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl</code></td>\n<td><code>1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl</code></td>\n<td><code>c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl</code></td>\n<td><code>7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl</code></td>\n<td><code>0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a</code></td>\n</tr>\n<tr>\n<td><code>litellm-1.107.0-cp310-abi3-win_amd64.whl</code></td>\n<td><code>12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484</code></td>\n</tr>\n</tbody>\n</table>\n<p>The planted files have different hashes on Windows because of the CRLF line endings:</p>\n<table>\n<thead>\n<tr>\n<th>File</th>\n<th>Linux and macOS</th>\n<th>Windows</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>cook.pth</code></td>\n<td><code>7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb</code></td>\n<td><code>84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d</code></td>\n</tr>\n<tr>\n<td><code>cook.py</code></td>\n<td><code>9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25</code></td>\n<td><code>03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea</code></td>\n</tr>\n<tr>\n<td><code>sitecustomize.py</code></td>\n<td><code>34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa</code></td>\n<td><code>b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465</code></td>\n</tr>\n</tbody>\n</table>\n<ul>\n<li>pypi</li>\n<li>python</li>\n<li>malware</li>\n<li>supply-chain</li>\n<li>litellm</li>\n</ul>\n<h3>Author</h3>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://safedep.io/images/authors/kunal-singh.jpg\" alt=\"Kunal Singh\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>The Latest from SafeDep blogs</h2>\n<p>Follow for the latest updates and insights on open source security &amp; engineering</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://safedep.io/illustrations/bottom.png\" alt=\"Background\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://safedep.io/_astro/logo.BdYG7kdO_Z2m03dG.svg\" alt=\"SafeDep Logo\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>Ship Code.</h2>\n<h2>Not Malware.</h2>\n<p>Start free with open source tools on your machine. Scale to a unified platform for your organization.</p>\n",
  "content_markdown": "• Oct 21, 2026 • 6 min read\n\nThe [LiteLLM 1.107.0](https://pypi.org/project/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. PyPI has since removed the release.\n\nVersions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean. The companion packages that 1.107.0 pins, `litellm-proxy-extras==0.4.110` and `litellm-enterprise==0.1.78`, are also 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 tokens, 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, runs 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\npyproject.toml (litellm-1.107.0.tar.gz)\n\nTOML 6 lines\n\nThe `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`.\n\nThe sdist leaves out the `cook/` folder, so this line is the only visible change. No public commit in [BerriAI/litellm](https://github.com/BerriAI/litellm) contains `data = \"cook\"` or a `cook/` folder.\n\nThe wheel `RECORD` lists the three added files:\n\nlitellm-1.107.0.dist-info/RECORD\n\nTEXT 4 lines\n\n### The release does not match its tag\n\nThe tag `v1.107.0` points to commit [`66c8f5b`](https://github.com/BerriAI/litellm/commit/66c8f5b1a9cefaa7c55b758f86f4ab552f2e859e) (06:59 UTC). Its `pyproject.toml` says `version = \"1.108.0\"`. SafeDep compared every sdist file with the git trees by blob hash. The sdist matches a later `main` commit, [`a8113b0`](https://github.com/BerriAI/litellm/commit/a8113b0dd721804c752a6f1952367fb642205224) (08:50 UTC), plus five edited files and the `cook/` folder.\n\nFour of the edited files are `Cargo.toml` files that gain a `readme = \"README.md\"` line. The legitimate 1.106.2 and rc2 releases have the same edit. The fifth is `pyproject.toml`, compared here with `a8113b0`:\n\npyproject.toml (a8113b0 → litellm-1.107.0.tar.gz)\n\nDIFF 11 lines\n\nThe edit sets the version back to 1.107.0 and pins the companion packages to the rc2 values. As a result, a dependency diff against rc2 shows nothing unusual. The legitimate 1.106.2 sdist matches its tag commit [`49943cd`](https://github.com/BerriAI/litellm/commit/49943cd52ea46aacf8db2e5cb2b890d580fe9bb4) except for the `Cargo.toml` `readme` lines.\n\n### How the wheels were built\n\nThe malicious wheels match the output of the normal BerriAI release pipeline:\n\n- The `WHEEL` file names `maturin` 1.9.4, the same version as in the legitimate releases. The implant files have correct `RECORD` entries, which shows no edits after the build.\n- Zip timestamps show separate native builds: macOS at 11:20, Linux at 11:21, and Windows at 11:37 UTC.\n- In the Windows wheel, all 2,804 `.py` files have CRLF line endings, including the three implant files. The Linux wheel has none. A git checkout on Windows with line-ending conversion produces this pattern.\n\nNo public GitHub Actions run built or published the release. SafeDep checked all 539 runs between 09:30 and 12:30 UTC. At 11:50 UTC, about 10 minutes after the PyPI upload, `litellm-dispatcher[bot]` published the GitHub release `v1.107.0`. The same delay occurred for 1.106.2. PyPI has no provenance attestations for 1.107.0, 1.106.2, or rc2.\n\nSafeDep assesses, with moderate to high confidence, that BerriAI’s private release pipeline built these wheels from an attacker-modified git source. The attacker could have used a compromised maintainer or bot credential, a private branch or mirror, or the reference the pipeline builds from. A stolen PyPI token used from the attacker’s own machines fits the evidence less well. Only BerriAI’s pipeline logs can confirm the cause.\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 `import cook`, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and CI jobs.\n\n`cook.py` wraps about 61 KB of Python in one layer of `exec(zlib.decompress(base64.b64decode('...')))`. The decoded code starts with this configuration:\n\ncook.py (decoded)\n\nPYTHON 10 lines\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 also 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\nsitecustomize.py\n\nPYTHON 5 lines\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 detach 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()`.\n\nThe attacker can point the daemon at a new server by editing `domain.txt`. Each infected host runs whatever that server returns as `codepy`.\n\nAt 13:40 UTC, the GitHub account `letters9813` and its repository returned 404, about two hours after the release. The backdoor now finds no server address and does nothing. Hosts that ran the daemon before the deletion could have received and run second-stage code. SafeDep could not recover the server address.\n\n## Timeline\n\nAll times are UTC on 2026-10-21.\n\n| Time           | Event                                                           |\n| -------------- | --------------------------------------------------------------- |\n| 06:59          | Tag `v1.107.0` commit `66c8f5b` created                         |\n| 08:50          | Commit `a8113b0` on `main`, the base of the release source      |\n| 10:44          | Legitimate 1.106.2 uploaded to PyPI                             |\n| 11:20 to 11:37 | Malicious 1.107.0 wheels built                                  |\n| 11:39 to 11:40 | 1.107.0 uploaded to PyPI (all 8 files)                          |\n| 11:50          | `litellm-dispatcher[bot]` publishes GitHub release `v1.107.0`   |\n| 13:40          | Observed: 1.107.0 gone from PyPI, `letters9813` account deleted |\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 (account deleted) |\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 have different hashes on Windows because of the CRLF line endings:\n\n| File               | Linux and macOS                                                    | Windows                                                            |\n| ------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |\n| `cook.pth`         | `7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb` | `84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d` |\n| `cook.py`          | `9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25` | `03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea` |\n| `sitecustomize.py` | `34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa` | `b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465` |\n\n- pypi\n- python\n- malware\n- supply-chain\n- litellm\n\n### Author\n\n![Kunal Singh](https://safedep.io/images/authors/kunal-singh.jpg)\n\n## The Latest from SafeDep blogs\n\nFollow for the latest updates and insights on open source security & engineering\n\n![Background](https://safedep.io/illustrations/bottom.png)\n\n![SafeDep Logo](https://safedep.io/_astro/logo.BdYG7kdO_Z2m03dG.svg)\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.\n",
  "dateCreated": "2026-10-21T19:14:42.600331207+00:00",
  "date_created": "2026-10-21T14:00:26+00:00",
  "date_modified": "2026-10-21T14:00:26+00:00",
  "date_published": "2026-10-21T14:00:26+00:00",
  "description": "• Oct 21, 2026 • 6 min read 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…",
  "external_url": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/",
  "git": {
    "commit": "33e7967b937ef6329f2d9194d87eba92105b7ced",
    "path": "items/hnrss-org/2026/10/2026-10-21-compromised-litellm-1-107-0-on-pypi-installs-a-credential.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/33e7967b937ef6329f2d9194d87eba92105b7ced/items/hnrss-org/2026/10/2026-10-21-compromised-litellm-1-107-0-on-pypi-installs-a-credential.md"
  },
  "id": "urn:aggr:item:7f5cd86fe818d3bb5ecbadfd2766b602acbdc91e",
  "isBasedOn": {
    "@id": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
  },
  "mainEntity": {
    "@id": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-21-compromised-litellm-1-107-0-on-pypi-installs-a-credential/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "abhisek"
      }
    ],
    "dateModified": "2026-10-21T14:00:26+00:00",
    "datePublished": "2026-10-21T14:00:26+00:00",
    "headline": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor",
    "image": {
      "@type": "ImageObject",
      "caption": null,
      "height": 90,
      "url": "https://safedep.io/images/litellm-pypi-compromise-credential-stealer-backdoor-banner.jpg",
      "width": 160
    },
    "timeRequired": "PT7M",
    "url": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/",
    "wordCount": 1472
  },
  "name": "Archived snapshot: Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor",
  "preview": {
    "alt": null,
    "color": null,
    "height": 90,
    "placeholder": {
      "data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
      "hash": ""
    },
    "url": "https://safedep.io/images/litellm-pypi-compromise-credential-stealer-backdoor-banner.jpg",
    "width": 160
  },
  "reading_minutes": 7,
  "replicated_at": null,
  "source": "hnrss-org",
  "source_name": "Hacker News: Front Page",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-21T19:14:42.600331207+00:00",
  "title": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor",
  "url": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-21-compromised-litellm-1-107-0-on-pypi-installs-a-credential/",
  "word_count": 1472
}