{"slug": "litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line", "title": "LiteLLM 1.107.0 was malware. A 5-minute check for AI developers, and the one-line setting that would have skipped it", "summary": "A malicious litellm==1.107.0 was published to PyPI on October 21, 2026, roughly an hour after the legitimate 1.106.2 release, and was removed by PyPI the same day. The payload drops a .pth file and sitecustomize.py into site-packages so any Python process in the environment executes it, then targets credentials for AI coding tools including Claude Code, Cursor, Copilot, Gemini CLI and Codex CLI. The writeup, drawing on GMO Flatt Security's analysis, recommends searching for litellm-*.dist-info directories, checking lockfiles, and applying a release-age quarantine that would have skipped the hours-old malicious version.", "body_md": "On October 21, 2026, a malicious `litellm==1.107.0` was published to PyPI. It is the second compromise of the package this year, separate from the one in March. PyPI has removed it. The safe version is **1.106.2**.\n\nLiteLLM is the library many agents and proxies use to call Claude, GPT and other models through one interface. It often arrives as a dependency you never installed on purpose. And this payload goes after **AI coding tool credentials by name**: Claude, Cursor, Copilot, Gemini CLI, Codex CLI and others.\n\nThe best technical analysis so far is the write-up by GMO Flatt Security (in Japanese). This post summarizes it for an English-speaking audience and adds the part I couldn't find elsewhere: how to make your installer refuse brand-new releases.\n\n[https://blog.flatt.tech/entry/litellm_2nd_compromise](https://blog.flatt.tech/entry/litellm_2nd_compromise)\n\nTimes are UTC, from Flatt Security's timeline and PyPI's release metadata.\n\n| Time | Event | \n|---|---|\n| 10-13 06:43 | Attacker's GitHub account created | \n| 10-13 06:46 | TLS certificate issued for the exfiltration domain `litellm[.]dev` | \n| 10-21 10:44 | Legitimate 1.106.2 published | \n| 10-21 11:39 | **Malicious 1.107.0 published** , about an hour later | \n| 10-21 | PyPI removes 1.107.0 | \n\nThree details matter.\n\n`.pth` file and a `sitecustomize.py` into `site-packages`. Python loads both automatically at startup, so any Python process in that environment triggers it, even one that never imports `litellm`.\nThe legitimate `1.107.0rc1` and `rc2` had been published days earlier, so `1.107.0` looked like the expected next release. Anyone who ran `pip install -U litellm` on October 21 is the most likely victim.\n\nFlatt Security lists 16 categories. The ones AI developers should care about most:\n\n`/proc/*/environ`. If you pass `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` as env vars, assume they're gone`.env` files\nOn Claude Code specifically: per the official docs, credentials live in the **macOS Keychain** on macOS and in **`~/.claude/.credentials.json`** on Linux and Windows. macOS also falls back to the file when the Keychain can't be written, for example over SSH. If you run Claude Code in CI or on a Linux box, treat that file as exposed.\n\nI ran these on my own Mac. Nothing matched, but `pip show` alone would not have told me that.\n\n**1. Find every installed copy.** `pip show` only checks the current environment. Search for the dist-info folder instead, which carries the version in its name and also catches copies inside `uv tool` and `pipx` environments.\n\n```\nfind ~ -maxdepth 8 -type d -iname 'litellm-*.dist-info' 2>/dev/null\n```\n\n`litellm-1.107.0.dist-info` means that environment is infected.\n\n**2. Check lockfiles.** A pinned `1.107.0` will reinfect whoever rebuilds the environment from an internal mirror or cache.\n\n```\ngrep -rIl -i 'litellm' --include='requirements*.txt' --include='pyproject.toml' \\\n  --include='uv.lock' --include='poetry.lock' ~/code 2>/dev/null\n```\n\n**3. Look for the markers.**\n\n```\nls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>&1\nps aux | grep -E 'cook|remote_code_loader' | grep -v grep\n```\n\nThe first file means the data has already been sent. The lock file or a matching process means the backdoor is running. On macOS `/tmp` is cleared on reboot, so a missing marker is not proof of safety on its own. Combine it with step 1.\n\n**4. If you were hit**, follow Flatt Security's removal steps, then rotate everything as if it was stolen. The AI-specific items people forget:\n\n`/logout`, then `/login`. Do the same for Cursor, Codex and the rest\nAlso check each provider's usage page for traffic since October 21 that you don't recognize.\n\nThe malicious release was live for less than a day. **Refusing releases that are only hours old** would have skipped it entirely. Flatt Security's own registry proxy applies a 3-day quarantine, and its users were not affected. You can get the same effect with features already built into uv and pip.\n\n**uv**: `exclude-newer` accepts a duration such as `24 hours`, `1 week` or `3 days`, or an ISO 8601 duration such as `P3D`. It is compared against each file's upload time.\n\n```\n# pyproject.toml\n[tool.uv]\nexclude-newer = \"3 days\"\n```\n\nIn a global `~/.config/uv/uv.toml`, drop the `[tool.uv]` line and keep `exclude-newer = \"3 days\"`.\n\n**pip**: `--uploaded-prior-to` accepts a duration in days in the `P3D` form. It only works with indexes that expose upload times, which PyPI does.\n\n```\npip install --uploaded-prior-to P3D litellm\n\n# or make it the default\nexport PIP_UPLOADED_PRIOR_TO=P3D\n```\n\nIf your pip doesn't recognize the option, upgrade pip.\n\n**The catch is urgent fixes.** When a library ships a same-day security fix, you can't wait three days. uv lets you exempt single packages with `exclude-newer-package`:\n\n```\n[tool.uv]\nexclude-newer = \"3 days\"\nexclude-newer-package = { cryptography = false }\n```\n\nWith pip, unset the variable for that one install. \"Wait by default, opt out on purpose\" leaves a trace in your shell history or commit when you skip the delay.\n\nFor Node.js, pnpm has a setting for the same purpose, `minimumReleaseAge` ([https://pnpm.io/settings](https://pnpm.io/settings)).\n\n`litellm==1.107.0` is malware. Use 1.106.2. The GitHub source was clean; only the PyPI wheels were poisoned.`find`, not just `pip show`. Check lockfiles and the marker files.` exclude-newer = \"3 days\"` in uv or `PIP_UPLOADED_PRIOR_TO=P3D` for pip, and exempt urgent fixes explicitly.\nIf you run Claude agents on Claude Managed Agents, vault credentials of type `environment_variable` sit in the sandbox only as opaque placeholders and are swapped for the real secret at egress, so a poisoned dependency that dumps environment variables gets the placeholder, not the key: [https://platform.claude.com/docs/en/managed-agents/vaults](https://platform.claude.com/docs/en/managed-agents/vaults)\n\nI 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](https://persimmoq.gumroad.com/l/claude-5-migration-guide)", "url": "https://wpnews.pro/news/litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line", "canonical_source": "https://dev.to/persimmoq/litellm-11070-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line-setting-that-5h2a", "published_at": "2026-10-26 04:10:30+00:00", "updated_at": "2026-10-26 04:13:47.914382+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "ai-safety", "developer-tools"], "entities": ["LiteLLM", "PyPI", "GMO Flatt Security", "Claude Code", "Cursor", "GitHub Copilot", "Gemini CLI", "Codex CLI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line", "markdown": "https://wpnews.pro/news/litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line.md", "text": "https://wpnews.pro/news/litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line.txt", "jsonld": "https://wpnews.pro/news/litellm-1-107-0-was-malware-a-5-minute-check-for-ai-developers-and-the-one-line.jsonld"}}