Use package ID from decoded SBOMs when provided (#1872)

* fix: use package id from cyclonedx when provided

Signed-off-by: James Neate <jamesmneate@gmail.com>

* override package IDs from converted SBOMs

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix typo

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove extractSyftID function

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: James Neate <jamesmneate@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
James Neate
2025-05-08 11:25:30 -04:00
committed by GitHub
co-authored by Alex Goodman
parent 39396cfff9
commit 00c4a4e72a
17 changed files with 238 additions and 56 deletions
@@ -5,7 +5,6 @@ import (
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
@@ -66,12 +65,16 @@ func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[str
case cyclonedx.ComponentTypeApplication, cyclonedx.ComponentTypeFramework, cyclonedx.ComponentTypeLibrary:
p := decodeComponent(component)
idMap[component.BOMRef] = p
syftID := extractSyftPacakgeID(component.BOMRef)
if component.BOMRef != "" {
// always prefer the IDs from the SBOM over derived IDs
p.OverrideID(artifact.ID(component.BOMRef))
} else {
p.SetID()
}
syftID := p.ID()
if syftID != "" {
idMap[syftID] = p
idMap[string(syftID)] = p
}
// TODO there must be a better way than needing to call this manually:
p.SetID()
s.Artifacts.Packages.Add(*p)
}
@@ -82,19 +85,6 @@ func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[str
}
}
func extractSyftPacakgeID(i string) string {
instance, err := packageurl.FromString(i)
if err != nil {
return ""
}
for _, q := range instance.Qualifiers {
if q.Key == "package-id" {
return q.Value
}
}
return ""
}
func linuxReleaseFromComponents(components []cyclonedx.Component) *linux.Release {
for i := range components {
component := &components[i]
@@ -421,3 +421,39 @@ func Test_decodeDependencies(t *testing.T) {
})
}
}
func Test_useBomRefOverDerivedSyftArtifactID(t *testing.T) {
packageWithId := cyclonedx.Component{
BOMRef: "pkg:maven/org.springframework.boot/spring-boot-starter-test?package-id=646a5a71a4abeee0",
Type: cyclonedx.ComponentTypeLibrary,
Name: "spring-boot-starter-test",
Version: "",
PackageURL: "pkg:maven/org.springframework.boot/spring-boot-starter-test",
}
packageWithoutId := cyclonedx.Component{
BOMRef: "pkg:maven/org.springframework.boot/spring-boot-starter-webflux",
Type: cyclonedx.ComponentTypeLibrary,
Name: "spring-boot-starter-webflux",
Version: "",
PackageURL: "pkg:maven/org.springframework.boot/spring-boot-starter-webflux",
}
bom := cyclonedx.BOM{Metadata: nil,
Components: &[]cyclonedx.Component{
packageWithId,
packageWithoutId,
}}
s, err := ToSyftModel(&bom)
assert.Nil(t, err)
assert.NotNil(t, s.Artifacts.Packages.Package("pkg:maven/org.springframework.boot/spring-boot-starter-test?package-id=646a5a71a4abeee0"))
pkgsWithoutID := s.Artifacts.Packages.PackagesByName(packageWithoutId.Name)
assert.Len(t, pkgsWithoutID, 1)
assert.NotEqual(t, "", pkgsWithoutID[0].ID())
}
+69 -1
View File
@@ -28,6 +28,7 @@ func (r RedactorFn) Redact(b []byte) []byte {
type PatternReplacement struct {
Search *regexp.Regexp
Groups []string
Replace string
}
@@ -39,7 +40,67 @@ func NewPatternReplacement(r *regexp.Regexp) PatternReplacement {
}
func (p PatternReplacement) Redact(b []byte) []byte {
return p.Search.ReplaceAll(b, []byte(p.Replace))
if len(p.Groups) == 0 {
return p.Search.ReplaceAll(b, []byte(p.Replace))
}
return p.redactNamedGroups(b)
}
func (p PatternReplacement) redactNamedGroups(b []byte) []byte {
groupsToReplace := make(map[string]bool)
for _, g := range p.Groups {
groupsToReplace[g] = true
}
subexpNames := p.Search.SubexpNames()
return p.Search.ReplaceAllFunc(b, func(match []byte) []byte {
indexes := p.Search.FindSubmatchIndex(match)
if indexes == nil {
return match
}
result := make([]byte, len(match))
copy(result, match)
// keep track of the offset as we replace groups
offset := 0
// process each named group
for i, name := range subexpNames {
// skip the full match (i==0) and groups we don't want to replace
if i == 0 || !groupsToReplace[name] {
continue
}
// get the start and end positions of this group
startPos := indexes[2*i]
endPos := indexes[2*i+1]
// skip if the group didn't match
if startPos < 0 || endPos < 0 {
continue
}
// adjust positions based on previous replacements
startPos += offset
endPos += offset
// replace the group with our replacement text
beforeGroup := result[:startPos]
afterGroup := result[endPos:]
// calculate the new offset
oldLen := endPos - startPos
newLen := len(p.Replace)
offset += (newLen - oldLen)
result = append(beforeGroup, append([]byte(p.Replace), afterGroup...)...) //nolint:gocritic
}
return result
})
}
// Replace by value //////////////////////////////
@@ -86,6 +147,13 @@ func (r *Redactions) WithPatternRedactors(values map[string]string) *Redactions
return r
}
func (r *Redactions) WithPatternRedactorSpec(values ...PatternReplacement) *Redactions {
for _, v := range values {
r.redactors = append(r.redactors, v)
}
return r
}
func (r *Redactions) WithValueRedactors(values map[string]string) *Redactions {
for k, v := range values {
r.redactors = append(r.redactors,