Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pypi-supply-scan

Catch install-time PyPI malware before pip install runs it. Zero dependencies, one file, MIT.

python pypi_supply_scan.py requests
# 🟢 requests 2.34.2  [SAFE]

python pypi_supply_scan.py some-sketchy-package
# 🔴 some-sketchy-package 1.0.0  [CRITICAL]
#     CRITICAL Network call during install (setup.py:14)
#     CRITICAL Decode-then-execute chain (setup.py:15)

Why this exists

pip install foo executes foo's setup.py to build and install it. A legitimate setup.py only declares metadata — but nothing stops a malicious one from opening a network connection, spawning a shell, exec-ing an obfuscated blob, or reading your ~/.ssh and *_SECRET env vars the moment you install it, as your user. That is the entire PyPI supply-chain attack surface, and it runs before any of your own code does.

This tool downloads a package's sdist without executing it and scans setup.py for those install-time behaviors. Because a real setup.py never phones home or runs code, the signal is unusually clean — far more precise than a general code scan where subprocess is normal.

What it catches

Signature Severity
Network call at install (requests/urllib/socket/…) CRITICAL
Dynamic code exec (exec/eval/marshal.loads/__import__('…')) CRITICAL
Obfuscated payload (base64.b64decode/bytes.fromhex/\x.. blobs) CRITICAL
Decode-then-execute chain (exec(base64…) CRITICAL
Download-and-run (curl … | sh, urlretrieve+run) CRITICAL
Reads credentials/secrets (*_SECRET/~/.ssh/id_rsa/~/.aws/credentials/browser cookies) HIGH
Process spawn (os.system/subprocess.Popen/…) HIGH

Calibrated, not noisy

Most supply-chain scanners get muted within a week because they flag every subprocess and every env-var read. This one is tuned on both sides:

  • A normal setup.py — including git-based versioning (subprocess.Popen(["git","rev-list",…])), build flags (os.environ["CMAKE_ARGS"], os.getenv("RELEASE_VERSION")), and dependency names (install_requires=["keychain"]) — scores 0. Those are the three false-positive traps that make naive scanners useless, and they are explicitly suppressed.
  • Five real-world malware patterns (install-time exfiltration, exec(base64…), subprocess download, a cmdclass post-install hook, a curl-pipe-to-shell) all trip at CRITICAL/HIGH.

If a scanner can't pass a clean setup.py, its silence on the real threats means nothing.

Usage

# scan named packages (exit code 2 if any is CRITICAL — CI-friendly)
python pypi_supply_scan.py requests numpy some-package

# find + scan typosquats of packages you depend on (the #1 real vector: a typo fetches malware)
python pypi_supply_scan.py --typosquat requests django discord

# scan the freshly-published feed (where malware lands before removal)
python pypi_supply_scan.py --recent 50

# machine-readable
python pypi_supply_scan.py --json requests

In CI, before you install

python pypi_supply_scan.py $(python - <<'EOF'
import tomllib,sys
# print your top-level deps, one per line, then feed them to the scanner
EOF
) || { echo "install-time risk found"; exit 1; }

The real risk is the typo

Scanning the fresh-upload feed usually shows ~100% clean at any instant — PyPI removes malware fast. The durable risk isn't the registry being "full of malware"; it's a typo fetching one of the typosquat slots that are already claimed. Run --typosquat on your own dependencies and see how many one-keystroke-away names already exist. For 42 popular libraries, 138 typosquat-named packages exist right now (dango, crytography, aihttp, ddiscord, bitcoin-cli, …).

Defense checklist

  1. Scan setup.py before install (this tool) and refuse on CRITICAL.
  2. Prefer wheels: pip install --only-binary :all: — no setup.py executed at install.
  3. Pin + hash: pip install --require-hashes so a compromised new release of a trusted name can't silently swap in.
  4. Allowlist exact names so a typo fails closed instead of fetching a loaded slot.
  5. Sandbox installs of untrusted packages — no credentials, no ~/.ssh, no egress beyond the index.

Related

  • Hosted, no-install scanning and a security-and-web-data API suite for agents (pay-per-call, no signup): the same precision engine behind this tool runs as an x402 API — eltociear-skill-audit.hf.space / eltociear-tokenguard.hf.space.
  • Hosted on the Apify Store (Repo Security Scanner) — the supply-chain engine as a no-install Actor: scan a GitHub org or repo list, pay per repo.
  • A deeper writeup — the six signatures, the false-positive taxonomy, and a calibrated scan of 259 real packages — is available as a research report.

License

MIT — see LICENSE. Findings are pattern matches that warrant human review, not verdicts; confirmed malware should be reported to PyPI security, never published as an unverified accusation.

About

Catch install-time PyPI malware before pip install runs it. Zero-dep setup.py scanner + typosquat hunter, six signatures with a documented false-positive taxonomy, calibrated over a 259-package scan.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages