From 3dbca60945649c46df8a0ed33459e6eaf9dd9b08 Mon Sep 17 00:00:00 2001 From: Anurag Tiwari Date: Mon, 29 Jun 2026 13:14:58 +0530 Subject: [PATCH] fix: backfill SCA recommendedVersion from ScanReportJson export --- internal/commands/result.go | 74 +++++++++++++++++++++++++++++++- internal/commands/result_test.go | 52 ++++++++++++++++++++++ internal/wrappers/export.go | 10 +++-- 3 files changed, 131 insertions(+), 5 deletions(-) diff --git a/internal/commands/result.go b/internal/commands/result.go index 378022fec..f1ff78de0 100644 --- a/internal/commands/result.go +++ b/internal/commands/result.go @@ -1650,6 +1650,7 @@ func enrichScaResults( if scaPackageModel != nil { resultsModel = addPackageInformation(resultsModel, scaPackageModel, scaTypeModel) } + backfillRecommendedVersionsFromExport(resultsModel, scaExportDetails.ScaTypes) } if slices.Contains(scan.Engines, commonParams.ContainersType) && !wrappers.IsContainersEnabled { resultsModel = removeResultsByType(resultsModel, commonParams.ContainersType) @@ -1660,7 +1661,12 @@ func enrichScaResults( func parseExportScaVulnerability(types []wrappers.ScaType) *[]wrappers.ScaTypeCollection { var scaTypes []wrappers.ScaTypeCollection for _, t := range types { - scaTypes = append(scaTypes, wrappers.ScaTypeCollection(t)) + scaTypes = append(scaTypes, wrappers.ScaTypeCollection{ + ID: t.ID, + Type: t.Type, + IsIgnored: t.IsIgnored, + PackageID: t.PackageID, + }) } return &scaTypes } @@ -2879,6 +2885,72 @@ func buildVulnerabilityIdentifier(result *wrappers.ScanResult) string { return fmt.Sprintf("%s:%s", result.ID, result.ScanResultData.PackageIdentifier) } +func scaUpgradeVersionLookupKey(cveID, packageID string) string { + return fmt.Sprintf("%s|%s", cveID, packageID) +} + +func buildScaUpgradeVersionLookup(vulnerabilities []wrappers.ScaType) map[string]string { + lookup := make(map[string]string) + for _, vulnerability := range vulnerabilities { + cveID := vulnerability.CveName + if cveID == "" { + cveID = vulnerability.ID + } + if cveID == "" || vulnerability.PackageID == "" || vulnerability.NextFixedVersion == "" { + continue + } + lookup[scaUpgradeVersionLookupKey(cveID, vulnerability.PackageID)] = vulnerability.NextFixedVersion + } + return lookup +} + +func isRecommendedVersionEmpty(version interface{}) bool { + if version == nil { + return true + } + value, ok := version.(string) + if !ok { + return fmt.Sprint(version) == "" + } + return strings.TrimSpace(value) == "" +} + +func backfillRecommendedVersionsFromExport( + resultsModel *wrappers.ScanResultsCollection, + vulnerabilities []wrappers.ScaType, +) { + if resultsModel == nil || len(vulnerabilities) == 0 { + return + } + + lookup := buildScaUpgradeVersionLookup(vulnerabilities) + if len(lookup) == 0 { + return + } + + for _, result := range resultsModel.Results { + if result.Type != commonParams.ScaType { + continue + } + if !isRecommendedVersionEmpty(result.ScanResultData.RecommendedVersion) { + continue + } + + cveID := result.ID + if cveID == "" { + cveID = result.VulnerabilityDetails.CveName + } + packageID := result.ScanResultData.PackageIdentifier + if cveID == "" || packageID == "" { + continue + } + + if version, ok := lookup[scaUpgradeVersionLookupKey(cveID, packageID)]; ok { + result.ScanResultData.RecommendedVersion = version + } + } +} + func addPackageInformation( resultsModel *wrappers.ScanResultsCollection, scaPackageModel *[]wrappers.ScaPackageCollection, diff --git a/internal/commands/result_test.go b/internal/commands/result_test.go index dff116ed3..763abe5d9 100644 --- a/internal/commands/result_test.go +++ b/internal/commands/result_test.go @@ -1045,6 +1045,58 @@ func Test_addPackageInformation(t *testing.T) { assert.Equal(t, expectedFixLink, actualFixLink, "FixLink should match the result ID") } +func Test_backfillRecommendedVersionsFromExport(t *testing.T) { + resultsModel := &wrappers.ScanResultsCollection{ + Results: []*wrappers.ScanResult{ + { + Type: "sca", + ID: "CVE-2024-0001", + ScanResultData: wrappers.ScanResultData{ + PackageIdentifier: "pkg-empty", + RecommendedVersion: "", + }, + }, + { + Type: "sca", + ID: "CVE-2024-0002", + ScanResultData: wrappers.ScanResultData{ + PackageIdentifier: "pkg-existing", + RecommendedVersion: "1.2.3", + }, + }, + }, + } + exportVulnerabilities := []wrappers.ScaType{ + { + CveName: "CVE-2024-0001", + PackageID: "pkg-empty", + NextFixedVersion: "4.5.6", + }, + { + CveName: "CVE-2024-0002", + PackageID: "pkg-existing", + NextFixedVersion: "9.9.9", + }, + } + + backfillRecommendedVersionsFromExport(resultsModel, exportVulnerabilities) + + assert.Equal(t, "4.5.6", resultsModel.Results[0].ScanResultData.RecommendedVersion) + assert.Equal(t, "1.2.3", resultsModel.Results[1].ScanResultData.RecommendedVersion) +} + +func Test_buildScaUpgradeVersionLookup_usesCveIDFallback(t *testing.T) { + lookup := buildScaUpgradeVersionLookup([]wrappers.ScaType{ + { + ID: "CVE-2024-9999", + PackageID: "pkg-1", + NextFixedVersion: "2.0.0", + }, + }) + + assert.Equal(t, "2.0.0", lookup["CVE-2024-9999|pkg-1"]) +} + func TestRunGetResultsByScanIdGLSastFormat_NoVulnerabilities_Success(t *testing.T) { // Execute the command and perform nil assertion execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK_NO_VULNERABILITIES", "--report-format", "gl-sast") diff --git a/internal/wrappers/export.go b/internal/wrappers/export.go index 331a79262..1aa660c20 100644 --- a/internal/wrappers/export.go +++ b/internal/wrappers/export.go @@ -36,8 +36,10 @@ type PackagePath struct { } type ScaType struct { - ID string `json:"Id,omitempty"` - Type string `json:"Type,omitempty"` - IsIgnored bool `json:"IsIgnored,omitempty"` - PackageID string `json:"PackageID,omitempty"` + ID string `json:"Id,omitempty"` + CveName string `json:"CveName,omitempty"` + Type string `json:"Type,omitempty"` + IsIgnored bool `json:"IsIgnored,omitempty"` + PackageID string `json:"PackageID,omitempty"` + NextFixedVersion string `json:"NextFixedVersion,omitempty"` }