Skip to main content

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.

The gate hands osv-scanner the already-resolved closure and scans it with --no-resolve, so osv-scanner checks exactly the versions the gate resolved instead of re-resolving the manifest itself. --no-resolve is an osv-scanner 2.x flag (the version installed by brew install osv-scanner); on an older 1.x binary the scan exits non-zero and the gate fails closed (pass --allow-unknown to override), so install the 2.x line.

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.

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.3.tar.gz (48.7 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.3-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pip_cve_gate-0.3.3.tar.gz
  • Upload date:
  • Size: 48.7 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.3.tar.gz
Algorithm Hash digest
SHA256 de5fd1eebeafb8bf133a6fdd1787e886a56cfa12967d7bf7f455843dbc7460cb
MD5 f2cd28775b3b488bc5c68c53da2acac0
BLAKE2b-256 0e9a2233af0f5cdfd6369731efbdcb834f94a7494d925a77218a0d06dc00efb1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pip_cve_gate-0.3.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: pip_cve_gate-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 29.8 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d24aa5add87a31bb6de48913054e36357dc874fd404b5f49814690ebc871637a
MD5 ba67ecaf1a1751e449318aef2ee5d154
BLAKE2b-256 81ba11f2726d9a5531f91ff2517a3527bed889e8e4c3894422de32a348002d80

See more details on using hashes here.

Provenance

The following attestation bundles were made for pip_cve_gate-0.3.3-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.

Release history Release notifications | RSS feed

This release

0.3.3 This release

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

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