Anatomy of the LiteLLM Supply Chain Attack — How a Security Scanner Became the Weapon
A full breakdown of the March 2026 LiteLLM supply chain compromise — how attackers weaponized Trivy to backdoor 3.4M daily downloads, the three-stage malware payload, and the exact commands to check if you're affected and recover.
Table of Contents
How It Was Discovered
Security researcher Callum McMahon at FutureSearch found the compromise almost by accident. While testing a Cursor MCP plugin, his machine suddenly experienced severe RAM exhaustion shortly after Python started. Investigating the anomaly, he found a suspicious 34,628-byte file named litellm_init.pth in Python’s site-packages directory, encoded in double base64.
The reason it was noticeable at all was an unintended bug in the malware itself — the .pth payload spawned Python subprocesses that recursively triggered the .pth startup mechanism, creating a fork bomb that consumed all memory. Without that bug, the backdoor would have operated silently.
The Attack Chain
This was a multi-stage software supply chain compromise executed by a threat actor known as TeamPCP (aliases: PCPcat, Persy_PCP, ShellForce, DeadCatx3). Here’s how it unfolded.
Phase 1 — Compromise of Trivy (the Security Scanner)
In late February 2026, attackers exploited a pull_request_target workflow vulnerability in Aqua Trivy’s CI/CD pipeline to exfiltrate the aqua-bot credentials. On March 19, they rewrote Git tags in trivy-action to redirect users to a malicious release v0.69.4.
Yes — the security scanner itself was the entry point.
Phase 2 — Poisoning LiteLLM via Its Own CI/CD
On March 24, LiteLLM’s build pipeline used Trivy from apt without version pinning. When it ran the compromised Trivy GitHub Action, the action exfiltrated LiteLLM’s PYPI_PUBLISH token.
Using the stolen token, attackers published two malicious PyPI versions:
- v1.82.7 (10:39 UTC) — Embedded malicious base64 payload directly in
litellm/proxy/proxy_server.py, executing on import. - v1.82.8 (10:52 UTC) — Deployed
litellm_init.pthto site-packages, which executes on every Python interpreter startup — no import needed. This includespip,python -c, and even IDE language servers.
PyPI quarantined both versions roughly 3 hours later, but LiteLLM receives ~3.4 million downloads/day.
Phase 3 — Three-Stage Malware Payload
Once installed, the malware executed a devastating three-stage payload:
| Stage | Action |
|---|---|
| Stage 1: Credential Harvesting | Collected SSH keys, .env files, Git configs, shell history, cloud credentials (AWS/GCP/Azure including IMDSv2 + Secrets Manager + SSM), Docker registry creds, Kubernetes kubeconfig + service account tokens, and even cryptocurrency wallets (BTC, ETH, SOL, ADA, XMR) |
| Stage 2: Encrypted Exfiltration | Used hybrid encryption (AES-256-CBC + 4096-bit RSA OAEP), bundled everything into tpcp.tar.gz, and POSTed to https://models.litellm.cloud/ |
| Stage 3: Persistence & Lateral Movement | Installed a systemd service (sysmon.service) polling https://checkmarx.zone/raw every 5 minutes for new payloads. In Kubernetes environments, it read all secrets across namespaces and deployed privileged pods (node-setup-{node}) to every node, mounting host filesystems to backdoor underlying infrastructure |
Mitigation Plan — Step by Step
If you used LiteLLM in late March 2026, here’s exactly what to do.
Step 1: Determine If You’re Affected
# Check installed LiteLLM version
pip show litellm | grep Version
# If version is 1.82.7 or 1.82.8, you are affected
Step 2: Check for Malware Artifacts
# Check for the malicious .pth file in site-packages
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
-name "*.pth" -exec grep -l "base64\|subprocess\|exec" {} \;
# Verify file hashes (if files exist)
# litellm_init.pth (v1.82.8):
sha256sum $(python3 -c "import site; print(site.getsitepackages()[0])")/litellm_init.pth
# Expected malicious hash: 71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238
# Check for persistence backdoor
ls -la ~/.config/sysmon/sysmon.py
ls -la /root/.config/sysmon/sysmon.py
# Check for the malicious systemd service
systemctl --user status sysmon.service
# Check for network indicators
# Look for connections to attacker infrastructure
ss -tunap | grep -E "models\.litellm\.cloud|checkmarx\.zone"
# Check DNS resolution logs if available
grep -r "models.litellm.cloud\|checkmarx.zone" /var/log/
# Kubernetes: Check for malicious pods
kubectl get pods -A | grep "node-setup-"
# Check for temporary malware files
ls -la /tmp/pglog /tmp/.pg_state 2>/dev/null
Step 3: Remove Malware (If Compromised)
# Uninstall compromised LiteLLM
pip uninstall litellm -y
# Remove the malicious .pth file
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
-name "litellm_init.pth" -delete
# Remove persistence mechanism
rm -rf ~/.config/sysmon/
systemctl --user stop sysmon.service
systemctl --user disable sysmon.service
rm -f ~/.config/systemd/user/sysmon.service
systemctl --user daemon-reload
# Remove temp files
rm -f /tmp/pglog /tmp/.pg_state
# Kubernetes: Remove malicious pods
kubectl delete pods -n kube-system -l app=node-setup --all-namespaces
# Install a safe version
pip install "litellm<=1.82.6"
Step 4: Rotate ALL Credentials (Critical)
This is the most important step. The malware exfiltrated everything it could find — assume any credential on the affected machine is compromised.
# SSH Keys — regenerate
ssh-keygen -t ed25519 -C "[email protected]"
# Update authorized_keys on all servers
# AWS credentials
aws iam create-access-key --user-name <username>
aws iam delete-access-key --user-name <username> --access-key-id <OLD_KEY>
# Audit AWS Secrets Manager for unauthorized access
aws secretsmanager list-secrets
aws cloudtrail lookup-events --lookup-attributes \
AttributeKey=EventName,AttributeValue=GetSecretValue \
--start-time "2026-03-24T00:00:00Z"
# Audit AWS SSM Parameter Store
aws ssm describe-parameters
aws cloudtrail lookup-events --lookup-attributes \
AttributeKey=EventName,AttributeValue=GetParameter \
--start-time "2026-03-24T00:00:00Z"
# GCP — rotate service account keys
gcloud iam service-accounts keys list --iam-account=<SA_EMAIL>
gcloud iam service-accounts keys create new-key.json --iam-account=<SA_EMAIL>
gcloud iam service-accounts keys delete <OLD_KEY_ID> --iam-account=<SA_EMAIL>
# Docker registry credentials
docker logout
docker login # with new credentials
# Kubernetes — rotate service account tokens
kubectl delete secret <sa-token-secret> -n <namespace>
# Git tokens / API keys
# Rotate all tokens found in: .env, .git/config, shell history
# Check what was exposed:
grep -r "API_KEY\|SECRET\|TOKEN\|PASSWORD" ~/.bashrc ~/.zshrc ~/.bash_history ~/.env
Step 5: Pin Dependencies Going Forward
# Pin litellm in requirements.txt
echo "litellm<=1.82.6" >> requirements.txt
# Use hash-pinning for maximum security
pip install --require-hashes -r requirements.txt
# Generate hashes for your requirements
pip-compile --generate-hashes requirements.in
Lessons Learned
Supply Chain Attacks Are Transitive
A security scanner (Trivy) — the very tool meant to protect you — became the attack vector. LiteLLM didn’t have a direct vulnerability; it was compromised through its CI/CD dependency on a compromised security tool. Trust is transitive and fragile.
.pth Files Are a Dangerous Python Mechanism
The .pth startup hook technique (MITRE ATT&CK T1546.018) executes on every Python interpreter startup without any import. Most developers don’t know this mechanism exists, yet it’s been present in Python for decades. It bypasses all standard application-level security controls.
Hash Verification Is Insufficient for Supply Chain Attacks
Standard integrity checks (pip’s hash verification, wheel RECORD files) all passed because the malicious content was published using legitimately stolen credentials. The package was “authentic” from PyPI’s perspective.
Unpinned Dependencies in CI/CD Are Critical Risks
LiteLLM pulled Trivy without version pinning, meaning any upstream compromise automatically propagated. Every dependency in a build pipeline should be pinned to specific versions and verified.
Accidental Detection Is Not a Strategy
The malware was only discovered because of a bug in the attacker’s code (the fork bomb). A properly functioning backdoor could have operated undetected for weeks or months across millions of installs.
Community Response Can Be Suppressed
Attackers used 88 bot comments from 73 compromised accounts in 102 seconds and hijacked maintainer accounts to close disclosure issues. Having multiple communication channels and an incident response plan is essential.
What This Means for the Future
AI-Assisted Attack Automation
TeamPCP already operates hackerbot-claw, an AI agent system for automated targeting. Future attackers will use AI to automatically discover CI/CD misconfigurations at scale, generate more sophisticated payloads, and adapt malware in real-time based on the target environment.
Decentralized C2 Infrastructure
The attackers used Internet Computer Protocol (ICP) canisters as C2 infrastructure (CanisterWorm), making it resilient to traditional domain takedowns. Expect future attacks to increasingly use blockchain/decentralized infrastructure that cannot be seized.
Weaponization of Security Tools
This attack specifically targeted security scanners (Trivy, Checkmarx KICS) — tools that inherently run with elevated privileges in CI/CD pipelines. This “trust inversion” pattern will likely be replicated against SAST/DAST scanners, container image scanners, dependency audit tools, and code signing infrastructure.
Kubernetes-Native Worm Propagation
The lateral movement capability — automatically deploying privileged pods across every node — demonstrates that cloud-native infrastructure creates new worm propagation paths. Future variants could target service meshes (Istio/Linkerd sidecars), GitOps controllers (ArgoCD/Flux), and admission webhooks.
The .pth Class of Pre-Import Hooks
Every language ecosystem has analogous mechanisms: Node.js --require, Ruby’s $LOAD_PATH manipulation, Java agents. Expect similar techniques to emerge across ecosystems where startup hooks can execute code before the application is even aware.
Key Takeaways
This incident is a wake-up call that modern software supply chains have deep, transitive trust relationships. The attacker compromised a security tool to compromise a build pipeline to compromise a package to compromise end-user systems — a four-hop chain that traditional security models are not designed to detect.
What you can do today:
- Pin every dependency in your CI/CD pipeline — including security scanners
- Use hash-pinned requirements (
pip-compile --generate-hashes) for Python projects - Audit
.pthfiles in your site-packages regularly - Monitor for anomalous process behavior — fork bombs, unexpected network connections, new systemd services
- Treat credential rotation as mandatory after any suspected supply chain compromise — not optional
References
- Snyk: Poisoned Security Scanner Backdooring LiteLLM
- Snyk Vulnerability ID: SNYK-PYTHON-LITELLM-15762713
- MITRE ATT&CK: T1546.018 (Python Startup Hooks)
Related Articles
- Axios Hijacked on npm — A RAT Disguised as Your Favorite HTTP Client
- When the Malware Calls the Cloud — A Case Study in AI-Driven Cyberattacks
- Bleeding Llama — How a Single GGUF File Can Bleed Your Ollama Server Dry
- Claude Code vs OpenClaw vs NemoClaw — Which AI Agent Fits Your Workflow?
- Parallelization: Concurrent Execution for Agentic Workflows