FRIDAY, OCTOBER 23, 2026|No. 19788
Cybersecurity · Supply Chain

LiteLLM PyPI Package Compromised in Supply Chain Attack

A malicious version of the LiteLLM package was briefly distributed on PyPI, prompting a rapid response and security investigation by the project maintainers.

A security incident involving the LiteLLM package has prompted urgent updates for developers.
A security incident involving the LiteLLM package has prompted urgent updates for developers. · Photo by FlyD on Unsplash
1 sources
Pipeline ingest
3 reads
Positive / Neutral / Negative
0 countries
Related coverage

Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor

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.

LiteLLM confirmed the compromise in its incident report, which lists remediation steps. PyPI removed the release by 13:23 UTC on 2026-10-21. The Docker images v1.107.0, latest, main-latest, and main-stable on Docker Hub and GHCR also carried the malicious code between 11:46 and 14:20 UTC.

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

What the attack does

Installing litellm==1.107.0 places three files in site-packages. Python loads two of them each time it starts.

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

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" # /environ` |
| `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 |
| `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

1. It encrypts the JSON results with a random AES-256-CBC key, and encrypts that key with RSA-OAEP and the embedded public key.
2. 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`.
3. 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.

## Payload 2: the backdoor in sitecustomize.py

Python imports `sitecustomize` at startup, so this file also runs in every Python process.

1. It takes an exclusive lock on `/tmp/remote_code_loader.lock`, so only one daemon runs per host.
2. It calls `fork()` and `setsid()`, sends standard input and output to `/dev/null`, and changes directory to `/` to detach from the parent process.
3. Every hour, it reads `domain.txt` from the attacker’s GitHub repository, runs its embedded copy of the stealer, and downloads and runs code from the attacker's server.

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

PAN's pipeline reviewed approximately 1 open sources for this article. No human editor reviewed this article before publication.

Related Reads

Show on timeline →

Earlier on PAN

More in Technology →