Two versions of litellm on PyPI were compromised with credential-stealing payloads. Version 1.82.7 embeds the payload in litellm/proxy/proxy_server.py , triggering on import. Version 1.82.8 escalates by adding a malicious .pth file that executes automatically when the Python interpreter starts, no import required. Both versions collect SSH keys, cloud credentials, Kubernetes secrets, crypto wallets, and environment variables, encrypt them with a hardcoded RSA public key, and exfiltrate the archive to an attacker-controlled server. On Kubernetes clusters, the payload creates privileged pods on every node to establish persistence. On all systems, it installs a systemd service that polls a C2 server for arbitrary binaries to execute.
Indicators of Compromise (IoC):
litellm is a widely used Python library by BerriAI that provides a unified interface to 100+ LLM providers. On March 24, 2026, a security advisory was filed reporting that version 1.82.8 published to PyPI contained a malicious .pth file not present in the source repository. Rami McCarthy’s post on X amplified the signal and brought it to our attention, prompting this analysis.
This compromise is likely a downstream consequence of the Trivy supply chain attack . LiteLLM’s CI/CD pipeline ( ci_cd/security_scans.sh ) installed Trivy from the apt repository without version pinning:
When the poisoned Trivy apt repository served a compromised binary, this script installed it with full CI runner privileges. The malicious Trivy binary exfiltrated runner secrets, including PYPI_PUBLISH_PASSWORD . The attacker then used the stolen PyPI credentials to publish malicious versions directly. The litellm maintainer confirmed the Trivy connection on Hacker News . Neither version corresponds to an official GitHub release (releases only go up to v1.82.6.dev1 ).
Community analysis in the GitHub advisory thread confirmed two compromised versions with different attack vectors:
Version 1.82.8 is an escalation: the .pth file ensures execution even if the proxy module is never imported. The exfiltration domain models.litellm.cloud was registered on 2026-03-23, one day before the malicious packages appeared on PyPI. This analysis focuses on 1.82.8 as the more dangerous variant.
The wheel’s RECORD file confirms the injected file:
The wheel contains 2,598 files across three top-level entries: the legitimate litellm/ package directory, the standard litellm-1.82.8.dist-info/ , and the injected litellm_init.pth . The package metadata (author, homepage, dependencies) is identical to the legitimate litellm project. This is not a typosquat. The attacker used stolen PyPI credentials to publish a trojaned version of the real package.
Warning: All analysis below was conducted inside an isolated Docker container used as a filesystem sandbox. Do not execute any of the malicious payloads on a host system. Download and extract only; never pip install the compromised wheel.
Set up an isolated directory for analysis:
Download the wheel without installing it:
Verify the SHA256 hash:
List .pth files in the wheel (legitimate wheels should not contain any):
Extract the malicious file without installing the package:
Decode each base64 layer statically (never execute the payloads):
All code snippets below are taken from these decoded files. The following sections walk through each stage.
.pth (path configuration) files exist so that Python’s site module can extend sys.path at interpreter startup. The site module documentation specifies that any line beginning with import is passed to exec() , allowing initialization code to run automatically. This mechanism is intended for configuring specialized environments or loading third-party hook functionality (see also PEP 648 ), but it makes .pth files an effective attack vector: the payload runs every time Python starts, not just when litellm is imported.
The .pth file contains a single line:
This spawns a detached subprocess that decodes and executes a base64-encoded Stage 1 payload. The use of Popen (not run or call ) ensures the malicious code runs in the background without blocking the interpreter.
The decoded Stage 1 script (25,844 bytes) manages the overall attack. It contains three key components: a hardcoded 4096-bit RSA public key, a second base64-encoded blob ( B64_SCRIPT ) containing the collection logic, and the encryption/exfiltration routine.
After executing the collector and capturing its output, the orchestrator encrypts the stolen data using a hybrid encryption scheme:
A random 32-byte AES session key encrypts the collected data. The session key itself is encrypted with the attacker’s RSA public key. Both are packed into tpcp.tar.gz and POSTed to hxxps://models[.]litellm[.]cloud/ . The domain litellm.cloud (not litellm.ai , the official domain) is attacker-controlled infrastructure chosen to blend in with legitimate litellm services.
The second base64 layer (17,281 bytes) is a comprehensive credential harvester. It defines three core utility functions and then systematically raids every credential store on the system.
The collector outputs everything to stdout, which Stage 1 captures into a file for encryption. The targeted credential categories:
System reconnaissance and environment:
SSH keys (all key types, all users):
Cloud credentials with active exploitation:
The collector doesn’t just read credential files. When it finds AWS credentials, it implements a full AWS SigV4 signing routine in pure Python (no boto3 dependency) and actively dumps Secrets Manager and SSM Parameter Store:
It also queries EC2 IMDS v2 for role credentials, escalating from static IAM keys to temporary role credentials when running on EC2.
Cryptocurrency wallets (broad targeting):
Solana gets special treatment with targeted searches for validator keypairs, vote account keys, and Anchor project deploy directories, suggesting the attacker anticipated litellm running on Solana validator infrastructure or crypto-adjacent developer machines.
The most aggressive behavior targets Kubernetes clusters. After dumping secrets from all namespaces, the collector creates a privileged pod on every node in the cluster:
Each pod mounts the host root filesystem, runs as privileged with hostPID and hostNetwork , and uses chroot /host to install the persistence script and systemd unit directly onto the node. The pods are named node-setup-* in the kube-system namespace to avoid suspicion. The toleration {'operator': 'Exists'} ensures pods schedule on tainted nodes, including control plane nodes.
The persistence mechanism, installed both locally and on K8s nodes, is a polling loop disguised as “System Telemetry Service”:
The script polls hxxps://checkmarx[.]zone/raw every 50 minutes for a URL. If the URL changes and is not a YouTube link (likely a dead-man’s switch or testing artifact), it downloads the binary to /tmp/pglog , marks it executable, and runs it. The state file at /tmp/.pg_state prevents re-execution of the same payload. The domain checkmarx.zone impersonates Checkmarx , the application security company.
The persistence is registered as a systemd user service with StartLimitIntervalSec=0 (ensuring systemd never stops restarting it) and suppressed output via StandardOutput=null .
The attack uses triple base64 encoding as its primary obfuscation. The .pth file contains a base64 blob that decodes to Stage 1, which contains B64_SCRIPT (another base64 blob) that decodes to Stage 2, which contains PERSIST_B64 (a third base64 blob) that decodes to Stage 3. No hex encoding, no string rotation, no minification beyond compressed whitespace. The obfuscation is functional (fits the payload into a .pth one-liner) rather than evasive. The 34,628-byte .pth file is notably large for what should be a simple path configuration file.
This is likely a second-order supply chain compromise: the Trivy attack appears to have poisoned litellm’s CI/CD pipeline, leading to stolen PyPI credentials and a trojaned package publish. The attacker did not typosquat or create a lookalike; they used legitimate publishing credentials to push a compromised version of the real package. The payload is comprehensive: it steals credentials from every major cloud provider, exploits Kubernetes service account tokens for lateral movement across cluster nodes, and establishes a persistent C2 channel that can deliver arbitrary follow-on payloads.
If you installed litellm==1.82.7 or litellm==1.82.8 , treat every credential on that system as compromised. Rotate all API keys, SSH keys, cloud provider credentials, and database passwords. On Kubernetes clusters, audit for node-setup-* pods in kube-system and check for sysmon.service systemd units on every node. Check for the persistence files at ~/.config/sysmon/sysmon.py and ~/.config/systemd/user/sysmon.service .
To proactively detect compromised packages before they reach your environment, run vet against your dependency lockfiles. For continuous monitoring of your dependencies, SafeDep Cloud provides real-time detection of malicious packages across your organization’s repositories.
Follow for the latest updates and insights on open source security & engineering
An obfuscated IIFE hidden in astro.config.mjs fires at every build, beacons an HTTP C2, and pulls staged commands from a Tron-to-BSC blockchain dead drop.
The Miasma worm source code appeared on GitHub through compromised developer accounts. The codebase is a full supply chain attack toolkit with credential exfiltration across AWS, Azure, GCP, and...
Eight days after the Miasma worm forged a credential stealer into public GitHub repositories, most are still serving it. A re-scan of the published victim list plus a fresh code- sweep found...
Editor and package-manager config files auto-execute commands when a developer opens a folder or installs dependencies. The Miasma worm wired one dropper into seven of them across Claude Code,...
Start free with open source tools on your machine. Scale to a unified platform for your organization.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
