Skip to content

Offline NVD CVE intel: cached feed → auto-match to (product, version) #39

Description

@kocaemre

Idea

KB `known_vulns[]` is currently human-curated per-service YAML. It drifts — a CVE published last month won't show up until someone files a PR. NVD publishes a daily JSON feed (`nvdcve-1.1-modified.json.gz`) that's roughly 5-15 MB and can be downloaded once, cached locally, and matched against `(port.product, port.version)` at render time.

Stays consistent with recon-deck's offline-first ethos: the operator triggers a refresh manually (or via a cron / launchd job they configure); recon-deck never reaches out on its own.

Proposal

Persistence

```
cve_intel {
cve_id PRIMARY KEY, -- CVE-YYYY-NNNNN
description,
cvss_v3, -- numeric, nullable
severity, -- critical|high|medium|low|info, derived from cvss_v3
published_at,
modified_at,
cpe_match, -- JSON array of CPE configurations from NVD
url -- canonical NVD link
}
```

CPE match is the load-bearing piece — NVD encodes affected versions as CPE 2.3 strings (`cpe:2.3:a:vsftpd:vsftpd:2.3.4:::::::*`). nmap already extracts `port.cpe[]`, so the match is a CPE-vs-CPE compare.

Refresh flow

  • `/api/cve-intel/refresh` route (POST, opt-in toggle in `/settings`, off by default)
  • Downloads `nvdcve-1.1-modified.json.gz` from `https://nvd.nist.gov/feeds/json/cve/1.1/\` (NVD allows unauthenticated, rate-limited public access)
  • Streams + parses → upserts into `cve_intel`
  • Stores last-refresh timestamp in `app_state`

Render integration

Per-port detail pane: alongside KB `known_vulns`, a new "NVD matches" section listing CVEs whose CPE matches the port's `cpe[]`, sorted by CVSS desc, capped at 20. Each row: `severity badge · CVE-id · short description · NVD link`.

Add-as-finding hooks the same prefill flow as KB known_vulns.

Conflict with searchsploit

Different surface: searchsploit = "is there an exploit?", NVD = "is this version vulnerable?". Rendered side-by-side; operators consult both.

Out of scope

  • Full NVD historical archive (ships only the modified-feed; full feed = 100s of MB, deferred to a "deep refresh" command)
  • EPSS / KEV enrichment (CISA KEV would be a follow-up — small JSON, very useful)
  • Auto-refresh — strictly manual / external-cron triggered to keep offline contract

Acceptance

Notes

Pairs naturally with #14 conditionals — a future predicate `cve_present(severity_at_least)` could light up checklists when a high-severity CVE is matched, but that's deferred.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions