Security Research

The LiteLLM Compromise: What a Three-Hour Window Reveals About AI Infrastructure Security

A technical analysis of the LiteLLM supply chain attack - how a compromised security scanner led to credential theft across the AI ecosystem, what the three-stage payload did, and what it means for anyone building on LLM infrastructure.

PromptGuardPromptGuard
4 min read·
SecuritySupply ChainInfrastructureResearch

The LiteLLM Compromise: What a Three-Hour Window Reveals About AI Infrastructure Security

On March 24, 2026, a threat actor published two malicious versions of LiteLLM - a Python package downloaded 3.4 million times per day - to PyPI. For approximately three hours, anyone who installed litellm==1.82.7 or litellm==1.82.8 had their SSH keys, cloud credentials, API tokens, Kubernetes secrets, crypto wallets, and environment variables encrypted and exfiltrated to an attacker-controlled server.

The attack was only discovered because the malware had a bug. A sloppy implementation detail caused an exponential fork bomb that crashed the victim's machine, making the compromise visible. As Andrej Karpathy observed, without the malware's own poor quality, it would have gone unnoticed for much longer.

This is a technical analysis of the incident, what it means for AI infrastructure security, and what it reveals about the fragility of the current AI tooling ecosystem.

The Attack Chain

The short version: An attacker compromised Trivy (a security scanner), used it to steal LiteLLM's PyPI publishing credentials from CI/CD, then published poisoned versions of LiteLLM that steal everything on your machine.

The full chain:

DateEvent
Late Feb 2026Attacker (TeamPCP) exploits a pull_request_target workflow vulnerability in Trivy's GitHub Actions to steal the aqua-bot credentials
Mar 19Trivy v0.69.4 GitHub Action tags rewritten to point to a malicious release
Mar 23Checkmarx KICS GitHub Action compromised; models.litellm.cloud domain registered
Mar 24, 10:39 UTCMalicious litellm 1.82.7 published to PyPI
Mar 24, 10:52 UTCMalicious litellm 1.82.8 published with escalated .pth delivery
Mar 24, 11:48 UTCFutureSearch engineer discovers the malware after it crashes his machine
Mar 24, ~12:44 UTCAttacker uses compromised maintainer account to close the GitHub issue; floods it with 88 bot comments from 73 accounts in 102 seconds
Mar 24, ~13:38 UTCPyPI quarantines the package
Mar 24, 15:27 UTCCompromised versions deleted; all maintainer keys rotated

Timeline reconstructed from Snyk, FutureSearch, Endor Labs, and GitHub #24512.

The Payload: Three Stages of Compromise

The malware operates in three stages, each worse than the last.

Stage 1: Harvest Everything

The script systematically collects every credential it can find:

  • Cloud credentials - AWS (~/.aws/credentials), GCP (application default credentials), Azure tokens, full IMDSv2 metadata queries including Secrets Manager and SSM Parameter Store
  • SSH keys - Every private key format: RSA, Ed25519, ECDSA, DSA. Plus authorized_keys, known_hosts, and SSH config
  • Kubernetes - kubeconfig, service account tokens, cluster certificates, admin configs
  • Environment variables - printenv captures every API key, database URL, and secret loaded in the shell
  • Git credentials - .gitconfig, .git-credentials, credential store
  • Container registries - Docker config.json (registry auth tokens)
  • CI/CD - terraform.tfvars, Jenkinsfile, .gitlab-ci.yml, .travis.yml, .drone.yml
  • Database credentials - PostgreSQL, MySQL, Redis, LDAP configs
  • Crypto wallets - Bitcoin, Ethereum, Solana, Cardano, Monero wallet files and seed phrases
  • Shell history - Bash, Zsh, MySQL, psql, Redis CLI history
  • SSL/TLS private keys - /etc/ssl/private/, Let's Encrypt certificates

Stage 2: Encrypt and Exfiltrate

The collected data is encrypted with AES-256-CBC using a random session key, which is itself encrypted with a hardcoded 4096-bit RSA public key. The bundle is POSTed to https://models.litellm.cloud/ - a domain registered one day before the attack, designed to look legitimate alongside LiteLLM's real domain (litellm.ai).

The RSA public key is identical across the Trivy, Checkmarx KICS, and LiteLLM payloads. Wiz researchers confirmed this match across all three operations - the strongest single technical attribution link tying them to the same threat actor.

Stage 3: Persist and Spread

The malware installs a persistent backdoor at ~/.config/sysmon/sysmon.py with a systemd service disguised as "System Telemetry Service." This backdoor polls https://checkmarx.zone/raw every 5 minutes for new payloads.

If a Kubernetes service account token is present, the malware reads all cluster secrets across all namespaces and deploys privileged alpine:latest pods to every node in kube-system, mounting the host filesystem and installing the backdoor on the underlying infrastructure.

This is not a credential stealer that runs once. It's a persistent implant with worm capabilities.

Why .pth Files Are Dangerous

Version 1.82.7 hid the payload inside proxy_server.py - it only executed when the proxy module was imported. Version 1.82.8 escalated to a .pth file, which is fundamentally more dangerous.

Python's .pth files in site-packages/ execute automatically on every interpreter startup. Not when you import litellm. When you run any Python command. pip install, python -c "print('hello')", your IDE's language server, a linter - all trigger the payload.

This is a known design flaw. CPython maintainers have acknowledged the security risk in issue #113659, but no fix has been applied. MITRE tracks it as T1546.018 (Python Startup Hooks).

The .pth file is correctly listed in the wheel's RECORD with a matching hash. pip install --require-hashes passes. Every standard integrity check passes. The malicious content was published using legitimate stolen credentials, not injected after the fact. There is no hash mismatch, no misspelled package name, no suspicious domain in the metadata.

The Discovery: Saved by a Bug

Callum McMahon at FutureSearch was working on his Mac when it became unresponsive. 11,000 processes running, CPU at 100%, htop taking 10+ seconds to load.

The cause: a Cursor MCP plugin had pulled in LiteLLM as a transitive dependency via uvx. The .pth payload spawned a child Python process, which triggered the same .pth file, which spawned another child - an exponential fork bomb. The malware was never designed to do this. It was a bug in the malware itself.

McMahon used Claude Code to root-cause the issue, found the rogue package in his uv cache, reproduced the malware in a container, and published a disclosure blog post within minutes. He reported it to PyPI security (who quarantined the package) and the LiteLLM maintainers.

As McMahon wrote afterward: "There's definitely an irony here about how Simon Willison has been hammering on about the lethal trifecta for almost a year now surrounding MCP servers, yet MCP servers got us via regular old supply chain attacks, no tricking of LLMs required."

The Issue Suppression Campaign

When the community opened GitHub issue #24512, the attackers used the compromised maintainer account to close it as "not planned." Simultaneously, 88 bot comments from 73 previously-compromised developer accounts flooded the issue in a 102-second window with messages like "Worked like a charm, much appreciated" and "This was the answer I was looking for."

The accounts were not newly created - they were real developer accounts compromised in prior operations. Analysis by Rami McCarthy found 76% overlap with the botnet used during the earlier Trivy disclosure.

The community routed around the suppression by opening a parallel tracking issue (#24518) and continuing discussion on Hacker News, which reached 324 points.

Blast Radius

LiteLLM is not a niche library. It's a central piece of AI infrastructure - a unified interface for calling 100+ LLM APIs - used by DSPy, CrewAI, MLflow, OpenHands, Arize Phoenix, and hundreds of other projects. Multiple major projects filed emergency PRs within hours:

ProjectAction
DSPy (Stanford)PR #9498 - pin litellm
MLflowPR #21971 - pin litellm; PR #21996 - disable CI workflows using secrets
CrewAIPR #5040 - decouple from litellm entirely
OpenHandsPR #13569
Arize PhoenixPR #12342

The .pth execution mechanism means CI/CD pipelines that ran pip install during those three hours executed the payload during the build step - before any application code ran.

TeamPCP claimed to have stolen data from approximately 500,000 devices, though some sources note many were duplicates.

About TeamPCP

TeamPCP (also identified as PCPcat, Persy_PCP, ShellForce, and DeadCatx3 per Wiz Threat Center) has been active since at least December 2025. The LiteLLM compromise is Phase 09 of an ongoing campaign.

Consistent infrastructure ties all operations together: same RSA key pair, same tpcp.tar.gz bundle naming, same registrar (Spaceship, Inc.) and hosting provider (DEMENIN B.V.) across all domains.

The group has also deployed CanisterWorm - the first observed use of the Internet Computer Protocol (ICP) as a C2 channel, documented by Aikido researchers. ICP canisters cannot be taken down by domain registrars or hosting providers. A component called hackerbot-claw uses an AI agent for automated attack targeting - one of the first documented cases of an AI agent used operationally in a supply chain attack.

What This Means for AI Infrastructure

1. AI tooling is now a confirmed high-value target

The target selection across TeamPCP's campaign is deliberate: Trivy (security scanner), KICS (infrastructure scanner), LiteLLM (AI model router). Each of these tools requires broad credential access by design. LiteLLM specifically stores API keys for multiple LLM providers - a single compromised instance yields credentials for OpenAI, Anthropic, Google, AWS Bedrock, and potentially every AI provider an organization uses.

This isn't theoretical anymore. The AI infrastructure layer - the gateways, routers, and proxies that sit between applications and LLM providers - is a confirmed high-value target for supply chain attacks.

2. Pass-through credentials are an architectural risk

LiteLLM's architecture requires users to provide their LLM provider API keys, which it stores in environment variables or config files. When the supply chain is compromised, every provider credential is compromised in a single operation.

Any AI gateway or proxy that handles provider credentials faces this same risk. The question isn't whether you handle credentials - it's how they're isolated, how the supply chain is secured, and what happens when a dependency is compromised.

3. The MCP auto-install pattern is dangerous

The victim was infected because a Cursor MCP plugin pulled LiteLLM as a transitive dependency via uvx, which automatically downloads and runs dependencies. The user never explicitly installed LiteLLM. They never ran pip install litellm. A tool in their IDE pulled it in silently.

This is the "seamless ergonomics" of modern AI tooling working against us. The same frictionless install experience that makes tools easy to adopt also makes them easy to weaponize.

4. Security scanners themselves are attack vectors

The deepest irony of this incident: the attack started by compromising Trivy - a security vulnerability scanner. The tool organizations use to check for vulnerabilities in their dependencies was itself the vector that compromised those dependencies.

This is not a one-off. Checkmarx KICS (another security tool) was compromised in the same campaign. The attackers specifically targeted security infrastructure because it has the broadest access and the deepest trust.

5. Runtime security isn't the whole picture

We spend a lot of time thinking about prompt injection, jailbreaks, and output safety. Those threats are real. But if the code processing your prompts is itself compromised, none of that matters. The supply chain is part of the threat model.

Defense in depth means securing the entire pipeline: the code, the dependencies, the CI/CD, the credentials, and the runtime.

If You're Affected

Check your installed version:

pip show litellm 2>/dev/null | grep Version

If you see 1.82.7 or 1.82.8, treat the system as compromised. Do not just upgrade - the payload has already run.

Check for persistence:

ls -la ~/.config/sysmon/sysmon.py 2>/dev/null && echo "BACKDOOR FOUND"
ls -la ~/.config/systemd/user/sysmon.service 2>/dev/null && echo "PERSISTENCE SERVICE FOUND"

Check Kubernetes:

kubectl get pods -A 2>/dev/null | grep "node-setup-"

Check for the .pth file:

find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
  -name "litellm_init.pth" 2>/dev/null

If affected:

  1. Remove the package and purge caches (pip cache purge or rm -rf ~/.cache/uv)
  2. Remove persistence artifacts (~/.config/sysmon/, ~/.config/systemd/user/sysmon.service)
  3. Rotate every credential that existed on the machine: SSH keys, cloud credentials, API keys, database passwords, Docker registry tokens
  4. Audit Kubernetes clusters for node-setup-* pods
  5. Pin to litellm<=1.82.6 in all environments

Indicators of Compromise

File hashes (SHA-256):

FileHash
litellm_init.pth (1.82.8)71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238
proxy_server.py (1.82.7)a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b
sysmon.py (backdoor)6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a

Network indicators:

  • Exfiltration: https://models.litellm.cloud/ (POST)
  • C2 polling: https://checkmarx.zone/raw (GET)

Filesystem indicators:

  • ~/.config/sysmon/sysmon.py or /root/.config/sysmon/sysmon.py
  • ~/.config/systemd/user/sysmon.service (description: "System Telemetry Service")
  • /tmp/tpcp.tar.gz, /tmp/session.key, /tmp/payload.enc, /tmp/session.key.enc

Kubernetes indicators:

  • Pods: node-setup-{node_name} in kube-system
  • Container name: setup, image: alpine:latest

This analysis draws on reporting from Snyk, FutureSearch, FutureSearch postmortem, Endor Labs, Wiz, BleepingComputer, The Register, JFrog, Aikido, and the community discussion in GitHub #24512.