# LiteLLM PyPI compromise teardown: stolen token, .pth file

> How malicious litellm 1.82.7 and 1.82.8 reached PyPI with a token stolen via Trivy, what the payload stole at Python startup, and whether Chainsaw stops it.

Source: https://chain305.com/teardowns/litellm-pypi-compromise/

---

# Teardown: the LiteLLM PyPI compromise

A publish token stolen through a security scanner, and a stealer that ran every time Python started.

What happened on 24 March 2026, how a CI secret became a PyPI release, and where Chainsaw would and would not have stopped it.

By [Muhammad Zeeshan Sultan](https://chain305.com/about/#founder), founder of Chain305 · October 2026

Questions and answers

## What happened?

On 24 March 2026, two malicious versions of the litellm Python package, `1.82.7` and `1.82.8`, were published to PyPI with a credential stealer inside. LiteLLM’s security update says they were live from 10:39 UTC for about 40 minutes before PyPI quarantined them, though the same page lists 10:39 to 16:00 UTC as the window in which an install could have picked them up. The Python Packaging Authority published advisory PYSEC-2026-2 (also MAL-2026-2144) at 15:35 UTC that day. Researchers attribute the attack to TeamPCP, the group behind the Trivy compromise the week before.

LiteLLM says no malicious code reached its GitHub `main` branch, and that users of the official LiteLLM Proxy Docker image were not affected. The exposure was `pip install litellm` without a pinned version, directly or as a dependency of another tool.

## How did the attacker get publish rights?

With a PyPI API token taken from LiteLLM’s own CI pipeline. The PyPA advisory says the token was exposed through an exploited Trivy dependency. LiteLLM’s CI ran the Trivy security scanner, and TeamPCP had already tampered with Trivy’s releases and its GitHub Action tags. Berri AI’s CEO told The Register that the `PYPI_PUBLISH` token was stored as an environment variable in the project’s CI, was sent to Trivy, and was taken from there.

The maintainers’ accounts had two-factor authentication, but an API token does not need it. LiteLLM’s update says the attacker skipped the official release workflow and uploaded the packages to PyPI directly.

## What changed in the package?

The two malicious litellm versions published to PyPI on 24 March 2026, `1.82.7` and `1.82.8`, carry the same payload in different places, according to StepSecurity:

-   **`1.82.7`:** a base64 blob inside `litellm/proxy/proxy_server.py`, which runs when the proxy module is imported or started.
-   **`1.82.8`:** a new 34,628-byte file, `litellm_init.pth`, listed in the wheel’s own `RECORD` with a valid hash. LiteLLM says `1.82.8` additionally kept the `proxy_server.py` payload.

Both versions used the same RSA public key and the same exfiltration endpoint, `models.litellm[.]cloud`, which is not a LiteLLM domain. The `.pth` file is the bigger change. Python executes lines in `.pth` files in `site-packages` at interpreter startup, so in practice `1.82.8` ran the payload every time Python started, with no `import litellm` needed.

## What ran on import?

In litellm `1.82.8`, the payload was triggered every time Python started, with no `import litellm` needed. Python executes lines in `.pth` files in `site-packages` at interpreter startup, and `litellm_init.pth` launched a detached Python process to decode and run the payload. `1.82.7` ran the payload when code imported the LiteLLM proxy.

Then, according to StepSecurity and the PyPA advisory, the payload:

-   **Collected credentials,** for example SSH keys, Git and Docker credentials, AWS, GCP and Azure config, Kubernetes configs and service-account tokens, `.env` files, `~/.npmrc`, `~/.vault-token`, `~/.pgpass`, shell history, TLS keys, CI/CD files and crypto wallets.
-   **Called cloud APIs live:** queried the cloud metadata service at `169.254.169.254` and read secrets from AWS Secrets Manager and SSM Parameter Store.
-   **Sent the data out:** encrypted everything with AES-256, wrapped the key with RSA, and POSTed the result as `tpcp.tar.gz` to `models.litellm[.]cloud`, which is not a LiteLLM domain.
-   **Stayed on the host:** wrote `~/.config/sysmon/sysmon.py`, ran the script as a systemd user service named “System Telemetry Service”, and polled `checkmarx[.]zone/raw` for more instructions.
-   **Spread through Kubernetes:** with a service-account token, created privileged pods named `node-setup-<node-name>` in `kube-system`, mounting each node’s root filesystem and installing the same backdoor there.

## Would Chainsaw have stopped it?

Yes, for litellm installs that went through the Chainsaw proxy: `1.82.8` is refused on first download, and `1.82.7` is kept out by the release hold until the malware feed lists the version. The evidence is a re-run of 58 attacks from 2026 against production Chainsaw (v0.22.118) on 19 October 2026:

-   **`1.82.8` is refused on day 0.** A default rule refuses packages that execute code at import time in clearly malicious shapes. The `.pth` line in `1.82.8` launches a child Python interpreter on a base64-decoded payload, which is one of the refused shapes, so the first request for the wheel fails.
-   **`1.82.7` is not refused on its bytes.** The `1.82.7` payload is a bare `exec` of decoded code inside a module. A bare `exec` only adds to a package’s risk score, because a broader import-time rule flagged widely used packages such as pip, setuptools and numpy.
-   **The release hold covers `1.82.7`.** Chainsaw holds every new PyPI version for 48 hours by default, so an unpinned `pip install litellm` resolved to `1.82.6`. The known-malicious feed listed `1.82.7` about 11 hours after the attack date, well inside the hold, so `1.82.7` stays refused after the hold ends.
-   **Installs that bypassed the proxy got the malware.** A machine or CI runner pointed straight at pypi.org was not protected. Chainsaw therefore also gates CI, pins laptops through MDM and blocks direct registry traffic at the network.

## What should you do?

Treat any machine, container build or CI runner where litellm `1.82.7` or `1.82.8` was installed as compromised, according to LiteLLM and the PyPA advisory. First confirm the install (step 1), then rotate every credential the host could reach from a clean machine (step 3).

1.  **Check what you installed.** Run `pip show litellm`, and search lockfiles, Docker builds and CI logs for the two versions. Look for `litellm_init.pth` in `site-packages`.
2.  **Look for persistence.** Check for `~/.config/sysmon/sysmon.py` and `~/.config/systemd/user/sysmon.service`, and stop and remove the service if present. In Kubernetes, run `kubectl get pods -n kube-system | grep node-setup`, delete any matches, and check each node for `/root/.config/sysmon/sysmon.py`.
3.  **Rotate every credential the host could reach**, from a clean machine, for example SSH keys, AWS, GCP and Azure credentials, Kubernetes tokens, database passwords, API keys in `.env` files and environment variables, and package registry tokens. Rotate secrets held in AWS Secrets Manager and SSM too, even without evidence of a read.
4.  **Search network logs** for `models.litellm[.]cloud` and `checkmarx[.]zone`.
5.  **Pin litellm** to `1.82.6` or to a later verified release. LiteLLM shipped `1.83.0` on 30 March 2026 from a rebuilt release pipeline.

For the next one:

1.  **Pin CI tools and actions to a commit or digest, not a tag.** Trivy reached LiteLLM’s pipeline through version tags repointed at malicious code.
2.  **Keep long-lived publish tokens out of CI environment variables.** Any tool in the job can read them. LiteLLM says it is moving to PyPI trusted publishing and signed releases.
3.  **Pin your own dependencies.** The official LiteLLM Docker image pinned its dependencies and never pulled the bad versions.
4.  **Hold brand-new releases before installing.** Both windows LiteLLM reports, 40 minutes and about five hours, are shorter than a one-day hold.

## Sources

-   LiteLLM, [Security Update: Suspected Supply Chain Incident](https://docs.litellm.ai/blog/security-update-march-2026), March 2026, updated 27 March 2026.
-   Python Packaging Authority, [PYSEC-2026-2 / MAL-2026-2144](https://osv.dev/vulnerability/PYSEC-2026-2), 24 March 2026.
-   BerriAI/litellm, [issue #24512: supply chain incident report](https://github.com/BerriAI/litellm/issues/24512).
-   StepSecurity, [LiteLLM credential stealer hidden in PyPI wheel](https://www.stepsecurity.io/blog/litellm-credential-stealer-hidden-in-pypi-wheel).
-   The Register, [LiteLLM infected with credential-stealing code via Trivy](https://www.theregister.com/security/2026/03/24/litellm-infected-with-credential-stealing-code-via-trivy/5223422), 24 March 2026.
-   isotope13, [LiteLLM PyPI compromise](https://isotope13.ai/compendium/2026/litellm/).

One teardown per notable attack

## Get the next teardown by email

What happened, the install-path angle most coverage misses, and the rule that refuses it.

[Subscribe to teardowns](https://chain305.com/teardowns/) [Read what a package firewall catches →](https://chain305.com/what-is-a-package-firewall/)
