Metadata-Version: 2.4
Name: safe-pip-compile
Version: 0.1.9
Summary: pip-compile wrapper with CVE awareness
Author-email: N VENKATA SAI TEJA <venkatsaiteja1027@gmail.com>
License: MIT
Keywords: pip-compile,CVE,security,pinning,vulnerability-scanning,safe-pip-compile
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.0
Requires-Dist: httpx>=0.24
Requires-Dist: pyyaml>=6.0
Requires-Dist: packaging>=23.0
Requires-Dist: platformdirs>=3.0
Requires-Dist: rich>=13.0
Requires-Dist: pip-tools>=7.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-mock; extra == "dev"
Requires-Dist: respx>=0.20; extra == "dev"
Requires-Dist: coverage[toml]; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# safe-pip-compile

[![PyPI](https://img.shields.io/pypi/v/safe-pip-compile?color=0f766e&label=PyPI)](https://pypi.org/project/safe-pip-compile/)
[![Python](https://img.shields.io/pypi/pyversions/safe-pip-compile?color=2563eb)](https://pypi.org/project/safe-pip-compile/)
[![License: MIT](https://img.shields.io/badge/License-MIT-111827.svg)](LICENSE)

`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](https://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

```bash
pip install safe-pip-compile
```

Project page: <https://pypi.org/project/safe-pip-compile/>

## Quick start

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

Input:

```txt
django
requests
```

Output:

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

## How it works

```text
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`.

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

## Examples

Block only high and critical vulnerabilities:

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

Accept specific CVEs through an allowlist:

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

Generate a JSON report and fail CI on unresolved CVEs:

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

Preview without writing files:

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

Bypass or refresh the local cache:

```bash
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:

```yaml
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

```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

```bash
# 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:

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

Or set one of the standard certificate environment variables:

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

Lookup order:

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

## Development

```bash
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](https://www.venkatasaiteja.in/) or connect on
[LinkedIn](https://www.linkedin.com/in/venkatasaiteja/).

## License

This project is licensed under the [MIT License](LICENSE).
