<#1080 Add NVD CPE matching as possible findings> ...
# osctrl
g
#1080 Add NVD CPE matching as possible findings Pull request opened by javuto Add NVD CPE matching as possible findings Plan 4 of vulnerability monitoring. It builds on #1076 (backend), #1078 (frontend) and #1079 (alerts, posture and health). Problem OSV only covers packages from package managers. Windows programs, macOS apps, Homebrew and Chocolatey packages were never assessed. Many Debian and Ubuntu OSV advisories also carry no CVSS score, so their findings show
unknown
severity, and only rules with the
any
threshold alert on them. What changes Inventory • The
vuln-windows
profile now collects
programs
and
chocolatey_packages
. • The
vuln-darwin
profile now collects
apps
and
homebrew_packages
. • The vendor (Windows publisher or macOS bundle id) is stored for name matching. • While NVD is off, these packages count as not assessed, as the spec requires. NVD importer (
--vuln-nvd-enabled
, off by default)
• What it reads: the NVD CVE API 2.0, inside the existing API worker. • The first sync reads everything. • Later syncs read the
lastMod
window since the last success, starting 24 h early so CVEs that NVD indexes late aren't missed. • A gap longer than NVD's 120-day window triggers a full resync. • What it stores: • only CVEs that list vulnerable application CPEs (part
a
); • one
vuln_affected
row per
vendor:product
, with
ecosystem = 'cpe'
and NVD's version bounds; • a
vuln_cpe_products
lookup table of the products those CVEs name, instead of the full CPE dictionary. • Pacing and failures: • Requests are paced at NVD's documented limit: one every 6 s without a key, ten times faster with one. • Rate-limited (403, 429) and 5xx pages are retried twice. • A failed, truncated or empty response fails the sync. Stored data and the cursor stay put, and the retry waits 15 minutes after the failed sync ends. • Ownership: • A CVE id that an OSV record already uses stays the OSV record's. • A CVE that NVD later rejects is removed. • KEV: every NVD CVE is listed as its own alias, so the CISA KEV flag applies to it. • Turning it off: turning NVD off later deletes its data, resolves open possible findings, and re-matches the fleet. Matching • Names: each installed name and vendor is normalized to CPE spelling (lowercase, no version, architecture or bracketed text), then looked up exactly in
vuln_cpe_products
. • Fallback: without a matching vendor, a product is taken only when all of these hold: • a single vendor ships it; • the package's own vendor isn't a known CPE vendor that contradicts it (so Apple's Terminal never takes another vendor's "terminal"); • the name isn't just what's left after stripping the vendor. There is no fuzzy scoring. • Versions are compared as dotted numbers. A version without a digit is not assessed. • Findings are recorded with
confidence = 'possible'
,
ecosystem = 'cpe'
and
package = 'vendor:product'
. They never alert, because the watcher reads confirmed findings only. Severity • An OSV advisory without its own CVSS borrows the highest score of an aliased NVD CVE. •
vuln_advisories.cvss_from
names the CVE it came from, so the score is refreshed when the CVE changes and cleared when it goes away. Posture • A new column,
vuln_node_state.assessed
, counts packages assessed against exact OSV feeds. • The "Known vulnerabilities" control is evaluated only for nodes with confirmed findings, or nodes matched with at least one OSV-assessed package. • NVD-only packages and possible findings never pass, warn or fail a node. REST API • Advisories gain
source
(
osv
or
nvd
) and
cvss_from
. Their alias list no longer repeats the advisory's own id. • The environment summary gains
possible
.
kev
,
affected_nodes
and the top lists now count confirmed findings only. • OpenAPI is regenerated. Frontend • A Confidence filter (all, confirmed, possible), kept in the URL. • Possible rows are de-emphasized and keep the existing "possible" tooltip. • The Open findings card counts confirmed findings and notes how many possible matches it left out. Severity chip counts follow the confidence filter. • Three new strings in all 20 locales. The non-English strings are unreviewed drafts. Config and docs • New flags
--vuln-nvd-enabled
,
--vuln-nvd-url
and
--vuln-nvd-api-key
, with env vars
VULN_NVD_ENABLED
,
VULN_NVD_URL
and
VULN_NVD_API_KEY
. • Commented entries in
deploy/config/api.yml
. • Updates to
pkg/vulns/README.md
(coverage, an NVD section, air-gapped setup),
ARCHITECTURE.md
and the spec's implementation notes. Security and operational impact • Off by default. With
--vuln-nvd-enabled
off, nothing is fetched from NVD and matching behaves as before. The one change is that the newly collected application categories count as not assessed. • API key handling: • The key is sent only as the
apiKey
request header. • It is dropped when a redirect leaves the configured host. • It never appears in URLs, recorded errors or logs. • It is excluded from
service_config
(
json:"-"
), so admins can't read it through the service-config API. • Set it through `VULN_NVD_API_KEY`: a flag value shows in the process list. • Schema. These changes apply only where
--vuln-enabled
has created the vulnerability tables. All are additive, through AutoMigrate: •
vuln_advisories.source
, default `osv`; •
vuln_advisories.cvss_from
, default `''`; • `vuln_node_state.assessed`; • a new
vuln_cpe_products
table. • Untrusted content. NVD descriptions and references are external input, rendered as plain text like OSV's. • First sync. It reads about 150 pages and stores every CVE, so expect an hour or more. The worker matches nothing else meanwhile. Later syncs are small. • After upgrading. Nodes with no findings show no "Known vulnerabilities" control until they are re-matched: at the latest after the next advisory sync that changes anything, or their next inventory. • Known limitations: • A failed first sync starts again from the beginning. • One record the database rejects would fail its page on every retry. • The KEV card counts confirmed findings, while the "KEV only" filter lists both confidences. • API replicas with different
--vuln-nvd-enabled
values drop or resync NVD data on each change of worker. Validation •
gofmt -l pkg cmd
(empty),
go vet ./...
,
go build ./...
• `go test ./... -count=1`: all 55 packages pass • `golangci-lint run ./pkg/vulns/... ./pkg/config/... ./pkg/serviceconfig/... ./cmd/...`: 0 issues •
make openapi
and `make openapi-check`: the diff adds
source
and
cvss_from
to
vulns.Advisory
and
possible
to
vulns.Summary
• `cd frontend && npm run check && npm test && npm run build`: 491 tests pass • Mutation checks, each confirmed to fail the tests: • the vendor fallback guards; • the NVD-off gate in the matcher; • the cleanup when NVD is turned off; • the posture guards for assessed packages, confirmed categories and loaded data. • Fresh-context review of the whole branch. Its important findings are fixed, each with a test that failed first: • the API key in service config; • the key forwarded on redirects; • truncated pages counted as success; • immediate retries after a long failure; • CVEs missed in the incremental window; • incomplete cleanup when NVD is turned off; • wrong-vendor fallback matches. • Not run: • A sync against the live NVD API. Tests use servers that serve fixtures shaped like the NVD 2.0 docs. Please run one sync with
--vuln-nvd-enabled
before merging. • A manual check against a running stack. • Unrelated flake:
NodeConsolePage.test.tsx
failed once in a full frontend run, then passed in thre… jmpsec/osctrl