Metadata-Version: 2.4
Name: epss-py
Version: 0.1.0
Summary: Python client and CLI for the EPSS (Exploit Prediction Scoring System) API
Author-email: Security Researcher <devtwo@broadstonetech.com>
License: MIT
Project-URL: Homepage, https://github.com/yourname/epss-client
Project-URL: Documentation, https://github.com/yourname/epss-client#readme
Project-URL: Bug Tracker, https://github.com/yourname/epss-client/issues
Project-URL: API Reference, https://api.first.org/epss
Keywords: epss,cve,vulnerability,security,cvss,exploit
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Requires-Dist: click>=8.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: responses>=0.23; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: types-requests>=2.28; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=4.0; extra == "dev"
Dynamic: license-file

# epss-client

A Python client and CLI for the EPSS (Exploit Prediction Scoring System) API.

Fetch exploit prediction scores for CVEs, search with filters, track score changes over time, and download bulk data with this lightweight, zero-overhead client.

## Installation

```bash
pip install epss-client
```

## Quick Start (Python API)

```python
from epss import EPSSClient

client = EPSSClient()

# Get EPSS score for a single CVE
resp = client.get("CVE-2022-27225")
print(resp.data[0])  # EPSSScore object

# Batch lookup
resp = client.get(["CVE-2022-27225", "CVE-2022-27223"])

# Search with filters
resp = client.search(epss_gt=0.9, limit=50)

# Top 10 CVEs by EPSS score
resp = client.top(10, by="epss")

# Stream all matching records
for page in client.iter_pages(epss_gt=0.5):
    for score in page.data:
        print(score.cve, score.epss, score.risk_label())

# Historical data
resp = client.get("CVE-2022-27225", date="2022-03-05")

# Time series (last 30 days)
resp = client.get("CVE-2022-25204", scope="time-series")
for ts in resp.data[0].time_series:
    print(ts.date, ts.epss)

# Compare scores between two dates
comparison = client.compare("CVE-2022-27225", date1="2022-03-01", date2="2022-04-01")

# Download bulk CSV data
records = client.get_bulk_csv(date="2022-03-05", min_epss=0.5)
```

## Quick Start (CLI)

```bash
# Get EPSS score
epss get CVE-2022-27225

# Batch lookup
epss get CVE-2022-27225 CVE-2022-27223

# Search (default: top 100 by EPSS descending)
epss search --epss-gt 0.9

# Top 20 CVEs
epss top 20

# Top 10 by percentile
epss top 10 --by percentile

# Time series (last 30 days)
epss time-series CVE-2022-25204

# Compare between two dates
epss compare CVE-2022-27225 --date1 2022-03-01 --date2 2022-04-01

# Download bulk CSV
epss bulk --date 2022-03-05

# Filter bulk data by EPSS score
epss bulk --min-epss 0.5

# JSON output
epss search --epss-gt 0.9 -o json

# CSV output
epss search --percentile-gt 0.95 -o csv
```

## Client Reference

### EPSSClient

```python
from epss import EPSSClient

client = EPSSClient(timeout=30)
```

**Parameters:**
- `timeout` (int): HTTP timeout in seconds. Default: 30.
- `session` (requests.Session, optional): Pre-configured requests session for custom proxies, retry logic, or TLS verification.
- `user_agent` (str, optional): Custom User-Agent header. Default: `epss-client/{version} (python-requests)`.

### get()

Fetch EPSS scores for one or more CVEs.

```python
# Single CVE
resp = client.get("CVE-2022-27225")

# Batch lookup
resp = client.get(["CVE-2022-27225", "CVE-2022-27223"])

# Historical data (specific date)
resp = client.get("CVE-2022-27225", date="2022-03-05")

# Time series (last 30 days)
resp = client.get("CVE-2022-25204", scope="time-series")
```

Returns: `EPSSResponse` with `.data` list of `EPSSScore` objects.

### search()

Query the database with optional filters. All parameters are combinable.

```python
# Top CVEs by EPSS (default)
resp = client.search(epss_gt=0.9, limit=100)

# CVEs above a percentile threshold
resp = client.search(percentile_gt=0.95)

# By date
resp = client.search(date="2022-03-05", epss_gt=0.7)

# With custom ordering and pagination
resp = client.search(
    epss_gt=0.5,
    order="!epss",  # descending
    limit=50,
    offset=100,
)
```

Parameters:
- `date` (str or datetime.date): Restrict to a specific date. Format: `YYYY-MM-DD`.
- `epss_gt` (float): Only return CVEs with EPSS score greater than this value.
- `epss_lt` (float): Only return CVEs with EPSS score less than this value.
- `percentile_gt` (float): Only return CVEs with percentile greater than this.
- `percentile_lt` (float): Only return CVEs with percentile less than this.
- `order` (str): Sort order. Options: `epss`, `!epss`, `percentile`, `!percentile`. Default: `!epss`.
- `limit` (int): Results per page. Default: 100.
- `offset` (int): Pagination offset. Default: 0.

Returns: `EPSSResponse`.

### top()

Convenience wrapper around `search()` to get the top N CVEs.

```python
resp = client.top(100, by="epss", date="2022-03-05")
```

Parameters:
- `n` (int): Number of results. Default: 100.
- `by` (str): Sort by `epss` or `percentile`. Default: `epss`.
- `date` (str or datetime.date, optional): Restrict to a specific date.

Returns: `EPSSResponse`.

### iter_pages()

Memory-safe pagination generator. Yields `EPSSResponse` objects until all results are exhausted.

```python
for page in client.iter_pages(epss_gt=0.5, page_size=1000):
    for score in page.data:
        process(score)
```

Useful for traversing large result sets without loading everything into memory.

### get_all()

Fetch all matching records across all pages into a single list.

```python
# All CVEs with EPSS > 0.9
scores = client.get_all(epss_gt=0.9)

# Cap results to avoid memory issues
scores = client.get_all(epss_gt=0.5, max_records=10000)
```

Parameters:
- `max_records` (int, optional): Stop after this many records. Useful for large queries.

Returns: `list[EPSSScore]`.

### compare()

Compare EPSS scores for one or more CVEs between two dates.

```python
comparison = client.compare(
    ["CVE-2022-27225", "CVE-2022-27223"],
    date1="2022-03-01",
    date2="2022-04-01",
)

for cve_id, (score1, score2) in comparison.items():
    delta = score2.epss - score1.epss
    print(f"{cve_id}: {delta:+.4f}")
```

Returns: `dict[str, tuple[EPSSScore, EPSSScore]]` where values are (score_at_date1, score_at_date2).

### get_bulk_csv()

Download and parse the bulk EPSS CSV for a specific date from empiricalsecurity.com.

```python
# Today's data
records = client.get_bulk_csv()

# Specific date
records = client.get_bulk_csv(date="2022-03-05")

# Filter by EPSS score
records = client.get_bulk_csv(min_epss=0.5)
```

Returns: `list[CSVRecord]`.

## Data Models

### EPSSScore

Represents a single CVE EPSS score.

```python
score = resp.data[0]
print(score.cve)        # "CVE-2022-27225"
print(score.epss)       # 0.001870000
print(score.percentile) # 0.401060000
print(score.date)       # datetime.date(2026, 5, 4)
print(score.risk_label())  # "INFORMATIONAL"
```

Risk labels:
- `CRITICAL`: epss >= 0.7
- `HIGH`: epss >= 0.4
- `MEDIUM`: epss >= 0.1
- `LOW`: epss >= 0.01
- `INFORMATIONAL`: epss < 0.01

### EPSSResponse

A paginated API response.

```python
resp = client.search()
print(resp.status)       # "OK"
print(resp.total)        # 100000
print(resp.offset)       # 0
print(resp.limit)        # 100
print(resp.has_more)     # True
print(resp.next_offset)  # 100
print(resp.data)         # list[EPSSScore]
```

### EPSSTimePoint

A single entry in a time-series response.

```python
resp = client.get("CVE-2022-25204", scope="time-series")
for ts in resp.data[0].time_series:
    print(ts.date, ts.epss, ts.percentile)
```

### CSVRecord

A single row from bulk CSV data.

```python
records = client.get_bulk_csv()
for record in records:
    print(record.cve, record.epss, record.percentile, record.date)
```

## Error Handling

All exceptions inherit from `EPSSError` for convenient catching.

```python
from epss import (
    EPSSError,
    EPSSAPIError,
    EPSSValidationError,
    EPSSNetworkError,
    EPSSCSVError,
)

try:
    resp = client.get("CVE-2022-27225")
except EPSSAPIError as e:
    print(f"API error: {e.status_code}")
    if e.response_body:
        print(e.response_body)
except EPSSNetworkError as e:
    print(f"Network error: {e}")
except EPSSValidationError as e:
    print(f"Invalid request: {e}")
except EPSSCSVError as e:
    print(f"CSV download failed: {e}")
```

## Pagination Guide

### Small result sets

Use `search()` directly:

```python
resp = client.search(epss_gt=0.9, limit=100)
all_records = resp.data
```

### Large result sets

Use `iter_pages()` for memory efficiency:

```python
for page in client.iter_pages(epss_gt=0.5, page_size=1000):
    for score in page.data:
        # process one at a time
        process(score)
```

Or `get_all()` with a safety cap:

```python
scores = client.get_all(epss_gt=0.5, max_records=10000)
```

## Output Formats (CLI)

### Table (default)

```
CVE | EPSS | Percentile | Risk | Date
...
```

```bash
epss search --epss-gt 0.9
```

### JSON

```bash
epss search --epss-gt 0.9 -o json
```

### CSV

```bash
epss search --epss-gt 0.9 -o csv > results.csv
```

## Development

### Install with dev dependencies

```bash
pip install -e ".[dev]"
```

### Run tests

```bash
pytest -v
```

### Type checking

```bash
mypy src/epss
```

### Linting

```bash
ruff check src/epss
black src/epss
```

### Build package

```bash
python -m build
```

### Publish to PyPI

```bash
twine upload dist/*
```

## License

MIT License. See [LICENSE](LICENSE) for details.

## Links

- [EPSS API Documentation](https://api.first.org/epss)
- [Empirical Security](https://empiricalsecurity.com)
