跳到正文
DEV Community· Dhruv Agrawal·· 5 小時前精選AI 評分79

LiteLLM 5 月外洩密鑰如何導致 10 月發布惡意程式版本

How a Key Leaked in May Shipped Malware in October

AI 導讀

LiteLLM 一枚於 5 月 27 日外洩且未被撤銷的內部服務主密鑰,136 日後被用來取得 GitHub 權限,最終令含惡意程式的版本於 10 月 21 日發布至 PyPI 和 Docker。

推薦理由

文章沿憑證外洩、GitHub 權限擴張到發布審批鬆動,還原攻擊鏈,並指出撤銷閒置密鑰與維持雙人審批是事前可介入的控制點。

正文 · 原文

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.

Six-step chain from a key leaked on 27 May to a malicious release on 21 October, with no exploit at any step
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."

Two flows compared: the expected one where malicious code runs only on import, and the real one where the site module executes the .pth import line at interpreter startup
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 .pyc files 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-latest and main-stable all 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?

Sources

來源:DEV Community · dev.to