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.
- Keep the familiar
pip-compileworkflow. - 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-compileflags through when you need them.
pip install safe-pip-compilesafe-pip-compile requirements.in -o requirements.txtInput:
django
requestsOutput:
# Generated by safe-pip-compile
# Vulnerable versions are excluded when OSV.dev reports a blocking CVE.
django==...
requests==...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.
| 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-unsafeBlock only high and critical vulnerabilities:
safe-pip-compile requirements.in --min-severity highAccept specific CVEs through an allowlist:
safe-pip-compile requirements.in --allow-list cve-allowlist.yamlGenerate a JSON report and fail CI on unresolved CVEs:
safe-pip-compile requirements.in --json-report audit.json --strictPreview without writing files:
safe-pip-compile requirements.in --dry-run -vBypass 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-cacheCreate 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.
Every CLI flag can be set as a project-level default in pyproject.toml.
CLI flags always take precedence over file settings.
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 |
[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# 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 verboseCLI flags override pyproject.toml values.
| 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 |
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.
| 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-cachebeforepip uninstall safe-pip-compileto remove the leftovercache.dbfile from your user profile.
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.pemOr 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.pemLookup order:
Lookup order: `--cert` flag > `SSL_CERT_FILE` > `REQUESTS_CA_BUNDLE` > `CURL_CA_BUNDLE` > system default.
pip install -e ".[dev]"
python -m pytest tests -vBuilt and maintained by N Venkata Sai Teja.
For more projects, writing, and contact details, visit venkatasaiteja.in or connect on LinkedIn.
This project is licensed under the MIT License.