Skip to main content

Pre-install CVE gate for pip — blocks vulnerable and freshly published packages before install

Project description

pip-cve-gate

Pre-install CVE gate for pip. Blocks vulnerable and freshly published packages before any code runs on your machine.

safe-pip install flask requests django
# [pip-cve-gate] Scanning 3 package(s)…
# [pip-cve-gate] Resolved 27 package(s) (incl. transitive deps)
# [pip-cve-gate] All clear — delegating to pip

If a package is blocked:

safe-pip install somelib
# [pip-cve-gate] BLOCKED — install aborted
#   [CVE] 'somelib==1.2.3' has known vulnerabilities: GHSA-xxxx-yyyy-zzzz
#   [FRESH_HOLD] 'dep==0.0.1' was published 1d ago (hold: 3d). Use --skip-fresh-hold to override.

Exit code 0 = clean, 1 = blocked, 2 = error.


Why

Post-install tools (pip-audit, safety) run after pip has already downloaded and potentially executed install scripts. By then it's too late for zero-hour supply chain attacks.

pip has no native plugin hook for pre-install scanning. pip-cve-gate fills that gap with a wrapper that resolves the full dependency tree, scans every package against three independent feeds, and only delegates to real pip when everything is clean.

The closest prior art — pipask — checks PyPI advisories but lacks freshness hold and OSSF malicious package coverage. pip-cve-gate covers all three.


What it checks

Signal Source Fail behaviour
Known CVEs / advisories osv-scanner (the OSV database engine) Block
OSSF malicious packages ossf/malicious-packages Block
Freshness hold (default 3d) PyPI upload timestamp Block (overridable)

Borrow the engine, own the policy. CVE lookup is a commodity, so the gate delegates it to Google's osv-scanner (install: brew install osv-scanner, or see their releases) rather than hand-rolling OSV queries. pip-cve-gate keeps the policy layer — dependency resolution, freshness hold, OSSF check, and the fail-closed semantics below.

A package set is scanned with --data-source native, so osv-scanner checks exactly the versions the gate resolved.

Fail-closed by default

If a feed cannot produce a verdict — osv-scanner missing, a network error, the OSSF index unreachable — the gate emits an UNVERIFIED block and refuses the install. "Can't verify" means "don't allow." The only way past an unverifiable scan is the explicit opt-out:

safe-pip install flask --allow-unknown   # install despite an unverifiable feed (fail-open opt-in)

(A truncated OSSF index — common on GitHub without a token — is the one softer case: it warns and continues on the partial set rather than blocking everything.)

Known limitations

  • Version resolution is not pip-compatible. For unversioned specs (safe-pip install flask), the gate scans the latest release on PyPI, not the version pip would actually pick under your existing constraints. Pin or use a requirements file with explicit specifiers (flask==3.0.0 or flask>=3,<4) when you need the scan to match what pip will install.
  • Environment markers are evaluated in the current interpreter. A dep gated by sys_platform == "win32" scanned on Linux will be skipped, just as pip would skip it.
  • PyPI JSON API has a 60 req/min rate limit. Caching dedups transitive lookups, but very large dep graphs may still hit it. Set GITHUB_TOKEN to lift the OSSF feed's 60 req/hour limit.
  • Editable (-e), URL, and VCS installs are not scannable. They have no PyPI metadata to resolve, so they are forwarded to pip unscanned — each one is flagged with a stderr warning so the gap is visible.

Usage

safe-pip is a drop-in replacement for pip install:

safe-pip install flask
safe-pip install "django>=4.2" "celery==5.3.6"
safe-pip install -r requirements.txt
safe-pip install flask --skip-fresh-hold   # bypass freshness hold only
safe-pip install flask --allow-unknown     # install even if a feed can't be verified

Non-install subcommands pass through to real pip unchanged:

safe-pip list
safe-pip show flask
safe-pip uninstall flask

Install

pip install pip-cve-gate

Homebrew (macOS / Linux):

brew install sharkyger/tap/pip-cve-gate

Engine requirement (v0.3.0+). The CVE check is powered by the external osv-scanner binary. The Homebrew formula installs it automatically (depends_on "osv-scanner"); with pip install you must add it yourself (brew install osv-scanner or your platform's equivalent). If it is absent the gate fails closed — pass --allow-unknown to install anyway.

Or run directly from the repo without installing:

git clone https://github.com/sharkyger/pip-cve-gate
cd pip-cve-gate
python bin/safe-pip install flask

Configuration

Variable Default Description
PIP_CVE_GATE_FRESH_HOLD_DAYS 3 Days a new release must age before install (max 365)
PIP_CVE_GATE_TIMEOUT 10 HTTP timeout in seconds (min 1, max 3600)
PIP_CVE_GATE_MAX_DEPTH 5 Max transitive dependency depth (min 1, max 50)
PIP_CVE_GATE_PIP_BIN pip Path to real pip binary
PIP_CVE_GATE_PIP_TIMEOUT (unset) Optional pip subprocess timeout in seconds; unset = no timeout
PIP_CVE_GATE_OSV_SCANNER_BIN osv-scanner Path/name of the osv-scanner binary (the CVE engine)
PIP_CVE_GATE_OSV_SCANNER_TIMEOUT 120 osv-scanner subprocess timeout in seconds (min 5, max 3600)
GITHUB_TOKEN (unset) Raises OSSF feed rate limit from 60 req/h to 5000 req/h

Cross-platform support

CI runs the full happy + fail-closed + arg-parse fix smoke loop on each release inside fresh containers for:

Distro Install path
Ubuntu (latest) apt-get install python3 python3-pip
Debian (stable-slim) apt-get install python3 python3-pip
AlmaLinux 9 dnf install python3 python3-pip
RHEL UBI 9 dnf install python3 python3-pip
macOS (Homebrew tap) system python3 + Homebrew formula

Tested on Python 3.9 – 3.12. Python runtime dependencies: requests, packaging. The CVE engine is the external osv-scanner binary (brew install osv-scanner); if it is absent the gate fails closed (pass --allow-unknown to install anyway).

If you hit a distro-specific issue, open a PR with a fresh entry in scripts/ci-cross-distro.sh — the smoke loop is the source of truth.


Part of the safe-install fleet

pip-cve-gate is part of a pre-install CVE gate fleet for different package ecosystems:

Ecosystem Tool
Homebrew homebrew-safe-upgrade
Composer (PHP) composer-cve-gate
pip (Python) pip-cve-gate ← you are here

Support

If pip-cve-gate saves you time, consider sponsoring the work. Sponsorship funds maintenance of the pre-install CVE gate fleet across ecosystems.


Contributing

Bugs / feature requests: open an issue or PR. Security issues: see SECURITY.md.

Local development:

git clone https://github.com/sharkyger/pip-cve-gate
cd pip-cve-gate
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install
pytest -v
ruff check src/ tests/

License

MIT — see LICENSE.

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

pip_cve_gate-0.3.1.tar.gz (46.4 kB view details)

Uploaded Source

Built Distribution

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

pip_cve_gate-0.3.1-py3-none-any.whl (28.6 kB view details)

Uploaded Python 3

File details

Details for the file pip_cve_gate-0.3.1.tar.gz.

File metadata

  • Download URL: pip_cve_gate-0.3.1.tar.gz
  • Upload date:
  • Size: 46.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pip_cve_gate-0.3.1.tar.gz
Algorithm Hash digest
SHA256 1e04923fa92d7d7292b6f0976943d9fbb78d21acd220c501e2edf35ffa75712a
MD5 e5487640a7cc6fe73f8f3e1def6135e5
BLAKE2b-256 a6baec55610acde93ed420f5c034d7f698d80e9a74720ec41d1dfe9027a354ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for pip_cve_gate-0.3.1.tar.gz:

Publisher: publish.yml on sharkyger/pip-cve-gate

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

File details

Details for the file pip_cve_gate-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: pip_cve_gate-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 28.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pip_cve_gate-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1ab38b641ca50e467adaae0172cf90fbf3ce73b3d45925ab0e0c53fbf24b8ea1
MD5 be803876bb9940b53c65b6c5d8965fc0
BLAKE2b-256 5bc30740305a2ddd5cd7706c664ce18ba361e0b39ace230e597deab01ed9bb08

See more details on using hashes here.

Provenance

The following attestation bundles were made for pip_cve_gate-0.3.1-py3-none-any.whl:

Publisher: publish.yml on sharkyger/pip-cve-gate

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