
litellm 1.107.0 Compromised on PyPI: Backdoored Wheels Drop a Credential Stealer and an Hourly Remote Code Loader
litellm 1.107.0 was published to PyPI on 21 October 2026 with all seven wheels backdoored through an injected `data = "cook"` maturin build line. The wheels drop cook.pth, cook.py and sitecustomize.py, which run on every Python start. They install a "privcollect" credential stealer (browser, SSH, cloud, AI coding tool and LiteLLM proxy database secrets, sent to litellm.dev) and an hourly GitHub dead-drop remote code loader. The post gives IOCs, detection commands and recovery steps.
What it is
litellm 1.107.0 was published to PyPI on 21 October 2026 with malware in all seven compiled wheels (macOS, Linux glibc and musl, Windows). The release has since been removed from PyPI. The StepSecurity post says this is the second LiteLLM compromise of 2026, after 1.82.7 and 1.82.8 in March. The sdist carries no payload, but its pyproject.toml has a data = "cook" line under [tool.maturin] that is absent at git tag v1.107.0 (commit 66c8f5b1a9). The wheels carry no PyPI provenance attestations, so nothing ties the upload to a CI run.
How it works
The wheels set Root-Is-Purelib: false, so pip copies cook.pth, cook.py and sitecustomize.py into the top level of site-packages. Python runs them on every interpreter start, with no import litellm needed. cook.pth is import cook. Debian and Ubuntu system Python may ship an earlier sitecustomize.py that shadows the injected one, but the .pth path is unaffected.
Two payloads run:
- privcollect stealer (
cook.py, one layer of zlib and base64). It runs 16 modules on a daemon thread: browser passwords, SSH/GPG/TLS keys, cloud and registry credentials,.envfiles, cloud metadata credentials, CI secrets,/proc/<pid>/environof every readable process, configs of 20+ AI coding tools, and shell history. IfDATABASE_URLis set, it dumpsLiteLLM_CredentialsTableandLiteLLM_ProxyModelTable(upstream provider keys) through the proxy's Prisma client. Data is encrypted with AES-256-CBC, the key is wrapped with RSA-4096, and it is sent by plain HTTP POST tohttp://www.litellm.dev/healthcheck. The marker/tmp/.pc_once_72129118c59f5636c0bbis written only after an HTTP 200, so a failing exfiltration means the stealer reruns on every Python start. - Hourly remote code loader (
sitecustomize.py). It takes a lock at/tmp/remote_code_loader.lock, forks a detached daemon, and every hour reads a hostname fromdomain.txtin the GitHub repoletters9813/pythonclient-test. It then downloadshttp://<host>/codepyand runs it withexec(). It also embeds a second copy of the stealer.
The attacker prepared in advance: litellm.dev was registered on 13 October 2026 at 00:45 UTC, and the GitHub account was created six hours later.
Preconditions / Affected
- Any install of 1.107.0 from a wheel. Nearly all installs use wheels, and open-ended
litellm>=constraints or lockfile refreshes would have resolved to it. - LiteLLM released 12 times across four minor lines between 17 and 21 October 2026, so unpinned CI is likely exposed.
- Proxy hosts with
DATABASE_URLset lose stored provider keys, plus the master and salt keys from the environment. - On Windows the loader fails silently (no
fcntloros.fork), but the stealer runs, and likely reruns on every start because the marker path does not resolve.
What to do
Detect on hosts:
find / -name cook.pth -path "*site-packages*" 2>/dev/null
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null
lsof /tmp/remote_code_loader.lock 2>/dev/nullDetect in repos:
grep -rniE "litellm[=<>~! ]*1\.107\.0([^0-9rc]|$)" . --include=requirements*.txt --include=*.lock --include=pyproject.toml --include=Pipfile*Recover, in this order:
- Kill the process holding
/tmp/remote_code_loader.lock(parent PID 1, working directory/). Uninstalling the package alone does not stop it. - Delete
cook.pth,cook.pyandsitecustomize.pyfrom every site-packages (pip uninstall litellmremoves them, but verify), delete the marker file, and reinstall withpip install 'litellm==1.106.2'. - Rotate everything the host could reach: cloud keys and role sessions, SSH/GPG keys, browser passwords and cookies, registry and Git tokens, Kubernetes and Vault tokens, CI secrets, AI tool tokens. On proxies, also rotate all stored provider keys, the master key and the salt key.
- Block
litellm.devat DNS or egress and alert onraw.githubusercontent.com/letters9813/. - Search network logs for
litellm.dev, the dead-drop URL, and any path ending/codepy.
Other IOCs: header X-Token: XWiA8gZNa7NcX2XRoAEb, the MSIE 7.0 User-Agent, temp dirs .pc_sq_* and .pc_e_*, and PRIVCOLLECT_ACTIVE=1. In CI, any runner call to litellm.dev means the stealer ran, so treat every secret exposed to that job as stolen.
Caveats
- At the time of analysis
domain.txtwas empty, so the/codepystage was not armed. Earlier values werelocalhost:18909andtest.test.com:18909. One commit to that file would give code execution on every infected host within an hour. - A request to
/codepymeans attacker code ran, and the scope is then larger than the post analyses. - The post does not say how the wheels were altered: a compromised build environment or stolen PyPI credentials.
- The post's file hash table and timeline were not in the fetched text.
Organisations named here
- credential stealer
- dead drop
- letters9813
- litellm.dev
- remote code loader
- sitecustomize.py
- T1195.002
Technologies named here
- Dev Machine Guard
- LiteLLM proxy and gateway hosts with DATABASE_URL set
- StepSecurity Threat Center
- maturin
- lsof / fuser / find / grep
- Debian and Ubuntu system Python
- Windows
- Clean version recommended
- LiteLLM
- PyPI
- Python
- pip
- GitHub
- Harden-Runner
Techniques named here
- Python startup hooks
- Obfuscated loader
- Encrypted exfiltration
- Lookalike domain litellm.dev behind Cloudflare
- Execution guards
Threat actors named here
- privcollect
- cook.pth
Related in Supply Chain


