Metadata-Version: 2.4
Name: safe-pip-compile
Version: 0.1.3
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.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
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 data and re-fetch from OSV.dev | 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
```

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

```toml
[tool.safe-pip-compile]
max-iterations = 10
min-severity = "high"
allowlist = "cve-allowlist.yaml"
strict = true
```

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.
- The cache is stored per user and works across virtual environments.
- SQLite WAL mode allows concurrent readers.

## 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).
