💰 Calculate Gross-Net Salary→

LiteLLM 1.107.0: From an Unrevoked Agent Key to a Poisoned Release

· Aloka Technology· 0 views· 7 min read
LiteLLM 1.107.0: From an Unrevoked Agent Key to a Poisoned Release

LiteLLM's own post-mortem traces a malicious PyPI release to a master key committed to a public repo in May. We follow the chain, the .pth startup implant and the controls that would have broken it.

On October 21, 2026, pip install litellm could deliver a credential stealer that runs every time Python starts. The malicious release, 1.107.0, was live on PyPI for roughly two hours. According to LiteLLM's own post-incident account, it did not start with a bug in LiteLLM's code. It started with an internal agent-platform master key committed to a public repository in May and never revoked.

The question: how does a leaked key for an internal tool turn into a signed, legitimate-looking package release, and which controls would have interrupted the chain?

Background: what was affected

LiteLLM is an open-source gateway that fronts many LLM providers, so its hosts typically hold provider API keys and cloud credentials. The official incident post (October 21) lists: PyPI litellm==1.107.0, all seven wheels, live from 11:39 to about 13:23 UTC; and Docker Hub and GHCR tags v1.107.0, latest, main-latest and main-stable, pointing at compromised images from 11:46 UTC until removal, around 14:20 UTC for Docker Hub and 13:51 for GHCR. The official GitHub source tag does not contain the malicious files; the artifacts were built from an attacker-controlled repository. LiteLLM states that Enterprise customers running its private enterprise release were not affected, and recommends pinning 1.106.2.

This was the second LiteLLM compromise reported in 2026. CSA describes a March incident (versions 1.82.7 and 1.82.8) that arrived through the Trivy scanner; I did not verify that earlier incident against primary sources, and Socket reportedly found no link between the two.

How it works: the attack chain

LiteLLM's October 23 attack-chain post gives timestamps; the CSA note summarizes them. In order:

  1. May 27. A config file containing the LiteLLM Agent Platform master key was committed to a public repo. The commit was force-pushed over, but the key was not revoked.
  2. October 10. The attacker used the key against the Agent Platform API. One response returned a GitHub CLI OAuth token belonging to an organization owner; LiteLLM says it was not a service-account token and was not scoped down.
  3. October 10 to 12. Throwaway branches ran CI workflows that posted repository secrets, including two more maintainers' tokens, to an external webhook endpoint. The run records were deleted.
  4. October 21, morning. The release workflow was pointed at a lookalike fork, Berri-AI/litellm versus the real BerriAI/litellm. Branch protection blocked the push, so the owner token disabled protection and force-pushed. The production gate, relaxed days earlier so any two engineers could approve, was satisfied with two stolen maintainer tokens.
  5. 11:39 UTC. Wheels were published through the real pipeline. Run records were then deleted and the workflow change reverted.

The key point, in LiteLLM's analysis as relayed by CSA: earlier controls (private release repository, PyPI Trusted Publishing, immutable tags, image signing) all depended on privileged tokens, so a stolen owner token bypassed them. The artifacts carried valid provenance because the legitimate pipeline built them.

Deep analysis: why a .pth file is a good implant

The payload used Python's startup behavior. The Python documentation for the site module says lines in a .pth file that start with import are executed, and that executable lines "are always run at Python startup ... regardless of whether a particular module is actually going to be used", unless -S disables site. The package shipped cook.pth and cook.py, which LiteLLM describes as compiled malware that steals credentials and loads remote-code tools, running on any Python start in the environment, including pip itself. CSA reports a stealer of about 61 KB collecting cloud, SSH, browser, CI, Kubernetes and AI-tool credentials, encrypting them and posting them to a lookalike domain. It also reports a sitecustomize.py loader that polls a GitHub dead-drop hourly; Datadog is cited as finding only test values there and no second stage.

The Python documentation also says import lines in .pth files are deprecated from Python 3.15 and will be ignored in later versions, so this vector is on a removal path, though not yet closed for current interpreters.

One correction is instructive. LiteLLM's first post suggested the GitHub tokens had leaked through gateway logs capturing coding-agent response bodies. An October 23 update said parts of that were inaccurate, and the attack-chain post names the leaked master key. CSA treats the later account as canonical, as do I. Early incident write-ups, including LiteLLM's own first post, can be wrong; wait for the post-mortem before drawing lessons about agent tooling.

Comparison: which control would have interrupted the chain

ControlStops which stepCaveat
Revoke any secret found in a public repo before cleaning historyInitial access (step 1)Force-pushing does not remove copies in forks or scrapers
Short-lived, scoped service identities instead of human OAuth tokens in agent toolsEscalation (step 2)Needs an inventory of where agents store tokens
Release workflow allowlist of source repos, fail closed on forksPipeline abuse (step 4)A token that can edit the workflow can edit the allowlist unless protected separately
Approvals that stolen tokens alone cannot satisfyApproval gateNeeds a second factor outside GitHub tokens
Digest pinning plus a cooldown before adopting releasesConsumer sideChainguard reportedly flagged this release about 16 minutes after the first wheel

Practical example: checking an environment

LiteLLM warns not to run pip first, since each Python start re-runs the payload. This untested sketch only looks for the documented indicator files; their absence proves nothing, as LiteLLM states.

# Look for documented indicators (read-only)
find / -name "cook.pth" -o -name "cook.py" -o -name "litellm-1.107.0.dist-info" 2>/dev/null
ls -l /tmp/remote_code_loader.lock 2>/dev/null

# Which hosts or images resolved 1.107.0?
grep -rn "litellm==1.107.0" --include="*.txt" --include="*.lock" .

For prevention, pin by hash so a republished or newly poisoned file cannot slip in. Also untested:

# requirements.txt: pinned with hashes (generate with pip-compile --generate-hashes)
litellm==1.106.2 \
    --hash=sha256:<hash-from-your-lock-file>

pip install --require-hashes -r requirements.txt

Hash pinning helps against later substitution, not against a release that was malicious when you first locked it. A cooldown, meaning waiting days before adopting a new version, covers that gap.

Limitations and failure modes

  • Several timestamps are approximate, and the disclosure does not say when the attacker obtained the master key, so the gap between May and October is unexplained.
  • Exfiltrated data was encrypted, so packet captures cannot show what was taken. Wiz is reported to have seen stolen AWS role credentials validated about four and a half hours after exfiltration.
  • Killing the daemon matters: StepSecurity warns that a re-registered deleted GitHub account could revive surviving loaders.
  • Cooldowns delay legitimate security fixes too; use an exception path for urgent patches.

Engineering recommendations

If any host or image pulled 1.107.0 during the window, assume exfiltration, rebuild from clean images and rotate instance roles, provider keys in the proxy database, master and salt keys, the database password and environment secrets, from a clean machine. Beyond cleanup: run a secrets scanner with automatic revocation on every repository, remove personal OAuth tokens from agent platforms, and alert on branch-protection changes, force pushes to protected branches and workflow-run deletions.

Conclusion

The compromise worked because every defence assumed the credentials behind it were safe. Next step: search your lockfiles and registries today for litellm==1.107.0, then list every long-lived token your agent tooling can read and replace the broadest one with a scoped service identity.

Sources

Information current as of October 28, 2026. I read the two official LiteLLM posts and the CSA note; I did not install or analyze the malicious package, and the March 2026 incident, Chainguard's timing and the Wiz observation come only from CSA's summary.

Share:

Leave a comment

Explore More

Discover More Articles

Browse Articles
↑Zalo