Agentic CVE triage CLI: scan a software stack and produce a prioritised PDF risk briefing using NVD, OSV, EPSS, CISA KEV, and Exploit-DB.
Project description
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:
- Pass 1 — tool-using loop where the model collects + enriches findings
and emits a single
findings_jsonblock (no prose, no headers). - The Python side scores every finding and applies CVSS floor rules.
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cve_triage_agent-0.1.0.tar.gz.
File metadata
- Download URL: cve_triage_agent-0.1.0.tar.gz
- Upload date:
- Size: 28.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
633acfc7f54ae6084a1fd4551ab9900ab7e747397ed8813325494beb880d1405
|
|
| MD5 |
b5f815875cd637de409ede2763fbe05e
|
|
| BLAKE2b-256 |
d5def16e1926aba7ff56b6616b0baab853a3e7400bdd365e527cfc40293635f0
|
File details
Details for the file cve_triage_agent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cve_triage_agent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d6eb04e0e2e80211f29b48af51974eda925196d2b02b6207f8532d978167632
|
|
| MD5 |
b8b878a1173a454d95d693a9595285ed
|
|
| BLAKE2b-256 |
8dbf8e6b98f64da701823a93b6849c793183136aba554d6de68009d376007ce5
|