LiteLLM 1.107.0 was malware. A 5-minute check for AI developers, and the one-line setting that would have skipped it
On October 21, 2026, someone published a malicious litellm==1.107.0 to PyPI. It's the second time the package has been compromised this year, and it's unrelated to the March incident. PyPI has pulled it, and 1.106.2 is s
On October 21, 2026, someone published a malicious litellm==1.107.0 to PyPI. It's the second time the package has been compromised this year, and it's unrelated to the March incident. PyPI has pulled it, and 1.106.2 is safe.
If you build with LLMs, there's a decent chance LiteLLM is somewhere on your machine even if you never installed it yourself. A lot of agents and proxies use it to talk to Claude, GPT and the rest through one interface. That's what makes this one nasty: the payload goes looking for AI coding tool credentials by name, including Claude, Cursor, Copilot, Gemini CLI and Codex CLI.
GMO Flatt Security published the most detailed analysis I've seen, but it's in Japanese. This post summarizes it in English and adds something I couldn't find in any of the write-ups: how to tell uv and pip to refuse brand-new releases.
https://blog.flatt.tech/entry/litellm_2nd_compromise
What happened
The timeline below comes from Flatt Security's analysis and PyPI's release metadata. Times are UTC.
| Time | Event |
|---|---|
| 10-13 06:43 | Attacker's GitHub account created |
| 10-13 06:46 | TLS certificate issued for the exfiltration domain litellm[.]dev
|
| 10-21 10:44 | Legitimate 1.106.2 published |
| 10-21 11:39 | Malicious 1.107.0 published, about an hour later |
| 10-21 | PyPI removes 1.107.0 |
The v1.107.0 tag on GitHub is clean. The malicious files only exist in the wheels uploaded to PyPI, so reading the source wouldn't have saved you.
It also doesn't wait for you to import anything. The wheel drops a .pth file and a sitecustomize.py into site-packages, and Python loads both on every startup. Any Python process in that environment sets it off. On top of the data theft, it starts a daemon that pulls and runs remote code once an hour, with the server address read from a file on GitHub.
The timing was clever, too. 1.107.0rc1 and rc2 had gone out normally a few days earlier, so a final 1.107.0 was exactly what you'd expect next. If you ran pip install -U litellm on October 21, you're the person this was aimed at.
What it steals
Flatt Security counted 16 categories of data. For AI developers, the AI tool credentials are the obvious worry: Claude, Cursor, Copilot, Gemini CLI, Codex CLI, Windsurf, Aider, OpenCode and Hugging Face tokens are all on the list.
Environment variables are the less obvious one. It reads its own and other processes' through /proc/*/environ, so if you pass ANTHROPIC_API_KEY or OPENAI_API_KEY that way, assume they've been taken. It also collects .env files and private key files, and if you run the LiteLLM proxy, it queries the proxy's database for the provider keys stored there. SSH keys, cloud credentials, browser passwords and shell history round it out.
A note on Claude Code. According to the official docs, it keeps credentials in the Keychain on macOS and in ~/.claude/.credentials.json on Linux and Windows. macOS falls back to that file when the Keychain isn't writable, which happens over SSH. If you run Claude Code in CI or on a Linux server, treat that file as exposed.
The 5-minute check
I ran all of this on my own Mac. Nothing turned up, but I wouldn't have known that from pip show alone, because it only looks at whichever environment is active.
Searching for the dist-info folder works better. The version is in the folder name, and it finds copies tucked inside uv tool and pipx environments as well.
find ~ -maxdepth 8 -type d -iname 'litellm-*.dist-info' 2>/dev/null
If you see litellm-1.107.0.dist-info, that environment is infected.
Next, check your lockfiles. A pinned 1.107.0 will infect whoever rebuilds that environment from an internal mirror or a cache.
grep -rIl -i 'litellm' --include='requirements*.txt' --include='pyproject.toml' \
--include='uv.lock' --include='poetry.lock' ~/code 2>/dev/null
Then look for the files the malware leaves behind.
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>&1
ps aux | grep -E 'cook|remote_code_loader' | grep -v grep
The first file means your data has already been sent. The lock file, or a matching process, means the backdoor is running. Keep in mind that macOS clears /tmp on reboot, so not finding them doesn't prove much by itself. Read it together with the find result.
If you were hit, follow Flatt Security's removal steps and then rotate everything as if it were stolen. The ones I'd expect AI developers to forget are their model provider API keys (Anthropic Console, OpenAI, Google AI Studio), their Claude Code login (/logout, then /login, and the same for Cursor or Codex), and every key stored in a LiteLLM proxy. After that come the usual suspects: .env contents, GitHub and package registry tokens, SSH keys and cloud credentials. It's also worth checking each provider's usage page for anything since October 21 you don't recognize.
Don't install anything younger than 3 days
The bad release was up for less than a day. If your installer had simply ignored anything only a few hours old, you'd have skipped it without noticing. Flatt Security runs a registry proxy with a 3-day quarantine, and its users weren't affected. You don't need a proxy to get the same effect, though. Both uv and pip can do it today.
In uv, exclude-newer takes a duration like 24 hours, 1 week or 3 days, or an ISO 8601 duration like P3D, and compares it against each file's upload time.
# pyproject.toml
[tool.uv]
exclude-newer = "3 days"
For a global ~/.config/uv/uv.toml, leave out the [tool.uv] line.
In pip, the option is --uploaded-prior-to, and it takes days in the P3D form. It only works with indexes that report upload times, which PyPI does. If your pip doesn't know the option, upgrade it.
pip install --uploaded-prior-to P3D litellm
# or make it the default
export PIP_UPLOADED_PRIOR_TO=P3D
The downside is that urgent fixes get held back too, and you can't sit on a same-day security patch for three days. uv handles this with exclude-newer-package, which lets you exempt a single package.
[tool.uv]
exclude-newer = "3 days"
exclude-newer-package = { cryptography = false }
With pip, unset the variable for that one install. I like this split because skipping the delay becomes a deliberate act that shows up in your shell history or a commit, rather than something that happens by default.
If you're on Node.js, pnpm has a setting for the same thing called minimumReleaseAge (https://pnpm.io/settings).
Wrapping up
litellm==1.107.0 is malware, and 1.106.2 is the version to use. Search for installed copies with find instead of trusting pip show, check your lockfiles, and rotate your provider API keys along with everything else if you find it. Going forward, a 3-day delay in uv or pip would have kept this release off your machine.
One more thing if you run Claude agents on Claude Managed Agents. Vault credentials of type environment_variable only exist inside the sandbox as placeholders and get swapped for the real secret on the way out, so a poisoned dependency that dumps environment variables ends up with the placeholder. The docs are here: https://platform.claude.com/docs/en/managed-agents/vaults
I write about Claude API changes as they ship. The English edition of my Claude 5 migration guide is on Gumroad: https://persimmoq.gumroad.com/l/claude-5-migration-guide
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.