Compromised LiteLLM 1.107.0 stole credentials the moment Python started, without a single import
On October 21, 2026, a compromised LiteLLM 1.107.0 release was published to PyPI and to the official Docker images: three Python startup files ran a credential collector during interpreter initialization, without the application ever importing LiteLLM. If you installed 1.107.0, downgrading is not enough — rotate every credential reachable from each affected host and rebuild the environments.
October 21, 2026, 11:39 UTC. Seven malicious LiteLLM 1.107.0 wheels land on PyPI. October 21, 2026, 11:46 UTC. The Docker images v1.107.0, latest, main-latest, and main-stable point at the compromised index. October 23, 2026. Microsoft publishes its full technical analysis. The core finding: the application never had to import LiteLLM — simply starting Python was enough to fire a credential collector on the machine.
For a CISO, the lesson fits in one sentence: this was not another poisoned package, it was proof that Python startup hooks (*.pth and sitecustomize.py) turn a compromised dependency into a compromise of everything that runs on the host.
A gateway sitting on a very sensitive center of gravity
LiteLLM is an open-source gateway that unifies hundreds of LLM providers behind one API. In production it sits at the front door: it holds model-provider API keys, database connection strings, workload identities, routing configuration, and tenant policy data. The same library is also installed on developer workstations, build agents, test systems, and containers — which mechanically widens the exposed surface.
This is not the first time LiteLLM has been targeted. In March 2026, the TeamPCP group poisoned versions 1.82.7 and 1.82.8 through a compromised Trivy build, an incident we covered here. Since then the team had “meaningfully hardened” its pipeline: a private release repo, Trusted GitHub Publishers, immutable Docker tags, signed images. The October incident shows those controls did not hold — they were bypassed, not broken.
Three startup files, zero imports
The payload did not hide in the application code. The compromised wheel shipped _cook.pth, _cook.py, and _sitecustomize.py, placed under litellm-1.107.0.data/purelib/. On install, those files were relocated into the environment’s library directory, outside the litellm application folder.
That placement is the whole story. Python processes executable import lines in *.pth files during site initialization, then attempts to import the sitecustomize module. The result: starting Python — any invocation, including a pip install of an unrelated dependency — was enough to run the malicious code. The package did not need to be imported; it only needed to be installed.
The first execution path began with the single executable line in _cook.pth: import cook. _cook.py Base64-decoded and zlib-decompressed an embedded Python program that identified itself as PRIVCOLLECT. Before spawning its collection thread, the program checked the PRIVCOLLECT_ACTIVE environment variable: if empty, it set the flag and started collection in a daemon thread, limiting duplication across descendant processes.
What the collector went after
The target matrix Microsoft reconstructed is broader than model-key theft:
- Developer and AI tools: authentication and configuration files for AI assistants, IDEs, GitHub Copilot, VS Code, and MCP integrations.
- Source control and package ecosystems: Git credentials, package-manager configuration, selected
dotenvfiles, and private-key extensions. - Cloud and workload identities: AWS, Azure CLI, and Google Cloud credential caches; Kubernetes service-account files; Docker configuration; mounted secrets; and attempts to retrieve AWS and GCP metadata credentials.
- Browsers and histories: credential data from Chromium and Firefox browsers, plus shell, database-client, and interactive-tool histories.
- Process environments: its own environment and, on Linux, other processes’ environments readable through
procfs. - Private keys and infrastructure: SSH material, GPG, TLS, CI/CD, and Vault configuration.
- LiteLLM proxy data: two proxy database tables read through a Prisma child process, capped at 500 rows per query.
Each module ran with independent error handling, so collection kept going even when a target was unreachable.
Encryption and exfiltration
After collection, the payload serialized module results and host metadata as JSON. Each run generated a 32-byte AES key and a 16-byte IV; AES-256-CBC encrypted the document, and an embedded 4,096-bit RSA public key wrapped the AES key with OAEP. The result was packaged as a gzip tar with three members — data.bin, key.bin, and hdr.json — then posted to hxxp://www[.]litellm[.]dev/healthcheck, a lookalike of the legitimate litellm.ai domain. Only an HTTP 200 counted as success; the upload routine retried otherwise.
Alongside the collector, _sitecustomize.py carried a second, Unix-oriented mechanism: a loader that took an exclusive lock on /tmp/remote_code_loader.lock, then read a GitHub-hosted domain.txt file — a mutable pointer to an attacker-controlled server. If it resolved, the loader fetched /codepy and executed it as Python code in the affected environment. At analysis time domain.txt was empty and no second-stage execution was confirmed — but the mechanism remained a re-armable RCE, changeable without publishing another package.
How the release pipeline was bypassed
The maintainers’ report gives a minute-by-minute timeline on October 21:
- 10:08 UTC: branch protection rejects an unauthorized change to the private release workflow.
- 10:08: a credential tied to a privileged developer account overrides the protection and applies workflow commit
9cf97e9…. - 10:46: the modified workflow starts a staging run using the attacker-controlled source repo
Berri-AI/litellm— a near-homograph of the legitimateBerriAI/litellm. - 11:08: staging succeeds; the production release pipeline begins.
- 11:11: a credential tied to a second developer account satisfies the production approval gate.
- 11:39: seven malicious wheels are uploaded to PyPI.
- 11:53: a credential tied to a third account deletes the workflow records, amputating the investigation logs.
- 13:23: PyPI removes the version; by 14:20 the Docker Hub tags are repointed or removed.
The root cause is as instructive as the attack: on Monday, the team had temporarily widened release permissions to let two engineers publish daily bugfix backports. The attacker held three sets of developer GitHub CLI credentials, one of which could run workflows. Microsoft has not determined how they were obtained.
Verdict
If you installed, mirrored, or built LiteLLM 1.107.0, do not just downgrade. Microsoft and the maintainers agree: removing the package or the tag is insufficient, because the startup files live outside the application directory. Concretely:
- Do not run
pythonorpipinside a suspect environment to check for the package — that is the exact gesture that re-runs the payload. - Rebuild environments,
uv/pipcaches, internal mirrors, lockfiles, and images; prefer image digests over floating tags. - Rotate every credential reachable from each host: model-provider keys, cloud and workload identities, CI/CD secrets, Git tokens, SSH/GPG keys, browser sessions, and — on proxy hosts — stored provider credentials and the
DATABASE_URL. - Constrain egress: a dev workstation or build agent should not be able to reach arbitrary HTTP destinations.
If you run a LiteLLM proxy in production, treat it as a sensitive surface in its own right: it is a secret concentrator, not just a dependency. Pin versions to hashes, sign your images, and treat any compromised release window as a global rotation event, not a simple rollback.