Cut CVE alert fatigue — ranks pip-audit output by real exploitability using any LLM provider
Project description
vulntriage
Rank pip-audit CVEs by real exploitability using an LLM.
pip-audit reports every vulnerability your dependencies carry — but a CVSS 9.8 in a transitive dependency you never call is not the same risk as a CVSS 5.0 in your HTTP client that handles every request. vulntriage feeds your CVE list, your actual dependency stack, and authoritative threat intelligence (NVD CVSS, CISA KEV, EPSS) to an LLM, which ranks them by real reachability rather than raw severity score.
💸 No API key? Use the AI subscription you already pay for.
If you have Claude Code, OpenAI Codex, or the Gemini CLI installed and logged in, vulntriage can drive it directly — no API key, no metered billing, no extra cost. You get the same cloud-grade ranking as the paid API, billed against your existing subscription:
vulntriage scan --provider claude-cli # or: codex-cli / gemini-cliNothing to configure — vulntriage runs the CLI for you. Setup details below.
Requirements
- Python 3.11+
pip-auditinstalled and onPATH(pip install pip-audit)- An LLM backend — any one of: an Anthropic/OpenAI/Gemini API key; a logged-in coding CLI (
claude,codex, orgemini) on your existing subscription, no API key needed; or a local Ollama instance
Installation
pip install vulntriage
Usage
# Option A — no API key: use a coding CLI you're already logged into
vulntriage scan --provider claude-cli # or codex-cli / gemini-cli
# Option B — API key
export ANTHROPIC_API_KEY="sk-ant-..."
# Scan the current directory (needs requirements.txt or pyproject.toml)
vulntriage scan
# Scan a specific project
vulntriage scan --project-root /path/to/project
# Gate CI on CRITICAL only (not the default HIGH)
vulntriage scan --fail-on CRITICAL
# Save a timestamped JSON report
vulntriage scan --output-dir ./reports
# Skip all network fetches (use cached threat intel only)
vulntriage scan --offline
# Output machine-readable JSON (status messages go to stderr)
vulntriage scan --format json
Flags
| Flag | Default | Description |
|---|---|---|
--project-root / -p |
. |
Directory containing requirements.txt or pyproject.toml |
--provider |
anthropic |
LLM backend. API key: anthropic / openai / gemini. Subscription CLI (no key): claude-cli / codex-cli / gemini-cli. Local: ollama |
--model |
provider default | Model name to use (e.g. phi4-mini, gpt-4o-mini) |
--fail-on |
none |
Exit 1 if any CVE at or above this severity: CRITICAL / HIGH / MEDIUM / LOW / INFO / none |
--format / -f |
table |
Output format: table (Rich), json, or sarif |
--output-dir |
— | Save a timestamped JSON report to this directory after each scan |
--offline |
— | Skip all external API calls; use cached threat intel only |
--no-cache |
— | Bypass the 24-hour threat-intel cache and re-fetch from NVD/KEV/EPSS |
--batch-size |
10 |
Max CVEs per LLM call (reduce to ≤10 for local models with small context windows) |
--airgap |
— | Fully air-gapped mode: skip pip-audit network calls and all threat intel fetches |
Output
Findings are printed as priority-ordered cards — each tells you what to fix, why it matters, the exact version to upgrade to, and what might break:
vulntriage — CVE Priority Report (2 findings, highest priority first)
#1 HIGH requests 2.31.0 ★ CISA KEV ───────────────────────────────────
CVE CVE-2024-35195
Why Attack: SSRF via proxied requests. Path: app.py:52 requests.get()
on the request boundary. Verdict: REACHABLE — called every request.
Fix pip install "requests>=2.32.0" (you have 2.31.0 → need ≥ 2.32.0)
Breaking verify=True is now the default — audit any verify=False call sites.
Code app.py:52 requests.get(verify=, timeout=)
Severity CVSS 9.1 · EPSS 12.3% · ★ exploited in the wild (CISA KEV)
#2 LOW jinja2 3.1.0 ───────────────────────────────────────────────────
CVE CVE-2024-22195
Why Attack: XSS via xmlattr filter. Path: no call site listed —
filter not used in source. Verdict: UNLIKELY.
Fix pip install "jinja2>=3.1.3" (you have 3.1.0 → need ≥ 3.1.3)
Breaking Safe, backwards-compatible patch release.
★ CISA KEV — CISA has confirmed this CVE is actively exploited in the wild. Code and Severity lines appear only when there is something to show. For machine-readable output use --format json or --format sarif.
Threat Intelligence
Before the LLM call, vulntriage fetches authoritative threat data from three public feeds and injects it into the prompt:
| Feed | What it provides | Rate limit |
|---|---|---|
| NVD REST API v2 | CVSS v3.1/v3.0/v2 base score per CVE | 5 req/30s free; 50 req/30s with NVD_API_KEY |
| CISA KEV catalog | Whether each CVE is actively exploited in the wild | Single request, no key needed |
| FIRST EPSS API | Exploitation probability percentage | Batch request, no key needed |
All three are cached at ~/.cache/vulntriage/ with a 24-hour TTL. The first scan pays the network cost; subsequent scans are instant.
NVD scores are authoritative. The NVD CVSS value always overrides whatever score the LLM returns.
Speeding up NVD fetches
Without an NVD API key, the tool pauses 6.1 seconds between CVE lookups to stay under the public rate limit. With a key, the pause drops to 0.7 seconds — significant for projects with many CVEs.
# Get a free key at https://nvd.nist.gov/developers/request-an-api-key
export NVD_API_KEY="your-key-here"
vulntriage scan
Offline mode
# Skip all three feeds; use whatever is in the local cache
vulntriage scan --offline
Use --offline in air-gapped environments or when deterministic scan time matters. The scan proceeds without threat intel if the cache is empty — CVSS, KEV, and EPSS fields are simply absent from the prompt.
Suppressing CVEs
Create a .vulnignore file in your project root to suppress CVEs your team has reviewed and accepted:
# Accepted — only reachable in development scripts, not at runtime
CVE-2022-40897
# Reviewed and accepted
CVE-2023-32681 Not reachable via our API surface — verified 2024-01-15
Lines starting with # are comments. Text after the CVE ID is treated as a reason and ignored by the tool. Suppressed CVEs are excluded before the LLM call and do not count toward the exit code.
Provider Selection
vulntriage supports three kinds of LLM backend. Use --provider to switch.
API-key providers — metered pay-per-token billing:
| Provider | API key env var | Install extra | Default model |
|---|---|---|---|
anthropic (default) |
ANTHROPIC_API_KEY |
— | claude-sonnet-4-6 |
openai |
OPENAI_API_KEY |
pip install 'vulntriage[openai]' |
gpt-4o-mini |
gemini |
GOOGLE_API_KEY |
pip install 'vulntriage[gemini]' |
gemini-2.0-flash |
Subscription-CLI providers — no API key; uses an AI coding CLI you are already logged into (see below):
| Provider | Requires | Cost |
|---|---|---|
claude-cli |
claude (Claude Code) installed + logged in |
your Claude subscription |
codex-cli |
codex (OpenAI Codex CLI) installed + logged in |
your ChatGPT/Codex subscription |
gemini-cli |
gemini (Google Gemini CLI) installed + logged in |
your Gemini subscription |
Local provider — fully offline, no data leaves your machine:
| Provider | Install extra | Default model |
|---|---|---|
ollama |
pip install 'vulntriage[ollama]' |
llama3.2 |
# Use Gemini API (free tier at aistudio.google.com/apikey)
export GOOGLE_API_KEY="AIza..."
vulntriage scan --provider gemini
# Use Ollama — fully local, no dependency data leaves your machine
vulntriage scan --provider ollama --model phi4-mini
No API key? Use your coding-CLI subscription
If you pay for Claude Code, OpenAI Codex, or the Gemini CLI but have no separate
API key, vulntriage can borrow the subscription you are already logged into.
There is nothing to configure in vulntriage — it spawns the CLI as a
subprocess (exactly like it spawns pip-audit) and reads back the ranking. The
credentials live in the CLI's own config; vulntriage never sees a key.
One-time setup — install and log into whichever CLI you have:
# Claude Code
npm i -g @anthropic-ai/claude-code # then run `claude` once to log in
vulntriage scan --provider claude-cli
# OpenAI Codex
npm i -g @openai/codex && codex login
vulntriage scan --provider codex-cli
# Google Gemini CLI
npm i -g @google/gemini-cli # then run `gemini` once to log in
vulntriage scan --provider gemini-cli
Notes:
- Each CLI runs read-only with tool use disabled — it only ranks the CVE list.
- First run is slower than a raw API call (agentic CLIs have heavier startup); the per-provider timeout defaults to 180s and is overridable via
VULNTRIAGE_CLAUDE_CLI_TIMEOUT/VULNTRIAGE_CODEX_CLI_TIMEOUT/VULNTRIAGE_GEMINI_CLI_TIMEOUT. - Output quality matches the underlying model (Sonnet/GPT/Gemini), so this is a far better free option than local Ollama models for reachability judgements.
- Bound to your local login — best for local/dev use, not shared CI runners (use the API-key providers for unattended CI).
- If the CLI is missing or not logged in,
vulntriagefails with a clear message; it never hangs or silently degrades.
Privacy note: Every provider except ollama sends your dependency names off the machine — the API providers to their REST API, the subscription-CLI providers via the coding CLI's own session. If your dependency list is sensitive, use --provider ollama.
Ollama quickstart
brew install ollama
ollama pull llama3.2
pip install 'vulntriage[ollama]'
vulntriage scan --provider ollama
By default Ollama connects to http://localhost:11434. Override with OLLAMA_HOST. If the Ollama server is not running, vulntriage will start it automatically.
CI Integration
vulntriage scan exits 1 if any CVE is ranked at or above --fail-on (default: none, meaning the scan always exits 0 unless you set a threshold), and 0 otherwise.
GitHub Actions
- name: Audit CVEs
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
NVD_API_KEY: ${{ secrets.NVD_API_KEY }} # optional but speeds up NVD lookups
run: |
pip install pip-audit vulntriage
vulntriage scan --fail-on HIGH
Gate on CRITICAL only:
vulntriage scan --fail-on CRITICAL
Save a report as a CI artifact:
vulntriage scan --output-dir ./reports --format json
GitLab CI
audit:
script:
- pip install pip-audit vulntriage
- vulntriage scan --fail-on HIGH
variables:
ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
NVD_API_KEY: $NVD_API_KEY
How it works
- Runs
pip-audit --format jsonas a subprocess - Reads
requirements.txtorpyproject.tomlto understand your actual stack - Loads
.vulnignoreand removes suppressed CVEs - Fetches threat intelligence from NVD, CISA KEV, and EPSS (skipped with
--offline; all three cached 24h at~/.cache/vulntriage/) - Sends the enriched CVE list and stack context to the configured LLM
- NVD CVSS overrides any score the LLM returns
- Renders a ranked Rich table or JSON output
- Exits 1 if any CVE is at or above
--fail-onseverity
LLM reasoning example:
"requests is a direct dependency called at every API boundary — HIGH (SSRF via proxied requests). setuptools is not reachable at application runtime — LOW despite CVSS 7.5."
Cost
Each scan makes one LLM API call. At claude-sonnet-4-6 pricing (~$3/M input, $15/M output), a typical scan with 5–10 CVEs costs roughly $0.004–0.01. The static system prompt is cached across repeat scans (Anthropic 5-min TTL), cutting cost on subsequent runs by ~80%.
Scope
- pip only (no npm, cargo, etc.)
- Context from
requirements.txt/pyproject.toml— no static call-graph analysis - Threat intel cached at
~/.cache/vulntriage/with a 24-hour TTL
Development
git clone https://github.com/Nivish-21/Vuln
cd Vuln
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
pytest
# Lint + format
black . && ruff check .
License
MIT
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 vulntriage-0.15.1.tar.gz.
File metadata
- Download URL: vulntriage-0.15.1.tar.gz
- Upload date:
- Size: 56.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
965e4267ee17be631fba0a890ebd77516c63e210055f1e9f514ecedc67c97570
|
|
| MD5 |
59a70267da0f74bee263402da9f490f4
|
|
| BLAKE2b-256 |
29c743c70c60f5d01ae56342f0a9f3242c3377d8d3726ff8b655826966502935
|
File details
Details for the file vulntriage-0.15.1-py3-none-any.whl.
File metadata
- Download URL: vulntriage-0.15.1-py3-none-any.whl
- Upload date:
- Size: 39.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ba9c5203a56a5cfa4aa163edf1235f61f674cc76a1540174c3691a769df030e
|
|
| MD5 |
be5950d133b0b35af089cd06f0311e90
|
|
| BLAKE2b-256 |
ef841cb1a19175f2107cd5566c67e81ee733678e652125dad2ed45eff9b35258
|