Skip to content

Commit 6976bb9

Browse files
authored
Fix SBOM component ref compare (#618)
1 parent 14b63fa commit 6976bb9

File tree

3 files changed

+72
-25
lines changed

3 files changed

+72
-25
lines changed

sca/bom/bomgenerator_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestGetDiff(t *testing.T) {
8484
Sbom: &cyclonedx.BOM{
8585
Components: &[]cyclonedx.Component{
8686
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"},
87-
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", Version: "1.0"},
87+
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", PackageURL: "pkg:component1", Version: "1.0"},
8888
},
8989
Dependencies: &[]cyclonedx.Dependency{
9090
{Ref: "root", Dependencies: &[]string{"component1"}},
@@ -96,8 +96,8 @@ func TestGetDiff(t *testing.T) {
9696
sbom: &cyclonedx.BOM{
9797
Components: &[]cyclonedx.Component{
9898
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"},
99-
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", Version: "1.0"},
100-
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", Version: "2.0"},
99+
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", PackageURL: "pkg:component1", Version: "1.0"},
100+
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", PackageURL: "pkg:component2", Version: "2.0"},
101101
},
102102
Dependencies: &[]cyclonedx.Dependency{
103103
{Ref: "root", Dependencies: &[]string{"component1", "component2"}},
@@ -107,7 +107,7 @@ func TestGetDiff(t *testing.T) {
107107
expectedSbom: &cyclonedx.BOM{
108108
Components: &[]cyclonedx.Component{
109109
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"},
110-
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", Version: "2.0"},
110+
{Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", PackageURL: "pkg:component2", Version: "2.0"},
111111
},
112112
Dependencies: &[]cyclonedx.Dependency{
113113
{Ref: "root", Dependencies: &[]string{"component2"}},

utils/formats/cdxutils/cyclonedxutils.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,18 @@ func SearchComponentByRef(components *[]cyclonedx.Component, ref string) (compon
260260
return
261261
}
262262

263+
func SearchComponentByCleanPurl(components *[]cyclonedx.Component, purl string) (component *cyclonedx.Component) {
264+
if components == nil || len(*components) == 0 {
265+
return
266+
}
267+
for i, comp := range *components {
268+
if techutils.PurlToXrayComponentId(comp.PackageURL) == techutils.PurlToXrayComponentId(purl) {
269+
return &(*components)[i]
270+
}
271+
}
272+
return
273+
}
274+
263275
func CreateFileOrDirComponent(filePathOrUri string) (component cyclonedx.Component) {
264276
component = cyclonedx.Component{
265277
BOMRef: GetFileRef(filePathOrUri),
@@ -309,12 +321,12 @@ func Exclude(bom cyclonedx.BOM, componentsToExclude ...cyclonedx.Component) (fil
309321
}
310322
filteredSbom = &bom
311323
for _, compToExclude := range componentsToExclude {
312-
if matchedBomComp := SearchComponentByRef(bom.Components, compToExclude.BOMRef); matchedBomComp == nil || GetComponentRelation(&bom, matchedBomComp.BOMRef, false) == RootRelation {
324+
if matchedBomComp := SearchComponentByCleanPurl(bom.Components, compToExclude.PackageURL); matchedBomComp == nil || GetComponentRelation(&bom, matchedBomComp.BOMRef, false) == RootRelation {
313325
// If not a match or Root component, skip it
314326
continue
315327
}
316328
// Exclude the component from the dependencies
317-
filteredSbom.Dependencies = excludeFromDependencies(bom.Dependencies, compToExclude.BOMRef)
329+
filteredSbom.Dependencies = excludeFromDependencies(bom.Dependencies, bom.Components, compToExclude)
318330
}
319331
toExclude := datastructures.MakeSet[string]()
320332
for _, comp := range *filteredSbom.Components {
@@ -366,25 +378,25 @@ func excludeFromComponents(components *[]cyclonedx.Component, excludeComponents
366378
return &filteredComponents
367379
}
368380

369-
func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, excludeComponents ...string) *[]cyclonedx.Dependency {
381+
func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, components *[]cyclonedx.Component, excludeComponents ...cyclonedx.Component) *[]cyclonedx.Dependency {
370382
if dependencies == nil || len(*dependencies) == 0 || len(excludeComponents) == 0 {
371383
return dependencies
372384
}
373-
excludeRefs := datastructures.MakeSet[string]()
374-
for _, compRef := range excludeComponents {
375-
excludeRefs.Add(compRef)
385+
excludePurls := datastructures.MakeSet[string]()
386+
for _, component := range excludeComponents {
387+
excludePurls.Add(techutils.PurlToXrayComponentId(component.PackageURL))
376388
}
377389
filteredDependencies := []cyclonedx.Dependency{}
378390
for _, dep := range *dependencies {
379-
if excludeRefs.Exists(dep.Ref) {
391+
if excludePurls.Exists(GetTrimmedPurlByRef(dep.Ref, components)) {
380392
// This dependency is excluded, skip it
381393
continue
382394
}
383395
filteredDep := cyclonedx.Dependency{Ref: dep.Ref}
384396
if dep.Dependencies != nil {
385397
// Also filter the components from the dependencies of this dependency
386398
for _, depRef := range *dep.Dependencies {
387-
if !excludeRefs.Exists(depRef) {
399+
if !excludePurls.Exists(GetTrimmedPurlByRef(depRef, components)) {
388400
if filteredDep.Dependencies == nil {
389401
filteredDep.Dependencies = &[]string{}
390402
}
@@ -399,6 +411,15 @@ func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, excludeCompon
399411
return &filteredDependencies
400412
}
401413

414+
func GetTrimmedPurlByRef(dep string, components *[]cyclonedx.Component) string {
415+
component := SearchComponentByRef(components, dep)
416+
if component == nil {
417+
// couldn't find component
418+
return ""
419+
}
420+
return techutils.PurlToXrayComponentId(component.PackageURL)
421+
}
422+
402423
func AttachLicenseToComponent(component *cyclonedx.Component, license cyclonedx.LicenseChoice) {
403424
if component.Licenses == nil {
404425
component.Licenses = &cyclonedx.Licenses{}

utils/formats/cdxutils/cyclonedxutils_test.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,9 +1121,9 @@ func TestExclude(t *testing.T) {
11211121
bom := cyclonedx.NewBOM()
11221122
bom.Components = &[]cyclonedx.Component{
11231123
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1124-
{BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary},
1125-
{BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary},
1126-
{BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary},
1124+
{BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary},
1125+
{BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary},
1126+
{BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary},
11271127
}
11281128
bom.Dependencies = &[]cyclonedx.Dependency{
11291129
{Ref: "root", Dependencies: &[]string{"comp1", "comp3"}},
@@ -1152,9 +1152,9 @@ func TestExclude(t *testing.T) {
11521152
expected: &cyclonedx.BOM{
11531153
Components: &[]cyclonedx.Component{
11541154
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1155-
{BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary},
1156-
{BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary},
1157-
{BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary},
1155+
{BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary},
1156+
{BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary},
1157+
{BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary},
11581158
},
11591159
Dependencies: &[]cyclonedx.Dependency{
11601160
{Ref: "root", Dependencies: &[]string{"comp1", "comp3"}},
@@ -1164,25 +1164,25 @@ func TestExclude(t *testing.T) {
11641164
},
11651165
{
11661166
name: "Exclude single component with transitive dependencies",
1167-
exclude: []cyclonedx.Component{{BOMRef: "comp1"}},
1167+
exclude: []cyclonedx.Component{{BOMRef: "comp1", PackageURL: "pkg:comp1"}},
11681168
bom: *bom,
11691169
expected: &cyclonedx.BOM{
11701170
Components: &[]cyclonedx.Component{
11711171
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1172-
{BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary},
1172+
{BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary},
11731173
},
11741174
Dependencies: &[]cyclonedx.Dependency{{Ref: "root", Dependencies: &[]string{"comp3"}}},
11751175
},
11761176
},
11771177
{
11781178
name: "Exclude single component existing both directly and transitively",
1179-
exclude: []cyclonedx.Component{{BOMRef: "comp3"}},
1179+
exclude: []cyclonedx.Component{{BOMRef: "comp3", PackageURL: "pkg:comp3"}},
11801180
bom: *bom,
11811181
expected: &cyclonedx.BOM{
11821182
Components: &[]cyclonedx.Component{
11831183
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1184-
{BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary},
1185-
{BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary},
1184+
{BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary},
1185+
{BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary},
11861186
},
11871187
Dependencies: &[]cyclonedx.Dependency{
11881188
{Ref: "root", Dependencies: &[]string{"comp1"}},
@@ -1192,18 +1192,44 @@ func TestExclude(t *testing.T) {
11921192
},
11931193
{
11941194
name: "Exclude multiple components",
1195-
exclude: []cyclonedx.Component{{BOMRef: "comp2"}, {BOMRef: "comp3"}, {BOMRef: "exclude-me"}},
1195+
exclude: []cyclonedx.Component{{BOMRef: "comp2", PackageURL: "pkg:comp2"}, {BOMRef: "comp3", PackageURL: "pkg:comp3"}, {BOMRef: "exclude-me", PackageURL: "pkg:exclude-me"}},
11961196
bom: *bom,
11971197
expected: &cyclonedx.BOM{
11981198
Components: &[]cyclonedx.Component{
11991199
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1200-
{BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary},
1200+
{BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary},
12011201
},
12021202
Dependencies: &[]cyclonedx.Dependency{
12031203
{Ref: "root", Dependencies: &[]string{"comp1"}},
12041204
},
12051205
},
12061206
},
1207+
{
1208+
name: "Exclude by same name+version while ignoring hash",
1209+
bom: cyclonedx.BOM{
1210+
Components: &[]cyclonedx.Component{
1211+
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1212+
{BOMRef: "comp1", PackageURL: "pkg:npm/[email protected]?hash=4321", Type: cyclonedx.ComponentTypeLibrary},
1213+
{BOMRef: "comp2", PackageURL: "pkg:npm/[email protected]", Type: cyclonedx.ComponentTypeLibrary},
1214+
{BOMRef: "comp3", PackageURL: "pkg:npm/[email protected]", Type: cyclonedx.ComponentTypeLibrary},
1215+
},
1216+
Dependencies: &[]cyclonedx.Dependency{
1217+
{Ref: "root", Dependencies: &[]string{"comp1", "comp3"}},
1218+
{Ref: "comp1", Dependencies: &[]string{"comp2", "comp3"}},
1219+
},
1220+
},
1221+
// Exclude the same name+version, with a hash that should be ignored
1222+
exclude: []cyclonedx.Component{{BOMRef: "comp1", PackageURL: "pkg:npm/[email protected]?hash=1234"}},
1223+
expected: &cyclonedx.BOM{
1224+
Components: &[]cyclonedx.Component{
1225+
{BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary},
1226+
{BOMRef: "comp3", PackageURL: "pkg:npm/[email protected]", Type: cyclonedx.ComponentTypeLibrary},
1227+
},
1228+
Dependencies: &[]cyclonedx.Dependency{
1229+
{Ref: "root", Dependencies: &[]string{"comp3"}},
1230+
},
1231+
},
1232+
},
12071233
}
12081234

12091235
for _, tt := range tests {

0 commit comments

Comments
 (0)