LiteLLM 1.107.0 Shipped a Credential Stealer, Built From a Lookalike Repo
Security / news
LiteLLM 1.107.0 Shipped a Credential Stealer, Built From a Lookalike Repo
The poisoned release ran in Python startup hooks for 103 minutes on PyPI; the official GitHub tag is clean and LiteLLM 1.106.2 is the version to return to.
Anyone who installed litellm==1.107.0 from PyPI, or pulled the latest Docker tag, on October 21 should treat every credential on that host as stolen (provided the host had outbound internet access), according to the maintainers' own incident notice. The release was built by LiteLLM's real pipeline from a lookalike source repository, so the official GitHub tag for 1.107.0 is clean while the published artifacts are not.
The fix is to move to litellm==1.106.2, the last clean release named by the maintainers, and to rotate secrets from a machine that never ran the bad version.
What LiteLLM 1.107.0 does when Python starts
LiteLLM is an open-source gateway that routes requests to dozens of model providers, so its proxy databases and environments hold provider API keys by design. The attacker did not alter application code. According to BleepingComputer, the package adds three files to site-packages: cook.pth, cook.py and sitecustomize.py.
Python executes .pth import lines at startup and then imports sitecustomize, so the payload runs on every interpreter launch in that environment, whether or not the application imports LiteLLM. Running pip to investigate triggers it too. Socket's research team writes that downloading the wheel or reading its ZIP contents as data does not execute the hooks.
Socket describes a roughly 61 KB obfuscated collector with 16 modules covering cloud and deployment credentials, SSH and GPG keys, .env files, browser databases and AI tool credentials. When DATABASE_URL is set it also reads the proxy's credential and model tables. Output is encrypted with AES-256-CBC, the key is wrapped with RSA-4096, and the archive is POSTed to litellm[.]dev/healthcheck.
A second stage polls a pointer file in a GitHub repository and runs whatever Python it is served. Socket's summary of that stage: "There is no signature or content-hash verification." At 13:01 UTC the pointer returned an empty body, which stops the loader but not the collector.
Timeline of October 21 (UTC)
| Time | Event |
|---|---|
| 10:08 | Branch protection rejects a workflow change; a privileged developer credential overrides it |
| 11:39 | Seven malicious wheels uploaded to PyPI |
| 11:46 | Socket's alerts issued; Docker images with the payload published |
| 13:23 | PyPI has removed 1.107.0 |
| 14:20 | Last affected Docker Hub tags removed or repointed |
The scanner flagged the wheels about seven minutes after upload, per Socket, yet the artifacts stayed installable on PyPI for about 103 minutes, from 11:39 to 13:23.
How the attacker reached the release pipeline
The maintainers' post, signed by CEO Krrish Dholakia, CTO Ishaan Jaffer and Director of Security Oliver Jensen among others, says the release process had a private repository, Trusted GitHub Publishers, immutable Docker tags and signing, and that the attacker "had to compromise multiple independent security controls".
Their account is that release permissions had been widened for two engineers on Monday to ship backports, and that the attacker held three developers' GitHub CLI credentials. A token belonging to one of two GitHub organisation owners was then used to disable branch protection and force-push a change that pointed the releaser at Berri-AI/litellm, which Socket notes differs by one hyphen from the legitimate BerriAI/litellm.
The post also says an unexpected, innocuously named account existed on an internal LiteLLM deployment exposed to the internet, and that tokens may have been taken from its logs. It adds: "We have not yet confirmed how the attacker gained access to our internal deployment." Enterprise customers on the private release were not affected.
What to do now
No CVE or CVSS score applies, because this is a poisoned release rather than a flaw; exposure depends only on whether 1.107.0 ever executed.
- Pin to
litellm==1.106.2and rebuild images and virtual environments from scratch. - Do not start Python in an affected environment; delete the three payload files and kill the process holding
/tmp/remote_code_loader.lock. - Rotate cloud role credentials, every provider key, the proxy master key and the
DATABASE_URLpassword. - Search CloudTrail for
GetCallerIdentitycalls from the instance role near install time.
Agent tooling that stores many keys in one place is a recurring target; the sandbox changes in NVIDIA's OpenShell v0.1.5 address a different layer of that problem. For the pattern of exploitation beginning before a fix is public, see the PHP2Shell timeline. LiteLLM has said its investigation is continuing, with scope and further indicators still pending.
Sources
More in Security
- 01Railway Says Attacker Decrypted Environment Variables for Up to 1.85% of WorkspacesIts October 22 preliminary report points to a hunt for AI provider keys, names no root cause, and has not ruled out access to customer source code.
- 02Plex2Shell Chains Four CVEs Into Unauthenticated Code Execution; CISA Lists It as ExploitedPlex Media Server builds before 1.43.3.10861 are affected; a public proof of concept sits on GitHub and the federal patch deadline is October 23.
- 03GitLab Patches CVE-2026-110264, a CVSS 10.0 Unauthenticated RCE, in 19.5.1The regex compiler overflow reaches back to version 7.0, has no listed workaround, and is the second 10.0 flaw in that component in about ten days.
- 04Quad9 Will Stop Serving France on November 24 Rather Than Block 58 Pirate Streaming DomainsThe Swiss DNS resolver says a Paris court set a €1,000 daily penalty after beIN sought €580,000, and that it cannot tell which of its users are French.