GitHub
10/10/2026, 2:05 PMunknown 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/osctrlGitHub
10/10/2026, 4:02 PM