Skip to content

refactor(vulnfeeds): utilise a strategy pattern on a CNA basis to reduce carveout logic - #5971

Open
jess-lowe wants to merge 16 commits into
google:masterfrom
jess-lowe:refactor/strategies
Open

jess-lowe wants to merge 16 commits into
google:masterfrom
jess-lowe:refactor/strategies

Conversation

@jess-lowe

@jess-lowe jess-lowe commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Refactors CVE5 version extraction from nested fallback branches into a modular, ordered strategy pipeline under conversion/cve5/strategies. This will be so much easier to extend as we get better insights on CNA quirks.

  • Unified VersionStrategy & ExtractionState Contract: Introduced VersionStrategy operating on ExtractionState, which wraps an Affected block with a per-entry consumed bitmap and accumulated ranges. This allows whole-block (EmitAll), multi-entry (Emit(ranges, i, i+1)), and single-entry (ExtractPerVersion) strategies to compose seamlessly within a single ordered pipeline (ExtractAffectedRanges) and CNA-specific presets (Default(), GitHub(), MITRE(), Linux()).
  • Extracted Strategies (conversion/cve5/strategies/): Isolated modular strategies into dedicated files for inverse affected ranges (defaultStatus == "affected"), changes[] blocks, standard bounds, split/unspecified range pairs, string range expressions, git commit hashes (full & short SHAs), inline CPE strings, free-text version extraction, single-version heuristics, and CVE-level CPE applicability (cpeApplicability & affected[].cpes).
  • Extractor & Range Type Simplification: Removed VersionRangeType tallying and FindNormalAffectedRanges from VersionExtractor, unifying Linux and default extraction through ExtractAffectedRanges and inspecting osvschema.Range_GIT directly on extracted ranges.
  • Direct Git Ranges & Reference Commit Fallbacks: Bypass tag-to-commit normalization when ranges already contain full or short hex commit SHAs via IsDirectGitRange, and fall back to extracting fix/introduced commits from CVE references (ExtractCommitsFromRefs) when structured ranges cannot be resolved.
  • Tag Resolution Deduplication & Early Exit: Added a per-repository version-to-commit cache in ProcessRanges to avoid redundant tag lookups across strategies, and exit early to database_specific.unresolved_ranges when no repositories are available.
  • Tests: Added unit tests across conversion/cve5/strategies covering individual strategies, ExtractionState consumption semantics, CNA preset ordering, and git/split/inverse ranges.

@another-rex another-rex 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.

I like the refactor! I think we might need to adjust it a bit though so we're not dealing with all these manual checks within each strategy.

Comment thread vulnfeeds/conversion/cve5/version_extraction_test.go Outdated
Name() string
// Extract attempts to extract OSV version ranges from a CVE5 Versions entry.
// Returns the extracted ranges, the detected VersionRangeType, and true if this strategy handled the entry.
Extract(vers models.Versions, affected models.Affected, metrics *models.ConversionMetrics) (ranges []models.RangeWithMetadata, vrt VersionRangeType, handled bool)

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.

I like the strategy system, but the contract seems to be in the wrong place?

I.e. it looks like you are being called per version, but you also have strategies that extract at the "affected" level.

So you end up with the same thing extracted multiple times (e.g. if versions extraction always fails, but CPE extraction succeeds).

jess-lowe and others added 5 commits September 21, 2026 03:06
…ted-level contract

Refactor VersionStrategy from a per-version interface with numeric priorities to a slice-ordered, Affected-level contract using ExtractionState to atomically track consumed version entries and prevent duplicate extractions. Also unify CPE fallback extraction across cpeApplicability and affected[].cpes, memoize per-repo version tag resolution in ProcessRanges, and fix Linux conversion outcome counting in AddAffected.

@another-rex another-rex 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.

Nice, looking great! Some minor comments, otherwise LGTM

}

func (s *ZeroIntroducedSingleVersionStrategy) Extract(state *ExtractionState, metrics *models.ConversionMetrics) {
if len(state.Affected.Versions) != 1 || state.IsConsumed(0) {

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.

Quick question, this is specifically checking for a single version field right? I.e. if we have a range, and then a single version field after that, we specifically don't want to interpret that as 0..version

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes - the check on line 29 is checking there's only one range/version and that its not already consumed. If affected.versions contains a range followed by a single version (or multiple discrete versions like ["3.0", "3.1"]), ZeroIntroducedSingleVersionStrategy is skipped, the range is consumed by StandardRangeStrategy, and the remaining single version falls through to StandaloneSingleVersionStrategy, which treats it as an exact point (introduced: v, last_affected: v).

@@ -0,0 +1,80 @@
package strategies

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.

Might be worth it to link to an example for each of these (Link meaning the path to the test file for each CNA) (I'm assuming we have at least 1 test file per CNA strategy?)

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.

It's a bit unclear right now for ones you don't have StandaloneSingleVersionStrategy as a fallback whether that's intentional or not.


// GetVersionExtractor returns the appropriate VersionExtractor for a given CNA.
// GetVersionExtractor returns the appropriate VersionExtractor configured with CNA-specific strategies.
func GetVersionExtractor(cna string) VersionExtractor {

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.

Should the wordpress stuff be here? Or as we wiring those up later?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This will happen later, so we dont bloat this pr

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants