diff --git a/utils/results/conversion/simplejsonparser/simplejsonparser.go b/utils/results/conversion/simplejsonparser/simplejsonparser.go index 7a2292e63..ad9559057 100644 --- a/utils/results/conversion/simplejsonparser/simplejsonparser.go +++ b/utils/results/conversion/simplejsonparser/simplejsonparser.go @@ -113,7 +113,7 @@ func (sjc *CmdResultsSimpleJsonConverter) ParseSbomLicenses(sbom *cyclonedx.BOM) LicenseKey: license.License.ID, LicenseName: name, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - ImpactedDependencyName: strings.ReplaceAll(compName, "/", ":"), + ImpactedDependencyName: normalizeCdxComponentName(compName, compType), ImpactedDependencyVersion: compVersion, ImpactedDependencyType: results.FormalTechOrCdxCompType(compType, sjc.pretty), Components: results.ExtractComponentDirectComponentsInBOM(bomIndex, component, impactPaths), @@ -234,7 +234,7 @@ func (sjc *CmdResultsSimpleJsonConverter) createVulnerabilityOrViolationRowFromC Summary: summary, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ SeverityDetails: severityutils.GetAsDetails(severity, applicabilityStatus, sjc.pretty), - ImpactedDependencyName: strings.ReplaceAll(compName, "/", ":"), + ImpactedDependencyName: normalizeCdxComponentName(compName, compType), ImpactedDependencyVersion: compVersion, ImpactedDependencyType: results.FormalTechOrCdxCompType(compType, sjc.pretty), Components: directComponents, @@ -272,7 +272,7 @@ func (sjc *CmdResultsSimpleJsonConverter) createLicenseViolationRow(licenseKey, LicenseName: licenseName, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ SeverityDetails: severityutils.GetAsDetails(severity, jasutils.NotScanned, sjc.pretty), - ImpactedDependencyName: strings.ReplaceAll(compName, "/", ":"), + ImpactedDependencyName: normalizeCdxComponentName(compName, compType), ImpactedDependencyVersion: compVersion, ImpactedDependencyType: results.FormalTechOrCdxCompType(compType, sjc.pretty), Components: directComponents, @@ -288,7 +288,7 @@ func (sjc *CmdResultsSimpleJsonConverter) createOpRiskViolationRow(opRiskViolati ViolationContext: convertToViolationContext(opRiskViolation.Violation), ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ SeverityDetails: severityutils.GetAsDetails(opRiskViolation.Severity, jasutils.NotScanned, sjc.pretty), - ImpactedDependencyName: strings.ReplaceAll(compName, "/", ":"), + ImpactedDependencyName: normalizeCdxComponentName(compName, compType), ImpactedDependencyVersion: compVersion, ImpactedDependencyType: results.FormalTechOrCdxCompType(compType, sjc.pretty), Components: opRiskViolation.DirectComponents, @@ -731,3 +731,16 @@ func sortSourceCodeRow(rows []formats.SourceCodeRow) { return rows[i].File > rows[j].File }) } + +// Converts a PURL-derived component name to Xray-compatible format. +// SplitPackageURL joins namespace and name with "/". +// For Maven/Gradle (package type "maven"), Xray and package updaters expect "groupId:artifactId" with ":" as the separator. +// For Debian (package type "deb"), Xray expects "distro:version:name" with ":" as the separator. +// For all other ecosystems (Go, npm, etc.) the "/" is semantically part of the package identifier and must be preserved. +func normalizeCdxComponentName(compName, compType string) string { + switch compType { + case techutils.Maven.String(), techutils.Debian.String(): + return strings.ReplaceAll(compName, "/", ":") + } + return compName +} diff --git a/utils/xray/remediation/cdxremediation.go b/utils/xray/remediation/cdxremediation.go index cf0c44405..88ba9905b 100644 --- a/utils/xray/remediation/cdxremediation.go +++ b/utils/xray/remediation/cdxremediation.go @@ -2,6 +2,7 @@ package remediation import ( "fmt" + "strings" "github.com/CycloneDX/cyclonedx-go" @@ -58,6 +59,11 @@ func matchVulnerabilityToRemediationOptions(bom *cyclonedx.BOM, vulnerability *c } } +// TODO remove this when https://jfrog-int.atlassian.net/browse/XRAY-137306 is done, as fix versions should be already normalized when returning from remediation API. +func normalizeVersion(version string) string { + return strings.TrimPrefix(version, "v") +} + func getAffectComponentCveRemediationStepsByFixedVersion(cve string, component cyclonedx.Component, cveRemediationOptions []utils.Option, strategy utils.FixStrategy) (steps []utils.OptionStep) { for _, cveRemediationOption := range cveRemediationOptions { if cveRemediationOption.Type != utils.InLock { @@ -78,7 +84,7 @@ func getAffectComponentCveRemediationStepsByFixedVersion(cve string, component c continue } // We only want FixVersion step type - if step.StepType == utils.FixVersion && step.PkgVersion.Name == component.Name && step.PkgVersion.Version == component.Version { + if step.StepType == utils.FixVersion && step.PkgVersion.Name == component.Name && normalizeVersion(step.PkgVersion.Version) == normalizeVersion(component.Version) { steps = append(steps, step) } } diff --git a/utils/xray/remediation/cdxremediation_test.go b/utils/xray/remediation/cdxremediation_test.go index 4830ab562..1b21fbbdb 100644 --- a/utils/xray/remediation/cdxremediation_test.go +++ b/utils/xray/remediation/cdxremediation_test.go @@ -441,6 +441,54 @@ func TestMatchVulnerabilityToRemediationOptions(t *testing.T) { expectedAffectedVersions: nil, description: "Should ignore remediation steps when component name doesn't match", }, + { + name: "Go component with v-prefix version mismatch between API and BOM", + bom: &cyclonedx.BOM{ + Components: &[]cyclonedx.Component{ + { + BOMRef: "golang-component-ref", + Name: "golang.org/x/crypto", + Version: "0.33.0", + }, + }, + }, + vulnerability: &cyclonedx.Vulnerability{ + ID: "CVE-2023-1234", + Affects: &[]cyclonedx.Affects{ + { + Ref: "golang-component-ref", + }, + }, + }, + remediationOptions: utils.CveRemediationResponse{ + "CVE-2023-1234": []utils.Option{ + { + Type: utils.InLock, + Steps: map[utils.FixStrategy][]utils.OptionStep{ + utils.QuickestFixStrategy: { + { + StepType: utils.FixVersion, + PkgVersion: utils.PackageVersionKey{ + Name: "golang.org/x/crypto", + Version: "v0.33.0", + }, + UpgradeTo: utils.PackageVersionKey{ + Version: "v0.40.0", + }, + }, + }, + }, + }, + }, + }, + expectedAffectedVersions: []cyclonedx.AffectedVersions{ + { + Version: "v0.40.0", + Status: cyclonedx.VulnerabilityStatusNotAffected, + }, + }, + description: "Should match Go component when API returns v-prefixed version but BOM stores without prefix", + }, { name: "Component version mismatch should be ignored", bom: &cyclonedx.BOM{