Best-effort scanner to detect IOCs from the compromised LiteLLM PyPI packages v1.82.7/v1.82.8 (TeamPCP, March 2026)
Project description
LiteLLM Supply Chain Attack Scanner
A best-effort scanner that attempts to detect indicators of compromise (IOCs) left by the malicious LiteLLM PyPI packages v1.82.7 and v1.82.8, published on March 24, 2026. The compromise was identified by security researchers at Endor Labs, Datadog Security Labs, Snyk, and Sonatype, with the initial public disclosure filed as GitHub Issue #24512.
No guarantees. This tool attempts to find known artifacts associated with the compromise. It does not guarantee detection of all malicious activity, nor does it guarantee that a clean scan means your system was not affected. A determined attacker may have removed traces, and the tool cannot detect secrets that have already been exfiltrated. Use this scanner as one input in your incident response — not as a definitive verdict.
Quick Start
Option A: Install from PyPI
Linux / macOS:
# Recommended — installs in an isolated environment
pipx install scan-litellm-compromise
scan-litellm
If you don't have pipx: sudo apt install pipx (Debian/Ubuntu) or brew install pipx (macOS).
Alternatively, using pip inside a virtual environment:
python3 -m venv /tmp/scanner && /tmp/scanner/bin/pip install scan-litellm-compromise
/tmp/scanner/bin/scan-litellm
Note:
pip installdirectly will fail on modern Linux distributions (Debian 12+, Ubuntu 23.04+, Fedora 38+) due to PEP 668. Usepipxor a virtual environment instead.
Windows:
pip install scan-litellm-compromise
scan-litellm
Or using a virtual environment:
py -m venv %TEMP%\scanner && %TEMP%\scanner\Scripts\pip install scan-litellm-compromise
%TEMP%\scanner\Scripts\scan-litellm
Option B: Run from source (no install needed)
Linux / macOS:
git clone https://github.com/CognitiveSand/scan_litellm_compromise.git
cd scan_litellm_compromise
python3 run_scan.py
Windows — double-click run_scan.bat, or from a terminal:
git clone https://github.com/CognitiveSand/scan_litellm_compromise.git
cd scan_litellm_compromise
py run_scan.py
No dependencies required — uses only the Python standard library.
Command-Line Options
| Flag | Description |
|---|---|
--scan-path DIR |
Restrict Phase 1 (discovery) and Phase 4 (source scan) to a specific directory instead of scanning the entire system. IOC artifact checks (Phase 3) still run system-wide. |
--resolve-c2 |
Enable live DNS queries to C2 domains in addition to using hardcoded known IPs. See warning below. |
--help |
Show usage information. |
Warning about
--resolve-c2: This flag causes the scanner to make live DNS queries tomodels.litellm.cloudandcheckmarx.zone— both attacker-controlled domains. This carries real risks:
- Operational security: Your DNS query is visible to the attacker's infrastructure, revealing that you are actively investigating the compromise. This could prompt them to rotate infrastructure, wipe logs, or accelerate exploitation of already-stolen credentials.
- Network monitoring: The queries may trigger alerts in your own SIEM, IDS, or SOC dashboards, creating noise during an active incident response.
- DNS logging: Corporate DNS resolvers and upstream providers log queries. The domains may already be on threat intel blocklists, which could flag your machine as compromised.
In most cases you do not need this flag. The scanner ships hardcoded known IPs (
46.151.182.203,83.142.209.11) confirmed by multiple threat intelligence sources. Live DNS is only useful if you suspect the attacker has rotated to new infrastructure since this tool was last updated.
Examples:
# Scan only a specific project directory
python3 -m scan_litellm_compromise --scan-path /home/user/myproject
# Enable live DNS resolution for C2 connection checks
python3 -m scan_litellm_compromise --resolve-c2
# Combine both
python3 -m scan_litellm_compromise --scan-path ./myproject --resolve-c2
Video Overview
This scanner was inspired by Fahd Mirza's video highlighting the incident — thanks to him for bringing it to attention.
What Happened
On March 24, 2026, two backdoored versions of the litellm Python package were published to PyPI:
- v1.82.7 (uploaded ~10:39 UTC) — malicious code injected into
litellm/proxy/proxy_server.py(base64-encoded) - v1.82.8 (uploaded ~10:52 UTC) — added a
litellm_init.pthfile that executes on every Python interpreter startup, regardless of whether litellm is imported
The packages were available for approximately 3 hours before PyPI quarantined them. LiteLLM has roughly 95 million monthly downloads.
How the Attack Worked
The attacker compromised BerriAI's PYPI_PUBLISH token by poisoning the aquasecurity/trivy-action GitHub Action used in LiteLLM's CI/CD pipeline. Malicious commits were force-pushed onto 75 of 76 existing version tags in the Trivy action repository. When LiteLLM's CI ran, it fetched the attacker-controlled action, which exfiltrated the PyPI publishing token. This was identified and documented by researchers at Endor Labs, Snyk, and StepSecurity.
Three-Stage Payload
Stage 1 — Credential Harvester: Sweeps the local filesystem and live process environments for secrets including:
- SSH private keys (
~/.ssh/) - AWS, GCP, and Azure credentials
- Kubernetes kubeconfig and service account tokens
- Database credentials (PostgreSQL, MySQL, Redis)
- CI/CD tokens (PyPI, npm, Docker registry, GitHub PATs)
- All environment variables (capturing API keys for OpenAI, Anthropic, etc.)
.envfiles (recursively discovered)- Shell histories, git credentials, TLS private keys
- Cryptocurrency wallets
Stage 2 — Encrypted Exfiltration:
Harvested data is encrypted with AES-256-CBC (random session key) and the session key is encrypted with a hardcoded RSA-4096 public key. The bundle is archived as /tmp/tpcp.tar.gz and sent via HTTPS POST to models.litellm.cloud.
Stage 3 — Persistence Backdoor:
- Drops
~/.config/sysmon/sysmon.py - Creates
~/.config/systemd/user/sysmon.service("System Telemetry Service") - Every 50 minutes, polls
checkmarx.zone/rawfor a secondary payload, downloads it to/tmp/pglog, and executes it - Tracks state in
/tmp/.pg_state
Kubernetes Lateral Movement
If a Kubernetes service account token is found, the payload deploys privileged pods named node-setup-{node_name} into the kube-system namespace on every node. These pods mount the host root filesystem and install the sysmon backdoor at the node level.
What This Scanner Attempts to Find
This scanner makes a best-effort attempt to detect known IOCs. It automatically detects the platform (Linux, macOS, or Windows) and adjusts scan paths accordingly.
| Phase | What It Looks For |
|---|---|
| 1 | litellm metadata directories (dist-info / egg-info) across filesystem (Linux: /home, /opt, /usr, /srv, /var; macOS: /Users, /opt/homebrew, /Library; Windows: %USERPROFILE%, %APPDATA%, Program Files). Can be restricted with --scan-path. |
| 2 | litellm version from metadata files — no Python interpreter execution needed |
| 3 | IOC artifacts: litellm_init.pth, sysmon persistence, temp staging files, C2 network connections (matched against hardcoded known IPs by default; --resolve-c2 enables live DNS), suspicious Kubernetes pods. On Windows: also checks Registry Run keys and Scheduled Tasks |
| 4 | Source files and dependency configs (pyproject.toml, requirements.txt, etc.) that reference litellm, flagging any pinned to compromised versions |
Limitations
- This scanner cannot detect exfiltrated secrets. If your credentials were sent to the attacker's C2 server, no local scan can undo that. Credential rotation is required regardless of scan results.
- Artifacts may have been cleaned up. The absence of IOC files does not prove the system was never compromised.
- The scanner does not inspect Docker image layers, CI/CD runner caches, or remote systems. Each environment must be checked independently.
- Root-owned paths (
/rooton Linux) are excluded. If you need to scan those, run a separate check with appropriate privileges. - The scanner cannot decrypt exfiltrated data. Only the attacker holds the RSA-4096 private key.
- Windows Registry scanning is best-effort. The scanner checks common Run key locations but cannot cover all possible persistence mechanisms (WMI subscriptions, COM hijacking, etc.).
Usage
Linux / macOS:
python3 run_scan.py
# or
python3 -m scan_litellm_compromise
# or, if installed via pip:
scan-litellm
Windows — double-click run_scan.bat, or from a terminal:
py run_scan.py
The scanner auto-detects the platform (Linux, macOS, or Windows) and adjusts scan paths, network commands, and persistence checks accordingly.
Exit code is 1 if compromise indicators are found, 0 otherwise.
See Command-Line Options for --scan-path and --resolve-c2.
Platform Support
| Feature | Linux | macOS | Windows 10/11 |
|---|---|---|---|
| litellm detection | /home, /opt, /usr, /srv, /var |
/Users, /opt/homebrew, /usr/local, /Library |
%USERPROFILE%, %APPDATA%, Program Files |
| Conda/pipx detection | /opt/conda, ~/.local/share/pipx |
Homebrew Caskroom, ~/.local/share/pipx |
%LOCALAPPDATA%\Miniconda3, %LOCALAPPDATA%\pipx |
| Persistence check | systemd user services | sysmon files (inert without systemd) | Registry Run keys, Scheduled Tasks, Startup folder |
| Temp artifacts | /tmp/ |
/tmp/ |
%TEMP% |
| Network connections | ss -tnp |
lsof -i -P -n |
netstat -ano |
| C2 IP matching | Hardcoded known IPs (opt-in live DNS via --resolve-c2) |
Same | Same |
| ANSI terminal colors | Native | Native | Auto-enabled via Virtual Terminal Processing |
macOS Note
macOS support was added based on threat intelligence analysis of the TeamPCP malware's behavior on Darwin systems. It has not been tested on actual macOS hardware as the maintainer does not currently have access to a Mac. The malware's credential theft and .pth backdoor are confirmed to work on macOS, but the systemd persistence mechanism is inert (macOS uses launchd, not systemd). If you run this scanner on macOS and encounter issues, please open an issue.
If Compromise Is Detected
Assume ALL secrets on the affected machine are compromised. The following steps are recommended — this list is not exhaustive:
- Rotate credentials immediately — SSH keys, cloud provider credentials (AWS/GCP/Azure), API keys, database passwords, CI/CD tokens,
.envfile contents - Remove malicious artifacts — delete
litellm_init.pth, sysmon persistence files, and temp staging files (see platform-specific paths above) - Downgrade litellm —
pip install litellm==1.82.6(last known clean version) or upgrade past the compromised range once verified safe - Update pinned versions — check all
requirements.txt,pyproject.toml, and lock files for references to 1.82.7 or 1.82.8 - Inspect Kubernetes clusters — look for
node-setup-*pods inkube-system, audit ClusterRoleBindings - Block C2 domains —
models.litellm.cloudandcheckmarx.zoneat DNS/firewall level - Audit cloud provider logs — check AWS CloudTrail, GCP Audit Logs, Azure Activity Logs for unauthorized API calls using potentially stolen credentials
Advisories and References
| ID | Source |
|---|---|
| PYSEC-2026-2 | Python Packaging Authority (PyPA) |
| SNYK-PYTHON-LITELLM-15762713 | Snyk |
| GitHub Issue #24512 | Initial disclosure |
| GitHub Issue #24518 | Official timeline |
Security Research and Technical Analyses
- Endor Labs — full campaign timeline and attribution
- Datadog Security Labs — payload and artifact analysis
- Snyk — IOC hashes and advisory
- Sonatype — stage-by-stage payload analysis
- StepSecurity — Trivy supply chain compromise analysis
- Microsoft Security Blog — detection and defense guidance
Known IOC Hashes (SHA-256)
| File | SHA-256 |
|---|---|
litellm_init.pth (v1.82.8) |
71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238 |
proxy_server.py (v1.82.7) |
a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b |
sysmon.py (dropped backdoor) |
6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a |
Project Structure
scan_litellm_compromise/
__main__.py Entry point for python -m
scanner.py Orchestrator (argparse CLI)
config.py Cross-platform constants, patterns, and known C2 IPs
models.py Typed data structures
formatting.py Terminal output (ANSI with Windows support)
platform_policy.py Platform abstraction (Strategy pattern)
platform_linux.py Linux-specific paths and commands
platform_darwin.py macOS-specific paths and commands
platform_windows.py Windows-specific paths and commands
ioc_windows.py Windows-only IOC checks (Registry, Tasks)
discovery.py Phase 1 — find litellm metadata directories
version_checker.py Phase 2 — read litellm version from metadata
ioc_scanner.py Phase 3 — IOC artifact detection
source_scanner.py Phase 4 — source/config file scanning
report.py Phase 5 — summary and remediation
tests/ pytest test suite (299 tests)
run_scan.py Direct entry point
run_scan.bat Double-click launcher for Windows
Disclaimer
This tool is provided as-is, with no warranty of any kind. It attempts to find known indicators of the LiteLLM v1.82.7/v1.82.8 compromise but cannot guarantee complete detection. A clean scan does not mean your system was unaffected. Always perform credential rotation if there is any possibility that a compromised version was installed in your environment, even briefly.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scan_litellm_compromise-0.3.2.tar.gz.
File metadata
- Download URL: scan_litellm_compromise-0.3.2.tar.gz
- Upload date:
- Size: 38.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5087be8e11d5d632c5d4645256938e5492557c1ee1b1adb0299a77e9c60309ce
|
|
| MD5 |
ad560e052beb681a56bfd6d59aec513e
|
|
| BLAKE2b-256 |
bc5163ce2cac908dfccea6f761627f5c962b46f6e158795617f02b173013d6d8
|
Provenance
The following attestation bundles were made for scan_litellm_compromise-0.3.2.tar.gz:
Publisher:
pypi_publish.yml on CognitiveSand/scan_litellm_compromise
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scan_litellm_compromise-0.3.2.tar.gz -
Subject digest:
5087be8e11d5d632c5d4645256938e5492557c1ee1b1adb0299a77e9c60309ce - Sigstore transparency entry: 1194145397
- Sigstore integration time:
-
Permalink:
CognitiveSand/scan_litellm_compromise@45502a0fd3d8cfac4d2f52c600d806c45f75f4c5 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/CognitiveSand
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi_publish.yml@45502a0fd3d8cfac4d2f52c600d806c45f75f4c5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file scan_litellm_compromise-0.3.2-py3-none-any.whl.
File metadata
- Download URL: scan_litellm_compromise-0.3.2-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3137a3f6dd7593a451228483fe9128b52ff346fde043fcdbab2e600a5111499
|
|
| MD5 |
6d2dd8aa89de6121bc05e816c6072436
|
|
| BLAKE2b-256 |
c89a91622b9891bab04fc185f6875949fe82dc8feeb3197d372a9ff820a8abb1
|
Provenance
The following attestation bundles were made for scan_litellm_compromise-0.3.2-py3-none-any.whl:
Publisher:
pypi_publish.yml on CognitiveSand/scan_litellm_compromise
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scan_litellm_compromise-0.3.2-py3-none-any.whl -
Subject digest:
b3137a3f6dd7593a451228483fe9128b52ff346fde043fcdbab2e600a5111499 - Sigstore transparency entry: 1194145403
- Sigstore integration time:
-
Permalink:
CognitiveSand/scan_litellm_compromise@45502a0fd3d8cfac4d2f52c600d806c45f75f4c5 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/CognitiveSand
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi_publish.yml@45502a0fd3d8cfac4d2f52c600d806c45f75f4c5 -
Trigger Event:
release
-
Statement type: