Metadata-Version: 2.4
Name: devsecure-intelligence
Version: 0.1.0
Summary: Python client for the DevSecure Intelligence API: rank CVEs by real-world risk (RPS from CVSS, EPSS and CISA KEV), with the reason for every rank.
Project-URL: Homepage, https://intelligence.devsecure.io
Project-URL: Documentation, https://intelligence.devsecure.io/docs/guides/python
Author: DevSecure Limited
License-Expression: MIT
License-File: LICENSE
Keywords: cisa-kev,cve,cvss,epss,prioritisation,sbom,security,vulnerability
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# DevSecure Intelligence for Python

Rank CVEs by real-world risk from Python. Each CVE gets an RPS score (from CVSS, EPSS and CISA KEV), a Final Priority, and the reason for its rank. Exploit and fix evidence come with it.

- Standard library only: no dependencies.
- Typed (`py.typed`), Python 3.10+.
- Retries network errors, 5xx and `429 rate_limited`, honouring `Retry-After`. It never retries `quota_exceeded`.
- Accepts a list of IDs or a whole scanner report, splits long lists to fit your plan, and merges the results into one ranking.

```bash
pip install devsecure-intelligence
export DEVSECURE_API_KEY=dsec_live_...   # free key: https://intelligence.devsecure.io/signup
```

## Rank a scanner's findings

```python
from pathlib import Path
from devsecure_intelligence import Client

client = Client()  # reads DEVSECURE_API_KEY
result = client.prioritize(Path("trivy.json").read_text())   # any text that mentions CVE IDs

for cve in result["ranked"]:
    print(cve["rank_in_request"], cve["cve_id"], cve["priority_band"], cve["rps_score"], cve["reason"])
```

`priority_band` is the **Final Priority**: the RPS band after the CISA KEV floor and the EPSS gate. `band_basis` says which rule set it (`rps`, `kev_floor` or `epss_gate`). CVEs not in the corpus come last with band `unknown` and are listed in `not_found`.

Free keys take 25 IDs per call and Pro and above take 1,000. You can pass a longer list anyway: if the API rejects it as `list_too_long`, the client re-sends it in chunks of your plan's limit and merges them. The merged list uses the API's own order: band first, then RPS within a band. Pass `chunk_size=` to choose the size yourself.

## Fail a CI step on High, Critical or KEV

```python
import sys
blocking = [c for c in result["ranked"]
            if c["priority_band"] in ("critical_priority", "high_priority") or (c.get("evidence") or {}).get("kev", {}).get("listed")]
for c in blocking:
    print(f"{c['cve_id']}\t{c['priority_band']}\t{c['reason']}")
sys.exit(1 if blocking else 0)
```

Your organisation decides what blocks a build. For a ready-made GitHub Action with allowlists, VEX and a job summary, see the [CI gate](https://intelligence.devsecure.io/docs/guides/ci-gate).

## Other calls

| Method | Endpoint | Plan |
|---|---|---|
| `cve(id)`, `rps(id)` | `GET /api/v1/cve/{id}`, `/rps/{id}` | Any key |
| `cve_batch(ids)` | `POST /api/v1/cve/batch` | Any key (Free 100 IDs, Pro+ 1,000) |
| `prioritize(ids_or_text, chunk_size=None)` | `POST /api/v1/prioritize` | Any key |
| `movers(window="7d", types=[...])`, `iter_movers(...)` | `GET /api/v1/movers` | Any key |
| `recent(since=...)` | `GET /api/v1/cve/recent` | Any key |
| `cwe_cves(79, is_kev=True)` | `GET /api/v1/cwe/{cwe}/cves` | Any key |
| `intelligence(id)` | `GET /api/v1/cve/{id}/intelligence` | Pro and above |
| `patches(id)` | `GET /api/v1/patches/{id}` | Business and Enterprise |
| `public_cve(id)`, `kev()`, `stats()`, `status()`, `health()` | public endpoints | No key needed |
| `request(method, path, params=..., json_body=...)` | anything else | |

What changed in the last 24 hours:

```python
for m in client.iter_movers(window="24h", types=["kev_added", "priority_change"]):
    print(m["occurred_on"], m["cve_id"], m["type"], m.get("priority_now"), m["reason"])
```

## Command line: `devsecure`

Installing the package also installs a `devsecure` command:

```bash
devsecure cve CVE-2024-3400                        # no key needed: falls back to the public record
devsecure prioritize trivy.json grype.json         # ranked table of every CVE found in the files
trivy image -f json app:1.2 | devsecure prioritize - --fail-on high,kev
devsecure movers --window 24h --type kev_added,priority_change
devsecure kev --limit 10                           # CISA KEV ranked by RPS, no key needed
devsecure --json prioritize sbom.cdx.json          # the API's JSON instead of a table
```

`--fail-on` takes `critical`, `high`, `medium` and/or `kev` (comma-separated). Exit codes: `0` done, `1` a CVE matched `--fail-on`, `2` a usage or API error, with the error's docs link printed. `python -m devsecure_intelligence` works too.

## Errors, limits and quota

Every error raises a `DevSecureError` carrying `status`, `code`, `message`, `docs_url`, `request_id` and `retry_after`. These subclasses let you catch the common cases:

| Class | When |
|---|---|
| `AuthenticationError` | 401: missing, invalid or revoked key |
| `PlanError` | 403 `forbidden_plan`: your plan doesn't include the endpoint |
| `NotFoundError` | 404: the CVE isn't in the corpus |
| `RateLimitError` | 429 `rate_limited`, after the retries are used up |
| `QuotaExceededError` | 429 `quota_exceeded`: the month's allowance is used (resets on the 1st, UTC) |

`client.last_quota` holds the rate-limit and quota headers from the last response, for example `{"quota_limit": 500, "quota_remaining": 412, "rate_limit_remaining": 19}`. `client.last_request_id` is the ID to quote to support.

```python
Client(api_key="...", base_url="https://api.intelligence.devsecure.io", timeout=30, max_retries=3)
```

The key is sent only in the `Authorization` header, and never appears in URLs or in `repr(client)`. `base_url` must be `https://`, except `http://localhost` for testing.

## Development

```bash
cd sdk/python
pip install pytest
PYTHONPATH=src pytest            # unit tests, no network
PYTHONPATH=src pytest -m live    # a few public calls to the production API, no key
```

Licence: MIT. API docs: <https://intelligence.devsecure.io/docs>. Support: support@devsecure.io.
