Active Incident

LiteLLM & Trivy Supply Chain Attack
What it is & what to do.

Authors: Immanuel Chavoya & Ruben Sarino · Last updated
Am I affected? — Quick check
  • Do you use litellm? (installed via pip install litellm)
  • Do you use Trivy in CI/CD? (e.g., aquasecurity/trivy-action in GitHub Actions)
  • Did you install or update either tool between March 19 and March 24, 2026?
Yes to any? You need to scan — go to section 03.
No to all? You're likely safe, but you can still scan to be sure.
01

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
  • .env files

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.

02

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.

Mar 19
Trivy GitHub Actions tag hijacked; malicious binary exfiltrates CI secrets
Mar 20
Trivy compromise discovered, tags restored to legitimate commits
Mar 24 10:39 UTC
LiteLLM 1.82.7 published to PyPI with credential-stealing backdoor
Mar 24 16:00 UTC
LiteLLM 1.82.7 and 1.82.8 yanked from PyPI
Mar 23
Checkmarx maintainer credentials compromised; checkmarx[.]zone C2 activated
03

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
  1. Mac / Linux: Open Terminal (search "Terminal" in Spotlight or your app launcher).
  2. Windows: Open PowerShell (search "PowerShell" in Start menu). Use the PowerShell tab below.
  3. Click Copy on the script below, paste it into your terminal, and press Enter.
  4. The script only reads your system — it does not change or delete anything.
  5. 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")
04

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.

01 Remove compromised packages & pin to clean versions
What this does: Uninstalls the compromised versions of LiteLLM and Trivy, installs the last known-safe versions, and deletes the persistence artifacts the malware dropped to survive reboots.
# 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
02 Find and rotate exposed credentials
What this does: Lists every API key, password, and cloud credential on the compromised machine — these are what the attacker likely stole. You then go to each service and generate new keys so the stolen ones stop working.
# 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
03 Audit CI/CD — find and pin mutable Trivy action refs
What this does: Searches your GitHub Actions workflow files for references to the Trivy action. If you used a version tag (like @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.
04 Block C2 at host level
What this does: Adds the attacker's server addresses to your computer's blocklist so even if malicious code is still running, it can't phone home to send stolen data.
# 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
05

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.

Indicators of Compromise for the March 2026 LiteLLM, Trivy, and Checkmarx supply chain attack
TypeIndicatorAffected Tool
package versionlitellm==1.82.7, 1.82.8LiteLLM
filesystemlitellm_init.pth (site-packages)LiteLLM
filesystem~/.config/sysmon/sysmon.pyLiteLLM
filesystem~/.config/systemd/user/sysmon.serviceLiteLLM
C2 domainmodels.litellm[.]cloudLiteLLM
C2 IP83.142.209[.]11Checkmarx
package versiontrivy==0.69.4Trivy
C2 domainscan.aquasecurtiy[.]org (typosquat)Trivy
C2 IP45.148.10[.]212Trivy
C2 tunnelplug-tab-protective-relay.trycloudflare.comTrivy
ICP blockchain C2tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]ioTrivy
GitHub exfil repotpcp-docsTrivy
C2 domaincheckmarx[.]zoneCheckmarx
C2 IP46.151.182[.]203Checkmarx
06

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.

We ship updates, not spam.

you're on the list. we'll be in touch.
Need it now? Talk to the founders →