A malicious LiteLLM 1.107.0 release appeared on PyPI at 11:39 UTC on October 21 and was quarantined by 13:20 UTC; matching images also reached GHCR and Docker Hub before being removed around 13:53 UTC. The package plants Python startup hooks that launch a credential stealer, encrypt its loot, and POST it to an attacker-controlled domain masquerading as LiteLLM infrastructure. LiteLLM traces the release compromise to stolen GitHub CLI OAuth tokens exposed through logged Claude Code responses and an internet-facing internal deployment. The newest safe version cited in the report is v1.106.2.
A short exposure window on PyPI and Docker
At 11:39 UTC on October 21, a malicious 1.107.0 build of LiteLLM was uploaded to PyPI. The package was quarantined on PyPI as of 13:20 UTC. LiteLLM Docker containers on GHCR and Docker Hub also contained the trojanized version, including the v1.107.0, latest, main-latest, and main-stable tags. Those images were removed at roughly 13:53 UTC.
The most recent safe release is v1.106.2. Any environment that resolved 1.107.0, or pulled one of those four container tags, should be treated as potentially exposed.
How the release workflow was compromised
According to LiteLLM, the malicious actions were carried out through three engineers’ GitHub accounts and appear to have used stolen GitHub CLI OAuth tokens. One account triggered the project-releaser run; another account approved it. LiteLLM says all tokens involved in the attack have been revoked.
The investigation identified two enabling conditions. First, Claude Code can embed a user’s GitHub CLI OAuth token into a response body in specific situations. When those response bodies are logged, the token can appear in LiteLLM logs. Second, LiteLLM found an unexpected, blandly named user account in an internal LiteLLM deployment that was exposed to the public internet. LiteLLM believes the attacker accessed that deployment, recovered the GitHub CLI OAuth tokens from its logs, and used the credentials against the release workflow.
The initial access route has not been confirmed. LiteLLM currently suspects it involved a recently patched vulnerability.
The startup-hook execution chain
The malicious package drops three files directly into the root of site-packages: cook.pth, cook.py, and sitecustomize.py. Each is designed to execute when a Python interpreter starts. Python processes .pth files and imports sitecustomize during initialization, so the payload gets multiple opportunities to run before application code does.
The chain works as follows:
- The interpreter processes
cook.pth, which contains a command toimport cook. - That import loads
cook.py. The script deobfuscates itself, runs its collection routine, and writes/tmp/.pc_once_72129118c59f5636c0bb. This path generally does not exist on Windows, so that branch of the payload fails there. - The stealer gathers system, browser, developer, cloud, and AI-tool secrets, encrypts them, and sends them in a POST request to
www.litellm.dev/healthcheck. The domain is attacker-controlled and impersonates LiteLLM infrastructure. - The interpreter also attempts to run
sitecustomize.py, but that component is restricted to Linux and macOS. It fetchesdomain.txtfrom a GitHub dead drop atletters9813/pythonclient-test/main/domain.txt. - If the dead drop returns a value, the script downloads a second-stage Python program from
http://and executes it./codepy
The domain.txt file was never populated beyond initial test values, according to the analysis, and GitHub had taken the repository down as of 1:30 PM UTC.
The simultaneous use of a .pth file and sitecustomize.py is notable. Wiz describes it as apparently the first documented in-the-wild case of this dual technique.
What the privcollect stealer takes
The embedded stealer is internally named privcollect and is organized into 16 modules. Its collection targets include:
- Browser credentials and cookies from Chrome, Firefox, Edge, and Safari.
- AWS credentials from
~/.aws/, environment variables, and EC2/ECS instance metadata (IMDS). - GCP service account keys and application default credentials.
- API keys for OpenAI, Anthropic, Cohere, and other LLM providers, taken from environment variables or configuration files.
- SSH private keys, GPG keys, and shell history files.
LiteLLM Proxy stores upstream API keys in a PostgreSQL database. The stealer queries that database directly to recover configured provider credentials:
SELECT * FROM "LiteLLM_CredentialsTable" LIMIT 500
Before exfiltration, the harvested data is encrypted with AES-256-CBC, with the encryption key wrapped using RSA-4096. The malware then sends the result over HTTP to www.litellm.dev/healthcheck, using a hardcoded X-Token header and an Internet Explorer 7 user-agent string.
Fingerprints of LLM-assisted development
The payload shows strong signs of LLM-aided development, according to the analysis. Those signs include verbose variable names such as encrypted_private_key_output, broad try/except blocks that suppress errors, and platform-specific branches that fail silently on systems the author apparently did not test. On Windows, the marker path under /tmp/ is unavailable; on other platforms, fcntl and fork calls can crash the loader. The code also contains a dead module, exec_py, that is defined but never called.
Wiz describes the overall structure as consistent with iterative prompting: the happy path works, but edge cases are brittle and untested.
The commit trail behind 1.107.0
The malicious payload traces back to commits in github.com/Berri-AI/litellm, a hyphenated lookalike of the legitimate BerriAI organization. The repository is not visible on GitHub, but it was used to inject the payload into the release pipeline.
Between 08:56 and 10:46 UTC on October 21, the attacker pushed 10 commits using the author email ckdi82fkoos@proton.me. The first nine commits were created on GitHub.com and therefore carry GitHub’s verified signature.
The first three commits, made within two minutes, added the persistence files cook.pth, cook.py, and sitecustomize.py. Four more commits bumped the version numbers to 1.107.0. After a 39-minute gap, two commits refined the payload and added the critical data = "cook" directive to pyproject.toml, which instructed the build tool to bundle the malware into the wheel. A final commit, made 58 minutes later, used a different email address, Berri-AI@users.noreply.github.com, and was unsigned, suggesting a different environment. That commit added COPY cook ./cook to the Dockerfiles.
Although the lookalike repository is unavailable, the commits remain accessible through the parent repository because of Git’s fork object sharing. The full public diff shows the changes.
Post-exploitation: AWS and LLMjacking
Wiz observed abuse of the stolen credentials beginning approximately 4.5 hours after exfiltration. 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. The validation attempts came from multiple source IP addresses.
Each credential was checked with sts:GetCallerIdentity, followed by bedrock:ListFoundationModels and a ListModels call. That sequence is consistent with LLMjacking. In a small number of environments, the attacker also enumerated IAM with ListRoles, ListUsers, and ListAccessKeys, and performed general discovery using calls such as ListAccounts and DescribeTrails.
Wiz pointed customers to its Threat Intel Center advisory, “Supply Chain Attack Targeting LiteLLM.”
Detection and mitigation
Treat any environment that installed LiteLLM 1.107.0, or pulled the v1.107.0, latest, main-latest, or main-stable images, as compromised. Rebuild from v1.106.2, the newest safe version named in the report, rather than relying on uninstalling the package.
Check the Python environment for the three dropped files. The commands below are generic audit examples, not additional indicators published by LiteLLM or Wiz:
python -m pip show litellm
python - <<'PY'
import pathlib, sysconfig
root = pathlib.Path(sysconfig.get_paths()["purelib"])
for name in ("cook.pth", "cook.py", "sitecustomize.py"):
path = root / name
if path.exists():
print(path)
PY
Run the check inside every virtual environment and container image; it only covers the interpreter that executes it. sitecustomize.py and .pth files have legitimate uses, so inspect their contents and provenance rather than deleting files by name alone.
Review egress and proxy logs for requests to www.litellm.dev/healthcheck. Rotate GitHub CLI OAuth tokens, AWS instance-role credentials, cloud provider keys, AI API keys, SSH and GPG keys, and active browser and shell sessions where exposure is suspected. Audit GitHub organization logs and release history for the project-releaser workflow, the Berri-AI lookalike organization, the author addresses above, and any unsigned release commit. In AWS, review CloudTrail for sts:GetCallerIdentity, bedrock:ListFoundationModels, ListModels, and the IAM enumeration calls listed earlier.