Skip to content

Repository files navigation

safe-pip-compile

PyPI Python License: MIT Documentation

safe-pip-compile is a drop-in pip-compile wrapper that avoids pinning Python packages with known CVEs.

pip-compile is excellent at resolving repeatable dependency sets, but it does not know whether a resolved version has a published vulnerability. safe-pip-compile adds that missing safety loop: compile, audit against OSV.dev, constrain vulnerable versions out of the result, and retry until the lock file is clean or the configured limit is reached.

Why use it?

  • Keep the familiar pip-compile workflow.
  • Block vulnerable package versions before they land in requirements.txt.
  • Use severity thresholds, allowlists, JSON reports, and CI-friendly exit codes.
  • Cache OSV results locally so repeated runs stay fast.
  • Pass normal pip-compile flags through when you need them.

Installation

pip install safe-pip-compile

Project page

Documentation

Quick start

safe-pip-compile requirements.in -o requirements.txt

Input:

django
requests

Output:

# Generated by safe-pip-compile
# Vulnerable versions are excluded when OSV.dev reports a blocking CVE.
django==...
requests==...

How it works

requirements.in
  -> pip-compile
  -> parse resolved packages
  -> check local cache or query OSV.dev
  -> filter by severity and allowlist
  -> add temporary constraints for vulnerable versions
  -> repeat until clean, stuck, or max iterations is reached
  -> requirements.txt

When a vulnerable package is found, safe-pip-compile asks the resolver for a safe alternative by adding constraints such as django>=3.2.25. It keeps the dependency resolver in charge instead of hand-editing pinned output.

CLI flags

Flag Description Default
-o, --output-file PATH Output requirements.txt path requirements.txt
--min-severity LEVEL Only block CVEs at this level or above: critical, high, medium, low low
--allow-list PATH YAML file of accepted CVEs to skip none
--max-iterations INT Maximum compile/audit loops before stopping 10
--strict / --no-strict Exit with code 1 if unresolved CVEs remain --strict
--dry-run Preview actions without writing output files off
--json-report PATH Write a machine-readable JSON vulnerability report none
--cert PATH CA bundle for SSL verification behind corporate proxies auto-detect from env
--no-cache Disable the local CVE cache and always query OSV.dev cache enabled
--refresh-cache Clear cached CVE data and re-fetch from OSV.dev, then run normally off
--clear-cache Delete the entire cache directory and exit (useful before uninstalling) off
-v, --verbose Increase output detail. Use -v or -vv quiet

All other flags after -- are passed through to pip-compile.

safe-pip-compile requirements.in -- --generate-hashes --allow-unsafe

Examples

Block only high and critical vulnerabilities:

safe-pip-compile requirements.in --min-severity high

Accept specific CVEs through an allowlist:

safe-pip-compile requirements.in --allow-list cve-allowlist.yaml

Generate a JSON report and fail CI on unresolved CVEs:

safe-pip-compile requirements.in --json-report audit.json --strict

Preview without writing files:

safe-pip-compile requirements.in --dry-run -v

Bypass or refresh the local cache:

safe-pip-compile requirements.in --no-cache
safe-pip-compile requirements.in --refresh-cache

# Wipe the entire cache directory (e.g. before uninstalling)
safe-pip-compile --clear-cache

Allowlist format

Create cve-allowlist.yaml when your team has reviewed and accepted a specific risk:

allowed_cves:
  - id: CVE-2024-12345
    reason: "Not applicable; we do not use the affected feature"
    expires: 2025-06-01

  - id: GHSA-xxxx-yyyy-zzzz
    reason: "Accepted risk, tracked in JIRA-789"

Allowlist entries are matched against the vulnerability ID and aliases such as CVE, PYSEC, and GHSA identifiers.

pyproject.toml config

Every CLI flag can be set as a project-level default in pyproject.toml. CLI flags always take precedence over file settings.

Option reference

pyproject.toml key CLI equivalent Example value
max-iterations --max-iterations INT 10
min-severity --min-severity LEVEL "high"
allowlist --allow-list PATH "cve-allowlist.yaml"
strict --strict / --no-strict true
output-file -o / --output-file PATH "requirements.txt"
json-report --json-report PATH "audit.json"
dry-run --dry-run false
cert --cert PATH "/etc/ssl/certs/corp-ca.pem"
no-cache --no-cache false
refresh-cache --refresh-cache false
verbose -v / -vv 1

pyproject.toml

[tool.safe-pip-compile]
# ── Core resolution ────────────────────────────────────────
max-iterations = 10          # Maximum compile/audit loops (default: 10)
min-severity   = "high"      # Minimum blocking severity: critical/high/medium/low
allowlist      = "cve-allowlist.yaml"  # Path to CVE allowlist YAML
strict         = true        # Fail on unresolved CVEs (default: true)

# ── Output / reporting ─────────────────────────────────────
output-file  = "requirements.txt"   # Default output file (-o)
json-report  = "audit.json"         # Write JSON vulnerability report
dry-run      = false                # Preview without writing files

# ── Network / SSL ──────────────────────────────────────────
cert = "/etc/ssl/certs/corporate-ca.pem"  # CA bundle path (--cert)

# ── Cache ──────────────────────────────────────────────────
no-cache      = false   # Always query OSV.dev, skip local cache
refresh-cache = false   # Clear cache before run

# ── Verbosity ──────────────────────────────────────────────
verbose = 1             # 0 = quiet, 1 = -v, 2 = -vv

CLI equivalent commands

# Core resolution
safe-pip-compile requirements.in --max-iterations 10
safe-pip-compile requirements.in --min-severity high
safe-pip-compile requirements.in --allow-list cve-allowlist.yaml
safe-pip-compile requirements.in --strict
safe-pip-compile requirements.in --no-strict

# Output / reporting
safe-pip-compile requirements.in -o requirements.txt
safe-pip-compile requirements.in --output-file requirements.txt
safe-pip-compile requirements.in --json-report audit.json
safe-pip-compile requirements.in --dry-run

# Network / SSL
safe-pip-compile requirements.in --cert /etc/ssl/certs/corp-ca.pem

# Cache
safe-pip-compile requirements.in --no-cache
safe-pip-compile requirements.in --refresh-cache
safe-pip-compile --clear-cache     # delete cache directory and exit

# Verbosity
safe-pip-compile requirements.in -v    # verbose
safe-pip-compile requirements.in -vv   # extra verbose

CLI flags override pyproject.toml values.

Exit codes

Code Meaning
0 Clean result: no blocking CVEs found
1 Unresolved CVEs remain in strict mode
2 pip-compile resolution failed
3 Network or configuration error

Local CVE cache

Vulnerability data is cached in a local SQLite database to avoid repeated OSV.dev API calls.

Platform Cache path
Linux ~/.cache/safe-pip-compile/cache.db
macOS ~/Library/Caches/safe-pip-compile/cache.db
Windows C:\Users\Teja\AppData\Local\safe-pip-compile\Cache\cache.db

Cache behavior:

  • Fixed vulnerabilities are cached for 6 months.
  • Vulnerabilities without a fix are not cached permanently, so a newly published fix can be picked up on a later run.
  • pip-compile results are also cached for 30 minutes. If the input files and Python version haven't changed, the resolver is skipped entirely and CVE scanning starts immediately — saving 30-120 s on complex dependency sets.
  • The cache is stored per user and works across virtual environments.
  • SQLite WAL mode allows concurrent readers.

Cache management

Flag What it does
--no-cache Disables both the CVE cache and the pip-compile result cache for this run
--refresh-cache SQL DELETE all cached rows, then runs normally and refills the cache
--clear-cache Deletes the entire cache directory from disk (shutil.rmtree) and exits — useful before uninstalling

Tip: run safe-pip-compile --clear-cache before pip uninstall safe-pip-compile to remove the leftover cache.db file from your user profile.

Corporate proxy and SSL

If your organization performs SSL inspection, OSV.dev requests may fail with a certificate verification error.

Use a custom CA bundle:

safe-pip-compile requirements.in --cert /path/to/corporate-ca-bundle.pem

Or set one of the standard certificate environment variables:

export SSL_CERT_FILE=/path/to/corporate-ca-bundle.pem
export REQUESTS_CA_BUNDLE=/path/to/corporate-ca-bundle.pem

Lookup order:

Lookup order: `--cert` flag > `SSL_CERT_FILE` > `REQUESTS_CA_BUNDLE` > `CURL_CA_BUNDLE` > system default.

Development

pip install -e ".[dev]"
python -m pytest tests -v

Author

Built and maintained by N Venkata Sai Teja.

For more projects, writing, and contact details, visit venkatasaiteja.in or connect on LinkedIn.

License

This project is licensed under the MIT License.

About

A drop-in pip-compile wrapper that avoids pinning packages with known CVEs.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages