Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor

Hacker News by 8 min read 51x views
Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor

Share Post

• Oct 21, 2026 • 6 min read

The LiteLLM 1.107.0 publish on PyPI carries a credential stealer and a remote code implementation backdoor. All seven wheels (Linux, macOS, Windows) instal them as three records in site-packages. Both run whenever any Python procedure starts on the host, equal one that never imports litellm. The upstream GitHub origin does not merge them. PyPI has since removed the release.

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 additionally clean.

What the assault does

Installing litellm==1.107.0 places three records in site-packages. Python loads two of them all period it starts.

  1. cook.pth imports cook.py, a credential stealer. It collects SSH keys, haze 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 backdrop daemon. Every hour, the daemon says a server location 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 origin allocation (sdist) has one additional row in the maturin build section:

pyproject.toml (litellm-1.107.0.tar.gz)

TOML 6 lines

The maturin data = "cook" environment copies a cook/ directory into litellm-1.107.0.data/purelib/ in the wheel. pip installs that directory into site-packages.

The sdist leaves out the cook/ folder, so this row is the lone apparent change. No community commit in BerriAI/litellm contains data = "cook" or a cook/ folder.

The rotor RECORD lists the three added files:

litellm-1.107.0.dist-info/RECORD

TEXT 4 lines

The publish does not equivalent its tag

The tag v1.107.0 points to commit 66c8f5b (06:59 UTC). Its pyproject.toml says type = "1.108.0". SafeDep compared all sdist document alongside the git trees by blob hash. The sdist matches a afterward chief commit, a8113b0 (08:50 UTC), affirmative five edited records and the cook/ folder.

Four of the edited records are Cargo.toml records that acquire a readme = "README.md" line. The lawful 1.106.2 and rc2 releases have the identical edit. The fifth is pyproject.toml, compared current alongside a8113b0:

pyproject.toml (a8113b0 → litellm-1.107.0.tar.gz)

DIFF 11 lines

The edit sets the type rear 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 lawful 1.106.2 sdist matches its tag commit 49943cd apart from for the Cargo.toml readme lines.

How the wheels were built

The malicious wheels equivalent the output of the normal BerriAI publish pipeline:

  • The WHEEL document names maturin 1.9.4, the identical type as in the lawful releases. The implant records have accurate RECORD entries, which shows no edits following the build.
  • Zip timestamps display distinct native builds: macOS at 11:20, Linux at 11:21, and Windows at 11:37 UTC.
  • In the Windows wheel, all 2,804 .py records have CRLF row endings, including the three implant files. The Linux wheel has none. A git checkout on Windows alongside line-ending conversion produces this pattern.

No community GitHub Actions run built or published the release. SafeDep checked all 539 runs between 09:30 and 12:30 UTC. At 11:50 UTC, concerning 10 minutes following the PyPI upload, litellm-dispatcher[bot] published the GitHub release v1.107.0. The identical postpone occurred for 1.106.2. PyPI has no provenance attestations for 1.107.0, 1.106.2, or rc2.

SafeDep assesses, alongside average to elevated confidence, that BerriAI’s personal publish pipeline built these wheels from an attacker-modified git source. The attacker could have used a compromised maintainer or bot credential, a personal branch or mirror, or the citation the pipeline builds from. A taken PyPI token used from the attacker’s own machines fits the evidence less well. Only BerriAI’s pipeline logs can verify the cause.

Payload 1: the credential stealer in cook.py

At startup, Python runs all row that starts alongside import in a .pth document in site-packages. cook.pth contains import cook, so the stealer runs in all Python procedure on the host: the application, pip, IDE tongue servers, and CI jobs.

cook.py wraps concerning 61 KB of Python in one tier of exec(zlib.decompress(base64.b64decode('...'))). The decoded code starts alongside this configuration:

What it collects

ModuleTargets
sysinfo, netinfoHost name, user, functioning system, IP addresses, network settings
browser_pwChromium saved passwords, decrypted alongside the overview expert key (DPAPI on Windows). Firefox logins.json and key4.db
browser_histBrowser history
ssh_keys~/.ssh/*
shell_histbash and zsh history, PowerShell PSReadLine, cmd history
cloud_creds~/.aws/credentials and config, Azure overview and tokens, gcloud credentials, ~/.kube/config, ~/.docker/config.json, gh hosts.yml, rclone.conf, ~/.netrc
dev_secrets.pem, .key, .pfx, .p12, .pk8, .ppk records under $HOME, Composer auth.json, FileZilla saved servers
container_credsKubernetes assistance document token
cicd_secretsGitLab runner config.toml, Jenkins initialAdminPassword and master.key, .vault-token, Terraform credentials.tfrc.json, Packer settings
ai_devtoolsClaude 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
env_varsIts own surroundings and the surroundings of another processes through /proc/<pid>/environ
db_litellmReads DATABASE_URL and dumps the LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable tables, which clasp the LLM provider API keys of a LiteLLM proxy
cloud_metadataAWS case metadata assistance (IMDSv2, afterward v1): case character and function credentials
gpg_keys, tls_privkeyGPG keys, TLS personal keys

How it sends the data

  1. It encrypts the JSON results alongside a random AES-256-CBC key, and encrypts that key alongside RSA-OAEP and the embedded public key.
  2. It packs the outcome into a tar.gz (hdr.json, data.bin, key.bin) and sends it in a POST petition to www.litellm[.]dev/healthcheck alongside the header X-Token: XWiA8gZNa7NcX2XRoAEb.
  3. After a prosperous upload, it writes /tmp/.pc_once_72129118c59f5636c0bb so it collects lone formerly per host. It also sets PRIVCOLLECT_ACTIVE=1 to evade a second run in the identical procedure tree.

Payload 2: the backdoor in sitecustomize.py

Python imports sitecustomize at startup, so this document additionally runs in all Python process.

  1. It takes an exclusive fastener on /tmp/remote_code_loader.lock, so lone one daemon runs per host.
  2. It calls fork() and setsid(), sends norm input and output to /dev/null, and changes directory to / to detach from the genitor process.
  3. Every hour, it:
    1. reads domain.txt from the attacker’s GitHub repository and takes the archetypal row that looks akin a domain,
    2. runs its embedded copy of the stealer, which has no once-per-host marker,
    3. downloads http://<domain>/codepy and runs it alongside exec().

The attacker can item the daemon at a new server by editing domain.txt. Each infected presenter runs any that server returns as codepy.

At 13:40 UTC, the GitHub document letters9813 and its repository returned 404, concerning two hours following the release. The backdoor now finds no server location and does nothing. Hosts that ran the daemon before the deletion could have received and run second-stage code. SafeDep could not regain the server address.

Timeline

All times are UTC on 2026-10-21.

TimeEvent
06:59Tag v1.107.0 commit 66c8f5b created
08:50Commit a8113b0 on main, the basis of the publish source
10:44Legitimate 1.106.2 uploaded to PyPI
11:20 to 11:37Malicious 1.107.0 wheels built
11:39 to 11:401.107.0 uploaded to PyPI (all 8 files)
11:50litellm-dispatcher[bot] publishes GitHub publish v1.107.0
13:40Observed: 1.107.0 gone from PyPI, letters9813 document deleted

Indicators of compromise

Network

IndicatorPurpose
www.litellm[.]dev, hxxp://www.litellm[.]dev/healthcheckReceives taken data
HTTP header X-Token: XWiA8gZNa7NcX2XRoAEbSent alongside all upload
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txtBackdoor says its server location current (account deleted)
hxxp://<domain>/codepySecond-stage code download
Requests to 169.254.169.254 from Python processesTheft of AWS case credentials

Host

IndicatorMeaning
cook.pth, cook.py, sitecustomize.py in site-packagesImplant installed
/tmp/.pc_once_72129118c59f5636c0bbStealer already uploaded data
/tmp/remote_code_loader.lockBackdoor daemon has run
Environment changeable PRIVCOLLECT_ACTIVE=1Present in infected processes
Detached Python daemon (parent PID 1, own session, operating directory /)Backdoor loop

File hashes (SHA-256)

FileSHA-256
litellm-1.107.0.tar.gz2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819
litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7
litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6
litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00
litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whlc2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054
litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d
litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a
litellm-1.107.0-cp310-abi3-win_amd64.whl12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484

The planted files, from the rotor RECORD:

FileSHA-256 (URL-safe base64)
cook.pthfxkWMucyBwXRVLhSBPGuVGtUOsvX6IYSKbdvUEK5K9s
cook.pyndCjkm-Wo364bPHuIxDZvpMTnMTr7z754u_pUxOQviU
sitecustomize.pyNL7wT-iKydCq4EdWMLFzhoHRW5TyM6yKpG6s9ZaDBfo
  • pypi
  • python
  • malware
  • supply-chain
  • litellm

Author

Kunal Singh

The Latest from SafeDep blogs

Follow for the latest updates and insights on open origin safety & engineering

Background

SafeDep Logo

Ship Code.

Not Malware.

Start liberated alongside open origin tools on your machine. Scale to a unified phase for your organization.

Other Article Hacker News
↑
Close Right Ads
Close Left Ads