logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·27.10.26
← Back to news

Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor

Original article
safedep.io|582 points|190 comments|by abhisek|Oct 21, 2026

⚠️ Security Alert: Credential Stealer and Backdoor Found in LiteLLM 1.107.0

A critical security compromise has been identified in the LiteLLM version 1.107.0 release hosted on PyPI. This version contains a sophisticated credential stealer and a remote code execution (RCE) backdoor.

🚨 Executive Summary

The malicious release affects all seven distributed wheels across Linux, macOS, and Windows. The attack is particularly dangerous because the implant triggers upon the start of any Python process on the infected host—regardless of whether litellm is actually imported.

Note: The companion packages pinned by this version, litellm-proxy-extras==0.4.110 and litellm-enterprise==0.1.78, have been verified as clean.


🛠️ Anatomy of the Attack

What the Malware Does

Once litellm==1.107.0 is installed, it deposits three specific files into the site-packages directory. The malware performs the following actions:

  • Data Exfiltration: It targets a wide array of sensitive information:
    • SSH Keys
    • Cloud Provider Credentials
    • CI/CD Secrets
    • AI Tool Tokens
    • Browser-stored Passwords
    • LLM Provider Keys (extracted from the LiteLLM proxy database)
  • Persistence & Command and Control (C2): A daemon runs every hour to:
    1. Read a server address from a specific GitHub file.
    2. Re-execute the credential stealer.
    3. Download and execute arbitrary code from the identified server.

Infection Flow


🔍 Forensic Analysis: How it Happened

The Injection Point

The attacker modified the pyproject.toml file within the source distribution (sdist). A single line was added to the maturin build section:

[tool.maturin]
python-source = "."
data = "cook" # <--- MALICIOUS INJECTION
python-packages = [ "litellm_cli" ] # Legitimate

The data = "cook" directive instructs the build system to include the cook/ folder in the resulting wheel. Interestingly, the cook/ folder was omitted from the sdist itself, making the injection nearly invisible unless the pyproject.toml was scrutinized.

Discrepancies in the Release

SafeDep discovered that the PyPI release does not match the official Git tag v1.107.0 (commit 66c8f5b). Instead, the sdist was based on a later commit (a8113b0) with several manual edits:

FileChange Description
Cargo.toml (x4)Added readme = README.md
pyproject.tomlVersion downgraded 1.108.0→1.107.0\text{1.108.0} \rightarrow \text{1.107.0}1.108.0→1.107.0
pyproject.tomlPinned dependencies to rc2 values
pyproject.tomlAdded data = "cook"

By increasing decreasing the version number and aligning pins with the rc2 release, the attacker ensured that a dependency diff against the release candidate would appear normal.

The Build Pipeline

Evidence suggests the wheels were built using the official BerriAI pipeline:

  1. Tooling: Used maturin 1.9.4 (matching legitimate releases).
  2. Timestamps: Native builds occurred at 11:20 (macOS), 11:21 (Linux), and 11:37 (Windows) UTC.
  3. Formatting: Windows wheels used CRLF line endings across all 2,804 .py files, consistent with a Windows git checkout.

Conclusion on Vector:

SafeDep concludes with moderate-to-high confidence that the private release pipeline was compromised. This likely involved stolen maintainer/bot credentials or a compromised private branch, rather than a simple PyPI token theft.


📦 Payload Technical Details

Payload 1: cook.py

The execution is triggered via a .pth file. In Python, any line starting with import in a .pth file is executed upon interpreter startup.

The Chain: cook.pth →\rightarrow→ import cook →\rightarrow→ cook.py.

The cook.py file contains a heavily obfuscated payload. The decompression logic can be represented as: Payload=exec(zlib.decompress(base64.b64decode(encoded_string)))\text{Payload} = \text{exec}(\text{zlib.decompress}(\text{base64.b64decode}(\text{encoded\_string})))Payload=exec(zlib.decompress(base64.b64decode(encoded_string)))

Decoded Configuration Snippet:

PC_URL = 'http://www.litellm.dev/healthcheck' 
PC_TOKEN = 'XWiA8gZNa7NcX2XRoAEb' # Sent as X-Token header 
PC_PUBKEY = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMII...'

File Manifest (from RECORD)

The following files were added to the wheel:

  • litellm-1.107.0.data/purelib/cook.pth (12 bytes) →\rightarrow→ The Trigger
  • litellm-1.107.0.data/purelib/cook.py (22,252 bytes) →\rightarrow→ The Stealer
  • litellm-1.107.0.data/purelib/sitecustomize.py (25,509 bytes) →\rightarrow→ The Loader/Backdoor