Skip to main content

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-cli

Nothing to configure — vulntriage runs the CLI for you. Setup details below.


Requirements

  • Python 3.11+
  • pip-audit installed and on PATH (pip install pip-audit)
  • An LLM backend — any one of: an Anthropic/OpenAI/Gemini API key; a logged-in coding CLI (claude, codex, or gemini) 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, vulntriage fails 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

  1. Runs pip-audit --format json as a subprocess
  2. Reads requirements.txt or pyproject.toml to understand your actual stack
  3. Loads .vulnignore and removes suppressed CVEs
  4. Fetches threat intelligence from NVD, CISA KEV, and EPSS (skipped with --offline; all three cached 24h at ~/.cache/vulntriage/)
  5. Sends the enriched CVE list and stack context to the configured LLM
  6. NVD CVSS overrides any score the LLM returns
  7. Renders a ranked Rich table or JSON output
  8. Exits 1 if any CVE is at or above --fail-on severity

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vulntriage-0.15.1.tar.gz (56.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vulntriage-0.15.1-py3-none-any.whl (39.0 kB view details)

Uploaded Python 3

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

Hashes for vulntriage-0.15.1.tar.gz
Algorithm Hash digest
SHA256 965e4267ee17be631fba0a890ebd77516c63e210055f1e9f514ecedc67c97570
MD5 59a70267da0f74bee263402da9f490f4
BLAKE2b-256 29c743c70c60f5d01ae56342f0a9f3242c3377d8d3726ff8b655826966502935

See more details on using hashes here.

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

Hashes for vulntriage-0.15.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6ba9c5203a56a5cfa4aa163edf1235f61f674cc76a1540174c3691a769df030e
MD5 be5950d133b0b35af089cd06f0311e90
BLAKE2b-256 ef841cb1a19175f2107cd5566c67e81ee733678e652125dad2ed45eff9b35258

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page