The incident issue in the LiteLLM repository says it plainly: “the litellm==1.107.0 wheels on PyPI contain a credential stealer and a remote-code loader. All 7 wheels are affected.” The payload is not in the v1.107.0 git tag; it “was injected into the build tree before the wheels were built”. The issue, opened on 21 October, carries the Upwind research team’s analysis. PyPI has removed the version; its metadata endpoint now returns {"message": "Not Found"}.
Containment moved during the day: at 13:23Z the images still pointed at the stealer build, and at 17:13Z the issue’s status reports latest, main-latest and main-stable on ghcr.io/berriai/litellm{,-database,-non_root} and docker.io/litellm/litellm yanked. The report notes the v1.107.0 images were not inspected.
Why uninstalling is not the cleanup
Each wheel drops three files into litellm-1.107.0.data/purelib/, and pip puts them in the root of site-packages: cook.pth (12 B), cook.py (22,252 B) and sitecustomize.py (25,509 B). The .pth file imports the stealer, and Python imports sitecustomize on its own at startup. Safedep puts the effect plainly: “Both run when any Python process starts on the host, even one that never imports litellm.”
The issue is blunt: “Any Python start in the affected environment runs cook.pth and sitecustomize.py again, and that includes running pip.” So pip uninstall litellm is not a fix, and neither is reinstalling.
What the payload takes
cook.py is a zlib and base64 wrapped stealer that calls itself privcollect; it runs once in a background thread and needs no command server. It collects browser passwords and cookies, ~/.ssh, cloud credentials for AWS, Azure, gcloud, Kubernetes and GitHub, package-registry tokens, .env files and private keys, CI secrets from GitLab runner, Jenkins, Vault, Terraform and Packer, AI tool tokens for Claude, Codex, Gemini and Hugging Face, every readable process’s environment from /proc, and cloud metadata credentials that it validates with STS GetCallerIdentity.
It also reaches into its own product: if DATABASE_URL is set, it spawns prisma and dumps LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable, every upstream provider key the proxy stores. The data is encrypted with AES-256-CBC under an RSA-4096-OAEP-wrapped key and posted as a tar.gz to www.litellm[.]dev, a domain registered on 13 October, not the project’s litellm.ai.
sitecustomize.py is the persistence. It forks a detached daemon that, every hour, reads a domain from a GitHub-hosted text file, reruns the stealer and execs whatever comes back from /codepy. That domain file has been empty since 20 October 09:15Z, so this stage is dormant, but one commit re-arms it. On Windows the fork fails silently and only the stealer runs.
Did I install it?
Start with artifacts, not a running interpreter: pip caches, lockfiles, compose files, tags and CI logs. The issue publishes SHA-256 hashes for all seven wheels and the tampered litellm-1.107.0.tar.gz.
The clean list is every wheel of 1.103.6, 1.103.7, 1.104.4, 1.104.5, 1.105.2, 1.105.3, 1.106.0, 1.106.1, 1.106.2, 1.107.0rc1 and 1.107.0rc2; litellm-proxy-extras 0.4.108 to 0.4.110; and litellm-enterprise 0.1.69.post2, 0.1.73.post1 and 0.1.76.post1. The report’s instruction: pin litellm==1.106.2, and do not treat a reinstall as remediation.
If 1.107.0 ever ran on a host with outbound internet, assume the exfiltration succeeded. The issue names a marker file, /tmp/.pc_once_72129118c59f5636c0bb, written only after a successful upload, and warns that its absence proves nothing, since containers and temp cleanup remove it. The work after that is rotation: the instance or task role, every provider key in the proxy database plus the master and virtual keys, the DATABASE_URL password, and any secret that was in any process’s environment on that host. Then check CloudTrail for GetCallerIdentity calls from the instance role around install time, and for those credentials used outside your infrastructure.
Cleanup on the host means deleting all three files plus __pycache__ copies of cook and sitecustomize, then killing the process holding /tmp/remote_code_loader.lock; the report recommends rebuilding over a reinstall.
How it got in
The payload is absent from the v1.107.0 tag. The source distribution’s pyproject.toml carries one extra line in the [tool.maturin] section, data = "cook", which is not in the tagged tree; maturin 1.9.4 built the wheels around 11:21Z with payload files sharing the legitimate files’ zip timestamps and metadata, and PyPI received the upload at 11:39:51Z. The issue’s title attributes the build to a lookalike repository, Berri-AI/litellm, and its authors say they could not find the compromise path: “We don’t know how the build environment or publish credential was compromised.”
There is no CVE: this is not a vulnerability in LiteLLM but a release built from a poisoned tree, and the lesson travels. A .pth file and a sitecustomize module are executed by the interpreter before any of your code runs, so a compromised build does not need your application to import anything. Pinning by hash and rebuilding from known-good images keeps that risk containable.
Sources: BerriAI/litellm issue #47826, Safedep’s analysis, the PyPI release page for 1.107.0, and the project’s releases record.

