Supported AmazonLinux for server mode#850
Merged
knqyf263 merged 1 commit intoJun 25, 2019
Merged
Conversation
Contributor
|
Did you confirm that a result by server mode is the same as a result by SSH? |
Contributor
Author
|
Yes. Confirmed in EC2 instance. |
Contributor
|
Thanks! |
shino
added a commit
that referenced
this pull request
Jun 19, 2026
Point at the merged dependency chain now that the upstream PRs landed: - vuls2 -> v0.0.1-alpha.0.20260619063813-98ac8e5258eb (nightly, #388) - vuls-data-update -> v0.0.0-20260618100055-bfedbc246360 (#850), matching the version vuls2's nightly pins Standalone (GOWORK=off) build and detector/models tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shino
added a commit
that referenced
this pull request
Jun 19, 2026
Point at the merged dependency chain now that the upstream PRs landed: - vuls2 -> v0.0.1-alpha.0.20260619063813-98ac8e5258eb (nightly, #388) - vuls-data-update -> v0.0.0-20260618100055-bfedbc246360 (#850), matching the version vuls2's nightly pins Standalone (GOWORK=off) build and detector/models tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shino
added a commit
that referenced
this pull request
Jun 24, 2026
* feat!(detector): route CPE detection to vuls2
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>
* feat(detector/vuls2): relax AND for CPE ecosystem in pruneCriteria
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>
* fix(detector/vuls2): preserve scanned CPE form + nil-AffectedPackages
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>
* feat(detector): restore go-cve-dictionary CPE detection for non-NVD sources
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>
* feat(detector/vuls2): map content-level Exploit/Mitigations into vuls0 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>
* build(deps): bump vuls-data-update to ca09bbc7 and vuls2 to 22e83337
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>
* fix(detector/vuls2): fetch vulnerability data after all detection paths 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>
* build(deps): go mod tidy after dependency bump
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>
* fix(detector): restore pkg detection inside DetectPkgCves for server 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>
* fix(detector): keep pre-strip NVD presence for the JVN advisory gate
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>
* docs(detector/vuls2): correct isVulnerableTrue doc + comment typo
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>
* style(detector/vuls2): fix goimports ordering in vuls2_test.go
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>
* fix(detector/vuls2): merge Exploits/Mitigations into existing VulnInfos + 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>
* fix(detector/vuls2): restrict Exploit/Mitigation mapping to NVD content
Per Copilot review on #2575:
- The content-level Exploit/Mitigations mapping ran for every
CveContentType while hard-coding ExploitTypeNVD — non-NVD sources
(e.g. Red Hat) that populate the same content slots would have their
entries mis-labelled. Gate the mapping on cctype == models.Nvd and
set the Mitigation CveContentType to models.Nvd explicitly, mirroring
ConvertNvdToModel exactly.
- Correct the §2 comment: AffectedPackages carries omitempty, so nil
vs empty slice is invisible in JSON; the real reasons to keep ps nil
are skipping the allocation and the nil-means-not-detected in-memory
convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor!(detector): unify package and CPE detection under DetectCves
Rename DetectPkgCves to DetectCves and fold the CPE pipeline into it,
so every caller shares one detection entry point:
- OS packages / Microsoft KB: vuls2.Detect, gated by family support
(unchanged).
- CPE URIs: DetectCpeURIsCves (go-cve-dictionary, non-NVD sources,
NVD stripped) followed by vuls2.DetectCPEs — moved here from the
main Detect flow. No family gate, so families the package path
skips (pseudo / macOS) still get their CPE lists checked.
- FixState / ListenPortStats post-processing keeps running last,
after all detection results exist.
The vuls2 library keeps Detect (OS pkg) and DetectCPEs (CPE) as
separate entry points on purpose: when CPE-capable sources beyond NVD
(JVN, Vulncheck, ...) move into the vuls2 DB, per-source confidence
aggregation can then live entirely inside the CPE path.
server mode passes nil cpes/cpeURIs — the CPE stages are no-ops and
today's behaviour (package detection only) is preserved, while the
plumbing to enable CPE detection there later is now a parameter
change away. Also resolves the stale main-flow comment that still
referred to vuls2.Detect for CPE detection (Copilot, #2575).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): merge CpeURIs and dedup CveContents across detection passes
Per Copilot review on #2575: with Detect (OS packages) and DetectCPEs
(CPE URIs) running as separate passes over the same ScannedCves map,
the merge into an existing VulnInfo
- appended CveContents wholesale, duplicating identical entries (same
SourceLink) when both passes detected the same CVE — now deduped by
SourceLink per content type;
- never merged CpeURIs, leaving them empty for CVEs first registered
by the package pass — now dedup-appended.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(detector/vuls2): cover CPE-AND relax and fsToOriginalCPE restore
Per Copilot review on #2575:
- Test_pruneCriteria gains an ecosystem arg and two cases: under
ecosystem "cpe" an AND with an env-only vulnerable=false guard
subtree keeps the vulnerable=true product criterion (relax skips the
guard); under a non-CPE ecosystem the same shape fails the AND.
- Test_postConvert now threads fsToOriginalCPE; the
"redhat vex + epel + cpe" case maps the detected FS string back to a
user-supplied CPE 2.2 URI and asserts CpeURIs carries the original
form.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): validate FS-form CPE inputs in toFSCPEs
Per Copilot review on #2575: strings with the "cpe:2.3:" prefix were
forwarded into vuls2 detection without validation, deferring the
failure to match time. Validate via naming.UnbindFS and skip invalid
entries with a warning, mirroring the UnbindURI branch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): include Microsoft KBs in the package-detection gate + doc fixes
Per Copilot review on #2575:
- detect() skipped ospkg.Detect when OSPackages was empty, but
ospkg.Detect also performs Microsoft-KB detection — a Windows scan
carrying only KBs (no packages) would silently lose KB-based
detection. Gate on either input being present.
- Detect / DetectCPEs doc comments still referenced DetectPkgCves,
which the unification renamed to detector.DetectCves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(scanner): refresh juddiv3 golden for packageurl-go v0.1.6 escaping
The vuls-data-update/vuls2 dependency bump transitively raised
github.com/package-url/packageurl-go from v0.1.5 to v0.1.6 (both
upstreams require v0.1.6, so pinning back — the #2438 approach — would
downgrade vuls2 itself). v0.1.6 no longer percent-encodes '&' in purl
namespace segments, so the "France Telecom R&D:ASM" entry in the
juddiv3 WAR golden changed from France%20Telecom%20R%26D to
France%20Telecom%20R&D.
Regenerated with `go test ./scanner/ -run TestAnalyzeLibrary_Golden
-update` against vulsio/integration@9b7a175 (the CI-pinned fixture
ref); the only diff is that one purl.
This also corrects the earlier triage: the Test workflow failure was
NOT pre-existing on master — it was introduced by this branch's
dependency bump. An earlier local "fails on master too" check was
invalidated by a locally modified integration submodule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): initialize nil CveContents before merging into existing VulnInfo
Per Copilot review on #2575: a pre-existing VulnInfo can carry a nil
CveContents map (unmarshaled from a result JSON where cveContents was
omitted); writing viBase.CveContents[ccType] would panic. Initialize
the map before the merge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(detector/vuls2): assert content-level Exploit/Mitigation mapping in postConvert
Per Copilot review on #2575: the NVD-only Exploit/Mitigations mapping
had no unit coverage. The "redhat vex + epel + cpe" postConvert case
now carries one Exploit (Link) and one Mitigation (Description) on the
NVD vulnerability content and asserts VulnInfo.Exploits /
VulnInfo.Mitigations surface the URLs with ExploitTypeNVD /
CveContentType=Nvd.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): only dedup merged CveContents on non-empty SourceLink
Per Copilot review on #2575: SourceLink can legitimately be empty for
some content types, and using it as the sole dedup key would collapse
distinct empty-link entries into one. Dedup only when SourceLink is
non-empty; always append otherwise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): skip empty-link Exploit/Mitigation entries
Per Copilot review on #2575: guard against content entries whose
Link/Description is empty so reports don't carry blank
Exploit/Mitigation rows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(detector): skip the dictionary CPE path for UseJVN=false entries
Per Copilot review on #2575: synthesised Apple CPEs carry UseJVN=false
(NVD-only by design), and with the NVD contribution stripped inside
DetectCpeURIsCves the dictionary lookup for them is a guaranteed no-op.
Filter them out before the call so macOS scans with many synthesised
CPEs don't pay a per-CPE DB lookup for nothing; user-supplied and
OWASP-DC CPEs (UseJVN=true) are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(detector/vuls2): use a SourceLink set for the CveContents merge dedup
Per Copilot review on #2575: replace the per-entry slices.ContainsFunc
scan with a set built once per content type, dropping the merge from
O(n²) to O(n) per ccType. Entry counts are small in practice, but the
set form is no more code and removes the concern outright.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector): fall back to report-time config when the scan result lacks server config
Per Copilot review on #2575: CPE list sourcing relied solely on the
scan-result's embedded config.scan.servers. Results produced by an
older Vuls (or an external producer) don't embed it, which would
silently disable CPE detection. Prefer the scan-time snapshot, fall
back to config.Conf.Servers when the server entry is absent — also
restoring the previous ability to adjust CPE settings at report time
for such inputs. Container overrides follow the same precedence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector): restore master-shaped DetectPkgCves / DetectCpeURIsCves API
External consumers drive detection as a library through the master-era
pair DetectPkgCves() -> DetectCpeURIsCves(cpes []Cpe). The interim
unified DetectCves() broke that contract twice over: DetectPkgCves was
gone (build break), and a caller that kept using the still-present
DetectCpeURIsCves would silently lose NVD CPE detection, because the
NVD contribution is stripped there in favour of vuls2.
Restore the two master-named entry points, each with a complete,
order-independent responsibility:
- DetectPkgCves(r, vuls2Conf, noProgress): OS-package / Microsoft-KB
detection via vuls2 (family-gated) plus the FixState /
ListenPortStats post-processing. Same name and signature as master.
- DetectCpeURIsCves(r, cpes, cveCnf, logOpts, vuls2Conf, noProgress):
the full CPE pipeline — go-cve-dictionary for the non-NVD sources
(NVD stripped, hadNvd-gated JVNDB advisories) followed by
vuls2.DetectCPEs for NVD. The two added parameters make the change
visible at compile time instead of silently dropping NVD results.
The UseJVN pre-filter is dropped: UseJVN=false means "exclude JVN
only" — NVD / Vulncheck / Cisco / Paloalto / Fortinet still apply to
the synthesised Apple CPEs (see detectCveByCpeURI), so skipping the
dictionary lookup for them lost detections. The flag is passed through
to the dictionary lookup unchanged, exactly as master does.
vuls2.Detect is renamed to vuls2.DetectPkgs to mirror
vuls2.DetectCPEs; server mode calls DetectPkgCves alone, preserving
its no-CPE behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(vuls2): report VendorProductMatch for index-only CPE detections
go-cve-dictionary reports CPE detections at three confidence levels:
ExactVersionMatch (100), RoughVersionMatch (80) and VendorProductMatch
(10). The vuls2 CPE path so far only produced ExactVersionMatch — a
detection whose conditions all failed to accept was dropped entirely,
even though the detection index had matched the scanned CPE's
part:vendor:product.
Add the VendorProductMatch fallback: when a CPE-ecosystem condition
yields no accepted criterion, walk the raw criteria tree and collect
the scanned CPEs sharing part:vendor:product with a vulnerable=true
CPE criterion. Those CVEs are reported with the source's
*VendorProductMatch confidence (score 10), and their CpeURIs only fill
from the fallback when the CVE has no exact match at all.
The confidence design for vuls2-migrated sources is intentionally
two-valued: ExactVersionMatch when a criterion accepts (cpe / range /
cpe_matches hit), VendorProductMatch when only the index matches.
RoughVersionMatch is retired — both remaining levels derive from
information the schema already carries, so no criterion-type extension
is needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(detector): clarify UseJVN is a per-CPE library-API flag
UseJVN=false is not specific to the synthesised Apple CPEs — external
library consumers set the flag per CPE on their own terms. Reword the
Cpe struct and DetectCpeURIsCves comments so the Apple case reads as
the report flow's example rather than the flag's definition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): make the vendor:product fallback honour AND structure
vendorProductCPEs collected every vulnerable=true CPE criterion from
the raw criteria tree regardless of AND/OR structure. A CVE whose NVD
configuration requires two products co-installed — AND(product A,
product B) — was therefore reported at VendorProductMatch when only A
was scanned, even though pruneCriteria correctly rejects that same
condition on the exact path.
Re-evaluate the AND/OR structure during the walk, at vendor:product
granularity: an AND node is satisfied only when every relevant child
is, an OR node when any child is. Mirroring the CPE-AND relax,
vulnerable=false criterions (environment / hardware guards) and
non-CPE criterion types stay neutral — they cannot be confirmed from a
CPE-only scan and never veto an AND.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(vuls2): update stale Detect references after DetectPkgs rename
Two comments still referred to the old Detect entry point; the package
exports DetectPkgs / DetectCPEs since the rename.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): move the cpeOnly input suppression into preConvert
The caller-side field zeroing after preConvert was easy to miss;
folding it into preConvert keeps the vuls2-shape ScanResult
construction in one place. No behavioral change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector): defer the NVD strip to just before confidence selection
The early strip forced a hadNvd flag to preserve the JVN-advisory gate,
and every future source migration to vuls2 would add another had* flag.
Stripping just before getMaxConfidence is equivalent — the skip gate
already lists the dictionary-remaining sources explicitly and the
advisory synthesis never reads Nvds — and lets the JVN gate read the
original detection as plain !detail.HasNvd(). No behavioral change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): align the vendor:product fallback with go-cve-dictionary's match()
The fallback reported every index-matched scanned CPE whose condition
had no accepted criterion — including CVEs whose range CONFIRMS the
scanned version is out of range, which go-cve-dictionary never
reported. On a linux_kernel 5.15.10 scan this more than doubled the
CVE count with score-10 noise.
Gate each part:vendor:product-matched criterion the way
go-cve-dictionary's match() (db/db.go) does:
- query version ANY/NA, or criterion version NA -> report (the classic
VendorProductMatch: no version information to compare)
- concrete criterion version differing from the query -> drop
- range present: evaluate the bounds with the range's comparator; an
incomparable pair (e.g. semver bound vs "21.4r3") is re-tried with
RPM-style comparison — gocve's matchRpmVer fallback, false-positive
tolerance included — and a confirmed out-of-range drops the criterion
- CPEMatches-only criterion whose enumeration missed the query -> drop
The retired RoughVersionMatch tier is thereby absorbed at score 10:
ranges that only RPM comparison can place in-range (juniper junos
21.4r3 < 22.2) still surface, while confirmed misses stay silent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): gofmt rangeVendorProductEligible bounds table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): extract Exploit / Mitigation dedup-append helpers
The natural-key dedup closures were copy-pasted between the
ScannedCves merge and mergeVulnInfo; pull them into
appendMissingExploits / appendMissingMitigations with a comment on why
the identity keys are (ExploitType, URL, ID) / (CveContentType, URL,
Mitigation) rather than full-struct equality (models.Exploit has no
Equal, and enrichment-only field differences must not defeat dedup).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: follow models conventions for merge helpers and split preConvert
Review follow-ups on the vuls2 bridge plumbing:
- models: add Exploits / Mitigations named slice types with
AppendIfMissing, following the Confidences / DistroAdvisories
convention, and use them on VulnInfo. Identity is the natural key
((ExploitType, URL, ID) / (CveContentType, URL, Mitigation));
enrichment-only field differences do not create new entries. The
vuls2 merge paths drop their hand-rolled dedup closures.
- vuls2: split preConvert into preConvertPkgs / preConvertCPEs over a
shared preConvertBase, and have detectWith take the converted
ScanResult instead of (cpeURIs, cpeOnly) — the two call paths are
packages-only and CPEs-only, so the mode is now explicit in the
function names and invalid combinations are unrepresentable.
- vuls2: make the FS->original CPE reverse map multi-valued. The map
exists to restore the user's input in VulnInfo.CpeURIs; a user
listing the same CPE in both URI and FS form now gets both back
(matching classic per-input go-cve-dictionary behaviour) while the
detection list stays deduped by FS form.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): rename isVulnerableTrue / hasVulnerableTrueCriterion
The True suffix was redundant for boolean predicates; isVulnerable /
hasVulnerableCriterion read naturally at the call sites.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): fold the vendor:product confidence pick into toVuls0Confidence
The caller-side branch between toVuls0Confidence and
toVuls0VendorProductConfidence forced every call site to know the
fallback rule. Pass the sourceData instead and let toVuls0Confidence
decide within its cpe-ecosystem branch — the only place the
vendor:product fallback exists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): drop redundant signal checks in the cpe confidence pick
Inside the cpe-ecosystem branch, cpes and vpCpes are the only
detection signals a source can carry — packStatuses / kbIDs come from
Version / KB criteria, which never appear under this ecosystem.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): report unrestricted-criterion accepts at VendorProductMatch
cpecriterion.Accept accepts on attribute match alone when a criterion
carries no narrowing (no Range, no CPEMatches), and short-circuits
version=NA criteria before any version check. Both accepts say nothing
about the scanned version, yet the bridge reported them at
ExactVersionMatch (score 100). JVN CPE data is mostly such version=*
criteria, so this mattered ahead of the JVN migration; NVD version=NA
criteria (junos:-) hit the same path today.
Classify accepted criteria in walkCriteria: an accept by a criterion
whose version is NA, or ANY with neither Range nor CPEMatches, lands
in vpCpes and surfaces as *VendorProductMatch (score 10) — matching
go-cve-dictionary, which never rated no-version-information matches
higher than VendorProductMatch. Restricted accepts keep
ExactVersionMatch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(models): let a verified exploit replace its unverified duplicate
Exploits.AppendIfMissing kept first-wins for same-key entries, which
discarded the Verified signal when the verified report arrived second.
Replace the existing entry when the incoming one is verified and the
existing one is not; every other duplicate still keeps the first
entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(models): rename Exploits.AppendIfMissing to AppendOrReplace
The method no longer shares AppendIfMissing's pure semantics — a
verified incoming entry replaces an unverified duplicate — so sharing
the name with Confidences / DistroAdvisories / Mitigations invited
wrong assumptions about its behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(models): update missed AppendOrReplace call in test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector): drop the appendApple closure
After the cpeURIs/cpes dual bookkeeping went away the closure wrapped
a single append. Let the repository switch pick the CPE product names
and build the synthesised Apple entries in one place instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(detector): slim the DetectCpeURIsCves comments
Describe the pipeline as "migrated sources (currently NVD) via vuls2,
the rest via go-cve-dictionary" so source migrations don't keep
rewriting an explicit list, and stop over-explaining UseJVN — the flag
behaves as its name says.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(models): start AppendOrReplace comment with the method name
revive (exported) flags a doc comment that still opens with the old
AppendIfMissing name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): align cpecriterion import aliases with ccTypes convention
vuls2 / vuls-data-update consistently alias the cpecriterion packages
as ccTypes / ccRangeTypes; follow suit instead of the longer
cpecriterionTypes / cpecRangeTypes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): simplify the cross-pass CveContents / CpeURIs merge
SourceLink is no dedup identity — vuls2 derives it from just the
content type and CVE ID, so within one VulnInfo it carries no more
information than the CveContents key itself. And today the two detect
passes never produce the same content type (distro types vs nvd), nor
does the package pass produce CpeURIs, so plain appending cannot
duplicate anything. Leave a comment that an overlapping future source
will need a smarter merge instead of pretending this one existed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): inline toFSCPEs into preConvertCPEs
The helper had become the body of its only caller. Also refresh the
doc comment: the "first user-supplied form wins" note predated the
multi-valued reverse map.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): propagate CPE unbind errors instead of warn-and-skip
Config-sourced CPEs are already validated and normalised at
config-load time (tomlloader's toCpeURI fails the load on a malformed
entry), so an unbind failure in preConvertCPEs means an unvalidated
input — a library caller's CPE or an OWASP DC entry. Silently
detecting nothing for that CPE hid the problem; return the error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): make detect() inputs exclusive and drop addDetection
DetectPkgs and DetectCPEs feed detect() exclusive inputs (packages/KB
vs CPE list), so the per-RootID accumulator that merged "both
detection paths" modeled a case that cannot happen. Error out if both
inputs arrive (a caller bypassed the entry points), pick the one
detector to run, and build the per-RootID data directly.
Also drop the §N comment markers — they pointed at section numbers of
a local working document that is not part of this repository.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(vuls2): explain the no-signal gate ahead of the VP fallback
The condition doubles as the m-registration gate: an empty sourceData
entry would make the downstream walk emit contents for an undetected
CVE. Spell that out instead of only describing the fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): cover the no-signal gate with an all-definitive-miss case
The unsatisfied-AND case exercises the gate via AND veto; add the
minimal OR form — a concrete-version mismatch and a confirmed
out-of-range criterion — asserting the index-reached CVE is not
reported at all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): evaluate cpe conditions in one raw-tree walk
CPE evaluation was spread over three places: pruneCriteria (AND/OR
gate with a cpe-AND relax), walkCriteria (accept -> exact/vp tiers)
and vendorProductCPEs (a second AND/OR implementation over the raw
tree for the fallback). The duplicated AND/OR semantics already bred
one bug — the fallback initially ignored AND structure.
Fold all of it into walkCPECriteria: one AND/OR-aware walk over the
RAW FilteredCriteria that classifies each vulnerable=true CPE
criterion (accepted+restricted -> exact tier, accepted+unrestricted ->
vendor:product tier, not-accepted-but-eligible -> vendor:product tier,
definitive miss -> unsatisfied) and demotes a conjunction to the
weaker tier when one leg only holds at vendor:product level — the
same outcome the prune-then-fallback pair produced implicitly.
pruneCriteria and walkCriteria return to their package/KB-only master
shape (no ecosystem relax, no CPE criterion handling), shrinking the
diff on the OS-package path; vendorProductCPEs, versionUnrestricted's
unbind and the isVulnerable/hasVulnerableCriterion helpers dissolve
into the new walk. All existing postConvert CPE cases pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Revert "refactor(detector): drop the appendApple closure"
This reverts commit c5b7860af356c6acceca4aa997eb0682dc714550.
* refactor(detector): restore the Apple CPE block to its master form
The synthesised-Apple-CPE switch, the OWASP parse block and the
cpeURIs->cpes conversion loop now match master byte for byte (the
appendApple closure and the formatting changes were churn this PR does
not need), and the DetectCpeURIsCves error message goes back to
reporting cpeURIs. The Cpe struct comment also returns to its master
text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): wrap the detector selection error in detect()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): narrow the per-RootID fetch again and group pkg/KB fields first
- detect(): fetch Vulnerability/Advisory data narrowed to the
detecting ecosystem / datasources, as master always did. The
unfiltered fetch existed for the era when one detect() run merged
both detection paths per RootID; with exclusive inputs that case is
structurally gone, and pulling other ecosystems' contents is
EnrichVulnInfos' job. Folding the fetch into the detections loop
also drops the second pass.
- Order the detection-signal fields and their handling consistently as
packages/KB first, CPE tiers last (sourceData, the affected
aggregate, the walk return values and the postConvert blocks), and
refresh the vpCpes comment — unrestricted accepts land there too,
not only the no-accept fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): rename walkCriteria to walkPkgCriteria
It is package/KB-only since CPE evaluation moved to walkCPECriteria;
name the pair symmetrically (Pkg covers KB, as in DetectPkgs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): name the AND case explicitly in walkCPECriteria
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): pin the CPE confidence-tier rules as unit-test tables
go-cve-dictionary will be archived once the migration completes, so
the rules walkCPECriteria replicates from its match() should be
readable here rather than in retired source. Test_walkCPECriteria
documents one rule per case (exact / vendor:product / definitive-miss
tiers plus the AND/OR structure and demotion), and
Test_rangeVendorProductEligible pins the bound-by-bound evaluation
including the RPM-comparison fallback for incomparable versions.
walkCPECriteria now returns nil instead of empty slices for empty
tiers, matching the package convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): fold the CPE rule tables into vuls2_test.go
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): dedup CpeURIs against the go-cve-dictionary pass
The plain append was justified by the two vuls2 passes being unable to
both produce CpeURIs, but the go-cve-dictionary pass can already have
registered the same user-supplied CPE on the same CVE — a CVE covered
by both NVD and a vendor advisory source. Restore the
contains-guarded append and state the actual reason.
Also align the preConvertCPEs doc comment with the error-returning
behaviour (the drop-with-warning wording predated it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): merge WindowsKBFixedIns in the ScannedCves merge
postConvert fills WindowsKBFixedIns for Microsoft detections, but the
detectWith merge dropped it when the CVE already existed in
ScannedCves — a CVE first registered by the CPE pass, or present in a
caller-provided result, lost its KB numbers. The report flow happened
to survive because the package/KB pass runs first and takes the
not-found branch. Every other field postConvert produces was already
merged; the remaining VulnInfo fields are owned by enrichment / other
detectors and never appear in vuls2 output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): extract and cover the ScannedCves merge
Pull the detectWith merge loop into mergeIntoScannedCves so it is unit
testable without a DB session, and pin its semantics:
a new CVE registers as-is, and merging into an already-registered CVE
appends packages, dedups KB numbers (the WindowsKBFixedIns regression),
append-if-missing advisories / confidences / mitigations, lets a
verified exploit replace its unverified duplicate, initializes a nil
CveContents map, and dedups CpeURIs against the go-cve-dictionary
pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(vuls2): contrast the two merge functions in their comments
mergeIntoScannedCves (across passes/sources, dedup-append only, no
vuls2-marker assumptions, carries the aggregate fields) vs
mergeVulnInfo (within one run, vuls2-produced inputs only, resolves
same-type content conflicts by vuls2-sources rank, content fields
only) — the distinction was not visible at a glance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(vuls2): explain what detectableCveIDs gates
Its only consumer — the contains filter in postConvert's final loop —
was hard to spot, and the why (content-driven vulninfos vs
signal-backed CVEs, comparePack's winner selection) was undocumented.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): rename attrEqual to pvpEqual
It compares exactly part:vendor:product, not attributes in general.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): rename consider to foldChild in walkCPECriteria
The closure folds a child's evaluation into the parent node under the
AND/OR semantics (adopt on OR, veto on AND) — "consider" undersold
that it decides.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): split walkCPECriteria into a prune pass and a plain walk
The single traversal carried a third truth value — relevant — to keep
nodes without any evaluatable criterion (guard-only subtrees, other
criterion types) from vetoing an AND, which made the walk harder to
follow than its job warrants. Remove the unevaluatable parts up front
in pruneCPECriteria (and any criterias they leave empty), so the
collecting walk needs only two-valued AND/OR logic and the top-level
"nothing to evaluate" case becomes an emptiness check.
Two table cases pin the emptied-node handling: a guard-only subtree
vanishing from its AND parent, and a tree emptying out entirely. The
remaining 17 cases pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): move the prune call inside walkPkgCriteria
The CPE path prunes inside walkCPECriteria while the package path
pruned at the call site — unify on prune-inside-walk, since
pruneCriteria has no other caller. walkPkgCriteria becomes the
non-recursive entry (prune, walk, drop the internal ignore flag) over
the recursive walkPrunedPkgCriteria, and the dispatch in
walkVulnerabilityDetections reads symmetrically for both ecosystems.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): split Test_preConvert to mirror the preConvert split
Test_preConvertPkgs keeps the package/family-mapping cases;
Test_preConvertCPEs takes the suppression case and gains coverage the
old test never had — the URI->FS conversion with FS passthrough, the
multi-valued reverse map keeping every user-supplied form, and the
error on an unparseable input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): split the mixed pkg+cpe postConvert case
detect() feeds postConvert exclusive inputs since the entry points
were separated, so "redhat vex + epel + cpe" exercised a DetectResult
that can no longer occur. Split it into "redhat vex + epel" (the
package merge and advisory handling) and "cpe exact accept with
exploit/mitigation lift" (NVD content mapping and multi-form CpeURIs
restoration); the cross-pass confluence the mixed case used to cover
lives in Test_mergeIntoScannedCves now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: group table-test inputs into args structs
Follow the file convention in the recently added tables
(Test_mergeIntoScannedCves, Test_walkCPECriteria,
Test_rangeVendorProductEligible, TestExploits_AppendOrReplace), and
shape Test_mergeIntoScannedCves's args after the function signature
itself (r models.ScanResult, vulnInfos models.VulnInfos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): write Test_walkCPECriteria fixtures out explicitly
Drop the cn/guard/or/and/semverLT helpers and the CPE string
constants in favour of fully spelled-out literals, matching how the
rest of this file (Test_postConvert and friends) writes fixtures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): lift section headers out of the test-case literals
They categorize runs of cases, not the single case they sat in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): version-less queries stay at VendorProductMatch on accept
cpecriterion.Accept short-circuits an ANY/NA-version query to true
before any version check, so a scan CPE without a version accepted
even against a concrete-version criterion — and the bridge then
reported ExactVersionMatch for a match that never confirmed a
version. go-cve-dictionary rated every no-version-information query
VendorProductMatch (match() rule 2).
Classify accepted indexes per scanned CPE: a version-less query lands
in the vendor:product tier alongside unrestricted-criterion accepts.
The "no accept, query without version" table case modeled a state the
real Accept never produces; it becomes "accepted with a version-less
query" with the accept the detect step would actually emit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): make the CPE confidence tiers exclusive per scanned CPE
A CPE confirmed at exact level by one OR leg also surfaced in the
vendor:product tier via another leg. Downstream already resolved that
in favour of exact (toVuls0Confidence prefers cpes, the CpeURIs fill
ignores vpCpes when exact ones exist), so the duplicate carried no
information — drop it at the walkCPECriteria boundary and state the
exclusiveness as part of the contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): align the OR(exact, vendor:product) case with tier exclusiveness
d20bbf7 made the tiers exclusive per scanned CPE but missed the case
update; the CPE now reports in the exact tier only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): gofmt -s the generated walkCPECriteria fixtures
Drop the redundant element-type names inside slice literals the
fixture generation left behind.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(vuls2): expand the rangeVendorProductEligible cases to multi-line
The one-liner table rows were dense to the point of unreadable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(vuls2): move the cpe postConvert cases below the master ones
They were interleaved before "redhat ignore pattern", shifting every
pre-existing case in the diff against master; appending them after the
microsoft KB cases cuts the test-file diff from ~9.5k to ~2.7k lines
without changing any case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(vuls2): rename pruneCriteria and fold the pkg walk recursion inside
prunePkgCriteria pairs with pruneCPECriteria now that both paths own a
prune, and the recursive tree walk becomes a closure inside
walkPkgCriteria — the same shape as walkCPECriteria, without exposing
the walkPrunedPkgCriteria intermediate (whose internal ignore flag
never left the function anyway). Test_prunePkgCriteria keeps pinning
the accepts-gate semantics directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(vuls2): record why RoughVersionMatch is retired at the cpe branch
The two-tier design and the reasoning (RPM-fallback fuzziness, score
80 slipping past low-confidence cutoffs, cpematch expansion making
true exact matches the norm) lived only in review threads and external
notes; pin it where the confidence is picked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(vuls2): name the detection pass in the per-run log line
detectWith runs once for packages and once for CPEs in a report run;
two identical "N CVEs are detected with vuls2" lines were
indistinguishable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector): initialize a nil ScannedCves map at the library entry 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>
* fix(detector): EUVD/MITRE contents do not keep a dictionary detail alive
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>
* fix(vuls2): version=* criterion is an all-versions exact match
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>
* docs(vuls2): mark the exact-tier JVN branch as currently unreachable
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>
* fix(vuls2): wrap the recursion error in walkCPECriteria
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>
* fix(vuls2): match the cpe recursion wrap message to the pkg pattern
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>
* fix(vuls2): wrap the walk error at the caller, matching master
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>
* fix(vuls2): wrap the remaining bare walk error returns
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>
* refactor(models): make Exploits dedup a plain AppendIfMissing
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>
* test(models): add symmetric coverage for Mitigations.AppendIfMissing
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>
* fix(detector): dedup NVD content when vuls2 already supplied it; gofmt 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>
* refactor(vuls2): drop the RPM-comparison fallback from CPE matching
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>
* refactor(detector/vuls2): project CPE match quality instead of re-deriving 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>
* refactor(detector/vuls2): tidy CPE projection helpers and naming
Follow-up cleanup now that exact/vendor:product categorization moved
into vuls-data-update and the RPM fallback is gone:
- unify the three dedup shapes (walkCPECriteria closure, the manual
slices.Contains loop in walkVulnerabilityDetections, and the three
near-identical blocks in postConvert) into one appendMissing helper;
let postConvert own accumulation-dedup so walkVulnerabilityDetections
appends uniformly
- rename the sourceData/affected cpes field to exactCpes for symmetry
with vpCpes, making the two-tier model self-documenting
- refresh stale comments that still described the removed
vendorProductEligible / RPM "fallback"; vpCpes is now simply the
VersionUnconfirmed accepts
Behavior-preserving: detector/models tests pass, gofmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): dedup CveContents on cross-pass merge
mergeIntoScannedCves dedups every merged field (WindowsKBFixedIns,
DistroAdvisories, Confidences, CpeURIs, Exploits, Mitigations) except
CveContents, which plain-appended. The two vuls2 passes can never
produce the same content type, but the function also merges across a
go-cve-dictionary pass and caller-provided ScannedCves — now that vuls2
emits Nvd/Jvn-typed contents, such a base can already carry the same
type for a CVE, duplicating it into repeated sources/links in reports.
Dedup identical entries on append (full-content equality via
reflect.DeepEqual; SourceLink is not an identity). Genuinely different
contents of the same type are still both kept — picking a winner
between conflicting sources stays out of scope here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector/vuls2): dedup CveContents by key, not reflect.DeepEqual
Avoid reflect in this production merge path. Compare on the
(type, CVE, source link) identity instead of full-content
reflect.DeepEqual, mirroring the key-based dedup the sibling merges
(Exploits, Mitigations, Confidences) already use.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(detector/vuls2): dedup AffectedPackages by name on cross-pass merge
mergeIntoScannedCves blindly appended vi.AffectedPackages into the base
VulnInfo, so a base already carrying package statuses — a caller-
provided ScannedCves, or a prior detection pass — accumulated duplicate
or contradictory rows for the same package.
Use PackageFixStatuses.Store(), the type's own insert-or-update-by-name
helper (last write wins), matching the key-based dedup the other merged
fields use.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* build(deps): bump vuls2 and vuls-data-update to merged commits
Point at the merged dependency chain now that the upstream PRs landed:
- vuls2 -> v0.0.1-alpha.0.20260619063813-98ac8e5258eb (nightly, #388)
- vuls-data-update -> v0.0.0-20260618100055-bfedbc246360 (#850),
matching the version vuls2's nightly pins
Standalone (GOWORK=off) build and detector/models tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector): source NVD CveContent from vuls2 enrich, not go-cve-dictionary
Move NVD enrichment to the vuls2 path so NVD content comes from one
place (the vuls2 DB), matching how NVD CPE detection already works:
- vuls2 enrich(): add NVDFeedCVEv2 to the enrichment DataSources.
- enrichNVD(): build CveContent[nvd] (+ NVD exploits/mitigations),
mirroring the NVD branch of the CPE detection path; guarded so it does
not duplicate an NVD-detected CVE's existing entry.
- FillCvesWithGoCVEDictionary: drop NVD CveContent and NVD-derived
exploits/mitigations (kept VulnCheck/JVN/EUVD/Fortinet/MITRE/Paloalto/
Cisco, and NVD cert alerts which vuls2 does not provide).
Verified against a locally-built NVD vuls2 DB (db add nvd-feed-cve-v2):
with an empty go-cve-dictionary, NVD CPE detection still finds CVEs and
both detected and cross-source-enriched CVEs carry a single nvd
CveContent — confirming NVD now flows entirely through vuls2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(detector): derive US-CERT alerts in vuls2 enrich, not go-cve-dictionary
US-CERT alerts are just NVD references whose URL contains "us-cert", so
they belong with the migrated NVD content. enrichNVD now derives
AlertDict.USCERT from those references (Title "US-CERT-<id>", matching
go-cve-dictionary's NvdCert), done regardless of whether the CPE
detection path already filled the nvd CveContent (that path does not
populate AlertDict).
FillCvesWithGoCVEDictionary stops filling US-CERT and now sets only
AlertDict.JPCERT, preserving the USCERT the vuls2 enrich pass set
earlier. JP-CERT stays on the go-cve-dictionary path because it derives
from JVN, which is not migrated.
Verified against the locally-built NVD vuls2 DB: enrich of
CVE-2021-32951 yields USCERT "US-CERT-icsa-21-229-02" with empty
go-cve-dictionary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(detector/vuls2): cover enrichNVD in the enrich path
Add an nvd-feed-cve-v2 enrich fixture (CVE-2014-0160) and two Test_enrich
cases, exercising enrichNVD through the enrich entry point — previously
only the detection-time postConvert path touched NVD content assembly:
- hasContent == false: NVDFeedCVEv2 -> enrichNVD dispatch generates the
nvd CveContent, NVD Exploit and Mitigation, and appends a US-CERT
alert from a us-cert reference URL.
- hasContent == true: a pre-existing nvd CveContent (as the CPE
detection path leaves it) is preserved — no duplicate/overwrite, no
exploits/mitigations re-added — while US-CERT is still derived.
Locks in both behaviors of the go-cve-dictionary -> vuls2 NVD migration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: empty commit to re-trigger CI
CI did not fire for the previous pushes (Actions stopped registering
pull_request events repo-wide after 2026-06-22T00:11). Re-trigger.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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 did you implement:
Supported AmazonLinux for server mode. Content-Type "application/json" was already implemented.
I implemented Content-Type "plain / text".
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Test Conent-Type text/plain
Test Conent-Type application/json
Test ssh mode with EC2 instance
Checklist:
You don't have to satisfy all of the following.
make fmtmake testIs this ready for review?: YES