At 11:39 UTC on 21 October 2026 a new release of LiteLLM, version 1.107.0, landed on PyPI. Within seven minutes an automated scanner had flagged every one of its seven wheels as malware. Within fifteen a security vendor had posted a public warning. Within two hours it was the biggest security story on X, in three languages, and the maintainers had declared a SEV-0.

This is the second time LiteLLM has been hijacked this year, and it is a different attack from the one in March. It is also different in kind from the default key and authentication bypass findings we wrote about in September. Those were bugs in the gateway. This is the gateway itself being replaced with a weapon, delivered through the normal install channel, with a clean git tag sitting next to it.
This post explains what happened in plain words, what the malware does, how it got into the release, who needs to act and what to do. At the end we explain why we think the gateway layer is the wrong place to carry this risk, and which Requesty controls limit the blast radius when something like this reaches a workstation anyway.
The short version
If you installed litellm==1.107.0 on any operating system, or pulled litellm/litellm:latest, main-latest, main-stable or v1.107.0 from Docker Hub or GHCR between roughly 11:40 and 14:30 UTC on 21 October, assume every credential that machine could reach has been copied. That includes the provider API keys stored inside the LiteLLM proxy database.
Safe version: litellm==1.106.2, pinned by exact version for pip and by digest for containers. The maintainers have deleted the floating Docker tags rather than repointing them, so an unpinned FROM litellm/litellm:latest will now fail to pull. That failure is the fix working.
What happened, hour by hour
Everything below comes from public sources: the GitHub issue where the incident was reported and tracked, the analyses published by JFrog, Socket, Aikido and Flatt Security, and the maintainers' own updates. All times are UTC.
| When | What |
|---|---|
| 13 Oct, 06:43 | GitHub account letters9813 is created. Three minutes later a TLS certificate is issued for litellm.dev, a lookalike of the project's real litellm.ai domain. |
| 18 Oct, 04:17 | The attacker adds domain.txt to a repository under that account. This file is the dead drop the backdoor will poll for its command server. |
| 21 Oct, 09:48 | In a lookalike source repository, Berri-AI/litellm (note the extra hyphen), a commit adds one line to pyproject.toml: data = "cook". |
| 21 Oct, 10:44 | The legitimate litellm==1.106.2 is published. |
| 21 Oct, 10:46 | A second commit adds COPY cook ./cook to the backend, gateway and migrations Dockerfiles. |
| 21 Oct, 10:51 to 11:06 | The project's private release workflow, project-releaser, builds the container images. Its provenance records source_owner: Berri-AI. |
| 21 Oct, 11:21 | The seven Python wheels are built with maturin. |
| 21 Oct, 11:39 | litellm==1.107.0 is uploaded to PyPI. |
| 21 Oct, 11:46 | Socket's scanner flags all seven wheels as malicious. |
| 21 Oct, 11:48 | The floating Docker tags on GHCR and Docker Hub start pointing at the infected image. |
| 21 Oct, 11:54 | Upwind posts the first public warning. |
| 21 Oct, 12:06 | A detailed report with file hashes and a decoded payload is opened on the LiteLLM repository. |
| 21 Oct, ~13:20 | PyPI quarantines 1.107.0. Anyone who installed during the roughly 100 minute window still has it. |
| 21 Oct, 13:50 | Maintainers acknowledge the report and declare a SEV-0. |
| 21 Oct, 14:32 | Maintainers confirm the Docker images have been removed. |
| 21 Oct, 16:32 | Maintainers report that the release was triggered through one engineer's GitHub account and approved through another's, using stolen GitHub CLI OAuth tokens, with a third account also involved. Tokens are revoked, all but one user removed from the GitHub organisation, and the public registries unlinked from GitHub. The original theft vector is still unknown. |
The thing to notice in that table is the gap between 09:48 and 11:39. The attacker did not break into the package registry. They prepared a source tree that looked right, got the project's own release machinery to build it, and let the normal publish path do the rest. The git tag v1.107.0 on the real repository has no malicious code in it. Anyone who reviewed the tag, or the source tarball, would have found nothing except, in the tarball, one extra line of build configuration.
How the payload works
Python wheels can carry a data directory whose contents get copied straight into site-packages on install. The added data = "cook" line told the build tool to do exactly that with a hidden cook/ directory that existed only in the attacker's build tree. Three files came along:
cook.pthis twelve bytes long and containsimport cook. Python processes.pthfiles insite-packagesevery time the interpreter starts. That one line makes the next file run on every Python invocation on the machine, whether or not the program has anything to do with LiteLLM.cook.pyis the credential stealer. It is a single line that decompresses and executes about 61 KB of Python. The decoded program calls itselfprivcollect.sitecustomize.pyis the remote access backdoor. Python also imports a module by this name automatically at startup. It forks a detached daemon that wakes up every hour, fetches a domain name from the attacker's GitHub dead drop, and if one is there, runs the stealer again and then downloads and executes whatever Python the attacker's server returns. There is no signature check. Whoever controls that domain can run arbitrary code on every infected machine, indefinitely.
At the time the researchers looked, the dead drop file was empty, so the hourly backdoor was dormant. That is not reassurance. The stealer in cook.py does not depend on the dead drop and runs regardless, and the attacker can arm the backdoor later by committing a single line to a text file.
What it takes
The collector was written for developer laptops, CI runners, cloud workloads and LiteLLM proxy hosts. It gathers:
- SSH, GPG and TLS private keys,
.pem,.key,.p12and.pfxfiles, shell and database client history - Saved passwords, cookies and history from Chromium browsers and Firefox
- AWS, Azure and Google Cloud credential files, plus live credentials from the cloud instance metadata services of all three
- Kubernetes configs and service account tokens, Docker configs and Swarm secrets
- GitLab Runner, Jenkins, Vault, Terraform and Packer secrets
.git-credentials,.npmrc,.pypirc, pip, Maven, Gradle, Cargo and other package manager tokens- Configuration and auth tokens for Claude Code, Cursor, Codex, Gemini CLI, GitHub Copilot, Windsurf, Kiro, Continue, aider, goose, opencode, Cline and others
- Every environment variable of the current process and, on Linux, of every other process it can read under
/proc - The LiteLLM proxy database itself, via
DATABASE_URL: it dumpsLiteLLM_CredentialsTable, which holds the API key for every upstream provider the gateway talks to, andLiteLLM_ProxyModelTable
That last bullet is the one that matters for anyone running the proxy in production. A gateway exists to hold one key per provider so that applications do not have to. The malware was written with that in mind. One infected proxy host hands over the OpenAI, Anthropic, Bedrock and Vertex credentials of the whole organisation in one archive.
Everything is encrypted with a hardcoded RSA key and posted over plain HTTP to www.litellm.dev/healthcheck. The hostname was chosen so that it looks like a health check to the real project's domain in a network log. It is not.
Who is exposed
Three groups, in order of likelihood:
- Anyone whose CI or deploy floated on a tag.
pip install litellmwithout a pin between 11:39 and about 13:20 UTC, or any pull of the floating container tags between 11:48 and about 14:30 UTC, got the payload. Dependabot style auto-merge of the bump counts. - Anyone who runs the LiteLLM proxy and updated today. The proxy host has the database URL, which means the stealer got the provider keys.
- Developers who installed it on a laptop. The laptop has the browser passwords, the SSH keys, the cloud CLI sessions and the coding agent tokens.
Two indicators tell you whether the stealer has already fired: /tmp/.pc_once_72129118c59f5636c0bb exists only after a successful upload, and /tmp/remote_code_loader.lock exists if the backdoor daemon has run. The absence of the first file does not mean you are clean, because a failed upload leaves no marker and the daemon will try again.
What to do
This is the consensus remediation from JFrog, Socket, Aikido and the issue reporters, in the order that matters.
- Do not start with
pip uninstall. Running pip starts Python, and starting Python runs the payload. Isolate the host first. - Kill the daemon. Look for a detached Python process whose working directory is
/and which holds/tmp/remote_code_loader.lock. - Remove the files without Python. Delete
cook.pth,cook.py,sitecustomize.pyand their__pycache__entries fromsite-packages, plus the two/tmpmarkers. - Rebuild rather than clean. Every vendor who decoded the sample recommends rebuilding the virtualenv, container or host from a verified image, pinned to
1.106.2by exact version and, for containers, by digest. - Rotate everything in the list above. Cloud keys, SSH keys, database URLs, package publishing tokens, git tokens, coding agent tokens, browser passwords, and every provider key that was stored in the LiteLLM proxy database. Rotate the LiteLLM master key and all virtual keys too.
- Block and hunt. Deny
litellm.devat egress, search proxy and DNS logs for it and for the GitHub dead drop URL, and check cloud audit logs forGetCallerIdentitycalls from unexpected places, because the stealer validates stolen AWS credentials before shipping them. - Stop floating. Pin the package version in every requirements file and lock file. Pin container images by digest. Treat a release that arrives one hour after the previous one as something to look at, not something to auto-merge.
Why the gateway is where this hurts most
We made an argument in September that an AI gateway is a tier one security asset because of its position: it holds every provider key, reads every prompt, and usually carries a cloud identity. This incident is the proof. The attacker did not need to find a bug in LiteLLM. They needed to become LiteLLM for a hundred minutes, and the stealer's database module shows they knew exactly which table to read when they did.
It also shows what supply chain risk looks like when the component is self hosted. You are now responsible for:
- verifying the artifact you install matches the source you can read, which in this case it did not, and which almost nobody checks
- the patch window: fast enough that you are not running known bugs, slow enough that you are not the first to install a poisoned release
- the container's floating tags, the IAM role it runs under, the database that holds the provider keys, the egress rules that would have caught
litellm.dev - the post-mortem of a release pipeline that is private, so the people depending on it cannot audit how releases are built
None of that is routing. All of it landed on every LiteLLM operator's desk today.
What a managed gateway changes, and what it does not
Requesty is a managed gateway, so there is no gateway package in your pip install, no gateway image in your cluster and no gateway database on a host you administer. The product is written in Go and served from our infrastructure; the only thing in your code is an OpenAI compatible endpoint URL and a Requesty key. That removes the specific thing that happened today from your supply chain. It is fair to say it is not magic, so here is precisely which controls map to which part of this incident.
- Provider keys that are not on your box. With bring your own keys, your OpenAI, Anthropic, Bedrock or Vertex credentials sit in encrypted storage on our side and are attached to requests by policy. There is no
LiteLLM_CredentialsTableequivalent for a stealer to dump from a host you run, and your application never holds a provider key at all. - A stolen key is a scoped key. Every Requesty API key carries its own spend limit and rate limit, its own access list of models it may call, and an organisation wide approved models list on top. If the stealer lifts a Requesty key from a laptop's environment variables, the attacker has one credential with a monthly cap and a model allowlist, not the account.
- Rotation is an API call. The key management API and service accounts let you revoke and reissue every key a compromised host could have seen in a script, today, instead of hunting through compose files.
- You find out when a key is being abused. Spending alerts fire a webhook when a key or the organisation crosses a threshold, and the request logs show which key called which model from where. A stolen credential that starts spending at 3am is visible in minutes.
- Guardrails that catch secrets in prompts. Organisation guardrails run secret and credential scanners on requests as configuration, not as Python you upload to the gateway, so the guardrail layer is not an execution surface and does catch the API key a coding agent pastes into a prompt.
- Logging you control. Data privacy controls turn payload logging off per key or enforce zero data retention across the organisation, so a breach of any logging layer is not a breach of every conversation.
What it does not change: if a developer installs a poisoned package of any kind on their laptop, their SSH keys and browser passwords are gone, and no gateway fixes that. What the gateway decides is whether the AI credentials on that laptop are a scoped key with a cap, or the organisation's raw provider keys.
If you are comparing the two models in detail, the LiteLLM comparison page and the build versus buy piece cover the feature and cost trade offs. This post is about the risk that got repriced today.
The takeaway
LiteLLM 1.107.0 was on PyPI for about a hundred minutes and on the Docker floating tags for a bit under three hours. In that window it reached machines in every time zone, and the X conversation went from one vendor's warning to a global story in English, Japanese and Chinese before the maintainers' first update, passing 250 tracked posts by 17:00 UTC. The project has now been hijacked twice in seven months, through two unrelated paths, and the second one left the git tag spotless.
If you run LiteLLM: pin 1.106.2 by digest, check for the two /tmp markers, rebuild anything that touched 1.107.0, and rotate every provider key the proxy ever stored. Then ask whether the gateway in front of your production AI traffic should be a Python package you install, or a service whose supply chain is somebody else's full time job, with your effort going into the policies that limit what any one key can do.
Frequently asked questions
- Which LiteLLM versions are affected by the October 2026 compromise?
- Only litellm 1.107.0, published to PyPI at 11:39 UTC on 21 October 2026 and removed by about 13:20 UTC. All seven compiled wheels (macOS, Linux glibc, Linux musl and Windows) contain the payload. The Docker images litellm/litellm:1.107.0 and the floating tags latest, main-latest and main-stable on Docker Hub and GHCR pointed to the same infected build until the maintainers removed them at about 14:30 UTC. The 1.107.0rc1 and rc2 pre-releases, 1.106.2 and every earlier wheel checked by the reporters are clean.
- Am I affected if my application never imports litellm?
- Yes, if the package is installed in that Python environment. The wheel drops cook.pth and sitecustomize.py into site-packages. Python runs both at interpreter startup, before any of your code, so any Python process in that environment triggers the credential stealer, including pip itself.
- What does the LiteLLM 1.107.0 malware steal?
- SSH, GPG and TLS private keys, shell history, browser saved passwords and cookies, AWS, GCP and Azure credentials including live instance metadata tokens, Kubernetes and Docker configs, CI/CD secrets, npm, PyPI and git credentials, configuration and tokens for Claude Code, Cursor, Copilot, Codex, Gemini CLI and a dozen other AI tools, every environment variable of every readable process, and the LiteLLM proxy database tables that hold your provider API keys and model configuration.
- How did the attacker get the payload into the release?
- The git tag is clean. The release workflow built from a lookalike repository, Berri-AI/litellm with an extra hyphen, whose commits added a data = cook setting to pyproject.toml so the build tool bundled a hidden directory of startup hooks into every wheel. The maintainers reported that the release was triggered and approved through three engineers' GitHub accounts using stolen GitHub CLI OAuth tokens. The original source of those tokens had not been identified at the time of writing.
- How do I clean an affected machine?
- Do not run pip or python in the affected environment first, because that re-triggers the payload. Isolate the host, kill detached Python processes holding /tmp/remote_code_loader.lock, remove cook.pth, cook.py and sitecustomize.py from site-packages with non Python tooling, then rebuild the environment or container from a clean image pinned to 1.106.2 by digest. Rotate every credential the host could reach. The security vendors who analysed the sample all recommend a rebuild over a cleanup.
- Does using a managed AI gateway prevent this?
- It removes the gateway itself from your software supply chain: there is no gateway package in your pip install, no gateway container in your cluster, and your upstream provider keys are not stored in a database on a box you administer. It does not protect a developer workstation that installs a poisoned package of any kind, which is why per key spend limits, model allowlists and short lived keys still matter: they decide how much damage any one stolen credential can do.
- SEP '26
One in ten exposed LiteLLM gateways still answers to sk-1234: your AI gateway is a tier one security asset now
Wiz Research scanned 3,074 internet facing LiteLLM instances and found 9.6% accepting the example master key or no authentication at all. CISA put the MCP auth bypass on its exploited list with a 16 September patch deadline. The gateway that holds every provider key, reads every prompt and can reach your cloud metadata service is the most privileged box in your AI stack. Here is what the findings mean and how to run one safely.
- JUL '25
Build vs Buy: Open-Source Routers (LiteLLM, Helicone) vs Requesty SaaS
- JUL '26
$1.8k before anyone noticed: how to cap runaway agent spend
Retry storms, agent loops, and stolen keys are the three ways teams lose four figures of LLM budget in a day. A practical setup for hard caps, per key limits, alerts, and loop detection.
- JUL '26
Why AI gateways should be built in Go: a language audit of every major gateway
We audited the language and runtime behind every major AI gateway: OpenRouter on Cloudflare Workers, LiteLLM in Python, Helicone's Rust rewrite, Kong on Nginx/Lua, Vercel on Node.js. Then we explain why Requesty's router is written in Go, and what that buys you in latency, reliability, and regional deployment.
- MAY '26
Give every team exactly the models they need (and nothing more)
Approved Models set the org floor. Access Lists narrow it per team or per key. Expiring keys enforce rotation. Together they give platform engineers a governance stack that scales from 3 people to 300 without a single Slack argument about who broke prod.
- SEP '26
Every agent uses the same key: why shared credentials break spend attribution and what to do instead
In the last 72 hours the same problem surfaced independently in r/AI_Agents, r/mlops, r/ciso and r/sysadmin: hundreds of agent instances on one API key, an invoice that is one number, a security team saying shared keys are bad, and an engineer who cannot see how managing hundreds of credentials is better. Gateway questions on Reddit ran 5.6x the prior 72 hours. Here is the answer: identity per agent is a routing layer feature, not a secrets management project.
