Skip to content

Commit 735335f

Browse files
fix(sbom): attach nested packages to Application (#8144)
Signed-off-by: knqyf263 <[email protected]> Co-authored-by: knqyf263 <[email protected]>
1 parent 9fd5cc5 commit 735335f

File tree

3 files changed

+283
-17
lines changed

3 files changed

+283
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
{
2+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
3+
"bomFormat": "CycloneDX",
4+
"specVersion": "1.6",
5+
"serialNumber": "urn:uuid:12346a7a-5819-43bf-9411-5c146304f023",
6+
"version": 1,
7+
"metadata": {
8+
"timestamp": "2024-12-20T10:57:13+00:00",
9+
"tools": {
10+
"components": [
11+
{
12+
"type": "application",
13+
"group": "aquasecurity",
14+
"name": "trivy",
15+
"version": "0.38.7-764-g30c7cb137"
16+
}
17+
]
18+
},
19+
"component": {
20+
"bom-ref": "1cb40520-a22c-481f-ad77-6bc6960430c5",
21+
"type": "application",
22+
"name": "/test",
23+
"properties": [
24+
{
25+
"name": "aquasecurity:trivy:SchemaVersion",
26+
"value": "2"
27+
}
28+
]
29+
}
30+
},
31+
"components": [
32+
{
33+
"bom-ref": "4021d631-e242-4e69-8a93-928665810a27",
34+
"type": "application",
35+
"name": "foo/bar/test.elf",
36+
"properties": [
37+
{
38+
"name": "aquasecurity:trivy:Class",
39+
"value": "lang-pkgs"
40+
},
41+
{
42+
"name": "aquasecurity:trivy:Type",
43+
"value": "gobinary"
44+
}
45+
]
46+
},
47+
{
48+
"bom-ref": "pkg:golang/github.com/aquasecurity/[email protected]",
49+
"type": "library",
50+
"name": "github.com/aquasecurity/go-pep440-version",
51+
"version": "v0.0.0-20210121094942-22b2f8951d46",
52+
"purl": "pkg:golang/github.com/aquasecurity/[email protected]",
53+
"properties": [
54+
{
55+
"name": "aquasecurity:trivy:PkgID",
56+
"value": "github.com/aquasecurity/[email protected]"
57+
},
58+
{
59+
"name": "aquasecurity:trivy:PkgType",
60+
"value": "gobinary"
61+
}
62+
]
63+
},
64+
{
65+
"bom-ref": "pkg:golang/github.com/aquasecurity/[email protected]",
66+
"type": "library",
67+
"name": "github.com/aquasecurity/go-version",
68+
"version": "v0.0.0-20210121072130-637058cfe492",
69+
"purl": "pkg:golang/github.com/aquasecurity/[email protected]",
70+
"properties": [
71+
{
72+
"name": "aquasecurity:trivy:PkgID",
73+
"value": "github.com/aquasecurity/[email protected]"
74+
},
75+
{
76+
"name": "aquasecurity:trivy:PkgType",
77+
"value": "gobinary"
78+
}
79+
]
80+
},
81+
{
82+
"bom-ref": "pkg:golang/github.com/aquasecurity/test",
83+
"type": "library",
84+
"name": "github.com/aquasecurity/test",
85+
"purl": "pkg:golang/github.com/aquasecurity/test",
86+
"properties": [
87+
{
88+
"name": "aquasecurity:trivy:PkgID",
89+
"value": "github.com/aquasecurity/test"
90+
},
91+
{
92+
"name": "aquasecurity:trivy:PkgType",
93+
"value": "gobinary"
94+
}
95+
]
96+
},
97+
{
98+
"bom-ref": "pkg:golang/golang.org/x/[email protected]",
99+
"type": "library",
100+
"name": "golang.org/x/xerrors",
101+
"version": "v0.0.0-20200804184101-5ec99f83aff1",
102+
"purl": "pkg:golang/golang.org/x/[email protected]",
103+
"properties": [
104+
{
105+
"name": "aquasecurity:trivy:PkgID",
106+
"value": "golang.org/x/[email protected]"
107+
},
108+
{
109+
"name": "aquasecurity:trivy:PkgType",
110+
"value": "gobinary"
111+
}
112+
]
113+
},
114+
{
115+
"bom-ref": "pkg:golang/[email protected]",
116+
"type": "library",
117+
"name": "stdlib",
118+
"version": "v1.15.2",
119+
"purl": "pkg:golang/[email protected]",
120+
"properties": [
121+
{
122+
"name": "aquasecurity:trivy:PkgID",
123+
"value": "[email protected]"
124+
},
125+
{
126+
"name": "aquasecurity:trivy:PkgType",
127+
"value": "gobinary"
128+
}
129+
]
130+
}
131+
],
132+
"dependencies": [
133+
{
134+
"ref": "1cb40520-a22c-481f-ad77-6bc6960430c5",
135+
"dependsOn": [
136+
"4021d631-e242-4e69-8a93-928665810a27"
137+
]
138+
},
139+
{
140+
"ref": "4021d631-e242-4e69-8a93-928665810a27",
141+
"dependsOn": [
142+
"pkg:golang/github.com/aquasecurity/test"
143+
]
144+
},
145+
{
146+
"ref": "pkg:golang/github.com/aquasecurity/[email protected]",
147+
"dependsOn": []
148+
},
149+
{
150+
"ref": "pkg:golang/github.com/aquasecurity/[email protected]",
151+
"dependsOn": []
152+
},
153+
{
154+
"ref": "pkg:golang/github.com/aquasecurity/test",
155+
"dependsOn": [
156+
"pkg:golang/github.com/aquasecurity/[email protected]",
157+
"pkg:golang/github.com/aquasecurity/[email protected]",
158+
"pkg:golang/golang.org/x/[email protected]",
159+
"pkg:golang/[email protected]"
160+
]
161+
},
162+
{
163+
"ref": "pkg:golang/golang.org/x/[email protected]",
164+
"dependsOn": []
165+
},
166+
{
167+
"ref": "pkg:golang/[email protected]",
168+
"dependsOn": []
169+
}
170+
],
171+
"vulnerabilities": []
172+
}

pkg/sbom/cyclonedx/unmarshal_test.go

+87
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,93 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
668668
},
669669
},
670670
},
671+
{
672+
name: "happy path for BOM with nested packages",
673+
inputFile: "testdata/happy/nested-packages-bom.json",
674+
want: types.SBOM{
675+
Applications: []ftypes.Application{
676+
{
677+
Type: "gobinary",
678+
FilePath: "foo/bar/test.elf",
679+
Packages: ftypes.Packages{
680+
{
681+
ID: "github.com/aquasecurity/test",
682+
Name: "github.com/aquasecurity/test",
683+
Identifier: ftypes.PkgIdentifier{
684+
PURL: &packageurl.PackageURL{
685+
Type: packageurl.TypeGolang,
686+
Namespace: "github.com/aquasecurity",
687+
Name: "test",
688+
},
689+
BOMRef: "pkg:golang/github.com/aquasecurity/test",
690+
},
691+
DependsOn: []string{
692+
"github.com/aquasecurity/[email protected]",
693+
"github.com/aquasecurity/[email protected]",
694+
"golang.org/x/[email protected]",
695+
696+
},
697+
},
698+
{
699+
ID: "github.com/aquasecurity/[email protected]",
700+
Name: "github.com/aquasecurity/go-pep440-version",
701+
Version: "v0.0.0-20210121094942-22b2f8951d46",
702+
Identifier: ftypes.PkgIdentifier{
703+
PURL: &packageurl.PackageURL{
704+
Type: packageurl.TypeGolang,
705+
Namespace: "github.com/aquasecurity",
706+
Name: "go-pep440-version",
707+
Version: "v0.0.0-20210121094942-22b2f8951d46",
708+
},
709+
BOMRef: "pkg:golang/github.com/aquasecurity/[email protected]",
710+
},
711+
},
712+
{
713+
ID: "github.com/aquasecurity/[email protected]",
714+
Name: "github.com/aquasecurity/go-version",
715+
Version: "v0.0.0-20210121072130-637058cfe492",
716+
Identifier: ftypes.PkgIdentifier{
717+
PURL: &packageurl.PackageURL{
718+
Type: packageurl.TypeGolang,
719+
Namespace: "github.com/aquasecurity",
720+
Name: "go-version",
721+
Version: "v0.0.0-20210121072130-637058cfe492",
722+
},
723+
BOMRef: "pkg:golang/github.com/aquasecurity/[email protected]",
724+
},
725+
},
726+
{
727+
ID: "golang.org/x/[email protected]",
728+
Name: "golang.org/x/xerrors",
729+
Version: "v0.0.0-20200804184101-5ec99f83aff1",
730+
Identifier: ftypes.PkgIdentifier{
731+
PURL: &packageurl.PackageURL{
732+
Type: packageurl.TypeGolang,
733+
Namespace: "golang.org/x",
734+
Name: "xerrors",
735+
Version: "v0.0.0-20200804184101-5ec99f83aff1",
736+
},
737+
BOMRef: "pkg:golang/golang.org/x/[email protected]",
738+
},
739+
},
740+
{
741+
742+
Name: "stdlib",
743+
Version: "v1.15.2",
744+
Identifier: ftypes.PkgIdentifier{
745+
PURL: &packageurl.PackageURL{
746+
Type: packageurl.TypeGolang,
747+
Name: "stdlib",
748+
Version: "v1.15.2",
749+
},
750+
BOMRef: "pkg:golang/[email protected]",
751+
},
752+
},
753+
},
754+
},
755+
},
756+
},
757+
},
671758
{
672759
name: "happy path only os component",
673760
inputFile: "testdata/happy/os-only-bom.json",

pkg/sbom/io/decode.go

+24-17
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,7 @@ func (m *Decoder) parseSrcVersion(ctx context.Context, pkg *ftypes.Package, ver
330330

331331
// addOSPkgs traverses relationships and adds OS packages
332332
func (m *Decoder) addOSPkgs(sbom *types.SBOM) {
333-
var pkgs []ftypes.Package
334-
for _, rel := range m.bom.Relationships()[m.osID] {
335-
pkg, ok := m.pkgs[rel.Dependency]
336-
if !ok {
337-
continue
338-
}
339-
pkgs = append(pkgs, *pkg)
340-
delete(m.pkgs, rel.Dependency) // Delete the added package
341-
}
333+
pkgs := m.traverseDependencies(m.osID)
342334
if len(pkgs) == 0 {
343335
return
344336
}
@@ -348,18 +340,33 @@ func (m *Decoder) addOSPkgs(sbom *types.SBOM) {
348340
// addLangPkgs traverses relationships and adds language-specific packages
349341
func (m *Decoder) addLangPkgs(sbom *types.SBOM) {
350342
for id, app := range m.apps {
351-
for _, rel := range m.bom.Relationships()[id] {
352-
pkg, ok := m.pkgs[rel.Dependency]
353-
if !ok {
354-
continue
355-
}
356-
app.Packages = append(app.Packages, *pkg)
357-
delete(m.pkgs, rel.Dependency) // Delete the added package
358-
}
343+
app.Packages = append(app.Packages, m.traverseDependencies(id)...)
359344
sbom.Applications = append(sbom.Applications, *app)
360345
}
361346
}
362347

348+
// traverseDependencies recursively retrieves all packages that the specified component depends on.
349+
// It starts from the given component ID and traverses the dependency tree, collecting all
350+
// dependent packages. The collected packages are removed from m.pkgs to prevent duplicate
351+
// processing. This ensures that all dependencies, including transitive ones, are properly
352+
// captured and associated with their parent component.
353+
func (m *Decoder) traverseDependencies(id uuid.UUID) ftypes.Packages {
354+
var pkgs ftypes.Packages
355+
for _, rel := range m.bom.Relationships()[id] {
356+
pkg, ok := m.pkgs[rel.Dependency]
357+
if !ok {
358+
continue
359+
}
360+
// Add the current package
361+
pkgs = append(pkgs, *pkg)
362+
delete(m.pkgs, rel.Dependency) // Delete the added package
363+
364+
// Add the nested packages
365+
pkgs = append(pkgs, m.traverseDependencies(rel.Dependency)...)
366+
}
367+
return pkgs
368+
}
369+
363370
// addOrphanPkgs adds orphan packages.
364371
// Orphan packages are packages that are not related to any components.
365372
func (m *Decoder) addOrphanPkgs(ctx context.Context, sbom *types.SBOM) error {

0 commit comments

Comments
 (0)