Independent AI intelligence Two editions daily · ET
Fervor AI

Analysis · October 21, 2026 · concept

LiteLLMagent-securityagent-infrastructuremcp-security

LiteLLM 1.107.0 Shows Your AI Gateway Is a Credential Vault

A poisoned release was caught in about three minutes. That speed did not help anyone who had already installed it, and the reason is worth understanding.

The most valuable file in a modern AI stack is usually not the model, the prompts or the code. It is the small database that holds the API keys for every model provider a company pays for.

On October 21, someone went straight for it.

LiteLLM is one of the most widely used open-source AI gateways: a Python library and proxy server that gives your apps one OpenAI-style interface to more than 100 model providers. Version 1.107.0 landed on PyPI that morning with a credential stealer and a backdoor inside. A dependency scanner caught it within minutes. For anyone who installed it inside those minutes, the payload was set to run at their next Python start, before any alert could reach them.

This means the usual comfort ("the ecosystem catches these fast now") misses the point. Fast detection protects the people who install after the alert. Everyone who installed in the gap needs a different kind of protection, and most AI teams have not set it up.

What happened, in plain terms

Security firm AISLE timestamps the upload of eight 1.107.0 files between 11:39:51 and 11:40:13 UTC, and its scanner flagged the first wheel (a wheel is the prebuilt package file pip actually installs) at 11:43:05. That is 3 minutes and 14 seconds from upload to alarm. Socket's scanner recorded its own alerts a few minutes later.

The malicious wheels carried three extra files, according to SafeDep's analysis:

  • cook.pth, a 12-byte file containing one line: import cook.
  • cook.py, a credential stealer that collects SSH keys, cloud credentials for AWS, Azure, GCP and Kubernetes, CI/CD secrets, browser passwords, AI tool tokens, environment variables, and the LLM provider keys stored in a LiteLLM proxy database. It encrypts what it finds and uploads it.
  • sitecustomize.py, a backdoor that runs as a background process, reads a server address from a file on GitHub every hour, and runs whatever code that server returns.

It did not stop at PyPI. Socket observed the v1.107.0, latest, main-latest and main-stable container tags on GitHub's registry all pointing at the same malicious image at about 13:14 UTC, roughly an hour and a half after the PyPI upload. That is when Socket looked, not necessarily when the tags were pushed, but it means "I use the Docker image, not pip" was no protection either.

How did it get published? SafeDep's first read was that "someone changed the build outside the public repository," via a compromised release pipeline or stolen PyPI credentials. Later on October 21, BerriAI posted an update on the public GitHub issue which, as RuntimeWire summarizes it, says the attacker used three engineers' GitHub accounts, apparently through stolen GitHub CLI OAuth tokens (the saved login tokens that let the gh command-line tool act as you): one account started the release run and another approved it. How the tokens were stolen is still unknown. The researchers who analyzed the release name 1.106.2 as clean.

Why "installed" was the same as "ran"

Here is the detail that turns a bad day into a bad week.

Most people assume a malicious package can only hurt you if your code imports it. If litellm came in as a transitive dependency (pulled in by another package, not by you) and you never called it, you would feel safe.

You were not, because of that one-line cook.pth file.

Python reads .pth files from your site-packages folder every time the interpreter starts. They were designed to add folders to the import path. But the Python docs keep an old rule for backward compatibility: any line that starts with import gets executed. And they spell out the consequence: those lines "are always run at Python startup ... regardless of whether a particular module is actually going to be used."

So once 1.107.0 was installed, every new Python process started in that environment ran the stealer (the only standard escape is starting Python with -S, which skips this step entirely, and almost nobody does). Your test suite. A one-off script. A linter written in Python. SafeDep puts it bluntly: the stealer runs "even if litellm is never imported."

Think of it like a building where any tenant can leave instructions in the lobby that the doorman reads aloud every morning. You do not have to visit that tenant's office to hear them.

sitecustomize.py works the same way from a different angle. Python attempts to import a module by that name at startup, which is why the attacker used it for the long-running backdoor.

The docs say import lines in .pth files are deprecated starting in Python 3.15 and will eventually be ignored. That helps in a few years. It does nothing for the Python you run today.

The gateway is the jackpot

The stealer's target list tells you what attackers think an AI gateway is worth.

A LiteLLM proxy exists to sit in front of your model providers. Its virtual keys feature hands out internal keys with spend tracking and model restrictions, backed by a Postgres database and an admin master key. That design is good for cost control. It also means one host holds, or can reach, the real keys for OpenAI, Anthropic, Google and whatever else your team uses.

Steal those and you get three things at once: free compute billed to someone else, access to whatever data flows through the prompts, and a quiet place to hide abuse inside legitimate-looking traffic.

This is the shift worth naming. An AI gateway is a credential vault that also happens to route requests. Most teams treat it like plumbing: install, pin loosely, update when a new model ships. Vaults get a different level of care, and the LiteLLM release is the argument for giving the gateway that care.

Fast detection protects the next person, not you

Three minutes is impressive. It is also irrelevant to anyone whose CI job ran pip install -U litellm at 11:41.

That splits your defenses into two jobs:

  1. Don't be early. Make sure brand-new releases cannot reach your machines until the ecosystem has had time to look at them.
  2. Assume you will be early once. Make sure a stolen key from one host opens as little as possible.

Both are cheap to set up. Neither depends on anyone else's scanner.

Put this into practice

Step 1: Check whether you were hit

One rule first: do not run the suspect environment's own python or pip to check it. Every Python start there triggers the hook again, which is why Socket advises against starting it as a first step. Look at the files from the outside with plain shell tools instead.

# Was 1.107.0 installed anywhere on this machine?
find / -type d -name "litellm-1.107.0.dist-info" 2>/dev/null

# The planted startup-hook files
find / -path "*site-packages*" \( -name "cook.pth" -o -name "cook.py" \) 2>/dev/null

# Host markers SafeDep lists for the stealer and the backdoor
ls -la /tmp/.pc_once_* /tmp/remote_code_loader.lock 2>/dev/null

# Running processes that carry the stealer's environment flag (Linux)
grep -l PRIVCOLLECT_ACTIVE /proc/*/environ 2>/dev/null

A sitecustomize.py can be legitimate, so if one turns up next to a cook.pth, compare it against the copy you expect rather than deleting on sight.

For containers, look at what your deployments actually run, not what the compose file says. If you pulled ghcr.io/berriai/litellm:latest or main-stable on October 21, treat that image as suspect.

Also search your lockfiles across repos for litellm==1.107.0 or "1.107.0". If you find a hit, AISLE's advice is direct: treat the machine as compromised, remove the files, kill the background Python process holding the lock file, and rotate every credential that machine could reach: cloud, SSH, CI/CD, API, browser and Kubernetes. Expect that to take an afternoon, not ten minutes. The provider keys behind your gateway come first.

Step 2: Add a cooldown so you are never the first installer

A cooldown means "do not install any release younger than N days." Most malicious releases get caught in hours. A few days of lag costs you almost nothing.

If you use uv, Astral's fast Python package manager, the setting is exclude-newer. It accepts a date or a duration:

[tool.uv]
exclude-newer = "P7D"

That tells uv to ignore anything uploaded in the last seven days. When you need a fresh security patch for one package, the docs offer exclude-newer-package to set a different date (or false) for just that package.

On the npm side, GitHub put the same idea into a managed product this week. Its package firewall, in public preview since October 20, sits between your CI runners or coding agents and the registry, blocks packages flagged as malware, and enforces "a configurable cooldown period." It covers only npm and only Enterprise Cloud for now, so it would not have caught this PyPI release, but the pattern is the same.

Step 3: Pin what you deploy, by hash and by digest

Version pins (litellm==1.106.2) stop surprise upgrades. They do not stop a poisoned file uploaded under a version you have not installed yet, and they mean nothing for container tags like latest, which can be repointed at any time.

For Python, use a lockfile that records file hashes (uv's lockfile does, and pip-tools can with --generate-hashes) so the installer refuses any file whose contents changed. For containers, deploy by digest:

image: ghcr.io/berriai/litellm@sha256:<digest-you-reviewed>

A digest (the sha256: fingerprint of an image) names exact bytes. Nobody can repoint it.

Step 4: Shrink what one stolen key can do

This is the step that pays off on the day the other three fail.

  • Give the gateway its own provider keys with spend caps set at the provider, separate from the keys developers use.
  • Use the gateway's own scoping. LiteLLM's virtual keys support a per-key max_budget and a models list, so a leaked app key cannot run up an unlimited bill on every model.
  • Keep the gateway host boring. No developer tools, no shared CI runner, no SSH keys for other systems. The stealer collected everything on the box; give it less to collect.
  • Watch egress (outbound traffic). The stealer uploaded to a lookalike domain (litellm[.]dev), and the backdoor checked a file on GitHub every hour for its next server. A gateway that only needs to reach model providers can be restricted to exactly those hosts.

Honest limitations

Some of this is still unknown, and the article should not pretend otherwise.

The full root cause is still open: BerriAI points to stolen GitHub CLI tokens but has not said how they were taken, and the story moved several times on October 21 alone. The researchers also differ on a detail: SafeDep says the source tarball (the "sdist") carried an extra build setting that points at the payload folder but not the payload files themselves, while AISLE describes the sdist as clean. As of writing, PyPI's page listed 1.106.2 as the latest release, but this article could not independently confirm when 1.107.0 was removed or the current state of every container tag and mirror. Check the GitHub issue for current status before acting on any timeline here.

Cooldowns have a real cost: when a critical fix ships, you have to override them on purpose, and someone has to know how. Digest pinning means you own the update schedule, which is work. And none of this protects a laptop where someone ran pip install -U by hand outside your lockfiles.

Finally, these steps reduce damage. They do not make a compromised maintainer account impossible.

The question to ask about your own stack

Look at the machine that runs your AI gateway and ask one question: if a package on this box ran arbitrary code at the next Python start, what could it take?

If the honest answer is "every model key we have, plus the cloud credentials sitting next to them," you already know where to spend an hour this week. The scanners will keep getting faster. The window between upload and alert will never be zero, and the only part of that window you control is what sits on the box when it opens.

Sources: AISLE, RuntimeWire, SafeDep, Socket, BerriAI/litellm issue #47826, Python site docs, uv settings reference, LiteLLM virtual keys docs, GitHub package firewall changelog. All checked October 21, 2026.