LiteLLM & Trivy Supply Chain Attack
What it is & what to do.
- Do you use
litellm? (installed viapip install litellm) - Do you use Trivy in CI/CD? (e.g.,
aquasecurity/trivy-actionin GitHub Actions) - Did you install or update either tool between March 19 and March 24, 2026?
No to all? You're likely safe, but you can still scan to be sure.
What is the LiteLLM and Trivy supply chain attack?
In March 2026, attackers compromised the LiteLLM Python package on PyPI, injecting credential-stealing malware into versions 1.82.7 and 1.82.8. The backdoor used a .pth persistence file to harvest API keys, AWS credentials, SSH keys, and environment variables from every Python process on affected machines, exfiltrating them to attacker-controlled servers. The Trivy GitHub Actions tag was also hijacked on March 19, and Checkmarx maintainer credentials were compromised on March 23 as part of the same campaign.
Key terms used on this page
- IOC (Indicator of Compromise)
- A clue that an attack happened — a suspicious file, a known-bad IP address, or a compromised package version.
- Supply chain attack
- When attackers compromise a tool or library you depend on, so installing it silently runs their code on your machine.
- C2 (Command & Control)
- A server controlled by the attacker where stolen data is sent.
- Credential rotation
- Changing your passwords, API keys, and tokens so that stolen copies become useless.
- Pinning (version/SHA pinning)
- Locking a dependency to an exact version or commit hash so it can't be silently swapped for a malicious one.
- Mutable tag
- A version label (like
v1) that can be moved to point at different code — unlike a SHA hash, which is permanent.
An attacker got hold of the credentials used to publish LiteLLM to PyPI — the package registry where pip install pulls from — and used them to push two fake versions with a payload hidden inside.
The payload used a Python trick most people don't know about.
When you install a Python package, it can drop a file with a .pth extension into your site-packages directory. Python reads those files every single time the interpreter starts — not just when you import the package, but on every Python process, forever, until the file is deleted. The attacker put their credential harvester in a file called litellm_init.pth. From the moment you ran pip install, every Python process on your machine was running their code.
It scanned for:
- Environment variables matching key patterns (API keys, secrets, tokens)
~/.aws/credentials- SSH keys in
~/.ssh/ - Kubernetes tokens
- Docker credentials
- Database passwords
.envfiles
Then it encrypted everything with the attacker's public key — meaning only they can decrypt what was taken — and sent it via HTTPS to models.litellm.cloud. That domain was registered hours before the attack, designed to look like legitimate LiteLLM infrastructure. If you skimmed your network logs, you might not have caught it.
The window was 10:39 to 16:00 UTC on March 24th — about five and a half hours. 16:00 is when PyPI quarantined and removed the packages; the actual installable window may have been shorter, but if your system pulled litellm in that range, treat it as compromised.
This was part of a broader campaign. The Trivy GitHub Actions compromise on March 19 was the initial attack — credentials stolen from CI runners were then used to hit Checkmarx and LiteLLM in parallel, not as a sequential chain.
When did the LiteLLM supply chain attack happen?
The attack campaign spanned March 19–24, 2026. The Trivy GitHub Actions tag was hijacked on March 19, Checkmarx maintainer credentials were compromised on March 23, and the malicious LiteLLM packages (versions 1.82.7 and 1.82.8) were live on PyPI for approximately five and a half hours on March 24 between 10:39 and 16:00 UTC.
Am I affected by the LiteLLM Trivy compromise?
If you installed or updated LiteLLM via pip install litellm between March 24 10:39–16:00 UTC, or used aquasecurity/trivy-action in GitHub Actions between March 19–20, your environment may be compromised. Run the IOC scanner below to check for known indicators including compromised package versions, persistence artifacts, and C2 domain connections.
New to this? How to run the scanner
- Mac / Linux: Open Terminal (search "Terminal" in Spotlight or your app launcher).
- Windows: Open PowerShell (search "PowerShell" in Start menu). Use the PowerShell tab below.
- Click Copy on the script below, paste it into your terminal, and press Enter.
- The script only reads your system — it does not change or delete anything.
- When it finishes, look for
[CRITICAL],[OK], or[INFO]in the output. See the guide below the script.
#!/bin/bash # ClawSwitch IOC Scanner — TeamPCP supply chain (Trivy + LiteLLM + Checkmarx) echo "[clawswitch] scanning for supply chain IOCs..." echo "──────────────────────────────────────────────" # --- LiteLLM version --- if pip show litellm >/dev/null 2>&1; then VER=$(pip show litellm | grep Version | awk '{print $2}') if [[ "$VER" == "1.82.7" || "$VER" == "1.82.8" ]]; then echo "[CRITICAL] litellm==$VER — COMPROMISED. Rotate all credentials NOW." else echo "[OK] litellm==$VER — not a known bad version" fi else echo "[INFO] litellm not installed" fi # --- litellm_init.pth persistence artifact --- PTH=$(find / -name "litellm_init.pth" 2>/dev/null) if [ -n "$PTH" ]; then echo "[CRITICAL] litellm_init.pth found: $PTH — persistence artifact" else echo "[OK] litellm_init.pth not found" fi # --- sysmon persistence artifacts --- if [ -f ~/.config/sysmon/sysmon.py ]; then echo "[CRITICAL] sysmon.py found: ~/.config/sysmon/sysmon.py — persistence artifact" else echo "[OK] sysmon.py not found" fi if [ -f ~/.config/systemd/user/sysmon.service ]; then echo "[CRITICAL] sysmon.service found — systemd persistence active" else echo "[OK] sysmon.service not found" fi # --- Trivy version --- if command -v trivy >/dev/null 2>&1; then TV=$(trivy --version 2>&1 | grep -o '[0-9]*\.[0-9]*\.[0-9]*' | head -1) if [[ "$TV" == "0.69.4" ]]; then echo "[CRITICAL] trivy==$TV — COMPROMISED VERSION" else echo "[OK] trivy==$TV — not a known bad version" fi else echo "[INFO] trivy not installed" fi # --- C2 domain and IP hunt --- echo "──────────────────────────────────────────────" echo "[clawswitch] hunting C2 indicators..." C2=("models.litellm.cloud" "checkmarx.zone" "scan.aquasecurtiy.org" "plug-tab-protective-relay.trycloudflare.com" "83.142.209.11" "45.148.10.212" "46.151.182.203" "tpcp-docs") for IOC in "${C2[@]}"; do HITS=$(grep -rl "$IOC" ~/.bash_history ~/.zsh_history /var/log/ /tmp/ 2>/dev/null | wc -l) if [ "$HITS" -gt 0 ]; then echo "[CRITICAL] IOC found: $IOC ($HITS file(s))" else echo "[OK] No hits: $IOC" fi done # --- GitHub: tpcp-docs exfil repo (requires gh cli) --- if command -v gh >/dev/null 2>&1; then if gh repo list --limit 200 2>/dev/null | grep -q "tpcp-docs"; then echo "[CRITICAL] tpcp-docs in GitHub org — secrets were exfiltrated. Begin IR." else echo "[OK] tpcp-docs not found in GitHub org" fi fi echo "──────────────────────────────────────────────" echo "[clawswitch] done. CRITICAL findings? → clawswitch.io"
# ClawSwitch IOC Scanner — TeamPCP supply chain # PowerShell: Windows Write-Host "[clawswitch] scanning..." -ForegroundColor Cyan Write-Host "──────────────────────────────────────────────" # --- LiteLLM version --- try { $out = pip show litellm 2>$null if ($out) { $ver = ($out | Select-String "Version").ToString().Split(" ")[1].Trim() if ($ver -in @("1.82.7","1.82.8")) { Write-Host "[CRITICAL] litellm==$ver COMPROMISED — rotate all credentials NOW" -ForegroundColor Red } else { Write-Host "[OK] litellm==$ver — clean" -ForegroundColor Green } } else { Write-Host "[INFO] litellm not installed" -ForegroundColor Gray } } catch { Write-Host "[INFO] pip not available" -ForegroundColor Gray } # --- litellm_init.pth --- $pth = Get-ChildItem -Path $env:USERPROFILE,$env:APPDATA -Filter "litellm_init.pth" -Recurse -ErrorAction SilentlyContinue if ($pth) { $pth | ForEach-Object { Write-Host "[CRITICAL] $($_.FullName)" -ForegroundColor Red } } else { Write-Host "[OK] litellm_init.pth not found" -ForegroundColor Green } # --- Trivy version --- try { $tv = trivy --version 2>$null if ($tv -match '(\d+\.\d+\.\d+)') { $ver = $Matches[1] if ($ver -eq "0.69.4") { Write-Host "[CRITICAL] trivy==$ver COMPROMISED" -ForegroundColor Red } else { Write-Host "[OK] trivy==$ver — clean" -ForegroundColor Green } } } catch { Write-Host "[INFO] trivy not installed" -ForegroundColor Gray } # --- C2 hunt: DNS cache + PS history --- Write-Host "──────────────────────────────────────────────" Write-Host "[clawswitch] hunting C2 indicators..." -ForegroundColor Cyan $c2 = @("models.litellm.cloud","checkmarx.zone","scan.aquasecurtiy.org", "plug-tab-protective-relay.trycloudflare.com", "83.142.209.11","45.148.10.212","46.151.182.203") $dnsCache = Get-DnsClientCache -ErrorAction SilentlyContinue | Out-String $psHist = Get-Content "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" -Raw -ErrorAction SilentlyContinue foreach ($ioc in $c2) { if ($dnsCache -match [regex]::Escape($ioc) -or $psHist -match [regex]::Escape($ioc)) { Write-Host "[CRITICAL] IOC found: $ioc" -ForegroundColor Red } else { Write-Host "[OK] No hits: $ioc" -ForegroundColor Green } } Write-Host "──────────────────────────────────────────────" Write-Host "[clawswitch] done → clawswitch.io" -ForegroundColor Cyan
#!/usr/bin/env python3 # ClawSwitch IOC Scanner — cross-platform (macOS / Linux / Windows) import subprocess, pathlib, sys, shutil, re R="\033[91m"; G="\033[92m"; C="\033[96m"; D="\033[90m"; X="\033[0m" ok = lambda m: print(f"{G}[OK] {X} {m}") crit= lambda m: print(f"{R}[CRITICAL]{X} {m}") info= lambda m: print(f"{D}[INFO] {X} {m}") div = lambda : print(f"{C}{'─'*46}{X}") print(f"\n{C}[clawswitch] TeamPCP supply chain IOC scanner{X}"); div() # LiteLLM version try: r = subprocess.run(["pip","show","litellm"], capture_output=True, text=True) vl = [l for l in r.stdout.splitlines() if l.startswith("Version")] if vl: v = vl[0].split(":")[1].strip() crit(f"litellm=={v} COMPROMISED. Rotate all credentials.") if v in ("1.82.7","1.82.8") else ok(f"litellm=={v} clean") else: info("litellm not installed") except: info("pip unavailable") # litellm_init.pth pth = [f for p in sys.path for f in (pathlib.Path(p).rglob("litellm_init.pth") if pathlib.Path(p).exists() else [])] [crit(f"litellm_init.pth: {f}") for f in pth] if pth else ok("litellm_init.pth not found") # Trivy version if shutil.which("trivy"): r = subprocess.run(["trivy","--version"], capture_output=True, text=True) m = re.search(r'(\d+\.\d+\.\d+)', r.stdout) if m: crit(f"trivy=={m.group(1)} COMPROMISED") if m.group(1)=="0.69.4" else ok(f"trivy=={m.group(1)} clean") else: info("trivy not installed") div(); print(f"{C}[clawswitch] hunting C2 indicators...{X}") C2=["models.litellm.cloud","checkmarx.zone","scan.aquasecurtiy.org", "plug-tab-protective-relay.trycloudflare.com","tpcp-docs", "83.142.209.11","45.148.10.212","46.151.182.203"] home = pathlib.Path.home() scan_paths = [ home/".bash_history", home/".zsh_history", home/"AppData/Roaming/Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt", pathlib.Path("/tmp") ] for ioc in C2: hits=[] for p in scan_paths: try: if p.is_file() and ioc in p.read_text(errors="ignore"): hits.append(str(p)) elif p.is_dir(): [hits.append(str(f)) for f in p.iterdir() if f.is_file() and ioc in f.read_text(errors="ignore")] except: pass crit(f"IOC [{ioc}] in {hits}") if hits else ok(f"No hits: {ioc}") div(); print(f"{C}[clawswitch] done → clawswitch.io{X}\n")
What should I do if I installed the compromised LiteLLM package?
If you installed LiteLLM 1.82.7 or 1.82.8, or used Trivy 0.69.4, you need to immediately: (1) remove the compromised packages and persistence artifacts, (2) rotate all credentials that were on the affected machine, (3) audit and pin your CI/CD action references to immutable commit SHAs, and (4) block the known C2 domains and IPs at the host level.
# Remove bad LiteLLM, pin to last clean version pip uninstall litellm -y pip install "litellm==1.82.6" # Verify Trivy — if 0.69.4, reinstall 0.69.3 trivy --version # Reinstall from: https://github.com/aquasecurity/trivy/releases/tag/v0.69.3 # Remove persistence artifacts find / -name "litellm_init.pth" -delete 2>/dev/null rm -f ~/.config/sysmon/sysmon.py # Flush DNS cache sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Remove bad LiteLLM, pin to last clean version pip uninstall litellm -y pip install "litellm==1.82.6" # Verify Trivy — if 0.69.4, reinstall 0.69.3 trivy --version # Reinstall from: https://github.com/aquasecurity/trivy/releases/tag/v0.69.3 # Remove persistence artifacts find / -name "litellm_init.pth" -delete 2>/dev/null rm -f ~/.config/sysmon/sysmon.py rm -f ~/.config/systemd/user/sysmon.service systemctl --user disable sysmon.service 2>/dev/null
# Remove bad LiteLLM, pin to last clean version pip uninstall litellm -y pip install "litellm==1.82.6" # Verify Trivy — if 0.69.4, reinstall 0.69.3 trivy --version # Reinstall from: https://github.com/aquasecurity/trivy/releases/tag/v0.69.3 # Remove persistence artifact Get-ChildItem -Path $env:USERPROFILE,$env:APPDATA -Recurse -Filter "litellm_init.pth" -ErrorAction SilentlyContinue | Remove-Item -Force
# Scan env vars for key patterns env | grep -iE "(api_key|secret|token|password|aws_|gcp_|azure_|openai|anthropic|github|stripe)" # Find .env files find ~ -name ".env" -o -name ".env.*" 2>/dev/null | head -20 # Cloud credential files cat ~/.aws/credentials 2>/dev/null ls ~/.config/gcloud/application_default_credentials.json 2>/dev/null ls ~/.kube/config 2>/dev/null ls ~/.docker/config.json 2>/dev/null ls ~/.ssh/ 2>/dev/null # --- Rotate via each provider --- # AWS: aws iam create-access-key / delete old key in console # GitHub: Settings > Developer Settings > Personal Access Tokens # OpenAI: platform.openai.com > API Keys > revoke + reissue # Anthropic: console.anthropic.com > API Keys # GCP: gcloud auth revoke + IAM service account key rotation
# Scan env vars for key patterns env | grep -iE "(api_key|secret|token|password|aws_|gcp_|azure_|openai|anthropic|github|stripe)" # Find .env files find ~ -name ".env" -o -name ".env.*" 2>/dev/null | head -20 # Cloud credential files cat ~/.aws/credentials 2>/dev/null ls ~/.config/gcloud/application_default_credentials.json 2>/dev/null ls ~/.kube/config 2>/dev/null ls ~/.docker/config.json 2>/dev/null ls ~/.ssh/ 2>/dev/null # --- Rotate via each provider --- # AWS: aws iam create-access-key / delete old key in console # GitHub: Settings > Developer Settings > Personal Access Tokens # OpenAI: platform.openai.com > API Keys > revoke + reissue # Anthropic: console.anthropic.com > API Keys # GCP: gcloud auth revoke + IAM service account key rotation # Azure: az ad sp credential reset # npm: npm token revoke
# Scan env vars for key patterns Get-ChildItem Env: | Where-Object { $_.Name -match "API_KEY|SECRET|TOKEN|PASSWORD|AWS_|GCP_|AZURE_|OPENAI|ANTHROPIC|GITHUB|STRIPE" } # Find .env files Get-ChildItem -Path $env:USERPROFILE -Recurse -Filter ".env" -ErrorAction SilentlyContinue | Select-Object -First 20 # Cloud credential files Get-Content "$env:USERPROFILE\.aws\credentials" -ErrorAction SilentlyContinue Test-Path "$env:APPDATA\gcloud\application_default_credentials.json" Test-Path "$env:USERPROFILE\.kube\config" Test-Path "$env:USERPROFILE\.docker\config.json" Get-ChildItem "$env:USERPROFILE\.ssh" -ErrorAction SilentlyContinue # --- Rotate via each provider --- # AWS: aws iam create-access-key / delete old key in console # GitHub: Settings > Developer Settings > Personal Access Tokens # OpenAI: platform.openai.com > API Keys > revoke + reissue # Anthropic: console.anthropic.com > API Keys # Azure: az ad sp credential reset
@v0.69.0), it could have been silently redirected to malicious code. This shows you how to lock it to an exact commit hash so that can't happen again.#!/bin/bash # Find all GitHub Actions workflows referencing trivy-action via mutable tag # If your pipeline ran on March 19-20, treat runner secrets as compromised # Find affected workflow files find . -path "*/.github/workflows/*.yml" -o -path "*/.github/workflows/*.yaml" 2>/dev/null \ | xargs grep -l "trivy-action\|setup-trivy" 2>/dev/null # Show exact lines find . -path "*/.github/workflows/*" 2>/dev/null \ | xargs grep -n "trivy-action\|setup-trivy" 2>/dev/null # Check for tpcp-docs exfil repo in your org (requires gh cli) gh repo list --limit 200 2>/dev/null | grep "tpcp-docs" # --- FIX: Pin to immutable commit SHA, not mutable tag --- # # UNSAFE: # uses: aquasecurity/trivy-action@v0.69.0 # # SAFE (pinned SHA): # uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # # Mutable tags can be silently redirected to malicious commits. # This is how this campaign worked.
# Find all GitHub Actions workflows referencing trivy-action via mutable tag # If your pipeline ran on March 19-20, treat runner secrets as compromised # Find affected workflow files Get-ChildItem -Recurse -Include *.yml,*.yaml -Path ".github\workflows" -ErrorAction SilentlyContinue | Select-String -Pattern "trivy-action|setup-trivy" # Check for tpcp-docs exfil repo in your org (requires gh cli) gh repo list --limit 200 2>$null | Select-String "tpcp-docs" # --- FIX: Pin to immutable commit SHA, not mutable tag --- # # UNSAFE: # uses: aquasecurity/trivy-action@v0.69.0 # # SAFE (pinned SHA): # uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # # Mutable tags can be silently redirected to malicious commits. # This is how this campaign worked.
# Add C2 domains to /etc/hosts sudo tee -a /etc/hosts << 'EOF' # ClawSwitch — TeamPCP C2 block March 2026 0.0.0.0 models.litellm.cloud 0.0.0.0 checkmarx.zone 0.0.0.0 scan.aquasecurtiy.org 0.0.0.0 plug-tab-protective-relay.trycloudflare.com EOF # Flush DNS cache sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Add C2 domains to /etc/hosts sudo tee -a /etc/hosts << 'EOF' # ClawSwitch — TeamPCP C2 block March 2026 0.0.0.0 models.litellm.cloud 0.0.0.0 checkmarx.zone 0.0.0.0 scan.aquasecurtiy.org 0.0.0.0 plug-tab-protective-relay.trycloudflare.com EOF # Flush DNS cache sudo systemd-resolve --flush-caches 2>/dev/null || sudo service nscd restart # Block C2 IPs via iptables sudo iptables -A OUTPUT -d 83.142.209.11 -j DROP sudo iptables -A OUTPUT -d 45.148.10.212 -j DROP sudo iptables -A OUTPUT -d 46.151.182.203 -j DROP
# Run as Administrator # Add C2 domains to hosts file $hosts = "C:\Windows\System32\drivers\etc\hosts" $entries = @( "0.0.0.0 models.litellm.cloud" "0.0.0.0 checkmarx.zone" "0.0.0.0 scan.aquasecurtiy.org" "0.0.0.0 plug-tab-protective-relay.trycloudflare.com" ) Add-Content -Path $hosts -Value $entries # Flush DNS cache ipconfig /flushdns
What are the indicators of compromise (IOCs) for the LiteLLM attack?
The March 2026 supply chain campaign left indicators across three tools: LiteLLM (compromised PyPI packages, .pth persistence files, and the models.litellm.cloud C2 domain), Trivy (hijacked GitHub Actions tag, scan.aquasecurtiy.org typosquat domain), and Checkmarx (compromised maintainer credentials, checkmarx.zone C2 domain). The full list of IOCs is below.
| Type | Indicator | Affected Tool |
|---|---|---|
| package version | litellm==1.82.7, 1.82.8 | LiteLLM |
| filesystem | litellm_init.pth (site-packages) | LiteLLM |
| filesystem | ~/.config/sysmon/sysmon.py | LiteLLM |
| filesystem | ~/.config/systemd/user/sysmon.service | LiteLLM |
| C2 domain | models.litellm[.]cloud | LiteLLM |
| C2 IP | 83.142.209[.]11 | Checkmarx |
| package version | trivy==0.69.4 | Trivy |
| C2 domain | scan.aquasecurtiy[.]org (typosquat) | Trivy |
| C2 IP | 45.148.10[.]212 | Trivy |
| C2 tunnel | plug-tab-protective-relay.trycloudflare.com | Trivy |
| ICP blockchain C2 | tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io | Trivy |
| GitHub exfil repo | tpcp-docs | Trivy |
| C2 domain | checkmarx[.]zone | Checkmarx |
| C2 IP | 46.151.182[.]203 | Checkmarx |
Frequently asked questions about the LiteLLM supply chain attack
Does this affect me if I use LiteLLM through a managed API proxy?
If you access LiteLLM as a hosted service or through a managed proxy where you never ran pip install litellm on your own machines, you are not directly affected. The compromised code only ran on systems that installed the malicious PyPI package locally. However, check with your provider to confirm they weren't running the compromised versions on their infrastructure.
Is the current version of LiteLLM safe to install?
Yes. The compromised versions (1.82.7 and 1.82.8) were yanked from PyPI on March 24, 2026 at 16:00 UTC. The maintainers have regained control of the package and subsequent releases are clean. Pin to version 1.82.6 or later verified releases, and verify the package hash after installation.
What data was exfiltrated by the compromised LiteLLM package?
The malware harvested environment variables matching key patterns (API keys, secrets, tokens), AWS credentials from ~/.aws/credentials, SSH keys from ~/.ssh/, Kubernetes tokens, Docker credentials, database passwords, and .env files. Everything was encrypted with the attacker's public key and sent to models.litellm.cloud over HTTPS.
Do I need to rotate credentials even if I updated quickly?
Yes. The malicious .pth file executed on every Python process start, not just when importing LiteLLM. Even a brief installation window means your credentials were likely exfiltrated. Rotate all API keys, cloud credentials, SSH keys, and tokens that were present on the affected machine, regardless of how quickly you updated.
How do I prevent supply chain attacks in my Python projects?
Pin dependencies to exact versions and verify package hashes in your lockfiles. Use pip install --require-hashes to enforce hash checking. For GitHub Actions, always reference actions by immutable commit SHA, not mutable version tags. Run dependency scanning tools in CI, and monitor for unexpected network connections from your build environments.
Can AI coding agents like Claude Code or Cursor detect this compromise?
AI coding agents can help you scan for indicators of compromise by running the detection scripts on this page. If you're using Claude Code, you can paste the bash or Python scanner directly into your terminal session. These agents can also help you audit your requirements.txt, Pipfile.lock, or poetry.lock for references to the compromised versions and assist with credential rotation workflows.