# Microsoft Security Blog

Compromised LiteLLM 1.107.0 PyPI release shipped cook.pth/sitecustomize.py startup hooks that auto-ran a credential stealer on any Python start, exfiltrating secrets to litellm.dev.

- Payout: Real-world hack
- Severity: critical
- Program: Microsoft
- Analysis by: Microsoft
- Disclosed: 2026-10-23
- Write-up quality: 8/10
- Original: https://www.microsoft.com/en-us/security/blog/feed/
- Library page: https://bugbountylibrary.com/r/blog%3Amicrosoft-com-en-us-security-blog-feed
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:incident platform:threat-intel sev:critical tech:python technique:source-review vuln:misconfiguration vuln:secrets-leak

**Target:** PyPI package / Python site initialization (.pth, sitecustomize.py)
**Root cause:** Stolen developer GitHub credentials let attackers alter the release workflow to publish from lookalike repo Berri-AI/litellm; the wheel placed cook.pth, cook.py and sitecustomize.py in purelib so Python's site init executed them on any interpreter start.
**Steps:**
1. Attacker used stolen GitHub creds to redirect release workflow to Berri-AI/litellm and a second account to pass production approval.
2. Wheel litellm-1.107.0.data/purelib/ ships `cook.pth` containing `import cook`, plus `cook.py` and `sitecustomize.py`, relocated into the environment library dir on install.
3. On any Python start, site init processes the .pth executable line, loading cook.py which Base64-decodes and zlib-decompresses the embedded PRIVCOLLECT program.
4. Collector checks `PRIVCOLLECT_ACTIVE` env var, sets it, spawns daemon thread gathering dev/AI tool tokens, cloud creds, metadata API creds, browser data, SSH/GPG keys, procfs envs, and LiteLLM proxy DB rows via Prisma.
5. Data JSON-serialized; fresh AES-256-CBC key/IV; key wrapped with embedded 4096-bit RSA-OAEP; packed into gzip tar (data.bin, key.bin, hdr.json).
6. HTTP POST to `hxxp://www[.]litellm[.]dev/healthcheck` with application/octet-stream and X-SHA256 header; only HTTP 200 treated as success, then writes `/tmp/.pc_once_72129118c59f5636c0bb`.
7. Separate sitecustomize.py loader locks `/tmp/remote_code_loader.lock`, reads GitHub-hosted `domain.txt` dead-drop, fetches `/codepy`, compiles and executes it (unconfirmed).
**How found:** Microsoft Threat Intelligence analyzed the affected wheels, recovering two execution paths (path extension and site-specific config), decoded the Base64/zlib PRIVCOLLECT payload, and matched endpoint telemetry on Windows/macOS to the collector's discovery, encryption and outbound behavior.
**Exploitation:** Supply chain compromise of the PyPI release and official container images; any environment that installed 1.107.0 and started Python (even without importing litellm) auto-executed the stealer, which encrypted harvested credentials and exfiltrated them to an attacker domain, with a re-armable remote-code loader on Unix.
**Escalation:** Startup hooks placed outside the litellm directory survive removal of the app files; the sitecustomize loader allows swapping second-stage payloads without republishing; harvested cloud/workload creds and proxy DATABASE_URL enable lateral cloud access.
**Insight:** Audit installed wheels for files landing in purelib outside the package dir — a single `import X` line in a .pth or a sitecustomize.py runs on every interpreter start, so package presence alone (not import) is the exposure signal; hunt for `.pc_e_` OpenSSL staging and .pth/sitecustomize hashes rather than the litellm folder.

## Proof of concept (copied exactly from the original report)

**Malicious .pth startup line importing cook**

```
The first execution path began with the single executable line in cook.pth: “import cook”. This loaded cook.py, whose wrapper Base64-decoded and zlib-decompressed an embedded Python program before executing the recovered source. The program identified itself as PRIVCOLLECT and performed credential collection in a daemon thread.
```

**Exfiltration POST to attacker healthcheck endpoint**

```
It then attempts an HTTP POST to hxxp://www[.]litellm[.]dev/healthcheck, using application/octet-stream, a fixed browser-like User-Agent, and a body checksum in an X-SHA256 header. The upload routine retries and treats only HTTP 200 as success.
```

**Remote loader fetching and executing /codepy**

```
If a valid server address is returned and successfully resolved, the loader is designed to request /codepy from that server, compile the returned content, and execute it as Python code within the affected environment.
```

