Daily Top · Supply chain / AI / Python

LiteLLM's Git tag is clean. The wheel still runs at Python start.

Vendor RCA 23 October. An unrevoked May master key minted an org-owner GitHub token on 10 October. 1.107.0 wheels and latest Docker tags shipped a credential stealer. cook.pth is outside the package directory. Pinning 1.106.2 is not the hunt.

RWP Ventures · 2026-10-25 · event 2026-10-21 · 15 min read · priority 9.1

Bottom line up front

CONFIRMED On 21 October 2026 LiteLLM’s official release pipeline published litellm==1.107.0 to PyPI and pointed Docker Hub / GHCR tags v1.107.0, latest, main-latest, and main-stable at a compromised image. The wheels carried three files that Python runs at interpreter start: cook.pth, cook.py, sitecustomize.py. They sit in site-packages, not under the litellm application directory. The official Git tag v1.107.0 does not contain them. PyPI pulled the wheels by 13:23 UTC. Docker Hub tags were cleaned by 14:20 UTC. Wiz counted about 49,000 PyPI downloads of the bad version.

On 23 October 21:00 UTC LiteLLM published the canonical attack-chain post. That is the overnight increment. The 21 October incident post had guessed GitHub CLI tokens came from logs of an internet-facing LiteLLM deployment. The RCA retracts that.

Do not collapse five facts.

  1. The Git tag is not the artifact. Reviewers who inspected BerriAI/litellm at v1.107.0, or the published sdist, did not see cook.pth. The seven platform wheels did. JFrog: the source tarball lacks the three payload files; pyproject.toml in the sdist gained a data = "cook" setting that the Git tag does not have.
  2. pip uninstall litellm is not the close. Microsoft: because the startup files lived outside the LiteLLM application directory, removing only the application files can leave the hooks. Starting Python — including starting pip — re-runs them. LiteLLM’s own cleanup: delete the three files and their __pycache__ copies from the shell, then rebuild the venv or image.
  3. Pinning 1.106.2 on a dirty interpreter is a second false green. 1.106.2 is the last safe OSS release LiteLLM named. It does not delete cook.pth. Rebuild from a known-clean Python, or the pin sits on top of the implant.
  4. The access path was an unrevoked key, not a novel CVE. 27 May 2026: master key for an experimental LiteLLM Agent Platform committed to public LiteLLM-Labs/lite-harness. The commit was force-pushed over. The key was not revoked. 10 October, from 69.48.229.143: that key called GET /api/v1/managed_agents/agents?limit=100 and the response contained a GitHub CLI OAuth token for an organization owner. GitHub CLI tokens are post-2FA and skip the “super-user” reauth GitHub uses for sensitive org actions.
  5. This is not Collabora, not PHP2Shell, and not a PLC. CISA KEV is still catalog 2026.10.23 / 1756. Newest rows remain [CVE-2026-110447](/posts/2026-10-24-collabora-code-cve-2026-110447-kev/) and [CVE-2026-114625](/posts/2026-10-23-arista-velocloud-cve-2026-114625/), both due 26 October. No LiteLLM CVE is on that list. [PHP2Shell](/posts/2026-10-22-php2shell-cve-2026-109618-109619/) is still off KEV.

The operator sentence: if any host or image pulled litellm==1.107.0 or the four Docker tags in the window, assume exfil, delete the three startup files without starting that Python, rebuild the environment, and rotate every secret the host could reach — including every provider key in the LiteLLM proxy database.

What happened

LiteLLM is an open-source gateway that fronts many LLM providers through one API. It is a Python package on PyPI and a set of official container images. Enterprise customers on LiteLLM’s private registry were not in this blast radius. The OSS artifacts were.

Timeline that is in primary sources, not reconstructed. All times UTC.

WhenWhat
27 May 2026, 18:53opencode.json with the Agent Platform master key committed to public LiteLLM-Labs/lite-harness. Commit later force-pushed. Key not revoked.
10 Oct, 21:13–21:28Attacker uses the master key from 69.48.229.143. GET /api/v1/managed_agents/agents?limit=100 returns an org-owner GitHub CLI token.
10 Oct, 22:11Throwaway branch ci-tmp-a351cc on BerriAI/litellm runs a workflow that posts six repo secrets, including GH_TOKEN, to webhook.site. Run deleted three minutes later.
10 Oct, 22:12Same pattern on BerriAI/litellm-auto posts RELEASE_PLEASE_TOKEN.
12 Oct, 12:08–12:30Second collection pass across private repos and GitHub Environments. Socket recovered the workflows independently.
21 Oct, 10:08:29Branch protection rejects a push to BerriAI/project-releaser.
21 Oct, 10:08:34Org-owner token overrides branch protection. Same commit lands.
21 Oct, 10:46Staging workflow dispatched with source Berri-AI/litellm (hyphen) at commit 20535d8f, not BerriAI/litellm.
21 Oct, 11:11Second maintainer token approves the production gate from 199.91.221.39.
21 Oct, 11:39:51–11:40:13Seven litellm==1.107.0 wheels on PyPI.
21 Oct, 11:46:56Docker Hub and GHCR images published. Compromised index sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e.
21 Oct, 11:53–11:54Third maintainer token deletes production and staging run records.
21 Oct, 12:06Lidor Machluf opens GitHub issue #47826.
21 Oct, by 13:23PyPI quarantines 1.107.0. Live window ~1 hour 44 minutes.
21 Oct, by 13:51Affected GHCR tags unavailable.
21 Oct, 14:20Docker Hub tags removed or repointed. Image window ~2 hours 34 minutes.
21 Oct, 16:20–17:01Wiz: stolen AWS instance-role credentials validated from 168.100.8.57 and 165.227.201.8 (sts:GetCallerIdentity, then bedrock:ListFoundationModels).
23 Oct, 21:00LiteLLM publishes the attack-chain RCA. Canonical.

LiteLLM had temporarily relaxed the production gate earlier that week so any two engineers could trigger and approve a release, to ship bugfix backports. The attacker used two stolen maintainer tokens to satisfy that gate. LiteLLM notes the owner token could have loosened the restriction itself; using two identities was the quieter path.

This is the second documented LiteLLM hijack in 2026. JFrog tracked March’s 1.82.7 / 1.82.8 (stolen PyPI credentials via an unpinned Trivy in CI) under a different payload and infrastructure. JFrog: treat the campaigns as separate. No public evidence in this retrieval ties 1.107.0 to TeamPCP.

Why it matters

Three operational reasons this is the daily.

First, the implant is the interpreter, not the import. cook.pth is one line: import cook. Python’s site module executes that at startup. Any process that starts Python in that environment — a proxy, a CI job, a developer laptop, pip itself — runs the stealer. Microsoft called the technique T1546.018. You do not have to “use LiteLLM” that hour.

Second, the blast radius is every secret the host could see. JFrog decoded privcollect (static, not a how-to): browser stores; AWS / Azure / GCP credentials including IMDS; Kubernetes and Docker secrets; GitHub CLI; package-manager tokens; AI-tool configs (Cursor, Claude, Codex, Copilot, and others named in that write-up); /proc/<pid>/environ on Linux; and, on a LiteLLM proxy host, **SELECT * FROM "LiteLLM_CredentialsTable". One compromised gateway is every upstream provider key it held. Wiz then watched those AWS roles used the same afternoon for Bedrock model listing, consistent with LLMjacking**. That is confirmed use of stolen cloud credentials, not a victim census.

Third, source review failed as a control. The GitHub source tag is clean. The sdist is almost clean. The wheel is not. Teams that attest “we reviewed the repo” or “we pin by Git SHA” still consumed cook.pth if they installed the PyPI wheel or the latest image during the window. Mutable tags latest / main-latest / main-stable extended the same payload past anyone who never typed 1.107.0.

Who / what is affected

Technical context

High-level, from LiteLLM, JFrog, Wiz, Socket, and Microsoft. This is not an exploit recipe and not a reconstruction of the encoded cook.py blob.

The lookalike repository Berri-AI/litellm (hyphen) received the payload commits on 21 October. Wiz recovered them through Git fork-object sharing after GitHub took the lookalike down. First commits added the three files; a later commit set data = "cook" in pyproject.toml so Maturin copied cook/ into the wheel’s purelib; a last unsigned commit added COPY cook ./cook to the Dockerfiles. Author email on the payload commits: ckdi82fkoos@proton.me.

On install, wheel purelib lands at site-packages root:

Exfil: HTTP POST to www.litellm.dev/healthcheck. Domain registered 13 October 2026. Official project is litellm.ai. Hardcoded header X-Token: XWiA8gZNa7NcX2XRoAEb. Payload encrypted AES-256-CBC, AES key wrapped RSA-4096 — network capture without the attacker’s private key does not recover the secrets.

Wiz: payload shows marks of LLM-aided development (verbose names, generic try/except, Windows paths that do not exist). That is an observation about the code, not attribution.

Socket’s independent recovery of the 10 and 12 October cache.yml workflows is what closed the dwell argument. Subject line both times: ci: bump cache config. Branches ci-tmp-<hex>, created from a recent legitimate commit, pushed, run, deleted. 20 distinct secret names referenced across the two runs, including GH_TOKEN, a GitHub App private key, Linear, Together AI, Anthropic, OpenAI, Bedrock, GitGuardian, Playwright Linear session state, and Postgres / Redis. The two secret sets do not overlap. Socket’s reading, which LiteLLM’s RCA accepts: the first delivery worked.

ATT&CK (defensive mapping, not attribution): T1195.001 compromise software dependencies; T1546.018 Python startup hooks; T1552 unsecured credentials; T1078 valid accounts (stolen GitHub CLI / cloud roles); T1530 data from cloud storage / provider APIs. Actor identity: UNKNOWN. Nation-state: UNKNOWN. Ransomware: not shown.

RWP is not publishing the encoded cook.py blob, the workflow YAML beyond what Socket already printed, or a how-to for the Maturin data = injection.

Threat / exploitation status

ClaimStatusEvidence
litellm==1.107.0 wheels and named Docker tags carried cook.pth / cook.py / sitecustomize.pyCONFIRMEDLiteLLM incident post; JFrog; Wiz; issue 47826
Official Git tag v1.107.0 does not contain the three filesCONFIRMEDLiteLLM; JFrog
PyPI live 11:39–13:23 UTC 21 Oct; Docker Hub to 14:20 UTCCONFIRMEDLiteLLM timeline
Enterprise private registry not affectedCONFIRMEDLiteLLM
Last safe OSS named 1.106.2CONFIRMEDLiteLLM; Wiz
Access: unrevoked May Agent Platform master key → org-owner GitHub CLI token on 10 OctCONFIRMEDLiteLLM RCA 23 Oct
Initial 21 Oct hypothesis (tokens from LiteLLM deployment logs / Claude Code) is withdrawnCONFIRMEDRCA: “some assumptions … inaccurate”; THN 24 Oct
Socket recovered 10 Oct and 12 Oct secret-stealing workflows; 11-day dwellCONFIRMEDSocket 23 Oct; RCA corroborates
~49,000 PyPI downloads of 1.107.0CONFIRMED (Wiz / ClickPy)downloads, not infections
Stolen AWS instance-role credentials used 16:20–17:01 UTC 21 Oct from 168.100.8.57 and 165.227.201.8CONFIRMED (Wiz)GetCallerIdentity then Bedrock list
Hourly remote loader active in the wildCONFIRMED dormant in the observation windowWiz; JFrog zero-byte domain.txt
Second LiteLLM compromise of 2026 (after 1.82.7 / 1.82.8)CONFIRMED as a separate campaignJFrog; no public link to TeamPCP
Actor, census of victim tenants, OT process impactUNKNOWNnot in this retrieval
LiteLLM CVE on CISA KEV catalog 2026.10.23 / 1756CONFIRMED absentKEV JSON this run

What defenders should do

  1. Inventory first, and do not start the dirty Python to do it. Search images, lockfiles, pip caches, and site-packages for litellm-1.107.0, cook.pth, cook.py, sitecustomize.py. Docker: digest sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e, tags v1.107.0 / latest / main-latest / main-stable pulled 11:46–14:20 UTC 21 October. CI runners and Kubernetes nodes are in scope, not only the proxy VM.
  2. From the shell, delete the hooks. cook.pth, cook.py, sitecustomize.py, matching __pycache__ files, /tmp/.pc_once_72129118c59f5636c0bb, /tmp/remote_code_loader.lock. Kill any detached Python holding the lock. Then rebuild the venv or image. Do not run pip as step one.
  3. Pin only after the rebuild. litellm==1.106.2 on a cleaned interpreter, or wait for a vendor release LiteLLM will only ship when it is confident in the pipeline. Unpinned litellm and mutable latest tags are how this landed.
  4. Assume exfil if the host had outbound internet. Rotate from a known-clean machine: instance / task IAM roles; AWS / Azure / GCP keys; every provider key in LiteLLM_CredentialsTable; master and virtual LiteLLM keys; DATABASE_URL password; GitHub CLI and GitHub App credentials; npm / PyPI / other package tokens; AI-tool configs on that host. Wiz’s Bedrock listing is the reason the cloud trail check is not optional.
  5. CloudTrail (or equivalent) around install time. GetCallerIdentity from the instance role, then use of that role from outside your ASNs. Wiz’s abuse IPs: 168.100.8.57, 165.227.201.8. Initial-access IP on the vendor side: 69.48.229.143. Approval IP: 199.91.221.39. Absence of those four is not a miss on the class.
  6. Network and host hunts that survive tmp cleanup. Egress to litellm.dev / www.litellm.dev/healthcheck. Header X-Token: XWiA8gZNa7NcX2XRoAEb. webhook.site collectors 65a42464-3cfa-4b02-a18c-17a6d7b86f37 and 4da03473-a3e7-4dc2-a087-9c564b4208e1. Env PRIVCOLLECT_ACTIVE=1. Hashes Wiz published for the hooks (Linux/macOS): cook.pth 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb, cook.py 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25, sitecustomize.py 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa.
  7. GitHub org hygiene if you run a similar releaser. Owner-level GitHub CLI tokens in agent platforms; secrets reachable from on: [push] on any branch; two-person release gates that are just two user tokens; mutable latest tags. LiteLLM locked the org down to one owner during IR. Copy the lesson, not the incident.
  8. Do not close adjacent tickets on this row. [Collabora 110447](/posts/2026-10-24-collabora-code-cve-2026-110447-kev/) and [Arista VCO 114625](/posts/2026-10-23-arista-velocloud-cve-2026-114625/) are still due Monday 26 October, forensic triage Yes. [PHP2Shell](/posts/2026-10-22-php2shell-cve-2026-109618-109619/) is still the interpreter. Node.js CVE-2026-111497 (Brotli / Node2Shell) is a separate decoder bug with no reported in-the-wild use in this retrieval.

RWP assessment

High confidence that litellm==1.107.0 wheels and the four named Docker tags carried Python startup-hook malware on 21 October, that the Git tag did not, that PyPI and the registries pulled the artifacts the same day, that enterprise private-registry customers were out of scope, and that 1.106.2 is the last safe OSS release LiteLLM has named.

High confidence in the 23 October RCA on initial access: unrevoked May Agent Platform master key, 10 October owner-token theft, 10–12 October workflow exfil, 21 October lookalike-repo release. Socket’s recovered cache.yml runs are independent corroboration of the 10 and 12 October steps.

High confidence that Wiz observed stolen AWS instance-role credentials used for GetCallerIdentity and Bedrock listing the same afternoon. That is not a count of how many of the 49,000 downloads ran the stealer.

Moderate confidence that the hourly sitecustomize.py loader stayed dormant (empty domain.txt, repo taken down). Any surviving copy can still be re-armed. Treat hosts as if the stealer ran; treat the loader as a persistence check, not as “no second stage.”

Low confidence in actor identity, whether the May key was found by opportunistic Git history scraping or directed collection, and whether March’s 1.82.x operator is involved. JFrog says treat them as separate; we do.

Confidence that this is not OT process impact: high. No evidence in this retrieval that a controller, safety system, or physical process was affected.

This outranked the rest of the window because it is a new product, never a Daily Top, with a vendor RCA in the last 24 hours that changed the access path, confirmed credential reuse in AWS, and a built-in false close (uninstall / pin without deleting cook.pth). Collabora and Arista are yesterday’s KEV rows; their Monday due date is a clock. Node2Shell’s blast radius grew to Bun / Python / Envoy on 24 October, but CISA’s 23 October note still recorded exploitation none. GitLab CVE-2026-110264 remains CVSS 10 without confirmed in-the-wild use. HPE ArubaOS HPESBNW05162 is still “no public exploit, no exploitation claimed.” PHP CVE-2026-115560 is still a narrow unpatched config path.

What we are watching

Sources

Primary: LiteLLM attack-chain RCA, 23 October 21:00 UTC; LiteLLM 21 October incident post (corrected by the RCA); LiteLLM GitHub issue 47826. Independent technical: Wiz 21 October (downloads, payload, AWS follow-on); JFrog 21 October (wheel vs sdist, privcollect); Socket 23 October (10 and 12 October workflows); Microsoft 23 October (startup-hook placement). Secondary: The Hacker News 24 October. Catalog check this run: CISA KEV JSON 2026.10.23 / 1756. Adjacent, not this story: Collabora Daily Top 24 October; Arista Daily Top 23 October; PHP2Shell Daily Top 22 October.

Sources

  1. LiteLLM — The attack chain behind the v1.107.0 LiteLLM release, 23 Oct 2026
  2. LiteLLM — Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised
  3. LiteLLM — GitHub issue 47826
  4. Wiz — LiteLLM Compromised, Steals Credentials, 21 Oct 2026
  5. JFrog Security Research — LiteLLM Hijacked Again, 21 Oct 2026
  6. Socket — New Evidence Shows LiteLLM Secret-Stealing Workflows Ran 11 Days Before Supply Chain Attack, 23 Oct 2026
  7. Microsoft — Inside the LiteLLM 1.107.0 supply chain compromise, 23 Oct 2026
  8. The Hacker News — Leaked LiteLLM Master Key Let Attackers Steal GitHub Tokens and Hijack Release Pipeline, 24 Oct 2026
  9. CISA — KEV JSON feed catalog 2026.10.23
  10. CISA — Adds One Known Exploited Vulnerability to Catalog, CVE-2026-110447, 23 Oct 2026
  11. RWP Ventures — Collabora CODE Daily Top 2026-10-24
  12. RWP Ventures — PHP2Shell Daily Top 2026-10-22
  13. RWP Ventures — Arista VeloCloud Daily Top 2026-10-23