DEV Community

yutianle
yutianle

Posted on

The .pth file that runs before your code: lessons from the LiteLLM 1.107.0 compromise

The .pth file that runs before your code: lessons from the LiteLLM 1.107.0 compromise

Version 1.107.0 of the LiteLLM Python package was published with a credential stealer and a backdoor inside it. The package has been quarantined on PyPI and the malicious Docker images have been removed. The delivery mechanism is what makes this incident worth a closer reading rather than the payload list. A path configuration file executes Python before any application import, so an operator who never called a LiteLLM function could still be compromised.

What happened

LiteLLM's director of security said the malicious actions were taken 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 tokens involved in the attack have been revoked. The organisation removed all but one unaffected user from its GitHub organisation as a temporary measure, and it has updated public registries to remove trusted links to its GitHub organisation so that further malicious packages cannot be published through that path.
The company also stated that the mechanism by which the tokens were acquired has not been identified, and that it therefore cannot assert that access to those GitHub accounts has ended. That sentence drives your own verification.

The wheel list

The affected artefacts are specific. Reporting identifies eight wheels and one source archive:

  • litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl
  • litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl
  • litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl
  • litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl
  • litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl
  • litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl
  • litellm-1.107.0-cp310-abi3-win_amd64.whl
  • litellm-1.107.0.tar.gz, which carries no payload files but contains a tampered pyproject.toml The source archive is on the list for a reason. Installing from a source distribution can regenerate a wheel on your own machine, which means a build pipeline that treats sdist as a safe fallback is not safe here.

Two payloads with different lifetimes

The first payload, self-named privcollect, is a credential stealer. It collects cloud tokens, developer accounts and private keys, AI tooling credential JSON files, browser data for Chromium and Firefox, and environment variables. It also targets LiteLLM deployments specifically, aiming at the proxy's saved credentials and model configuration. The collected data is packaged into a GZIP-compressed TAR archive and sent by HTTP POST to litellm[.]dev/healthcheck.
The second payload forks a background daemon that checks a GitHub dead drop every hour and runs whatever Python the attacker serves. The mechanism described involves a text file named domain.txt in a repository, and an HTTP GET against a path on that domain. At the time of reporting, that repository returned a 404, but a dead drop is only dead while nobody re-establishes it.
The first payload steals once. The second keeps a foothold that outlives any single version pin.

Why the .pth file matters more than the payload

The malware is launched by Python before LiteLLM is imported. The mechanism is a path configuration file named cook.pth containing a single line, import cook, which triggers the credential collector at interpreter start-up.
Path configuration files are processed by the site module during interpreter initialisation, which is the reason they are such a clean delivery vehicle. A .pth file does not need to be imported by your code, does not need a hook in your application, and does not care whether the package is used at all. Any Python process using that environment, including a one-line script or a scheduled job, runs the payload.
This is also not the first time the technique has appeared in this package. The source reporting notes that the same behaviour was observed in earlier LiteLLM compromises tied to the TeamPCP supply-chain campaign in March 2026. A repeat mechanism points to where to look, which is .pth files in site-packages directories across build agents, notebooks and container images.

What to check, in order

  1. Determine whether litellm==1.107.0 was installed anywhere, including base images, CI runners and developer laptops. Check the hash of any LiteLLM wheel you hold rather than trusting a version string in a lock file.
  2. List .pth files in every site-packages directory you can reach. An unexpected entry whose name does not belong to a known package is worth reading line by line. A .pth line beginning with import executes code; ordinary entries are just paths.
  3. If 1.107.0 was present, treat every credential reachable from that environment as exposed. The reported collection scope covers cloud tokens, developer accounts, private keys, AI tool credential JSON and browser data, which is a wider set than the LiteLLM configuration alone.
  4. Revoke and replace from a clean machine. Reading the stealer's target list on a host that may still run the daemon is not a clean environment.
  5. Pin to 1.106.2 by digest, as the reporting recommends, rather than by version string.

The part that does not get fixed by rotating keys

The unaudited question is upstream. Three GitHub accounts were used to publish a release through the project's own automation, and the entry point into those accounts is unknown. Rotating the credentials you exposed handles your side. It does not tell you whether the publishing pipeline of any other package you depend on has the same two-person approval weakness that let this release through.
The reporting does not establish how many downloaders installed 1.107.0 before quarantine, and it does not state how long the malicious release was available. Neither gap prevents action. Both mean the answer to "were we affected" has to come from your own package records rather than from the incident timeline.

References

Top comments (1)

Collapse
 
leverlark profile image
Leverlark •

Useful write-up, especially the point about not trusting a version string in a lock file. One update: LiteLLM published its root-cause report on Oct 23, so the token question now has an answer. An admin key for an internal agent platform was committed to a public repo in May. The file was deleted a few hours later, but the key was never revoked. On Oct 10 the attacker used it to pull an org owner's GitHub CLI token, then ran throwaway CI workflows to grab two maintainers' tokens. On Oct 21 the owner token overrode branch protection to point the release workflow at the lookalike fork, and a stolen maintainer token approved the production gate. LiteLLM also says the device-locked release approver had been relaxed to "any two engineers" that same week.

Two lessons I'd add to your list: a deleted secret isn't a revoked secret, and CLI OAuth tokens are unscoped and skip GitHub's sudo re-auth, so they shouldn't sit in a service's config. Report: docs.litellm.ai/blog/security-inci...

The Leverlark team

⌂ 换网址