deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Malware in LiteLLM 1.107.0 traced to a key deleted from git but never revoked

LiteLLM says a leaked admin key, deleted from a public repo in May 2026 but never revoked, let attackers hijack its release pipeline and push credential-stealing malware to PyPI for about two hours.

Malware in LiteLLM 1.107.0 traced to a key deleted from git but never revoked

LiteLLM version 1.107.0, published to PyPI on October 21, 2026, carried compiled malware designed to steal credentials and pull in remote-code-execution tooling, according to postmortems published by the LiteLLM team. The compromise began months earlier with an administrative key committed to a public repository in May and never revoked, even though the file holding it was deleted within hours. Security firm Wiz, citing ClickPy data, estimates the malicious build was downloaded roughly 49,000 times during the approximately two hours it was live. A detailed timeline assembled by Gabriel Anhaia on dev.to, drawing on LiteLLM's incident posts, reconstructs how one leaked key cascaded into a poisoned release.

How the attack unfolded

On May 27, 2026, a file named opencode.json appeared in the public LiteLLM-Labs/lite-harness repository containing an administrative key for an internal LiteLLM Agent Platform deployment. A follow-up commit removed the file about three hours later, but the key stayed valid and the commit remained reachable in public history until October 22.

On October 10, an attacker began making authenticated calls to the Agent Platform API using that key. One request returned the configuration of every managed agent, and one of those configurations held the GitHub CLI OAuth token of one of the two owners of LiteLLM's GitHub organization. The token was there because the platform needed GitHub access to open pull requests; instead of a narrowly scoped personal access token, it had been given an owner's CLI login, which carries no scoping. Minutes later, two throwaway branches ran CI workflows that posted repository secrets to an external webhook endpoint, harvesting tokens belonging to two more maintainers. Both workflow runs were deleted within about three minutes.

The main event came on October 21. Branch protection initially rejected a push to LiteLLM's private release repository; five seconds later the same change went through with the owner token. The modification added a source_owner input to the release workflow so it could build from any organization. Staging then ran against a lookalike repository, Berri-AI/litellm rather than BerriAI/litellm, and a second stolen token approved the production gate, which LiteLLM had recently relaxed so that any pair of engineers could trigger and approve a release. Between 11:39 and 11:47 UTC, seven malicious wheels landed on PyPI, and container images were pushed to Docker Hub and GHCR under the v1.107.0, latest, main-latest and main-stable tags. A third stolen token then deleted the workflow run records. PyPI removed the release by 13:23 UTC and the Docker tags were repointed to clean images by 14:20. LiteLLM notes that the official v1.107.0 source tag on GitHub never contained the malicious files.

LiteLLM's first postmortem misidentified the origin of the stolen tokens, suspecting internal deployment logs; an October 23 correction traced the first token to the Agent Platform.

Why deleting the file changed nothing

As the dev.to analysis explains, a commit that deletes a file only adds a new snapshot. The old snapshot, key included, remains reachable by hash from every clone and fork made in between. That is why GitHub's own guidance on handling exposed secrets puts revocation first: a dead key sitting in public history is harmless, while a live key in a deleted file is still a live key. This one stayed live for more than four months.

The piece also flags a coding-agent wrinkle. An agent session can add a config file in one turn and remove it in a later one, so a reviewer reading only the final pull-request diff sees neither change. LiteLLM does not say who or what made the May commit, and the analysis does not assume an agent did; the point is that final-diff review would have missed it either way.

Hardening steps

The dev.to post closes with practices aimed at this exact failure shape. Keep literal secrets out of agent config files: OpenCode, whose config file is opencode.json, supports environment and file-path substitution so keys live outside the repository. Search history before assuming a secret is gone, with commands like git log --all -S 'sk-', which catches both the commit that added a string and the one that removed it. When something turns up, revoke at the provider first, check the provider's logs for use since the commit date, and only then consider rewriting history. Finally, scope the GitHub credentials an agent session can reach: a service that only needed to open pull requests should never hold an owner-level CLI login.

LiteLLM's October 30 follow-up turns revocation into process, with secret scanning now opening tickets automatically and tickets closing only once revocation is confirmed.

Why it matters

This is a supply-chain attack aimed squarely at AI development tooling, and the roughly 49,000 downloads in the exposure window show the blast radius of even a short-lived poisoned release. The chain is a checklist of common failure modes: an unrevoked key, an over-privileged token standing in for a scoped one, relaxed release gates, a lookalike organization name and deleted audit trails. Every lesson here generalizes to any team that runs coding agents and CI pipelines anywhere near production credentials, and the incident is a concrete reminder that in git, deleting a file hides a secret from the working tree but not from anyone willing to read the history.

  • #supply-chain
  • #pypi
  • #security
  • #python
  • #litellm

Related posts