feat!(detector): route NVD CPE detection to vuls2, keep go-cve-dictionary for non-NVD sources#2575
Draft
shino wants to merge 100 commits into
Draft
feat!(detector): route NVD CPE detection to vuls2, keep go-cve-dictionary for non-NVD sources#2575shino wants to merge 100 commits into
shino wants to merge 100 commits into
Conversation
CPE-URI detection moves from the go-cve-dictionary-backed DetectCpeURIsCves to the vuls2 library: preConvert forwards the per-server CPE list (converted to CPE 2.3 FS form) on scanTypes.ScanResult.CPE, detect() consults cpe.Detect alongside ospkg.Detect in the same DB session, and walkCriteria maps accepted CPE indices back to the scanned CPE forms. vuls2's NVD data carries cpematch-expanded criteria, which both fixes non-semver version matching (cisco ios "15.1(4)m3" etc.) and picks up range edge cases the dictionary path missed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NVD configurations frequently use an AND between a vulnerable product and an environment guard (e.g. "vulnerable iff running on broadcom hardware"). Existing vuls + go-cve-dictionary users rely on the historical behaviour of ignoring the env side — they typically scan with only the OS / app CPE and never list the hardware. To preserve that behaviour after migrating CPE detection to the vuls2 backend, relax AND evaluation for ecosystem="cpe": - pruneCriteria takes the detection ecosystem as an argument. - For ecosystem=="cpe" inside an AND node, child subtrees / criterions whose Vulnerable flag is false (i.e. environment guards) are skipped before the standard "every child must be affected" check runs. - Non-CPE ecosystems are unchanged: AND stays strict. - Empty AND after stripping → returns empty (correctly treated as unaffected by the caller). Effect on the vuls-compare detection diff (5 non-cisco CPEs): vim AND-FP-2 cases 3 → 1 (CVE-2025-66476 & CVE-2022-0319 now hit) linux-kernel CVE-2022-3643 (broadcom AND) now hit apple-safari +139 CVEs migrated from "only gocve" to common (most macOS-conditioned safari vulns) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related cleanups exposed by vuls-compare against the classic gocve
path:
1. AffectedPackages: postConvert assigned an initialised-but-empty
`models.PackageFixStatuses{}` to CVEs with no affected packages
(i.e. CPE-only detections). Classic gocve leaves the field nil, so
the encoded JSON shifted from `null` to `[]`. Drop the make() when
the source map is empty.
2. CpeURIs: walkCriteria emitted the criterion's CPE field, which is
the matched-spec form (e.g. `cpe:2.3:a:vim:vim:*:*:*:*:*:*:*:*`
with the version anonymised to `*`). Use the SCANNED CPE indexed
by `fcn.Accepts.Version` instead, which preserves the user's
configured version. postConvert then maps each FS string back to
the user-supplied URI/FS form via a new fsToOriginalCPE map
threaded from toFSCPEs → preConvert → postConvert.
Verified with vuls-compare against bash/jq/openssl/wget/vim scans:
both fields now match the classic-path output bit-for-bit.
Signature changes:
- preConvert returns (ScanResult, map[string]string)
- toFSCPEs returns ([]string, map[string]string)
- postConvert takes the map as a third arg
- existing tests pass `nil` for the map (already exercised in fix-path)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ources Bring back the DetectCpeURIsCves call that was dropped when CPE detection moved to vuls2, but with a twist: the NVD contribution of every go-cve-dictionary detection is stripped before use. Division of labour: - vuls2.Detect — authority for NVD CPE detection. Its DB carries the NVD feed with cpematch-expanded criteria (Strategy E), which both fixes non-semver version matching and avoids go-cpe over-match false positives. - DetectCpeURIsCves (go-cve-dictionary) — contributes only the sources vuls2 does not cover: JVN, Cisco, Paloalto, Fortinet, Vulncheck, EUVD, MITRE. detail.Nvds is nil-ed per detection; detections that were NVD-only disappear entirely and are re-detected by vuls2 from its own NVD data. Ordering matters: DetectCpeURIsCves runs BEFORE vuls2.Detect, so vuls2's NVD content lands on a ScannedCves map that never contains go-cve-dictionary's NVD remnants — the two paths cannot double-report the same source. (vuls2.Detect merges into existing VulnInfos per-source via CveContents append, so JVN entries registered first are preserved.) Implementation notes: - The CPE collection loop now builds two parallel views: cpeURIs []string for vuls2.Detect and cpes []Cpe for go-cve-dictionary. UseJVN mirrors the original pre-vuls2 behaviour: user-supplied and OWASP-DC CPEs consult JVN; synthesised Apple CPEs are NVD-only (UseJVN=false), so they contribute nothing on the dictionary path once NVD is stripped, and are effectively vuls2-only. - The `!detail.HasNvd() && detail.HasJvn()` advisory branch loses its HasNvd guard — NVD is always stripped, the condition was dead. - getMaxConfidence needs no change: with Nvds nil it simply never returns an Nvd* confidence; Cisco/Paloalto/Fortinet/Vulncheck/JVN ranks are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0 models
vuls-data-update extractors lift detection-relevant NVD reference tags
("Exploit", "Mitigation") into the vulnerability content's Exploit /
Mitigations slots at extract time (Exploit.Link and
Remediation.Description carry the reference URL). Surface those in the
vuls2-routed path:
- walkVulnerabilityDatas: convert v.Content.Exploit into
models.Exploit{ExploitType: NVD, URL} and v.Content.Mitigations into
models.Mitigation{CveContentType, URL}, attached to the built
VulnInfo. The classic gocve path derives the same entries in
ConvertNvdToModel, so without this the vuls2 path silently drops
them.
- mergeVulnInfo: dedup-append Exploits and Mitigations when merging
per-source VulnInfos — previously the merge dropped both fields
entirely.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track the merged upstream work this branch depends on: - MaineK00n/vuls-data-update@ca09bbc7 — cpecriterion sibling kind (#836), NVD v2 feed extractor with cpematch expansion + reference-tag Exploit/Mitigation lift (#827), concrete-attribute over-match guard (#841). - MaineK00n/vuls2@22e83337 — CriterionTypeCPE consumption in detect (#382) and part:vendor:product CPE detection index (#384). With these pins the branch builds standalone (no go.work overrides). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reroutes NVD-based CPE detection to the vuls2 DB (to leverage cpematch-expanded criteria and avoid version-format pitfalls) while keeping go-cve-dictionary CPE detection for non-NVD sources (JVN, Cisco, Paloalto, Fortinet, Vulncheck, EUVD, MITRE, etc.). It also updates vuls2-path data conversion to preserve user-supplied CPE forms and to carry exploit/mitigation metadata through merges.
Changes:
- Update the vuls2 detector to run CPE detection (NVD) alongside OS-package detection in one DB session and convert CPEs to/from CPE 2.3 FS.
- Update the main detector flow to run go-cve-dictionary CPE detection first (stripping NVD) and then run vuls2 detection.
- Bump dependencies (vuls2, vuls-data-update, and related indirect modules) and update tests/source ID handling.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
detector/detector.go |
Collect CPEs from scan result snapshot + OWASP + synthesized Apple CPEs; strip NVD results from go-cve-dict CPE detection; run unified vuls2 detection afterwards. |
detector/vuls2/vuls2.go |
Add vuls2 CPE detection path, CPE FS conversion/restoration, CPE-AND relax pruning, exploit/mitigation conversion & merge behavior. |
detector/vuls2/vendor.go |
Update Red Hat VEX source ID handling and sorting/tag comparison logic. |
detector/vuls2/vuls2_test.go |
Update tests for new exported helper signatures and updated criterion/source types. |
go.mod |
Pin updated vuls2 and vuls-data-update versions and bump several direct/indirect deps. |
go.sum |
Module sum updates corresponding to dependency bumps. |
Comments suppressed due to low confidence (1)
detector/vuls2/vuls2.go:101
- When merging vuls2-detected
VulnInfointo an existingr.ScannedCvesentry (e.g., afterDetectCpeURIsCvesran first), the merge currently ignoresCpeURIs,Exploits, andMitigations, so those fields can be silently dropped for CVEs present in both paths. Also,viBase.CveContentsmay be nil for existing entries, which would panic on assignment.
vulnInfos, err := postConvert(vuls2Scanned, vuls2Detected, fsToOriginalCPE)
if err != nil {
return xerrors.Errorf("Failed to post convert. err: %w", err)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…hs run Per Copilot review on #2575: - detect(): when the same RootID was flagged by both the OS-package and CPE paths, addDetection fetched Vulnerability/Advisory data only for the FIRST detection's ecosystem/datasources and just appended the later detection — dropping the content the other path needs. Collect detections first, then fetch once per RootID without ecosystem/datasource narrowing (mirrors vuls2's pkg/detect.detect). - detector.go: format the cpeURIs slice with %v instead of %s in the DetectCpeURIsCves error message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ca09bbc7/22e83337 bump was done with `go get` alone; CI's `go mod tidy && git diff --exit-code` gate caught the residue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mode Per Copilot review on #2575: DetectPkgCves had become post-processing only, with the actual vuls2 detection moved to the main Detect flow. But server mode (server/server.go) calls DetectPkgCves directly and never the main flow — OS-package detection silently disappeared there. Restore the master-era division of responsibility: - DetectPkgCves runs vuls2.Detect (OS packages / Microsoft KB) again, so every caller — report flow and server mode alike — gets package detection, and the FixState / ListenPortStats post-processing inside DetectPkgCves once again runs AFTER detection results exist. - vuls2.Detect drops the cpeURIs parameter; CPE detection moves to a new vuls2.DetectCPEs, called from the main Detect flow after the CPE list is collected. DetectCPEs suppresses the OS-package / Microsoft-KB inputs in the converted scan result so the two entry points cannot double-detect packages (vuls2's merge appends AffectedPackages, so a double run would duplicate them). - Both share the session/convert plumbing via detectWith. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `!detail.HasNvd() && detail.HasJvn()` guard means "emit JVNDB advisories only when NVD does NOT cover this CVE" — the JVN advisory is redundant when NVD content exists. Rewriting it to an unconditional HasJvn() check after the NVD strip changed that semantics: CVEs covered by both NVD and JVN started emitting JVNDB DistroAdvisories that the classic path suppressed. Capture hadNvd before nil-ing detail.Nvds and gate the JVN advisory emission on the pre-strip value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per Copilot review on #2575: - isVulnerableTrue's comment claimed CPE criteria have no Vulnerable concept, but the implementation does consult CPE.Vulnerable; restate the doc to match (Version and CPE check their flag, the rest default to true). - "vuls/ normalises" → "vuls normalises". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cpecriterionTypes import landed mid-block during the rebase; CI's golangci-lint (goimports) flagged it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…os + dedup FS CPEs Per Copilot review on #2575: - Detect's ScannedCves merge loop now dedup-appends Exploits and Mitigations when the CVE already exists (e.g. registered first by the go-cve-dictionary non-NVD path); previously the vuls2-derived entries were silently dropped for mixed-source CVEs. - toFSCPEs dedups by FS form: the first user-supplied form wins in both the detection list and the reverse map, instead of re-detecting the same FS string for each duplicate input. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…points DetectPkgCves/DetectCpeURIsCves advertise themselves as library entry points, but a zero-value ScanResult (ScannedCves == nil) panicked on the first map assignment in mergeIntoScannedCves or the go-cve-dictionary helper. The report and server flows always initialize the map, so this only bit direct library consumers — guard both entry paths and pin it with a merge test case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The skip gate listed every Has* accessor, but go-cve-dictionary's GetByCpeURI admits a detail on Nvd/Vulncheck/Jvn/Fortinet/Paloalto/ Cisco matches only — EUVD and MITRE are enrichment contents that ride along and are no detection basis (getMaxConfidence has no tier for them either). An NVD-only detection carrying EUVD/MITRE content therefore survived the NVD strip and registered with a zero-value confidence. Align the gate with the dictionary's admission sources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A criterion with version=* and no Range / no CPEMatches states that every version of the product is affected, so an accepted match is exact regardless of the scanned version — not vendor:product. The previous code demoted all version-unrestricted accepts to VendorProductMatch, which was wrong for NVD (where a bare version=* is a deliberate "all versions" statement). Split the accept classification: - version=* (no Range/CPEMatches): all versions affected -> exact - version=NA: no version concept -> vendor:product (matches gocve) - version-restricted + version-less query: cannot compare -> vendor:product - version-restricted + concrete query: -> exact JVN, whose every criterion is version=*, must not be inflated to exact: it carries no version data at all. That demotion already lives in toVuls0Confidence (JVN maps to JvnVendorProductMatch from whichever tier it lands in); a comment now states why, and a postConvert case pins JVN version=* -> JvnVendorProductMatch alongside NVD version=* -> NvdExactVersionMatch. cpecriterion.Accept already accepts version=* (no narrowing -> accept on attribute match), and the NVD extractor emits such criteria, so no vuls-data-update change is needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JVN is not a vuls2 CPE source yet (go-cve-dictionary serves it), so the exact-tier JVN case in toVuls0Confidence is dead code today. Reframe the comment from "JVN never reaches exact" (an active-demotion claim) to "currently unreachable; when JVN migrates a version=* match is legitimately exact — revisit then (no JvnExactVersionMatch exists)". Drop the postConvert JVN case that forced this unreachable path and pinned the placeholder VP behaviour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
walkCPECriteria's recursive walk propagated the child error bare while
its sibling walkPkgCriteria wraps the same recursion with
xerrors.Errorf("Failed to walk criteria. err: %w", err). Match the
convention so a deep CPE-tree error keeps its locality.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous commit wrapped walkCPECriteria's recursion with "Failed to walk cpe criteria", the same message the walkVulnerabilityDetections closure already adds — a literal double wrap. walkPkgCriteria's recursion uses the generic "Failed to walk criteria" with the closure supplying the "pkg criteria" specificity; mirror that for cpe so the two paths are symmetric and the dup is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
master wraps the walkCriteria result with "Failed to walk criteria" at
the walkVulnerabilityDetections call site; the refactor's IIFE
dispatcher left a bare `return err` there, dropping that wrap. Replace
the IIFE with a plain if/else so each branch wraps the walk error
("Failed to walk cpe/pkg criteria") and returns directly — no bare
propagation, no double wrap, and no tuple-returning closure.
The walkCPECriteria / walkPkgCriteria top-level bare returns stay as
is: they propagate an already recursion-wrapped error up to this
caller wrap, which is net-identical to master's recursion-wrap +
caller-wrap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
walkCPECriteria / walkPkgCriteria propagated walk(ca)/walk(pruned) errors bare at their top-level. Wrap them too so no error return in the CPE detection path is bare — a reader never has to ask "why isn't this one wrapped?". The message repeats the recursion's "Failed to walk criteria" by design; consistency beats avoiding the minor duplication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the verified-replaces-unverified special case on Exploits and match the AppendIfMissing convention of Confidences / DistroAdvisories / Mitigations. A lone exploit whose only difference across passes is the Verified flag would mean the upstream data disagrees with itself — not something this merge should arbitrate — and a one-off "replace" rule here was the odd one out that invited "why is this different?". The merge test now asserts first-wins for a same-key exploit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exploits.AppendIfMissing got a test but its sibling Mitigations did not. Mirror it to lock in the dedup key (CveContentType, URL, Mitigation): append when missing, first-wins on the same key, and a differing Mitigation text being a distinct entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t test The vuls2 CPE path now emits NVD CveContents / Exploits / Mitigations, but FillCvesWithGoCVEDictionary later appended its own NVD-derived copies with no dedup, so vuls2-detected NVD CVEs ended up with duplicated cveContents[nvd], exploit, and mitigation entries. - Exploits / Mitigations: switch the plain append to AppendIfMissing. - nvd CveContents: drop any pre-filled nvd entries before appending go-cve-dictionary's, since gocve is the NVD content authority during the transition. A SourceLink dedup cannot be used here — gocve emits one nvd CveContent per CVSS source, all sharing the NVD SourceLink, so it would collapse those legitimate per-source entries. Also gofmt the Mitigations test added in the previous commit (the goimports gap that failed lint). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vendorProductEligible mirrored go-cve-dictionary's match() by re-evaluating
a range with RPM-style comparison when the semver comparator could not parse
the query (e.g. juniper "21.4r3", safari "1.0.0b1"), reporting in-range hits
at VendorProductMatch. Empirically that fallback is neither necessary nor
sufficient:
- Not necessary: with a well-formed query the matcher already reaches every
affected version at ExactVersionMatch. Normalising juniper's joined form to
version=21.4 / update=r3 makes the same wildcard range ("< 22.2") evaluate
as plain semver; detection is byte-identical with the fallback on or off
(199 CVEs either way). The fallback only compensates for a malformed query
representation, which is a detect-side normalizer's responsibility.
- Not sufficient: RPM comparison gives no consistent order for NVD's messy
pre-release strings. "4.0_beta" > "4.0" but "4beta" < "4.0" (the same
"4 beta" ordered oppositely by NVD spelling), and "1.0.0b1" > "1.0.0". It
only lands correct when the leading version digits already dominate; near a
boundary it mis-orders. Vendors like safari, whose NVD version formats are
inconsistent, cannot be served by any version-comparison heuristic here.
The fallback only ever produced the retired RoughVersionMatch tier (folded
to VendorProductMatch), so removing it leaves ExactVersionMatch untouched;
it drops only the fuzzy in-range VP guesses for non-semver query versions.
Splitting such version strings belongs in a future detect-side query
normalizer (tractable for regular forms like juniper, a known gap for
irregular ones like safari).
Simplify vendorProductEligible to the version-less cases (query ANY/NA, or
criterion NA), delete rangeVendorProductEligible, and drop the now-unused
go-rpm-version and cpecriterion range/criterion imports.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iving it
CPE match-quality determination moved upstream into vuls-data-update's
cpecriterion.Match (exact / version-unconfirmed). walkCPECriteria is now a
pure projection: it reads FilteredCriterion.Accepts.CPE.{Exact,
VersionUnconfirmed} and folds the supporting scanned CPEs up the AND/OR tree
into vuls0's exact / vendor:product tiers — no more raw CPE re-judgement.
Removed from vuls0: vendorProductEligible, the inline pvpEqual, the
accept-empty vendor:product fallback, and the scanned-version re-derivation
(go-cpe WFN matching, range compare, and the retired RPM fallback are gone
from this layer). The version=NA "all versions" detection the fallback used
to supply now comes through cpecriterion.Match at VersionUnconfirmed, so
NVD/cisco/linux detection is byte-identical (vuls-compare gate).
walkCPECriteria takes a sourceID and demotes JVN matches (JVNFeedRSS /
JVNFeedDetail) from exact to vendor:product — JVN carries no version data, a
source-semantics call kept in vuls0 rather than the source-agnostic matcher.
toVuls0Confidence's JVN exact branch is now documented as unreachable for that
reason. RoughVersionMatch stays retired.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Route NVD-based CPE detection through the vuls2 library while keeping go-cve-dictionary for the sources vuls2 does not cover (JVN, Cisco, Paloalto, Fortinet, …). Companion work to MaineK00n/vuls-data-update#827/#836/#841 and MaineK00n/vuls2#382/#384, which this PR pins via go.mod.
Why
The go-cve-dictionary CPE path matches NVD data by version-range comparison only, which cannot decide membership for non-semver version formats (
cisco ios 15.1(4)m3,juniper junos 21.4r3, …) and suffers from a go-cpe numeric-prefix over-match (5.15.10matching a5.15.103query). The vuls2 DB carries the NVD feed with cpematch-expanded criteria, fixing both — but it has no JVN/Cisco/Paloalto/Fortinet CPE data, so dropping go-cve-dictionary entirely would lose those sources.Division of labour after this PR:
cpe.Detect, cpematch-expanded criteria)DetectCpeURIsCves, NVD contribution stripped; EUVD / MITRE contents ride along as enrichment but are never a detection basis)How
detector API — master-shaped, library-consumer safe
External consumers drive detection as a library through the master-era pair
DetectPkgCves()→DetectCpeURIsCves(cpes []Cpe). Both entry points keep their master names and calling convention, each with a complete, order-independent responsibility:DetectPkgCves(r, vuls2Conf, noProgress)— unchanged signature. OS-package / Microsoft-KB detection viavuls2.DetectPkgs(family-gated) plus the FixState / ListenPortStats post-processing. Server mode calls this alone, preserving its no-CPE behaviour.DetectCpeURIsCves(r, cpes, cveCnf, logOpts, vuls2Conf, noProgress)— the full CPE pipeline; two parameters added so the change is visible at compile time rather than silently dropping NVD results. Internally: go-cve-dictionary first (non-NVD sources only — each detection'sNvdsis nil'ed before use; NVD-only detections disappear and vuls2 re-detects them; JVNDB advisories stay gated on the pre-strip NVD presence, matching classic behaviour), thenvuls2.DetectCPEsfor NVD over every entry. TheCpe.UseJVNflag passes through to the dictionary lookup unchanged:UseJVN=falseexcludes JVN only — NVD / Vulncheck / vendor sources still apply, exactly as master. The flag is part of the library API and is set per CPE by the caller (the report flow sets it false for the synthesised Apple CPEs; external consumers set it on their own terms).detector/vuls2 library layer
vuls2.DetectPkgs(OS packages / KB; renamed fromDetectto mirror) andvuls2.DetectCPEs(CPE URIs) stay separate entry points by design — when CPE-capable sources beyond NVD (JVN, Vulncheck, …) move into the vuls2 DB, per-source confidence aggregation can live entirely inside the CPE path.DetectCPEssuppresses the OS-package / Microsoft-KB inputs so the two entry points cannot double-detect; the ScannedCves merge dedups CveContents by SourceLink and merges CpeURIs/Exploits/Mitigations.preConvertforwards the CPE list (converted to CPE 2.3 FS) onscanTypes.ScanResult.CPE;walkCriteriahandlesCriterionTypeCPE, emitting the SCANNED CPE form whichpostConvertmaps back to the user-supplied URI form.ExactVersionMatch(100) when a criterion accepts (cpe / range / cpe_matches hit),VendorProductMatch(10) when no criterion accepts but the detection index matched on part:vendor:product (walkVulnerabilityDetectionswalks the raw criteria tree and collects scanned CPEs sharing part:vendor:product with a vulnerable=true CPE criterion; vulnerable=false hardware guards are excluded).RoughVersionMatch(80) is intentionally retired — both remaining levels derive from information the schema already carries. Fallback CPEs fillCpeURIsonly when the CVE has no exact match.pruneCriteria): for thecpeecosystem,vulnerable=falsesubtrees (hardware/environment guards under AND) are skipped — they cannot be confirmed from a CPE-only scan, and ignoring them matches historical go-cve-dictionary behaviour users rely on.Exploit,Mitigation) into content-level slots at extract time;walkVulnerabilityDatasconverts them tomodels.Exploit/models.Mitigation, andmergeVulnInfonow dedup-appends both (it previously dropped them on merge).vuls-data-update@ca09bbc7,vuls2@22e83337— the branch builds standalone, no go.work overrides.Testing
go test ./...— green. (scanner/TestAnalyzeLibrary_Goldeninitially failed on this branch: the dependency bump transitively raised packageurl-go v0.1.5 → v0.1.6, whose purl escaping no longer percent-encodes&; the juddiv3 golden was regenerated accordingly. Pinning back to v0.1.5 was not an option since both vuls-data-update and vuls2 require v0.1.6.)cpe:/a:apache:http_server:2.4.49: 57 CVEs via vuls2 (NvdExactVersionMatch) + 15 via go-cve-dictionary (JvnVendorProductMatch), 0 double-reports; JVNDB advisory IDs land in DistroAdvisories.vuls diff detectionover the 69 vulsio/integration scan fixtures (master binary vs this branch, same nightly DB): all Linux families 0 diff; Windows families Removed=0 with small Added counts attributable to the newer vuls2 library's KB-supersession improvements (feat(detect/ospkg/microsoft): include forward superseders in vcm scope MaineK00n/vuls2#370), not to this PR's CPE changes.Test_postConvertgains a "cpe vendor:product fallback" case: no accepted criterion, index-level vendor:product match →NvdVendorProductMatchconfidence with the scanned CPE restored to URI form; different-product and vulnerable=false criterions do not contribute.