From c7c9baecb5b1b81e5f4b2f88037f526bcf50da59 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 3 Sep 2025 15:16:08 +0400 Subject: [PATCH 1/7] fix(sbom): reuse existing BOM structure when available - Add reuseExistingBOM() to preserve original SBOM structure - Only update vulnerabilities when BOM is already present - Add BOM.Clone() and Component.Clone() methods to prevent side effects - Fix CycloneDX unmarshal to preserve empty dependsOn arrays - Use BOM-Ref for component lookup in vulnerability updates This change enables proper SBOM rescanning where the original structure is maintained and only vulnerability information is refreshed. --- ...d-multiple-lockfiles-short.cdx.json.golden | 36 +------- pkg/sbom/core/bom.go | 55 ++++++++++++ pkg/sbom/cyclonedx/marshal_test.go | 41 ++++++++- pkg/sbom/cyclonedx/unmarshal.go | 18 ++-- pkg/sbom/io/encode.go | 67 ++++++++++---- pkg/sbom/io/encode_test.go | 89 ++++++------------- pkg/vex/vex_test.go | 30 +++++-- 7 files changed, 205 insertions(+), 131 deletions(-) diff --git a/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden index aae2a448ebc7..bef8a43cbef4 100644 --- a/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden @@ -2,7 +2,7 @@ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.6", - "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000010", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000006", "version": 1, "metadata": { "timestamp": "2021-08-25T12:20:30+00:00", @@ -91,14 +91,6 @@ "name": "aquasecurity:trivy:LayerDigest", "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" }, - { - "name": "aquasecurity:trivy:PkgID", - "value": "bash@5.0-4" - }, - { - "name": "aquasecurity:trivy:PkgType", - "value": "debian" - }, { "name": "aquasecurity:trivy:SrcName", "value": "bash" @@ -124,14 +116,6 @@ "name": "aquasecurity:trivy:LayerDigest", "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" }, - { - "name": "aquasecurity:trivy:PkgID", - "value": "libidn2-0@2.0.5-1" - }, - { - "name": "aquasecurity:trivy:PkgType", - "value": "debian" - }, { "name": "aquasecurity:trivy:SrcName", "value": "libidn2" @@ -169,11 +153,7 @@ "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" }, { - "name": "aquasecurity:trivy:PkgID", - "value": "activesupport@6.0.2.1" - }, - { - "name": "aquasecurity:trivy:PkgType", + "name": "aquasecurity:trivy:Type", "value": "gemspec" } ] @@ -193,18 +173,6 @@ "353f2470-9c8b-4647-9d0d-96d893838dc8", "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" ] - }, - { - "ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", - "dependsOn": [] - }, - { - "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", - "dependsOn": [] - }, - { - "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", - "dependsOn": [] } ], "vulnerabilities": [ diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 4f8cec545e02..c74b927053fc 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -1,8 +1,11 @@ package core import ( + "slices" "sort" + "github.com/samber/lo" + dtypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/digest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -164,6 +167,18 @@ func (c *Component) ID() uuid.UUID { return c.id } +// Clone creates a deep copy of the Component +func (c *Component) Clone() *Component { + clone := *c // Shallow copy + + // Deep copy slices using slices.Clone + clone.Licenses = slices.Clone(c.Licenses) + clone.Files = slices.Clone(c.Files) + clone.Properties = slices.Clone(c.Properties) + + return &clone +} + type File struct { // Path is a path of the file. // CycloneDX: N/A @@ -352,3 +367,43 @@ func (b *BOM) bomRef(c *Component) string { } return p } + +// Clone creates a deep copy of the BOM, including all components, relationships, and vulnerabilities. +// This ensures that modifications to the cloned BOM do not affect the original BOM. +func (b *BOM) Clone() *BOM { + return &BOM{ + SerialNumber: b.SerialNumber, + Version: b.Version, + rootID: b.rootID, + + // Deep copy components + components: lo.MapValues(b.components, func(c *Component, _ uuid.UUID) *Component { + return c.Clone() + }), + + // Deep copy relationships + relationships: lo.MapValues(b.relationships, func(rels []Relationship, _ uuid.UUID) []Relationship { + return slices.Clone(rels) + }), + + // Deep copy vulnerabilities + vulnerabilities: lo.MapValues(b.vulnerabilities, func(vulns []Vulnerability, _ uuid.UUID) []Vulnerability { + return slices.Clone(vulns) + }), + + // Deep copy external references + externalReferences: slices.Clone(b.externalReferences), + + // Deep copy purls + purls: lo.MapValues(b.purls, func(ids []uuid.UUID, _ string) []uuid.UUID { + return slices.Clone(ids) + }), + + // Deep copy parents + parents: lo.MapValues(b.parents, func(parentIds []uuid.UUID, _ uuid.UUID) []uuid.UUID { + return slices.Clone(parentIds) + }), + + opts: b.opts, + } +} diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index 7fd1bc52bc56..0d518b3dadd3 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -64,7 +64,9 @@ var ( func TestMarshaler_MarshalReport(t *testing.T) { testSBOM := core.NewBOM(core.Options{GenerateBOMRef: true}) - testSBOM.AddComponent(&core.Component{ + + // Add root component + rootComponent := &core.Component{ Root: true, Type: core.TypeApplication, Name: "jackson-databind-2.13.4.1.jar", @@ -77,7 +79,40 @@ func TestMarshaler_MarshalReport(t *testing.T) { Value: "2", }, }, - }) + } + testSBOM.AddComponent(rootComponent) + + // Add the jackson-databind component that matches scan results + jacksonComponent := &core.Component{ + Type: core.TypeLibrary, + Name: "jackson-databind", + Group: "com.fasterxml.jackson.core", + Version: "2.13.4.1", + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4.1", + }, + }, + Properties: []core.Property{ + { + Name: core.PropertyPkgType, + Value: "jar", + }, + { + Name: core.PropertyFilePath, + Value: "jackson-databind-2.13.4.1.jar", + }, + }, + } + testSBOM.AddComponent(jacksonComponent) + + // Establish relationships + testSBOM.AddRelationship(rootComponent, jacksonComponent, core.RelationshipContains) + testSBOM.AddRelationship(jacksonComponent, nil, core.RelationshipDependsOn) tests := []struct { name string @@ -1533,7 +1568,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_6, JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index f1538efba881..60dc733c3b08 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -80,12 +80,20 @@ func (b *BOM) parseBOM(bom *cdx.BOM) error { if !ok { continue } - for _, depRef := range lo.FromPtr(dep.Dependencies) { - dependency, ok := components[depRef] - if !ok { - continue + + dependencies := lo.FromPtr(dep.Dependencies) + if len(dependencies) == 0 { + // Empty dependsOn array - create empty relationship to preserve this information + b.BOM.AddRelationship(ref, nil, core.RelationshipDependsOn) + } else { + // Process actual dependencies + for _, depRef := range dependencies { + dependency, ok := components[depRef] + if !ok { + continue + } + b.BOM.AddRelationship(ref, dependency, core.RelationshipDependsOn) } - b.BOM.AddRelationship(ref, dependency, core.RelationshipDependsOn) } } diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 9e2f5f8ac769..dfaca17d067d 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -12,6 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/digest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/purl" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/scan/utils" @@ -19,9 +20,8 @@ import ( ) type Encoder struct { - bom *core.BOM - opts core.Options - components map[uuid.UUID]*core.Component + bom *core.BOM + opts core.Options } func NewEncoder(opts core.Options) *Encoder { @@ -29,8 +29,12 @@ func NewEncoder(opts core.Options) *Encoder { } func (e *Encoder) Encode(report types.Report) (*core.BOM, error) { + // When report.BOM is not nil, reuse the existing BOM structure. + // This happens in two scenarios: + // 1. SBOM scanning: When scanning an existing SBOM file to refresh vulnerabilities + // 2. Library usage: When using Trivy as a library with a custom BOM in the report if report.BOM != nil { - e.components = report.BOM.Components() + return e.reuseExistingBOM(report) } // Metadata component root, err := e.rootComponent(report) @@ -101,11 +105,6 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { case ftypes.TypeRepository: root.Type = core.TypeRepository case ftypes.TypeCycloneDX, ftypes.TypeSPDX: - // When we scan SBOM file - // If SBOM file doesn't contain root component - use filesystem - if r.BOM != nil && r.BOM.Root() != nil { - return r.BOM.Root(), nil - } // When we scan a `json` file (meaning a file in `json` format) which was created from the SBOM file. // e.g. for use in `convert` mode. // See https://github.com/aquasecurity/trivy/issues/6780 @@ -253,14 +252,50 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { } } -// existedPkgIdentifier tries to look for package identifier (BOM-ref, PURL) by component name and component type -func (e *Encoder) existedPkgIdentifier(name string, componentType core.ComponentType) ftypes.PkgIdentifier { - for _, c := range e.components { - if c.Name == name && c.Type == componentType { - return c.PkgIdentifier +// reuseExistingBOM preserves the original SBOM structure and only updates the vulnerabilities section +// with newly detected vulnerabilities. This method handles two use cases: +// 1. SBOM scanning (CycloneDX): When scanning an existing SBOM file to refresh vulnerability data while +// preserving the original structure, components, and relationships +// e.g. $ trivy sbom sbom.cdx.json --scanners vuln --format cyclonedx +// 2. Library usage: When using Trivy as a library with a pre-existing custom BOM that needs +// to be enriched with vulnerability information +// +// For SBOM scanning (case 1), this approach is CycloneDX-specific +// because: SPDX 2.3 does not include vulnerabilities in the SBOM specification. +// Therefore, the method uses BOM-Ref for component-vulnerability lookup rather than SPDX-ID. +func (e *Encoder) reuseExistingBOM(report types.Report) (*core.BOM, error) { + bom := report.BOM.Clone() + + // Create a lookup map from BOM-Ref to component for efficient vulnerability assignment + // BOM-Ref is used as the key because it's the standard identifier in CycloneDX format + // and is guaranteed to be present in components from CycloneDX SBOMs + components := lo.MapKeys(report.BOM.Components(), func(v *core.Component, _ uuid.UUID) string { + return v.PkgIdentifier.BOMRef + }) + + for _, result := range report.Results { + // Group newly detected vulnerabilities by their component's BOM-Ref + vulns := make(map[string][]core.Vulnerability) + for _, vuln := range result.Vulnerabilities { + vulns[vuln.PkgIdentifier.BOMRef] = append(vulns[vuln.PkgIdentifier.BOMRef], e.vulnerability(vuln)) + } + + // Associate vulnerabilities with their corresponding components in the SBOM + for bomRef, componentVulns := range vulns { + c, ok := components[bomRef] + if !ok { + // This should never happen in proper SBOM rescanning because vulnerabilities + // should only be detected for components that exist in the original SBOM + log.Warn("Skipping vulnerabilities for component not found in SBOM", + log.String("bom-ref", bomRef), + log.Int("vulnerabilities", len(componentVulns))) + continue + } + bom.AddVulnerabilities(c, componentVulns) } } - return ftypes.PkgIdentifier{} + + return bom, nil } func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound *ftypes.OS) *core.Component { @@ -285,10 +320,8 @@ func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound component.Version = osFound.Name } component.Type = core.TypeOS - component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) case types.ClassLangPkg: component.Type = core.TypeApplication - component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) } e.bom.AddRelationship(root, component, core.RelationshipContains) diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index c89ead10e22f..2d67f29c4c1b 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -1000,44 +1000,20 @@ func TestEncoder_Encode(t *testing.T) { SchemaVersion: 2, ArtifactName: "report.cdx.json", ArtifactType: ftypes.TypeCycloneDX, - Results: []types.Result{ - { - Target: "Java", - Type: ftypes.Jar, - Class: types.ClassLangPkg, - Packages: []ftypes.Package{ - { - ID: "org.apache.logging.log4j:log4j-core:2.23.1", - Name: "org.apache.logging.log4j:log4j-core", - Version: "2.23.1", - Identifier: ftypes.PkgIdentifier{ - UID: "6C0AE96901617503", - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.apache.logging.log4j", - Name: "log4j-core", - Version: "2.23.1", - }, - }, - FilePath: "log4j-core-2.23.1.jar", - }, - }, - }, - }, - BOM: newTestBOM(t), + BOM: newTestBOM(t), }, wantComponents: map[uuid.UUID]*core.Component{ uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): appComponent, - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): libComponent, + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000002"): libComponent, }, wantRels: map[uuid.UUID][]core.Relationship{ uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): { { - Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"), + Dependency: uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000002"), Type: core.RelationshipContains, }, }, - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): nil, + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000002"): nil, }, wantVulns: make(map[uuid.UUID][]core.Vulnerability), }, @@ -1047,44 +1023,13 @@ func TestEncoder_Encode(t *testing.T) { SchemaVersion: 2, ArtifactName: "report.cdx.json", ArtifactType: ftypes.TypeCycloneDX, - Results: []types.Result{ - { - Target: "Java", - Type: ftypes.Jar, - Class: types.ClassLangPkg, - Packages: []ftypes.Package{ - { - ID: "org.apache.logging.log4j:log4j-core:2.23.1", - Name: "org.apache.logging.log4j:log4j-core", - Version: "2.23.1", - Identifier: ftypes.PkgIdentifier{ - UID: "6C0AE96901617503", - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.apache.logging.log4j", - Name: "log4j-core", - Version: "2.23.1", - }, - }, - FilePath: "log4j-core-2.23.1.jar", - }, - }, - }, - }, - BOM: newTestBOM2(t), + BOM: newTestBOM2(t), }, wantComponents: map[uuid.UUID]*core.Component{ - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): fsComponent, - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): libComponent, + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): libComponent, }, wantRels: map[uuid.UUID][]core.Relationship{ - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { - { - Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), - Type: core.RelationshipContains, - }, - }, - uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): nil, + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): nil, }, wantVulns: make(map[uuid.UUID][]core.Vulnerability), }, @@ -1600,7 +1545,17 @@ var ( func newTestBOM(t *testing.T) *core.BOM { uuid.SetFakeUUID(t, "2ff14136-e09f-4df9-80ea-%012d") bom := core.NewBOM(core.Options{}) - bom.AddComponent(appComponent) + + // Copy components to avoid UUID conflicts between tests + appComp := appComponent.Clone() + libComp := libComponent.Clone() + + bom.AddComponent(appComp) + bom.AddComponent(libComp) + // Add Contains relationship between appComponent and libComponent + bom.AddRelationship(appComp, libComp, core.RelationshipContains) + // Add empty relationship for libComponent to preserve structure for SBOM rescanning + bom.AddRelationship(libComp, nil, core.RelationshipDependsOn) return bom } @@ -1608,6 +1563,12 @@ func newTestBOM(t *testing.T) *core.BOM { func newTestBOM2(t *testing.T) *core.BOM { uuid.SetFakeUUID(t, "2ff14136-e09f-4df9-80ea-%012d") bom := core.NewBOM(core.Options{}) - bom.AddComponent(libComponent) + + // Copy component to avoid UUID conflicts between tests + libComp := libComponent.Clone() + + bom.AddComponent(libComp) + // Add empty relationship for libComponent to preserve structure for SBOM rescanning + bom.AddRelationship(libComp, nil, core.RelationshipDependsOn) return bom } diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index 6d9f25e92cd0..fe7cd2d80590 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -17,6 +17,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" "github.com/aquasecurity/trivy/pkg/vex" ) @@ -156,6 +157,9 @@ func TestFilter(t *testing.T) { // Set up the OCI registry tr, d := setUpRegistry(t) + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + testCycloneDXSBOM := createCycloneDXBOMWithSpringComponent() + type args struct { report *types.Report opts vex.Options @@ -329,10 +333,7 @@ func TestFilter(t *testing.T) { args: args{ report: &types.Report{ ArtifactType: ftypes.TypeCycloneDX, - BOM: &core.BOM{ - SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", - Version: 1, - }, + BOM: testCycloneDXSBOM, Results: []types.Result{ springResult(types.Result{ Vulnerabilities: []types.DetectedVulnerability{vuln1}, @@ -350,10 +351,7 @@ func TestFilter(t *testing.T) { }, want: &types.Report{ ArtifactType: ftypes.TypeCycloneDX, - BOM: &core.BOM{ - SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", - Version: 1, - }, + BOM: testCycloneDXSBOM, Results: []types.Result{ springResult(types.Result{ Vulnerabilities: []types.DetectedVulnerability{}, @@ -617,6 +615,22 @@ func ociPURLString(ts *httptest.Server, d v1.Hash) string { return p.String() } +func createCycloneDXBOMWithSpringComponent() *core.BOM { + bom := core.NewBOM(core.Options{}) + bom.SerialNumber = "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" + bom.Version = 1 + // Add the spring component to match vuln1's BOM-Ref + springComponent := &core.Component{ + Type: core.TypeLibrary, + Name: springPackage.Identifier.PURL.Name, + Group: springPackage.Identifier.PURL.Namespace, + Version: springPackage.Version, + PkgIdentifier: springPackage.Identifier, + } + bom.AddComponent(springComponent) + return bom +} + func fsReport(results types.Results) *types.Report { return &types.Report{ ArtifactName: ".", From 171a1304c9168631da0fc54357ad03500837b63c Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Thu, 4 Sep 2025 16:18:48 +0400 Subject: [PATCH 2/7] fix(sbom): preserve external property names in CycloneDX format Add External field to Property struct to distinguish between Trivy-generated and external properties. External properties maintain their original names while Trivy properties receive namespace prefixes during marshaling. - Add External bool field to core.Property struct - Update unmarshalProperties to detect and mark external properties - Modify marshal logic to preserve external property names - Use strings.CutPrefix for efficient prefix detection - Use cmp.Or and lo.Ternary for clean conditional logic This ensures SPDX compatibility and prevents corruption of third-party SBOM property names during processing. --- pkg/sbom/core/bom.go | 3 +++ pkg/sbom/cyclonedx/marshal.go | 11 ++++++----- pkg/sbom/cyclonedx/unmarshal.go | 17 ++++++++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index c74b927053fc..49760361fad3 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -196,6 +196,9 @@ type Property struct { Name string Value string Namespace string + // External indicates if this property came from external source (not generated by Trivy) + // When false (default), Trivy namespace prefix will be applied during marshaling + External bool } type Properties []Property diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 394fd7b956c6..75dd2db93b3d 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -1,6 +1,7 @@ package cyclonedx import ( + "cmp" "context" "fmt" "net/url" @@ -358,13 +359,13 @@ func (m *Marshaler) normalizeLicense(license string) expression.Expression { func (*Marshaler) Properties(properties []core.Property) *[]cdx.Property { cdxProps := make([]cdx.Property, 0, len(properties)) for _, property := range properties { - namespace := Namespace - if property.Namespace != "" { - namespace = property.Namespace - } + namespace := cmp.Or(property.Namespace, Namespace) + + // External property preserves original name, Trivy property gets namespace prefix + name := lo.Ternary(property.External, property.Name, namespace+property.Name) cdxProps = append(cdxProps, cdx.Property{ - Name: namespace + property.Name, + Name: name, Value: property.Value, }) } diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 60dc733c3b08..ff3e1e1f5025 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -289,10 +289,21 @@ func (b *BOM) unmarshalSupplier(supplier *cdx.OrganizationalEntity) string { func (b *BOM) unmarshalProperties(properties *[]cdx.Property) []core.Property { var props []core.Property for _, p := range lo.FromPtr(properties) { - props = append(props, core.Property{ - Name: strings.TrimPrefix(p.Name, Namespace), + prop := core.Property{ Value: p.Value, - }) + } + + // If the property has the Trivy namespace prefix, it's a Trivy property + if name, found := strings.CutPrefix(p.Name, Namespace); found { + prop.Name = name + prop.External = false // Trivy property (default) + } else { + // External property - preserve the original name and mark as external + prop.Name = p.Name + prop.External = true + } + + props = append(props, prop) } return props } From f07c26cc238e049e523b823beb15e6ee287c27f9 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Thu, 4 Sep 2025 22:13:32 +0400 Subject: [PATCH 3/7] test: update SPDX golden files after Property struct changes The addition of the External field to core.Property struct changes the hash calculation in calcSPDXID, resulting in different SPDX IDs being generated. This is expected behavior and does not affect functionality. --- integration/testdata/conda-spdx.json.golden | 18 ++++++++--------- integration/testdata/julia-spdx.json.golden | 22 ++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index ed4cffddb766..dc980b75a3e0 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -3,7 +3,7 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "testdata/fixtures/repo/conda", - "documentNamespace": "http://trivy.dev/filesystem/testdata/fixtures/repo/conda-3ff14136-e09f-4df9-80ea-000000000004", + "documentNamespace": "http://trivy.dev/filesystem/testdata/fixtures/repo/conda-3ff14136-e09f-4df9-80ea-000000000005", "creationInfo": { "creators": [ "Organization: aquasecurity", @@ -14,7 +14,7 @@ "packages": [ { "name": "openssl", - "SPDXID": "SPDXRef-Package-22a178da112ac20a", + "SPDXID": "SPDXRef-Package-cb268df467bc826c", "versionInfo": "1.1.1q", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -43,7 +43,7 @@ }, { "name": "pip", - "SPDXID": "SPDXRef-Package-c22b9ee9a601ba6", + "SPDXID": "SPDXRef-Package-1378bb10fcebba63", "versionInfo": "22.2.2", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -118,22 +118,22 @@ }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-22a178da112ac20a", + "relatedSpdxElement": "SPDXRef-Package-1378bb10fcebba63", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-c22b9ee9a601ba6", + "relatedSpdxElement": "SPDXRef-Package-cb268df467bc826c", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-22a178da112ac20a", - "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", + "spdxElementId": "SPDXRef-Package-1378bb10fcebba63", + "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-c22b9ee9a601ba6", - "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", + "spdxElementId": "SPDXRef-Package-cb268df467bc826c", + "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", "relationshipType": "CONTAINS" } ] diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 3a4ea79a3ed2..a2ded3e93395 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -14,7 +14,7 @@ "packages": [ { "name": "Manifest.toml", - "SPDXID": "SPDXRef-Application-18fc3597717a3e56", + "SPDXID": "SPDXRef-Application-c39d15beb6bdf085", "downloadLocation": "NONE", "filesAnalyzed": false, "primaryPackagePurpose": "APPLICATION", @@ -35,7 +35,7 @@ }, { "name": "A", - "SPDXID": "SPDXRef-Package-761ce79b41d8f121", + "SPDXID": "SPDXRef-Package-3aea0b160c3af98d", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -68,7 +68,7 @@ }, { "name": "B", - "SPDXID": "SPDXRef-Package-28f04edc422602a", + "SPDXID": "SPDXRef-Package-2264d5c424c073e7", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -101,7 +101,7 @@ }, { "name": "B", - "SPDXID": "SPDXRef-Package-6e0b0d1825d8c02c", + "SPDXID": "SPDXRef-Package-e29bcba688483642", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -150,13 +150,13 @@ ], "relationships": [ { - "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-6e0b0d1825d8c02c", + "spdxElementId": "SPDXRef-Application-c39d15beb6bdf085", + "relatedSpdxElement": "SPDXRef-Package-3aea0b160c3af98d", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-761ce79b41d8f121", + "spdxElementId": "SPDXRef-Application-c39d15beb6bdf085", + "relatedSpdxElement": "SPDXRef-Package-e29bcba688483642", "relationshipType": "CONTAINS" }, { @@ -166,12 +166,12 @@ }, { "spdxElementId": "SPDXRef-Filesystem-1be792dd0077c431", - "relatedSpdxElement": "SPDXRef-Application-18fc3597717a3e56", + "relatedSpdxElement": "SPDXRef-Application-c39d15beb6bdf085", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-761ce79b41d8f121", - "relatedSpdxElement": "SPDXRef-Package-28f04edc422602a", + "spdxElementId": "SPDXRef-Package-3aea0b160c3af98d", + "relatedSpdxElement": "SPDXRef-Package-2264d5c424c073e7", "relationshipType": "DEPENDS_ON" } ] From d309c4f5a701e64e6e42660bb309ca630036cccb Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Mon, 15 Sep 2025 17:01:11 +0400 Subject: [PATCH 4/7] fix(sbom): enable BOM-Ref auto-generation for CycloneDX formats - Enable GenerateBOMRef option for CycloneDX JSON and attestation formats - Keep GenerateBOMRef disabled for SPDX formats (no BOM-Ref concept) - Add test case for components with missing BOM-Ref fields - Verify that missing BOM-Refs are auto-generated from PURL --- pkg/fanal/artifact/sbom/sbom_test.go | 83 +++++++++++++++++ .../sbom/testdata/bom-missing-refs.json | 89 +++++++++++++++++++ pkg/sbom/sbom.go | 7 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 pkg/fanal/artifact/sbom/testdata/bom-missing-refs.json diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index 53cadf905123..327e8ba7800a 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -367,6 +367,89 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, + { + name: "components with missing BOM-REF", + filePath: filepath.Join("testdata", "bom-missing-refs.json"), + wantBlobs: []cachetest.WantBlob{ + { + ID: "sha256:512b9e999c9d7b4880c63ce55c2c74ea5c22b05cdbcb486097a16ec692c746a0", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.16.0", + }, + PackageInfos: []types.PackageInfo{ + { + Packages: types.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Layer: types.Layer{ + DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + // BOM-Ref should be auto-generated from PURL + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + }, + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "", + Packages: types.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + // BOM-Ref should be auto-generated from PURL + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + }, + }, + }, + }, + }, + }, + }, + want: artifact.Reference{ + Name: filepath.Join("testdata", "bom-missing-refs.json"), + Type: types.TypeCycloneDX, + ID: "sha256:512b9e999c9d7b4880c63ce55c2c74ea5c22b05cdbcb486097a16ec692c746a0", + BlobIDs: []string{ + "sha256:512b9e999c9d7b4880c63ce55c2c74ea5c22b05cdbcb486097a16ec692c746a0", + }, + }, + }, { name: "sad path with no such directory", filePath: filepath.Join("testdata", "unknown.json"), diff --git a/pkg/fanal/artifact/sbom/testdata/bom-missing-refs.json b/pkg/fanal/artifact/sbom/testdata/bom-missing-refs.json new file mode 100644 index 000000000000..949c89644971 --- /dev/null +++ b/pkg/fanal/artifact/sbom/testdata/bom-missing-refs.json @@ -0,0 +1,89 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "type": "library", + "name": "musl", + "version": "1.2.3-r0", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + } + ] + }, + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + }, + { + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + } + ], + "dependencies": [ + { + "ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182" + }, + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "dependsOn": [ + "60e9f57b-d4a6-4f71-ad14-0893ac609182" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go index 6a943e3f2822..65f2e7d3f083 100644 --- a/pkg/sbom/sbom.go +++ b/pkg/sbom/sbom.go @@ -184,18 +184,20 @@ func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { func Decode(ctx context.Context, f io.Reader, format Format) (types.SBOM, error) { var ( v any - bom = core.NewBOM(core.Options{}) + bom *core.BOM decoder interface{ Decode(any) error } ) switch format { case FormatCycloneDXJSON: + bom = core.NewBOM(core.Options{GenerateBOMRef: true}) v = &cyclonedx.BOM{BOM: bom} decoder = json.NewDecoder(f) case FormatAttestCycloneDXJSON: // dsse envelope // => in-toto attestation // => CycloneDX JSON + bom = core.NewBOM(core.Options{GenerateBOMRef: true}) v = &attestation.Statement{ Predicate: &cyclonedx.BOM{BOM: bom}, } @@ -205,6 +207,7 @@ func Decode(ctx context.Context, f io.Reader, format Format) (types.SBOM, error) // => in-toto attestation // => cosign predicate // => CycloneDX JSON + bom = core.NewBOM(core.Options{GenerateBOMRef: true}) v = &attestation.Statement{ Predicate: &attestation.CosignPredicate{ Data: &cyclonedx.BOM{BOM: bom}, @@ -212,9 +215,11 @@ func Decode(ctx context.Context, f io.Reader, format Format) (types.SBOM, error) } decoder = json.NewDecoder(f) case FormatSPDXJSON: + bom = core.NewBOM(core.Options{}) v = &spdx.SPDX{BOM: bom} decoder = json.NewDecoder(f) case FormatSPDXTV: + bom = core.NewBOM(core.Options{}) v = &spdx.SPDX{BOM: bom} decoder = spdx.NewTVDecoder(f) default: From b62ab485c55a7f51e37088764ba85acc150f5c07 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 19 Sep 2025 16:10:14 +0600 Subject: [PATCH 5/7] feat: add warning about filter flags for sbom-to-sbom-scan --- pkg/commands/app.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 06a9a35dd2fe..3665400ea173 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "slices" "time" "github.com/spf13/cobra" @@ -20,6 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" k8scommands "github.com/aquasecurity/trivy/pkg/k8s/commands" "github.com/aquasecurity/trivy/pkg/log" @@ -1214,6 +1216,13 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return xerrors.Errorf("flag error: %w", err) } + // For SBOM-to-SBOM scanning (for example, to add vulnerabilities to the SBOM file), we should not modify the scanned file. + // cf. https://github.com/aquasecurity/trivy/pull/9439#issuecomment-3295533665 + if slices.Contains(types.SupportedSBOMFormats, options.Format) && + (!slices.Equal(options.PkgTypes, types.PkgTypes) || !slices.Equal(options.PkgRelationships, ftypes.Relationships)) { + log.Warnf("Trivy doesn't support '--pkg-types' and '--pkg-relationships' options for SBOM to SBOM scan. These options will be ignored.") + } + return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) }, SilenceErrors: true, From 7a59f28075ce01d4337a46787061a2022834dc20 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Fri, 19 Sep 2025 15:59:34 +0400 Subject: [PATCH 6/7] refactor: move and improve package filtering validation - Move SBOM package filtering validation from app.go to run.go - Consolidate validation logic into checkOptions function - Include client/server mode warning alongside package filtering warning - Improve warning message clarity for SBOM to SBOM scanning - Add TODO comment for future refactoring --- pkg/commands/app.go | 6 ------ pkg/commands/artifact/run.go | 33 ++++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 3665400ea173..591236fbf134 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -1216,12 +1216,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return xerrors.Errorf("flag error: %w", err) } - // For SBOM-to-SBOM scanning (for example, to add vulnerabilities to the SBOM file), we should not modify the scanned file. - // cf. https://github.com/aquasecurity/trivy/pull/9439#issuecomment-3295533665 - if slices.Contains(types.SupportedSBOMFormats, options.Format) && - (!slices.Equal(options.PkgTypes, types.PkgTypes) || !slices.Equal(options.PkgRelationships, ftypes.Relationships)) { - log.Warnf("Trivy doesn't support '--pkg-types' and '--pkg-relationships' options for SBOM to SBOM scan. These options will be ignored.") - } return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) }, diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index a18ab636a8fe..8cbb6fd9d2ea 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -374,15 +374,6 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } }() - if opts.ServerAddr != "" && opts.Scanners.AnyEnabled(types.MisconfigScanner, types.SecretScanner) { - log.WarnContext(ctx, - fmt.Sprintf( - "Trivy runs in client/server mode, but misconfiguration and license scanning will be done on the client side, see %s", - doc.URL("/docs/references/modes/client-server", ""), - ), - ) - } - if opts.GenerateDefaultConfig { log.Info("Writing the default config to trivy-default.yaml...") @@ -423,6 +414,9 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } func run(ctx context.Context, opts flag.Options, targetKind TargetKind) (types.Report, error) { + // Perform validation checks + checkOptions(ctx, opts, targetKind) + r, err := NewRunner(ctx, opts, targetKind) if err != nil { if errors.Is(err, SkipScan) { @@ -466,6 +460,27 @@ func run(ctx context.Context, opts flag.Options, targetKind TargetKind) (types.R return report, nil } +// checkOptions performs various checks on scan options and shows warnings +func checkOptions(ctx context.Context, opts flag.Options, targetKind TargetKind) { + // Check client/server mode with misconfiguration and secret scanning + if opts.ServerAddr != "" && opts.Scanners.AnyEnabled(types.MisconfigScanner, types.SecretScanner) { + log.WarnContext(ctx, + fmt.Sprintf( + "Trivy runs in client/server mode, but misconfiguration and license scanning will be done on the client side, see %s", + doc.URL("/docs/references/modes/client-server", ""), + ), + ) + } + + // Check SBOM to SBOM scanning with package filtering flags + // For SBOM-to-SBOM scanning (for example, to add vulnerabilities to the SBOM file), we should not modify the scanned file. + // cf. https://github.com/aquasecurity/trivy/pull/9439#issuecomment-3295533665 + if targetKind == TargetSBOM && slices.Contains(types.SupportedSBOMFormats, opts.Format) && + (!slices.Equal(opts.PkgTypes, types.PkgTypes) || !slices.Equal(opts.PkgRelationships, ftypes.Relationships)) { + log.Warn("'--pkg-types' and '--pkg-relationships' options will be ignored when scanning SBOM and outputting SBOM format.") + } +} + func disabledAnalyzers(opts flag.Options) []analyzer.Type { // Specified analyzers to be disabled depending on scanning modes // e.g. The 'image' subcommand should disable the lock file scanning. From 360e677f389f9759452a32e7540b48c61ee80d35 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Fri, 19 Sep 2025 16:19:33 +0400 Subject: [PATCH 7/7] fix: lint issues Signed-off-by: knqyf263 --- pkg/commands/app.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 591236fbf134..06a9a35dd2fe 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "os" - "slices" "time" "github.com/spf13/cobra" @@ -21,7 +20,6 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" k8scommands "github.com/aquasecurity/trivy/pkg/k8s/commands" "github.com/aquasecurity/trivy/pkg/log" @@ -1216,7 +1214,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return xerrors.Errorf("flag error: %w", err) } - return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) }, SilenceErrors: true,