Skip to main content

CVE Triage & Risk Briefing Agent

An agentic Python CLI that takes a software stack (stack.json), autonomously enriches each package's known CVEs from public threat-intel sources, scores them by actual exploitability (not just CVSS), and produces a professional PDF risk briefing ready for a CTO/CISO.

The agent uses an OpenAI model (default gpt-4o) as the orchestrator — it decides which tools to call, in what order, and when it has enough data to write the final report. The Python side just runs the tools and feeds results back.

Data sources

  • NIST NVD — authoritative CVE catalogue
  • OSV.dev — best coverage for npm, PyPI, Go, Maven, RubyGems
  • FIRST EPSS — probability of exploitation in the wild (next 30 days)
  • CISA KEV — vulnerabilities confirmed exploited in the wild
  • CIRCL CVE / Exploit-DB — public exploit references

Install

pip install cve-triage-agent

Then create a .env file (or export the variables in your shell):

OPENAI_API_KEY=sk-...
NVD_API_KEY=                # optional — recommended for higher rate limits
OPENAI_MODEL=gpt-4o         # optional — override the default model

Get a free NVD API key at https://nvd.nist.gov/developers/request-an-api-key.

Usage

cve-triage --stack stack.json
# → writes report.pdf
cve-triage --stack stack.json --output briefing.pdf
cve-triage --stack stack.json --format markdown --output briefing.md
cve-triage --stack stack.json --format json --output findings.json
cve-triage --stack stack.json --min-cvss 7.0    # drop low-severity noise

You can also invoke without installing the console script:

python -m cve_triage --stack stack.json

Options

Flag Default Description
--stack required Path to the input stack JSON file
--output report.pdf (or .md / .json to match --format) Output path
--format pdf pdf, markdown, or json
--min-cvss 0.0 Drop findings below this CVSS base score
-v, --verbose off Debug logging in agent_run.log
--version — Print version and exit

Input format (stack.json)

[
  { "name": "django",  "version": "4.2.3",   "ecosystem": "PyPI" },
  { "name": "express", "version": "4.18.2",  "ecosystem": "npm"  },
  { "name": "openssl", "version": "3.0.7",   "ecosystem": "other" }
]

Supported ecosystems: PyPI, npm, Maven, Go, RubyGems, NuGet, other. Use other for system packages like nginx or openssl that OSV's ecosystem indices don't cover — NVD will still be queried, plus an ecosystem-agnostic OSV fallback.

A worked example lives at examples/stack.json.

Risk scoring

The composite risk score (0–100) weights:

  • CVSS base severity — 40%
  • EPSS exploitation probability — 35%
  • CISA KEV listing — +20
  • Public exploit available — +10
  • Known ransomware association — +5

Tiers: CRITICAL ≥ 75 · HIGH ≥ 50 · MEDIUM ≥ 25 · LOW < 25.

CVSS floor rules guarantee that a CVE with CVSS ≥ 9.5 is always CRITICAL, ≥ 9.0 is always at least CRITICAL, and ≥ 7.0 is always at least HIGH — exploitability signals can promote a tier but never demote one.

A CVE listed in CISA KEV with CVSS 7.5 will outrank a CVSS 9.8 with EPSS 0.001, which is what you want.

Output

A PDF briefing with:

  • Title block + scan metadata + tier counts
  • Executive summary (150–250 words, plain prose, generated by the LLM after scoring so it can never contradict the tables)
  • Packages-scanned table (every input package, even clean ones)
  • Critical/High findings table (tier-color coded)
  • Per-finding detail blocks with description, why-urgent rationale, remediation, references
  • Medium/Low findings table
  • Methodology + data-source list

Use --format markdown for a plain-text version (handy for diffing) or --format json for downstream tooling.

Architecture

cve-triage-agent/
├── cve_triage/
│   ├── cli.py              CLI entry point (argparse → run_agent)
│   ├── loop.py             Two-pass agent: collect → score → summarise
│   ├── tools.py            NVD / OSV / EPSS / KEV / Exploit-DB integrations
│   ├── scorer.py           Composite risk score + CVSS floor rules
│   ├── reporter.py         Markdown + JSON renderers
│   └── pdf_reporter.py     reportlab Platypus PDF renderer
├── examples/
│   └── stack.json
├── pyproject.toml
└── LICENSE

The agent runs a two-pass loop:

  1. Pass 1 — tool-using loop where the model collects + enriches findings and emits a single findings_json block (no prose, no headers).
  2. The Python side scores every finding and applies CVSS floor rules.
  3. Pass 2 — non-tool call where the model writes the executive summary, anchored to the scored findings (so the prose can never reference tiers that don't exist).

Tool-call traces and HTTP retry warnings land in agent_run.log.

Building and publishing

pip install -e ".[dev]"           # editable install with build/twine
python -m build                    # produces dist/*.whl and dist/*.tar.gz
python -m twine upload dist/*      # publish to PyPI

Update author and URL fields in pyproject.toml before your first publish.

License

MIT — see LICENSE.

Metadata

Release files for cve-triage-agent 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cve-triage-agent 0.1.0
File Size Uploaded
cve_triage_agent-0.1.0.tar.gz 28.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cve-triage-agent 0.1.0
File Interpreter ABI Platform
cve_triage_agent-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 58.2 kB

Release files / cve_triage_agent-0.1.0.tar.gz

Download URL cve_triage_agent-0.1.0.tar.gz
Size 28.6 kB
Tags Source
SHA-256 checksum
How to use checksums
633acfc7f54ae6084a1fd4551ab9900ab7e747397ed8813325494beb880d1405
BLAKE2b-256 checksum
How to use checksums
d5def16e1926aba7ff56b6616b0baab853a3e7400bdd365e527cfc40293635f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.4

Release files / cve_triage_agent-0.1.0-py3-none-any.whl

Download URL cve_triage_agent-0.1.0-py3-none-any.whl
Size 29.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0d6eb04e0e2e80211f29b48af51974eda925196d2b02b6207f8532d978167632
BLAKE2b-256 checksum
How to use checksums
8dbf8e6b98f64da701823a93b6849c793183136aba554d6de68009d376007ce5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.4

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page