A two-hour LiteLLM release stole the keys on the box
A poisoned gateway package ran at interpreter startup. The root cause was a master key nobody revoked. Cleanup is still credential rotation.
On October 21, 2026, LiteLLM published version 1.107.0 to PyPI and to its official container registries. For about two hours it was the package an unpinned pip install would fetch. You did not have to import LiteLLM. Starting Python was enough.
Microsoft's October 23 write-up recovered cook.pth, a file whose entire contents are the line import cook. Python runs executable .pth files during site initialization, before any application code. On one Windows host Microsoft watched, a routine invocation to install an unrelated dependency started the collector. I keep thinking about that: the payload did not wait for anyone to call the gateway.
The proxy was sitting on the keys
LiteLLM is the open source proxy teams put in front of OpenAI, Anthropic and the rest. Socket puts weekly downloads at about 23 million. The process sits next to provider keys, a DATABASE_URL, cloud roles, and whatever a developer left in the environment. A poisoned install is a credential incident that happens to live in an AI gateway.
The key they listed for revocation and never revoked
The malware is not even the interesting failure. On May 27, 2026, a configuration file with the master key for LiteLLM's Agent Platform was committed to the public LiteLLM-Labs/lite-harness repository. Maintainers spotted it, force-pushed the commit away, and listed the key for revocation. The revocation did not happen. On October 10 the attacker called that API with the leftover key and received a GitHub CLI OAuth token belonging to an organization owner. Those tokens sit after 2FA. With owner access, throwaway CI branches posted two more maintainer tokens to webhook.site, then the workflow runs were deleted. On October 21 the private release workflow was pointed at a lookalike source, Berri-AI/litellm instead of BerriAI/litellm. A production gate that had been loosened that week, so two engineers could ship daily bugfix backports, was approved with a stolen token.
Seven wheels landed on PyPI between 11:39:51 and 11:40:13 UTC. Docker Hub and GHCR images followed at 11:46:56. Socket's scanner flagged the wheels seven minutes after publication. LidorMachluf opened issue 47826 at 12:06:41 UTC, within 20 minutes of publication. PyPI had pulled 1.107.0 by 13:23; the Docker tags lasted until 14:20. Customers on the private enterprise registry were not affected. The official git tag was clean. The build came from the lookalike repo.
What the collector reached for
The collector named itself PRIVCOLLECT. Socket counted 16 modules, each with its own error handler so one miss did not stop the rest. It looked for cloud credential caches, Kubernetes service-account files, SSH material, browser stores, and the auth files used by Codex, Claude, Gemini, Cursor and Copilot. If DATABASE_URL was set it started a Prisma child and selected credential rows from LiteLLM_CredentialsTable, limited to 500 rows. Microsoft is careful: a target in the source is not proof the grab worked.
If you might have pulled it
If you ran pip install on 1.107.0, or started a container tagged latest, main-latest, main-stable or v1.107.0 in those windows, LiteLLM says assume the host is compromised. Do not start python or pip on it to look around. That re-runs cook.pth. Delete cook.pth, cook.py and sitecustomize.py from site-packages with a non-Python shell, kill the daemon holding /tmp/remote_code_loader.lock, rebuild from 1.106.2, and rotate every secret the identity could read. An absent /tmp/.pc_once marker proves nothing. Containers wipe /tmp on replace.
A 20-200 person firm in DIFC or ADGM probably does not run LiteLLM. Some of you do, via a shared proxy a contractor stood up. The useful check is which of your tools sit where this package sat: a developer laptop with Claude credentials in a JSON file, a CI runner with GH_TOKEN, a Dockerfile that says latest. The May key spent from spring to October in a public repo after someone wrote "revoke this." That is a task a managing partner can give this afternoon. Pin versions, drop floating tags, and rotate anything that ever appeared in a commit, including commits that were force-pushed. Ask the vendors whose agents file pull requests on your behalf which GitHub tokens they hold, and whether those tokens belong to a person.
LiteLLM's job is to sit between applications and models and hold the keys. When that layer is poisoned, prompt masking does not help, because the stealer never needed a prompt. After you rotate, a client or a DIFC questionnaire still asks which identity talked to which model and whether personal data was in the request. That is why we mask person, organization, address, phone and email, in English and Arabic, before a prompt leaves the gateway, and why the audit log is hash-chained. It would not have stopped PRIVCOLLECT. It is the record you still have to produce once the package is gone.
The pin, and the issue that is still open
LiteLLM's advice is to pin 1.106.2. The seven wheels sat on PyPI from 11:39:51 to 13:23 UTC on October 21. The issue that caught them is still 47826.