According to the project's director of security, the attacker used one account to trigger the release run and another to approve it. The poisoned package reached PyPI wheels and Docker images before it was quarantined.
It is the second LiteLLM compromise this year, and the pattern is worth reading carefully, because the weak point was not the code. It was the identities allowed to ship it.
What Happened
The compromised release is LiteLLM 1.107.0. Reporting from SafeDep, Socket, StepSecurity and Upwind, summarised by The Hacker News on 21 October, says the bad version shipped as PyPI wheels for macOS, Linux and Windows, plus Docker images. The source tarball carried a tampered pyproject.toml but no payload files. PyPI has quarantined the package, and the malicious images have been removed.
On how it was published, the project's director of security, Oliver Jensen, said the malicious actions went through three engineers' GitHub accounts, apparently by way of stolen GitHub CLI OAuth tokens. One account triggered the project-releaser run and another approved it. All the tokens involved have been revoked. LiteLLM had not yet worked out how the tokens were obtained, so it could not rule out further access to those accounts. As a stopgap it removed every user from its GitHub organisation except one unaffected account, and it pulled trusted links to that organisation from public registries so nothing more can be published through them. The project's issue thread has the detail.
The advice for anyone who installed 1.107.0 is to revoke and replace exposed credentials from a clean environment, and to pin 1.106.2 by digest.
This is not the first time. In March, versions 1.82.7 and 1.82.8 were compromised in an incident that reporting tied to the TeamPCP group and to a compromise of the Trivy scanner. It used the same startup-file trick you will see below.
How the Payload Works
Version 1.107.0 uses a path file called cook.pth, which contains a single line: import cook. Python processes .pth files in site-packages when the interpreter starts, so the code runs on any Python launch in that environment, whether or not anything ever imports litellm. Monitoring that watches imports never sees it. The March incident used the same mechanism.
There are two payloads, according to StepSecurity as relayed by The Hacker News. The first is a credential stealer that calls itself privcollect. It gathers cloud tokens, developer accounts and private keys, credential files for AI tooling, Chromium and Firefox browser data, and environment variables. If it finds a LiteLLM deployment, it also goes after the proxy's saved credentials and model configuration. Everything is packed into a compressed archive and sent by HTTP POST to an attacker-controlled endpoint.
The second payload forks a background daemon that checks a GitHub dead drop every hour. The dead drop is a text file in a repository that names a domain. If it holds one, the malware runs the stealer again and then fetches and executes whatever Python that domain serves. Using a legitimate platform for the pointer means the operator can move the backdoor's address without publishing anything new, and the traffic blends in. The repository now returns a 404.
Why LiteLLM? An LLM proxy is a credential aggregation point. Every provider key a team uses flows through it. NetSPI made that point in its write-up of the March attack: the same design that makes a proxy convenient makes it devastating when compromised.
Why This Is an Identity Problem
Start with the tokens. A GitHub CLI OAuth token is a bearer credential. Whoever holds it acts as the engineer, with no password prompt and, at the point of use, no MFA challenge. These tokens sit on developer machines, are often long-lived, and are rarely inventoried. LiteLLM has not yet found how they were taken, but the shape of the attack is clear: steal a few tokens, become a few engineers.
Then the control that was meant to catch it. Requiring one identity to trigger a release and another to approve it is sound design. It counts identities, though, not independent humans. When an attacker holds two of the identities, the four-eyes check becomes a formality that leaves a clean audit trail. Nothing in the pipeline had to be forged. Every step was performed by an account entitled to perform it.
Then the publishing identity itself. LiteLLM's emergency step was to remove the trusted links between its GitHub organisation and the public registries. That tells you where the real publishing privilege sat: in the identities of a handful of engineers.
The cost lands downstream. Anyone consuming an unpinned version gets the poisoned release automatically. In the March incident, InfoQ reported that a researcher's machine was compromised simply by starting a local MCP server through an editor, which pulled the latest package minutes after it was compromised. And the payload's target list reads like an identity inventory: cloud tokens, private keys, AI tool credentials, browser sessions and environment variables.
Five Practical Priorities
1. Treat release rights as privileged access
Whoever can publish a package can run code on every machine that installs it. Put maintainers and release engineers under the same discipline as administrators: scoped permissions, regular review of who holds them, and short-lived federated credentials in preference to personal long-lived tokens for anything that can publish.
2. Make approval independent in fact, not just on paper
Require approvals from accounts with phishing-resistant MFA, ask for fresh authentication at the moment of approval, and alert when a release is triggered and approved close together or from the same device or network. If one compromise can supply both signatures, you have one control, not two.
3. Pin and verify what you consume
Pin exact versions and, where you can, digests. Consider a short delay before adopting brand-new releases. In the March incident the package was quarantined within roughly 40 minutes of being reported, and a delay of even a day would have kept most consumers clear. LiteLLM's own advice for this incident is to pin 1.106.2 by digest.
4. Shrink what a developer machine or CI runner can hold
Environment variables full of provider keys are a stealer's first stop. Keep secrets in a manager, issue separate scoped keys per service, and use spend limits and allow-lists on provider keys so that a stolen one is worth less.
5. Know which keys were on the box
If 1.107.0 ran anywhere, treat everything reachable from that environment as exposed: cloud credentials, SSH keys, CI tokens, registry credentials and LLM provider keys. Rotate from a clean machine. Keep an inventory so you can answer "what was on there?" in minutes, not days.
The Trevonix Perspective
At Trevonix, we see this as an identity incident that happens to involve software. The attacker did not need to defeat code review or break the pipeline. They needed a few identities, and those identities were protected lightly relative to what they could do. That is the lesson we draw from supply chain and third-party identity security: the trust you extend to a dependency is, in the end, trust in the people and accounts behind it.
The AI angle makes the stakes plainer. LLM gateways, agent frameworks and the tokens they hold are non-human identities with access to expensive and sensitive things, and they are being adopted faster than they are being governed. Our work on identity for AI and non-human identities and on privileged access management starts from the question this attack asks: who, or what, is allowed to act, and how would you know if it was not them?
Key Takeaways
- LiteLLM 1.107.0 was published through three engineers' GitHub accounts, apparently using stolen GitHub CLI OAuth tokens. One account triggered the release and another approved it.
- The payload runs at Python startup through a .pth file, steals credentials and sets up a backdoor with a GitHub dead drop.
- The weak point was identity: bearer tokens, and a four-eyes control that two stolen accounts could satisfy.
- If you installed 1.107.0, rotate everything reachable from that environment and pin 1.106.2 by digest.
- Treat release and publishing rights as privileged access.
Source Credits: The Hacker News, StepSecurity, SafeDep, Socket, LiteLLM GitHub issue, InfoQ, NetSPI


