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 (0)