From 0a2592bf4517563d8fbf14d9fb6dac35a8d3ae0c Mon Sep 17 00:00:00 2001 From: Ahmed ElMallah Date: Thu, 18 Jun 2026 10:50:19 -0700 Subject: [PATCH] test(smoke): normalize volatile EPSS fields in golden comparison FIRST.org recomputes EPSS scores daily, so the model date, score, and percentile in enrich/reachability output drift every day regardless of the code under test. Left unnormalized they break smoke goldens (e.g. scan-go-reachability) on a daily cadence. Add normalizeEPSS to the normalizeJSON pipeline: scrub date, epss, and percentile on every "epss" array while keeping the CVE id so the golden still proves the EPSS payload attached to the right advisory. Co-Authored-By: Claude Opus 4.8 --- test/smoke/helpers_test.go | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/smoke/helpers_test.go b/test/smoke/helpers_test.go index 46f384f4..08dd294a 100644 --- a/test/smoke/helpers_test.go +++ b/test/smoke/helpers_test.go @@ -160,6 +160,12 @@ func normalizeJSON(t *testing.T, raw []byte) []byte { // source) so the same golden file is stable across runs. normalizeReachability(obj) + // Scrub volatile EPSS fields. FIRST.org recomputes EPSS scores daily, so + // the model date, score, and percentile drift every day independent of any + // code change; left untouched they break enrich/reachability goldens on a + // daily cadence. + normalizeEPSS(obj) + // Normalize synthetic project IDs (e.g. pkg:maven/bomly-git-NNNNNN) that // are derived from a non-deterministic hash of the temp clone directory. normalizeSyntheticIDs(obj) @@ -284,6 +290,48 @@ func normalizeReachabilityFile(value string) string { return filepath.Base(value) } +// normalizeEPSS walks the JSON tree and scrubs the volatile fields of every +// "epss" array. EPSS scores are recomputed daily by FIRST.org, so the date, +// score, and percentile change every day regardless of the code under test. +// The CVE id is left intact so the golden still proves the EPSS payload was +// attached to the right advisory. +func normalizeEPSS(node any) { + switch v := node.(type) { + case map[string]any: + for key, val := range v { + if key == "epss" { + if entries, ok := val.([]any); ok { + for _, e := range entries { + if entry, ok := e.(map[string]any); ok { + scrubEPSSEntry(entry) + } + } + continue + } + } + normalizeEPSS(val) + } + case []any: + for _, child := range v { + normalizeEPSS(child) + } + } +} + +// scrubEPSSEntry replaces the volatile fields of a single EPSS entry with +// stable placeholders, preserving the entry's shape and CVE id. +func scrubEPSSEntry(entry map[string]any) { + if _, ok := entry["date"]; ok { + entry["date"] = "" + } + if _, ok := entry["epss"]; ok { + entry["epss"] = float64(0) + } + if _, ok := entry["percentile"]; ok { + entry["percentile"] = float64(0) + } +} + // normalizeComparisonPath normalizes a comparison path field when it refers to // a filesystem path. Named references like container tags are left unchanged. func normalizeComparisonPath(m map[string]any, key string) {