4 min readdev.to
How a Key Leaked in May Shipped Malware in October
The LiteLLM attack had no clever trick in it. Every step was a key or token that still worked long after anyone needed it. And the malware ran before Python imported anything.
On 27 May 2026, someone committed a config file to a public repository by mistake. Inside it was the master key — the admin password, in effect — for one of LiteLLM's internal services. They rewrote the git history to hide it, and everyone moved on. Nobody switched the key off.
It was first used on 10 October. That is 136 days later.
Read what follows slowly, because nothing in it is clever. No bug, no zero-day. Every step is a key or token that still worked long after anyone needed it.
The chain
LiteLLM is an open-source gateway: one service your apps talk to instead of calling OpenAI, Anthropic, Bedrock and the rest directly. It routes requests, tracks spending and manages keys — which also means it holds every AI provider key a company owns.
The leaked key unlocked an internal tool for letting AI agents open pull requests. When the attacker called it, the tool handed back a GitHub token belonging to an account that owned the whole organisation. Nobody had limited what that token could do. LiteLLM's write-up says it "was not associated with a service account and was not scoped down." It could do anything, anywhere in their GitHub.
With that token, the attacker pushed a throwaway job into LiteLLM's main repository. GitHub Actions jobs can read the repository's secrets. So this one read them and posted them to a URL the attacker controlled: six, including a second maintainer's personal token. A second job took a third maintainer's. Both were deleted minutes later. Two days on, another round took fourteen more from the private repositories, including Anthropic, OpenAI and Bedrock keys.

Each step is a credential that outlived its purpose. Source: LiteLLM postmortem, 23 Oct 2026.
Then they waited nine days.
On 21 October they pointed LiteLLM's release pipeline at a fake copy of the repository: Berri-AI/litellm, with a hyphen, instead of the real BerriAI/litellm. Branch protection blocked the change. The owner token overrode it.
Publishing a release takes two people: one to start it, one to approve. The approver is normally a locked-down account tied to an office machine. That rule had been relaxed days earlier so any two engineers could do it. The attacker held three maintainers' tokens, so they started the release with one and approved with another — LiteLLM thinks to keep the audit log looking ordinary.
The poisoned version went up on PyPI and Docker. A researcher spotted it twenty-seven minutes later. PyPI pulled it within two hours.
The part that catches people
The bad package installed three files. One is cook.pth, and that extension is the whole story.
A .pth file in site-packages normally just lists extra folders for Python to search. But it has an old compatibility feature almost nobody thinks about. If a line in that file begins with import, Python runs it. Not when you import the package — at startup. The docs say these lines "are always run at Python startup... regardless of whether a particular module is actually going to be used."

A .pth line beginning with import is run by Python's site module at startup. Source: Python docs, site.
So the stealer never waited to be imported. It ran whenever any Python process started on that machine: your script, a test run, an unrelated project.
Including this one:
pip install --upgrade litellm
pip is itself a Python program. Running it starts Python, which runs the stealer again, before the upgrade fixes anything.
What to do this week
- Find the files without starting Python:
find / -name 'cook.pth' -o -name 'sitecustomize.py' 2>/dev/null. Delete them and any matching.pycfiles before you run pip. - Assume everything on that machine is gone, not just the AI keys. The stealer read cloud credentials, SSH keys, browser data, your database URL, and the config files for Codex, Claude, Gemini and Cursor.
- Pin container images by digest, not tag.
latest,main-latestandmain-stableall pointed at the bad build. - Go find keys nobody needs any more. That is the root cause, and the only item here you can act on before an incident rather than after.
Where this breaks down
Calling this "just credentials" undersells one thing. The relaxed two-person rule mattered. Had the approver still been that locked-down office machine, three stolen tokens would not have been enough. The control was doing real work, right up to the moment someone loosened it for convenience.
LiteLLM has said some of its early updates were wrong and more changes are coming, so treat this timeline as the best account so far.
Takeaway
Revoking a leaked key is not cleanup you do later. It is what decides whether a mistake in May becomes malware in October.
What is the oldest credential still working in your infrastructure, and would you know if someone used it?