Skip to content

refactor: replace hashicorp/go-version with Masterminds/semver in CVE matcher - #22013

Draft
davdhacs wants to merge 2 commits into
masterfrom
davdhacs/remove-go-version
Draft

refactor: replace hashicorp/go-version with Masterminds/semver in CVE matcher#22013
davdhacs wants to merge 2 commits into
masterfrom
davdhacs/remove-go-version

Conversation

@davdhacs

Copy link
Copy Markdown
Contributor

Description

Replace hashicorp/go-version with Masterminds/semver (v1, already in go.mod) in the CVE matcher. This consolidates two version-parsing libraries down to one.

Beyond the dep swap, this change fixes several pre-existing issues found during a three-pass code review cycle:

Bug fixes:

  • getBaseVersion used fragile strings.ReplaceAll(v.String(), "-"+prerelease, "") which could corrupt versions where the prerelease string appears in build metadata (e.g., 1.2.3-rc1+meta-rc1). Rewritten to use Major()/Minor()/Patch() — structurally correct, impossible to corrupt.
  • Fixed latent false-positive bug: if all constraint strings failed to parse, the old code's empty constraint loop evaluated to true (vacuous truth), incorrectly reporting a CVE match. The new code correctly returns no-match. This is security-critical code — a false positive means flagging a cluster as vulnerable when it isn't.
  • Fixed regex [v|V][vV] — the | inside a character class matched a literal pipe character.

Simplifications:

  • Inlined matchBaseVersion and matchExactVersion into MatchVersions — they were thin wrappers that re-parsed an already-parsed version string.
  • Removed getConstraints helper — replaced with direct semver.NewConstraint(strings.Join(parts, ", ")).
  • Changed getBaseVersion return type from (*Version, error) to *Version — the error was impossible (fmt.Sprintf("%d.%d.%d") with non-negative integers always produces valid semver).

Other:

  • Fixed typo: "funcitonality""functionality"
  • Rewrote TestMatchVersions to test through the public MatchVersions API instead of deleted internal helpers.

hashicorp/go-version demoted from direct to indirect (4 transitive deps still pull it in). Reviewed through a three-pass review cycle with convergence at 99/100.

User-facing documentation

  • CHANGELOG.md is updated OR update is not needed
  • documentation PR is created and is linked above OR is not needed

No user-facing behavior changes. The false-positive bug fix is a correctness improvement but was latent (unreachable with valid NVD data due to an upstream guard).

Testing and quality

  • the change is production ready: the change is GA, or otherwise the functionality is gated by a feature flag
  • CI results are inspected

Automated testing

  • modified existing tests

How I validated my change

  • go build ./central/cve/matcher/... passes
  • go test ./central/cve/matcher/... — all 10 test cases pass
  • Verified Masterminds/semver produces identical results for all version string patterns in the test data (standard semver, v-prefixed, prerelease, build metadata, GKE-style, 2-segment)
  • go mod tidy confirms hashicorp/go-version demoted to indirect

davdhacs and others added 2 commits July 29, 2026 22:01
… matcher

Replace hashicorp/go-version with Masterminds/semver (already in go.mod)
for version parsing and constraint checking in the CVE matcher.

Key changes:
- Use semver.NewVersion / semver.NewConstraint for all version operations
- Rewrite getBaseVersion to use Major()/Minor()/Patch() instead of fragile
  strings.ReplaceAll on the prerelease suffix; return *semver.Version (no
  error -- three non-negative integers always produce valid semver)
- Inline matchBaseVersion/matchExactVersion into MatchVersions -- they
  re-parsed already-parsed versions and added naming without semantic weight
- Remove getConstraints helper; use semver.NewConstraint(strings.Join(...))
  directly, which fixes a latent false-positive bug: the old code AND'd
  individually-parsed constraints in a loop, so if all parses failed the
  empty slice defaulted to true
- Fix regex bug: [v|V] -> [vV] (inside a character class, | is literal)
- Fix typo: "funcitonality" -> "functionality"
- Update TestMatchVersions to test through MatchVersions (the removed
  helpers were internal; integration tests already cover the same paths)

Generated with AI assistance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hashicorp/go-version is no longer imported directly by any StackRox code
after the CVE matcher migration to Masterminds/semver. It remains as an
indirect dependency via stackrox/istio-cves, stackrox/k8s-cves, and
stackrox/k8s-overlay-patch.

Generated with AI assistance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@openshift-ci

openshift-ci Bot commented Jul 30, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.83333% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.19%. Comparing base (fb7220b) to head (567363f).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
central/cve/matcher/matcher.go 70.83% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #22013      +/-   ##
==========================================
- Coverage   51.23%   51.19%   -0.04%     
==========================================
  Files        2864     2864              
  Lines      178713   178681      -32     
==========================================
- Hits        91560    91480      -80     
- Misses      79117    79155      +38     
- Partials     8036     8046      +10     
Flag Coverage Δ
go-unit-tests 51.19% <70.83%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Build Images Ready

Images are ready for commit 567363f. To use with deploy scripts:

export MAIN_IMAGE_TAG=4.12.x-622-g567363f558

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved CVE version matching for exact versions, version ranges, prerelease versions, and build metadata.
    • Corrected recognition of GKE and EKS versions with optional leading v or V.
    • Improved base-version matching for wildcard version rules.
  • Tests

    • Expanded coverage for exact, base, prerelease, and build metadata version matching.

Walkthrough

The CVE matcher now uses Masterminds semver for exact, base-version, and range comparisons. GKE/EKS version regexes are corrected, tests invoke the public matching API, and go-version is classified as an indirect dependency.

Changes

CVE version matching

Layer / File(s) Summary
Semver matcher implementation
central/cve/matcher/matcher.go, go.mod
MatchVersions uses semver parsing, equality, base-version comparisons, and combined range constraints; GKE/EKS regexes and dependency classification are updated.
Public API matching tests
central/cve/matcher/matcher_test.go
Table-driven tests exercise CVEMatcher.MatchVersions for wildcard, exact, prerelease, and build-version cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: janisz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: swapping the CVE matcher from hashicorp/go-version to Masterminds/semver.
Description check ✅ Passed The description covers the required sections and includes detailed change, documentation, testing, and validation notes; only CI inspection is left unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch davdhacs/remove-go-version

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
central/cve/matcher/matcher_test.go (1)

82-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for the range/"block of versions" branch.

All 8 cases exercise only the single-version path (cpeUpdate = * or a specific prerelease string). None set VersionStartIncluding/VersionEndIncluding/VersionEndExcluding, so the range-constraint logic (matcher.go lines 273-304) — including the PR's headline fix for false positives when all constraints fail to parse — has zero coverage in this suite.

Consider adding cases with VersionStartIncluding/VersionEndExcluding set, plus a case where the range bounds are unparseable, to lock in the described false-positive fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@central/cve/matcher/matcher_test.go` around lines 82 - 130, The MatchVersions
test table currently covers only single-version matching and not the
range-constraint branch. Extend the cases in the visible MatchVersions test to
populate VersionStartIncluding/VersionEndIncluding/VersionEndExcluding for valid
ranges, and add a case with unparseable range bounds that expects no match,
covering the false-positive fix when all constraints fail to parse.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@central/cve/matcher/matcher.go`:
- Line 247: Update the CPE version-range handling around the existing AllEmpty
guard and constraint parts construction to include VersionStartExcluding
alongside the other three range fields. Ensure entries with only
VersionStartExcluding use the range branch, and preserve that exclusive
lower-bound value when building parts for the generated constraint.

---

Nitpick comments:
In `@central/cve/matcher/matcher_test.go`:
- Around line 82-130: The MatchVersions test table currently covers only
single-version matching and not the range-constraint branch. Extend the cases in
the visible MatchVersions test to populate
VersionStartIncluding/VersionEndIncluding/VersionEndExcluding for valid ranges,
and add a case with unparseable range bounds that expects no match, covering the
false-positive fix when all constraints fail to parse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 78163a30-805a-40c4-a560-586edc85d4a1

📥 Commits

Reviewing files that changed from the base of the PR and between 0b71b54 and 567363f.

📒 Files selected for processing (3)
  • central/cve/matcher/matcher.go
  • central/cve/matcher/matcher_test.go
  • go.mod

// Note that cpeVersionAndUpdate can't be "*:*" in this case, since there is no info about start and end versions
// This is the case where there is just one version so check against it.
// Note that cpeVersionAndUpdate can't be "*:*" in this case, since there is no info about start and end versions.
if stringutils.AllEmpty(cpeMatch.VersionStartIncluding, cpeMatch.VersionEndIncluding, cpeMatch.VersionEndExcluding) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

VersionStartExcluding is never checked, causing mis-classification and missing lower bound.

The NVD NVDCVEFeedJSON10DefCPEMatch schema has four range fields: VersionStartIncluding, VersionStartExcluding, VersionEndIncluding, VersionEndExcluding. This code only inspects three of them:

  • Line 247's stringutils.AllEmpty(...) guard omits VersionStartExcluding, so a CPE entry that sets only VersionStartExcluding (a legitimate, documented NVD pattern) is incorrectly routed into the "single version" branch instead of the range branch.
  • Lines 279-289 build constraint parts from only VersionStartIncluding/VersionEndIncluding/VersionEndExcluding, so even when the range branch is correctly reached (because another field is also set), an exclusive lower bound is silently dropped from the constraint, widening the match beyond what the CVE data specifies.

This can cause both false negatives (single-version branch applied to what's actually a range) and false positives (missing lower-bound exclusion), which directly affects CVE-to-cluster/image matching correctness.

🐛 Proposed fix
-		if stringutils.AllEmpty(cpeMatch.VersionStartIncluding, cpeMatch.VersionEndIncluding, cpeMatch.VersionEndExcluding) {
+		if stringutils.AllEmpty(cpeMatch.VersionStartIncluding, cpeMatch.VersionStartExcluding, cpeMatch.VersionEndIncluding, cpeMatch.VersionEndExcluding) {
 			var parts []string
 			if cpeMatch.VersionStartIncluding != "" {
 				parts = append(parts, fmt.Sprintf(">= %s", cpeMatch.VersionStartIncluding))
 			}
+
+			if cpeMatch.VersionStartExcluding != "" {
+				parts = append(parts, fmt.Sprintf("> %s", cpeMatch.VersionStartExcluding))
+			}

Also applies to: 273-289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@central/cve/matcher/matcher.go` at line 247, Update the CPE version-range
handling around the existing AllEmpty guard and constraint parts construction to
include VersionStartExcluding alongside the other three range fields. Ensure
entries with only VersionStartExcluding use the range branch, and preserve that
exclusive lower-bound value when building parts for the generated constraint.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant