Date Observed: October 21, 2026
Ecosystem: PyPI (Python) and Docker (GHCR, Docker Hub)
Targets: Hosts, CI runners, and LiteLLM proxy containers that installed litellm==1.107.0 or pulled the v1.107.0, latest, main-latest, or main-stable images
Attack Type: Release pipeline takeover with stolen GitHub CLI OAuth tokens; Python startup-hook credential stealer and remote code loader
Impact: Theft of cloud, CI/CD, SSH, AI-tool, and LiteLLM proxy credentials; dormant hourly remote-code loader; stolen AWS role credentials validated within hours

Key Takeaways (TL;DR)

  • An exposed internal-platform master key led to theft of a GitHub organization owner’s token; the attacker then used malicious CI workflows to steal additional maintainer credentials before taking over the release process.
  • All seven litellm 1.107.0 wheels installed cook.pth, cook.py, and sitecustomize.py, which run on every Python start, including pip.
  • Stolen data is encrypted and posted to www.litellm[.]dev/healthcheck; a second payload installs an hourly remote-code loader on Linux and macOS, although researchers found its GitHub dead drop empty and observed no second-stage code delivery.
  • 1.107.0 was live on PyPI from 11:39:51 to 13:23 UTC and has been removed. LiteLLM initially advised users to revert to 1.106.2; verified clean releases have since resumed.
  • Wiz observed stolen AWS role credentials from CI runners and Kubernetes nodes being validated the same afternoon.

Introduction

On October 21, 2026, LiteLLM’s release pipeline published a compromised version, 1.107.0, to PyPI and its Docker registries. Socket reports the LLM proxy and SDK receive approximately 23 million weekly downloads. The release carried a credential stealer that runs whenever Python starts in the affected environment, independent of whether the application imports litellm, plus an hourly remote-code loader on Linux and macOS. LiteLLM states the official v1.107.0 tag does not contain the malicious files and that its own release workflow built the artifacts from an attacker-controlled repository. This is the second LiteLLM compromise this year, after versions 1.82.7 and 1.82.8 were hit in the TeamPCP campaign against build systems in March. JFrog reports no evidence linking the two incidents.

LiteLLM states the official v1.107.0 tag does not contain the malicious files and that its own release workflow built the artifacts from an attacker-controlled repository. This is the second LiteLLM compromise this year, after versions 1.82.7 and 1.82.8 were hit in the TeamPCP campaign against build systems in March. JFrog reports no evidence linking the two incidents.

Scope and Impact of the Attack

LiteLLM reports all seven 1.107.0 wheels were live on PyPI from 11:39:51 to 13:23 UTC. The v1.107.0, latest, main-latest, and main-stable tags pointed to the compromised image index from 11:46:56 UTC. GHCR tags were unavailable by 13:51 UTC, and Docker Hub tags were removed or repointed by 14:20 UTC. Lidor Machluf from Upwind reported the compromise in GitHub issue #47826 at 12:06 UTC.

StepSecurity’s analysis of the stealer’s 16 modules lists environment variables from every readable /proc/<pid>/environ on Linux; AWS, ECS, GCP, and Azure metadata credentials; Kubernetes service-account tokens; GitLab Runner, Jenkins, Vault, and Terraform credentials; .npmrc, .pypirc, and GitHub CLI tokens; and SSH keys. If DATABASE_URL is set, it queries up to 500 rows each from LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable, where the proxy stores upstream provider keys.

Wiz reports that between 16:20 and 17:01 UTC the attacker validated AWS instance role credentials harvested from compromised CI runners and Kubernetes nodes across multiple environments, calling sts:GetCallerIdentity and then bedrock:ListFoundationModels. They describe the activity as consistent with LLMjacking.

How the Attack Works

Stage 0: Initial access and credential theft

Although the compromise was discovered on October 21, LiteLLM’s later forensic investigation traced malicious activity back to October 10. The investigation found that the attacker used a master key for LiteLLM’s experimental Agent Platform that had been accidentally committed to a public repository in May and was never revoked. On October 10, the attacker used that key to access the Agent Platform API, which exposed a GitHub CLI OAuth token belonging to a LiteLLM organization owner.

With that owner-level GitHub access, the attacker then created short-lived branches containing malicious GitHub Actions workflows that exfiltrated additional CI secrets to attacker-controlled webhook.site endpoints. One of those secrets was the GH_TOKEN of a second maintainer; another workflow exposed a third maintainer credential. Those stolen identities were later used to modify the release workflow, approve the production release, and delete workflow records.

Stage 1: Build-config injection

StepSecurity reports ten attacker commits landed in the lookalike repository between 08:56 and 10:46 UTC. They added the payload files under cook/ and the line data = “cook” under [tool.maturin] in pyproject.toml. Maturin then packaged the three files into the wheel’s data directory, which installs them at the top level of site-packages. The final commit added COPY cook ./cook to the gateway, backend, and migrations Dockerfiles.

Stage 2: Startup execution

cook.pth is 12 bytes: import cook. At startup, Python’s site module executes .pth lines that begin with import, then imports sitecustomize if one is on the path, and LiteLLM confirms any Python start, including pip, runs both. cook.py base64-decodes and zlib-decompresses roughly 61 KB of Python that identifies itself as privcollect, then runs it on a daemon thread.

Stage 3: Exfiltration

The collected JSON is encrypted with AES-256-CBC under a random key wrapped with an embedded RSA-4096 public key, packed as a tar.gz, and posted over plain HTTP to http://www.litellm[.]dev/healthcheck with an X-Token header. The run-once marker /tmp/.pc_once_72129118c59f5636c0bb is written only after the server returns HTTP 200, so until an upload succeeds, each new Python process repeats the collection.

Stage 4: Hourly remote code loader

sitecustomize.py takes a non-blocking lock on /tmp/remote_code_loader.lock, forks, calls setsid(), and loops every 3,600 seconds. Each cycle reads a domain from raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt, fetches http://<domain>/codepy, and runs it with exec(). domain.txt was empty when 1.107.0 shipped, so the /codepy stage never ran. The letters9813 account and repository have since been deleted. Anyone who re-registers the username and recreates domain.txt would gain code execution on hosts still running the loader.

Why this Matters to DevOps and DevSecOps Teams

Because the stealer targets cloud, CI/CD, and AI-provider credentials and Wiz observed attackers validating stolen AWS credentials within hours, any exposure should be treated as an active-compromise incident rather than a precautionary cleanup. Any environment in which Python ran after litellm==1.107.0 was installed is in scope — including proxy containers and CI runners, where the job’s environment variables, cloud role credentials, and .pypirc or .npmrc tokens fall within the stealer’s collection scope.

Cleanup carries its own risk. LiteLLM warns that running pip in an affected environment re-runs both payloads, and Socket notes that deleting the litellm package directory leaves the top-level startup files and any running loader in place.

Why Existing Controls Miss This

  • Source review. The official tag lacks the malicious files. The sdist lacks them too, but its pyproject.toml carries the added data = “cook” line, which a tag-to-sdist diff exposes.
  • Signatures. GitHub signs commits made in its web editor, so the first nine attacker commits carried the “Verified” badge. The badge does not identify the author. LiteLLM states its own workflow signed the poisoned artifacts.
  • Branch protection and approvals. A stolen privileged token overrode protection, and a second stolen account cleared the approval gate.
  • Destination reputation. www.litellm[.]dev imitates the project’s official litellm.ai domain, and litellm[.]dev was registered on October 13, eight days before the release.

Traditional source review, signatures, provenance, branch protection, approval gates, and vulnerability scanning did not prevent this package from being built, signed, published, and consumed. Some specialized malware scanners detected the malicious release soon after publication, but by then the package was already live and could have been installed.

How InvisiRisk Protects Against This Attack

The InvisiRisk Build Application Firewall (BAF) could have provided protection at multiple stages of this attack—both inside LiteLLM’s own CI/CD environment and for downstream customers consuming the malicious release.

Block the initial CI credential exfiltration. Before the malicious release was published, the attacker used GitHub Actions workflows to collect LiteLLM CI secrets and POST them to attacker-controlled webhook.site endpoints. In a protected build environment, InvisiRisk can enforce destination policy and block unauthorized outbound traffic, potentially interrupting the credential theft that enabled later stages of the release compromise.

Stability Buffer. litellm 1.107.0 was on PyPI from 11:39:51 to 13:23 UTC. InvisiRisk BAF blocks newly published packages from being consumed in a build for a configurable window, 48 hours by default. A build resolving litellm in that period is blocked from pulling 1.107.0, which was removed long before the default window closed.

Build Proxy / Network Interception. InvisiRisk BAF brokers all outbound traffic from protected builds and can enforce destination policies so builds connect only to approved endpoints. The stealer’s POST to www.litellm[.]dev/healthcheck and any /codepy fetch from an attacker-controlled host can therefore be blocked before data leaves the runner or remote code reaches the build. The dead-drop read from raw.githubusercontent.com is blocked only where that host is not on the build’s allowlist.

Destination reputation.  www.litellm[.]dev imitates the project’s official litellm.ai domain, and litellm[.]dev was registered on October 13, eight days before the release.

Why Build-Time Defenses Matter

This attack shows why build-time defenses matter on both sides of the software supply chain. Before the malicious package was published, the attacker used LiteLLM’s own CI workflows to exfiltrate credentials that helped compromise the release process. After publication, downstream builds that consumed 1.107.0 could execute malware designed to steal their credentials as well.

Build-time enforcement can interrupt both stages without first having to identify the specific malware: block unexpected outbound traffic from a compromised CI workflow, hold newly published packages before they are consumed, and prevent malicious code that does execute from communicating with unauthorized destinations..