The Trivy Supply Chain Attack Reached LiteLLM

esc to closeOn March 24, LiteLLM versions 1.82.7 and 1.82.8 on PyPI were confirmed compromised.12 The package has 40,000+ GitHub stars and is the LLM routing layer sitting inside Cline, OpenHands, and dozens of other AI agent frameworks. Anyone who installed either version has had their SSH keys, cloud credentials, API tokens, and environment variables collected and sent to an attacker-controlled server.
The LiteLLM maintainer posted on Hacker News within the hour: "Looks like this originated from the trivy used in our ci/cd."1
That is not a coincidence. We documented the seed event for this attack in our March 18 post on the Trivy compromise. What has happened since is not a separate incident. It is the same campaign, still running, and it has been escalating every few days.
This post connects the full chain.
What the LiteLLM compromise actually does
The attack is delivered via a .pth file: litellm_init.pth, 34,628 bytes, included in the wheel package and listed in the package's own RECORD manifest.2
This matters because .pth files in Python's site-packages/ directory are executed automatically by the interpreter on startup, before any import statement runs. The HN reporter who discovered it confirmed that even a bare import litellm in 1.82.8 is enough to trigger it.1 A user in the thread clarified it more precisely: any Python process in an environment where LiteLLM 1.82.8 is installed is exposed, regardless of whether the code ever imports it.1
The payload is double base64-encoded. When decoded, it launches a two-stage credential stealer.
Stage 1: Collection. The script sweeps the host for:
- SSH private keys (
~/.ssh/id_rsa,id_ed25519,id_ecdsa,id_dsa,authorized_keys,known_hosts,config) - All environment variables (
printenv, which captures every API key and secret token in the process environment) - AWS credentials (
~/.aws/credentials,~/.aws/config, plus IMDS token and EC2 security credentials) - Kubernetes secrets (
~/.kube/config,/etc/kubernetes/admin.conf, service account tokens at/var/run/secrets/kubernetes.io/serviceaccount/token) - GCP credentials (
~/.config/gcloud/application_default_credentials.json) - Azure credentials (
~/.azure/) - Docker configs (
~/.docker/config.json,/kaniko/.docker/config.json) - Git credentials (
~/.gitconfig,~/.git-credentials) - Package manager credentials (
~/.npmrc,~/.netrc,~/.vault-token) - Shell history across bash, zsh, mysql, psql, redis
- Crypto wallets (Bitcoin, Ethereum, Solana, and 8 others)
- SSL/TLS private keys and Let's Encrypt certificates
- CI/CD config files (
terraform.tfvars,.gitlab-ci.yml,.travis.yml,Jenkinsfile,.drone.yml) - Slack and Discord webhook URLs via environment grep
Stage 2: Exfiltration. The collected data is encrypted with AES-256-CBC using a randomly generated session key, which is then encrypted against a hardcoded 4096-bit RSA public key. Both encrypted files are packed into tpcp.tar.gz and POSTed to https://models.litellm.cloud/, note the domain: litellm.cloud, not the official litellm.ai.2
The tpcp.tar.gz filename is not random. It is the signature artifact of a threat actor called TeamPCP, and it has appeared across every stage of this campaign.34
This did not start with LiteLLM
On February 27, 2026, a GitHub account called hackerbot-claw, describing itself as "an autonomous security research agent powered by claude-opus-4-5", executed a Pwn Request against Aqua Security's Trivy.54 The vulnerability was a pull_request_target trigger in a workflow called apidiff.yaml. The trigger ran with the base repository's secrets and write permissions, but checked out code from the pull request head. Attacker-controlled code ran with Trivy's credentials.
In 44 minutes, hackerbot-claw stole a Personal Access Token called ORG_REPO_TOKEN, a VSCE_TOKEN, and an OVSX_TOKEN, the credentials for publishing VS Code extensions. It renamed the repository, pushed an empty replacement, and deleted all 178 releases. Then it published a weaponized VS Code extension carrying a prompt injection payload designed to hijack AI coding agents on victims' machines.
That is what we documented on March 18. The account behind it, MegaGame10418 per BoostSecurity Labs, has since been attributed to the threat actor now known as TeamPCP (also tracked as PCPcat, Persy_PCP, ShellForce, and DeadCatx3).54
Aqua Security responded and restored the repository. But the credential rotation, by Aqua's own admission, "wasn't atomic and attackers may have been privy to refreshed tokens."4
That gap is what every subsequent incident in this chain flows through.
The campaign since our post
The timeline below, compiled by Wiz researcher Rami McCarthy, shows what happened after the initial compromise.34
March 19: TeamPCP returned to Trivy with the retained credentials. They pushed a malicious v0.69.4 tag pointing to an imposter commit, spoofing a real contributor's identity, backdating the commit to March 4, and making it look like a legitimate CI fix. The tag triggered release workflows that published malicious binaries to GitHub Releases, Docker Hub (aquasec/trivy:0.69.4), and ECR (public.ecr.aws/aquasecurity/trivy:0.69.4). The three-stage payload, collect, encrypt with AES-256 plus RSA-4096, exfiltrate to typosquatted scan.aquasecurtiy.org, is structurally identical to what appeared in the LiteLLM package.346
Using the compromised aqua-bot service account, TeamPCP then laterally moved to aquasecurity/tfsec, aquasecurity/traceeshark, and aquasecurity/trivy-action, injecting malicious workflows into each. The trivy-action compromise is particularly significant: 76 of 77 tags were poisoned during a 12-hour exposure window. Any CI/CD pipeline using aquasecurity/trivy-action without SHA pinning may have executed the credential stealer against its runner.46
March 20: Less than 24 hours after the Trivy Act II compromise, TeamPCP deployed a self-propagating worm across npm. The worm, tracked by Aikido Security as CanisterWorm, used the same ICP canister as the Trivy fallback C2, confirming shared infrastructure. It infected 28+ packages across @EmilGroup, @opengov, and others in under 60 seconds, installing a systemd persistence service called pgmon.service disguised as PostgreSQL monitoring.7
March 22: A payload called kamikaze.sh went live on the ICP canister C2. It evolved across the day through four versions: a Kubernetes DaemonSet escape targeting cloud runners, then an SSH and Docker API worm scanning local /24 subnets, then a modular split into kube.py and prop.py, then WAV steganography, Python modules embedded as base64 in valid audio files to evade .py extension filters. Throughout all versions, an Iran-targeted wiper executed rm -rf / --no-preserve-root on systems with Asia/Tehran timezone or fa_IR locale.4
Also on March 22, TeamPCP pushed aquasec/trivy:0.69.5 and aquasec/trivy:0.69.6 directly to Docker Hub, bypassing GitHub releases entirely, proving Docker Hub credentials were compromised separately. Both images propagated to mirror.gcr.io before removal.4
March 23: TeamPCP pivoted to Checkmarx. Two malicious VS Code extensions were published to OpenVSX via a compromised ast-phoenix account. All 35 tags of Checkmarx/kics-github-action (v1 through v2.1.20) were force-pushed to malicious commits. The ast-github-action was similarly compromised. Sysdig confirmed the same RSA public key across Trivy and Checkmarx payloads: same operator, new target.8
March 24: LiteLLM 1.82.7 and 1.82.8 were published with the credential stealer. The maintainer's own attribution was Trivy in their CI/CD.12
Why LiteLLM specifically matters for AI agents
LiteLLM is not a generic Python library. It is the LLM routing layer that sits at the center of the AI agent stack. It translates between OpenAI-compatible API calls and every major model provider, Anthropic, Google, Azure, Bedrock, and 100+ others. Cline uses it. OpenHands uses it. Almost every agent framework that claims "model agnosticism" is routing calls through LiteLLM or something that depends on it.
That means the machines affected by this compromise are not generic developer laptops. They are machines running AI coding agents, machines with active cloud provider sessions, active API keys for LLM providers, active Kubernetes configs, and frequently elevated local permissions to allow agents to do their work.
The credential profile of a developer running AI agents is exactly what the payload targets. printenv alone will capture Anthropic API keys, OpenAI API keys, AWS session tokens, and any secrets injected into the environment for agent workflows.
And because .pth files execute at interpreter startup, not at import time, any Python process in an environment where LiteLLM 1.82.8 is installed is affected. That includes CI/CD pipelines, Docker containers running agent workflows, and production servers that vendored the package before the quarantine.1
What grith sees that the rest of the stack misses
The payload in litellm_init.pth does not rely on convincing an LLM to do something dangerous. It does not require the developer to click anything. It fires before any application code runs.
That means prompt-layer defenses, agent permission systems, and model safety filters are completely irrelevant here. The attack is below all of them.
But it is not below the syscall layer.
When litellm_init.pth executes, it calls subprocess.Popen(), an execve syscall, to launch a Python process with a base64-encoded argument. That spawned process then issues openat syscalls against ~/.ssh/id_rsa, ~/.aws/credentials, ~/.kube/config, and every other credential file in its target list. Each of those file reads is a discrete, observable event at the OS level, independent of any story the payload is telling.
Then comes the exfiltration: a connect syscall to models.litellm.cloud:443, followed by a POST with Content-Type: application/octet-stream. The process lineage at that point is Python interpreter -> .pth bootstrap -> subprocess -> network connection. That lineage is not normal. Python processes in developer environments do not typically spawn subprocesses that make outbound connections to domains that do not match the official package domain.
grith evaluates these signals before execution completes:
- Process spawn filter: Unsigned
.pthbootstrap spawning a subprocess with encoded arguments scores high. This is not how legitimate library initialization behaves. - Sensitive path filter:
openat(/home/user/.ssh/id_rsa)from a Python subprocess with no relationship to the user's agent session scores critical. SSH private key reads outside expected context are a first-class signal. - Network filter: Outbound connection to
models.litellm.cloudfrom a detached subprocess. The domain is close enough tolitellm.aito pass casual inspection, but the process lineage makes it anomalous regardless of the destination.
The ambient-code detection we documented in the Trivy post worked because Claude Code Action evaluated the PR content and recognized the injection. That is the model making a judgment call about whether the prompt looks malicious.
Syscall enforcement does not make a judgment call. It scores what the process is actually doing. The .pth payload cannot explain away openat(/home/user/.ssh/id_rsa) with social engineering. The call is the call.
What to do right now
If you have LiteLLM 1.82.7 or 1.82.8 installed anywhere:
- Check for
litellm_init.pthin yoursite-packages/directory. Its presence confirms compromise. - Assume all credentials that were present as environment variables or in standard config locations on that machine have been exfiltrated. Rotate them: SSH keys, AWS credentials, API tokens, Kubernetes service account tokens, anything in
~/.npmrc,~/.netrc, or~/.vault-token. - Check your
~/.bash_historyand~/.zsh_historyfor commands containing credentials. Those were collected too. - If the affected machine ran CI/CD pipelines, audit what runner secrets were in scope.
- Pin to a clean version: LiteLLM 1.82.6 or earlier, or wait for a verified clean release.2
PyPI has quarantined the affected packages, blocking new downloads.1 But the quarantine does not reach machines that already have them installed.
The through-line
The February 27 Pwn Request against Trivy was initially discussed as a contained incident. Aqua restored the repository, revoked the malicious extension token, and republished a clean release.
What they could not fully contain was the credential exposure that happened during the 44 minutes of access. Tokens were rotated, but not atomically. Somewhere in that window, TeamPCP captured credentials that opened the door to everything that followed: the malicious v0.69.4 release, the trivy-action compromise, the npm worm, the Checkmarx lateral movement, and now LiteLLM's PyPI package.4
A single incomplete rotation. Twenty-six days later, the LLM proxy layer running inside AI agent pipelines across the industry is exfiltrating credentials to an attacker-controlled server.
Supply chain attacks move slowly until they do not. The Trivy compromise is still live.
grith intercepts file reads, process spawns, and network connections at the OS level before they execute, regardless of what application layer triggered them. That is what grith does.
Footnotes
-
Tell HN: Litellm 1.82.7 and 1.82.8 on PyPI are compromised ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
GitHub Issue #24512: CRITICAL: Malicious litellm_init.pth in litellm 1.82.8 ↩ ↩2 ↩3 ↩4 ↩5
-
Wiz: Trivy Compromised - TeamPCP Supply Chain Attack ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
BoostSecurity Labs: MegaGame10418 - The User Behind Hackerbot-Claw ↩ ↩2
-
Socket.dev: Trivy Under Attack Again - GitHub Actions Compromise ↩ ↩2
-
Sysdig: TeamPCP Expands Supply Chain Compromise to Checkmarx ↩