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.

Metadata

Release files for pip-cve-gate 0.3.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pip-cve-gate 0.3.3
File Size Uploaded
pip_cve_gate-0.3.3.tar.gz 48.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pip-cve-gate 0.3.3
File Interpreter ABI Platform
pip_cve_gate-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 78.5 kB

Release files / pip_cve_gate-0.3.3.tar.gz

Download URL pip_cve_gate-0.3.3.tar.gz
Size 48.7 kB
Tags Source
SHA-256 checksum
How to use checksums
de5fd1eebeafb8bf133a6fdd1787e886a56cfa12967d7bf7f455843dbc7460cb
BLAKE2b-256 checksum
How to use checksums
0e9a2233af0f5cdfd6369731efbdcb834f94a7494d925a77218a0d06dc00efb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 29, 2026.

Transparency log

Release files / pip_cve_gate-0.3.3-py3-none-any.whl

Download URL pip_cve_gate-0.3.3-py3-none-any.whl
Size 29.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d24aa5add87a31bb6de48913054e36357dc874fd404b5f49814690ebc871637a
BLAKE2b-256 checksum
How to use checksums
81ba11f2726d9a5531f91ff2517a3527bed889e8e4c3894422de32a348002d80
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.3 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page