Skip to content

Instantly share code, notes, and snippets.

@aldehir
Created March 25, 2026 00:30
Show Gist options
  • Select an option

  • Save aldehir/faad916112d97aa54ca30ea4688d823e to your computer and use it in GitHub Desktop.

Select an option

Save aldehir/faad916112d97aa54ca30ea4688d823e to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
Scans for Python virtual environments and checks for litellm installation,
flagging versions affected by the TeamPCP supply chain attack (v1.82.7, v1.82.8).
.DESCRIPTION
On 2026-03-24, malicious litellm versions 1.82.7 and 1.82.8 were published to PyPI
via stolen credentials. The payload:
- Harvests SSH keys, cloud creds, API keys, crypto wallets, shell history
- Exfiltrates data (AES-256 encrypted) to models.litellm[.]cloud
- Deploys privileged Kubernetes pods for lateral movement
- Installs persistent backdoor via systemd (sysmon.service)
- v1.82.8 uses a .pth file that runs on EVERY Python process startup
This script recursively finds virtualenvs and conda envs, checks for litellm,
reports the version, and flags compromised versions + indicators of compromise.
.EXAMPLE
.\Find-LiteLLM.ps1
.\Find-LiteLLM.ps1 -Path "D:\Projects"
.\Find-LiteLLM.ps1 -CheckIOCs
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[string]$Path = (Get-Location).Path,
[switch]$CheckIOCs
)
$CompromisedVersions = @("1.82.7", "1.82.8")
$MaliciousPthHash = "71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238"
$MaliciousProxyHash = "a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b"
$found = 0
$compromised = 0
function Write-Banner {
Write-Host ""
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host " litellm Supply Chain Attack Scanner" -ForegroundColor Cyan
Write-Host " Affected versions: 1.82.7, 1.82.8 (TeamPCP campaign)" -ForegroundColor Cyan
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host " Scanning: $Path" -ForegroundColor Gray
Write-Host ""
}
function Get-LiteLLMFromMetadata {
param([string]$SitePackages)
# Check dist-info metadata (pip-installed packages)
$distInfoDirs = Get-ChildItem -Path $SitePackages -Directory -Filter "litellm-*.dist-info" -ErrorAction SilentlyContinue
foreach ($dir in $distInfoDirs) {
$metaFile = Join-Path $dir.FullName "METADATA"
if (Test-Path $metaFile) {
$versionLine = Select-String -Path $metaFile -Pattern "^Version:\s*(.+)$" | Select-Object -First 1
if ($versionLine) {
return $versionLine.Matches[0].Groups[1].Value.Trim()
}
}
}
# Check egg-info metadata
$eggDirs = Get-ChildItem -Path $SitePackages -Directory -Filter "litellm-*.egg-info" -ErrorAction SilentlyContinue
foreach ($dir in $eggDirs) {
$pkgInfo = Join-Path $dir.FullName "PKG-INFO"
if (Test-Path $pkgInfo) {
$versionLine = Select-String -Path $pkgInfo -Pattern "^Version:\s*(.+)$" | Select-Object -First 1
if ($versionLine) {
return $versionLine.Matches[0].Groups[1].Value.Trim()
}
}
}
return $null
}
function Test-MaliciousPth {
param([string]$SitePackages)
$pthFile = Join-Path $SitePackages "litellm_init.pth"
if (Test-Path $pthFile) {
$hash = (Get-FileHash -Path $pthFile -Algorithm SHA256).Hash.ToLower()
return @{
Found = $true
Path = $pthFile
Hash = $hash
KnownMalicious = ($hash -eq $MaliciousPthHash)
}
}
return @{ Found = $false }
}
function Find-VirtualEnvs {
param([string]$SearchPath)
$envs = @()
# Find venv/virtualenv: look for pyvenv.cfg or Scripts/python.exe / bin/python
$cfgFiles = Get-ChildItem -Path $SearchPath -Recurse -Filter "pyvenv.cfg" -ErrorAction SilentlyContinue
foreach ($cfg in $cfgFiles) {
$envRoot = $cfg.DirectoryName
# Windows venvs have Scripts\python.exe
$pythonExe = Join-Path $envRoot "Scripts\python.exe"
if (-not (Test-Path $pythonExe)) {
# Unix-style venvs have bin/python
$pythonExe = Join-Path $envRoot "bin/python"
}
$sitePackages = Get-ChildItem -Path $envRoot -Recurse -Directory -Filter "site-packages" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($sitePackages) {
$envs += @{
Type = "venv/virtualenv"
Root = $envRoot
SitePackages = $sitePackages.FullName
}
}
}
# Find conda envs: look for conda-meta directories
$condaMeta = Get-ChildItem -Path $SearchPath -Recurse -Directory -Filter "conda-meta" -ErrorAction SilentlyContinue
foreach ($meta in $condaMeta) {
$envRoot = $meta.Parent.FullName
# Avoid duplicates if already found via pyvenv.cfg
if ($envs.Root -contains $envRoot) { continue }
$sitePackages = Get-ChildItem -Path $envRoot -Recurse -Directory -Filter "site-packages" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($sitePackages) {
$envs += @{
Type = "conda"
Root = $envRoot
SitePackages = $sitePackages.FullName
}
}
}
return $envs
}
function Test-IOCs {
Write-Host ""
Write-Host "--- Checking System-Wide Indicators of Compromise ---" -ForegroundColor Yellow
# Persistence: sysmon backdoor
$sysmonPath = Join-Path $env:USERPROFILE ".config/sysmon/sysmon.py"
if (Test-Path $sysmonPath) {
Write-Host " [CRITICAL] Backdoor found: $sysmonPath" -ForegroundColor Red
} else {
Write-Host " [OK] No sysmon backdoor at ~/.config/sysmon/" -ForegroundColor Green
}
# Persistence: systemd service (relevant on WSL)
$systemdService = Join-Path $env:USERPROFILE ".config/systemd/user/sysmon.service"
if (Test-Path $systemdService) {
Write-Host " [CRITICAL] Malicious systemd service found: $systemdService" -ForegroundColor Red
} else {
Write-Host " [OK] No sysmon systemd service" -ForegroundColor Green
}
# Check pip cache for compromised packages
$pipCacheDirs = @(
(Join-Path $env:LOCALAPPDATA "pip\cache"),
(Join-Path $env:USERPROFILE ".cache\pip"),
(Join-Path $env:USERPROFILE ".cache\uv")
)
foreach ($cacheDir in $pipCacheDirs) {
if (Test-Path $cacheDir) {
$maliciousCached = Get-ChildItem -Path $cacheDir -Recurse -Filter "litellm_init.pth" -ErrorAction SilentlyContinue
if ($maliciousCached) {
Write-Host " [WARNING] Malicious .pth file in cache: $($maliciousCached.FullName)" -ForegroundColor Red
}
}
}
# Check hosts file or DNS for C2 domains (informational)
Write-Host " [INFO] C2 domains to block/monitor:" -ForegroundColor Yellow
Write-Host " - models.litellm[.]cloud (exfiltration)" -ForegroundColor Yellow
Write-Host " - checkmarx[.]zone (C2 polling)" -ForegroundColor Yellow
Write-Host ""
}
# --- Main ---
Write-Banner
Write-Host "Searching for Python virtual environments..." -ForegroundColor Gray
Write-Host ""
$envs = Find-VirtualEnvs -SearchPath $Path
if ($envs.Count -eq 0) {
Write-Host "No Python virtual environments found under $Path" -ForegroundColor Yellow
} else {
Write-Host "Found $($envs.Count) virtual environment(s):" -ForegroundColor White
Write-Host ""
foreach ($env in $envs) {
$version = Get-LiteLLMFromMetadata -SitePackages $env.SitePackages
$pthResult = Test-MaliciousPth -SitePackages $env.SitePackages
$relPath = $env.Root
if ($relPath.StartsWith($Path)) {
$relPath = $relPath.Substring($Path.Length).TrimStart('\', '/')
if (-not $relPath) { $relPath = "." }
}
Write-Host " [$($env.Type)] $relPath" -ForegroundColor White
if ($version) {
$found++
$isCompromised = $CompromisedVersions -contains $version
if ($isCompromised) {
$compromised++
Write-Host " litellm $version" -ForegroundColor Red -NoNewline
Write-Host " *** COMPROMISED VERSION ***" -ForegroundColor Red
Write-Host " ACTION REQUIRED:" -ForegroundColor Red
Write-Host " 1. Uninstall immediately: pip uninstall litellm" -ForegroundColor Red
Write-Host " 2. Rotate ALL credentials on this machine" -ForegroundColor Red
Write-Host " 3. Check for lateral movement (K8s pods named node-setup-*)" -ForegroundColor Red
Write-Host " 4. Purge pip/uv caches: pip cache purge" -ForegroundColor Red
} else {
Write-Host " litellm $version" -ForegroundColor Green -NoNewline
Write-Host " (not affected)" -ForegroundColor Green
}
} else {
Write-Host " litellm not installed" -ForegroundColor Gray
}
if ($pthResult.Found) {
Write-Host " [CRITICAL] litellm_init.pth FOUND: $($pthResult.Path)" -ForegroundColor Red
if ($pthResult.KnownMalicious) {
Write-Host " SHA256 matches known malicious payload!" -ForegroundColor Red
} else {
Write-Host " SHA256: $($pthResult.Hash) (unknown — investigate manually)" -ForegroundColor Yellow
}
}
Write-Host ""
}
}
if ($CheckIOCs) {
Test-IOCs
}
# --- Summary ---
Write-Host "--- Summary ---" -ForegroundColor Cyan
Write-Host " Environments scanned : $($envs.Count)"
Write-Host " litellm installations: $found"
if ($compromised -gt 0) {
Write-Host " COMPROMISED : $compromised" -ForegroundColor Red
Write-Host ""
Write-Host " !!! $compromised environment(s) have a compromised litellm version !!!" -ForegroundColor Red
Write-Host " Treat affected systems as FULLY COMPROMISED." -ForegroundColor Red
Write-Host " Run with -CheckIOCs for persistence/backdoor checks." -ForegroundColor Red
exit 1
} else {
if ($found -gt 0) {
Write-Host " COMPROMISED : 0" -ForegroundColor Green
}
Write-Host ""
Write-Host " No compromised litellm versions detected." -ForegroundColor Green
exit 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment