diff --git a/syft/format/common/cyclonedxhelpers/to_format_model.go b/syft/format/common/cyclonedxhelpers/to_format_model.go
index 15f34a71d..6ad7e8102 100644
--- a/syft/format/common/cyclonedxhelpers/to_format_model.go
+++ b/syft/format/common/cyclonedxhelpers/to_format_model.go
@@ -1,6 +1,7 @@
package cyclonedxhelpers
import (
+ "fmt"
"slices"
"strings"
"time"
@@ -84,8 +85,9 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
}
return []cyclonedx.Component{
{
- Type: cyclonedx.ComponentTypeOS,
- // FIXME is it idiomatic to be using SWID here for specific name and version information?
+ BOMRef: toOSBomRef(distro.ID, distro.VersionID),
+ Type: cyclonedx.ComponentTypeOS,
+ // is it idiomatic to be using SWID here for specific name and version information?
SWID: &cyclonedx.SWID{
TagID: distro.ID,
Name: distro.ID,
@@ -94,7 +96,7 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
Description: distro.PrettyName,
Name: distro.ID,
Version: distro.VersionID,
- // TODO should we add a PURL?
+ // should we add a PURL?
CPE: formatCPE(distro.CPEName),
ExternalReferences: eRefs,
Properties: properties,
@@ -102,6 +104,16 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
}
}
+func toOSBomRef(name string, version string) string {
+ if name == "" {
+ return "os:unknown"
+ }
+ if version == "" {
+ return fmt.Sprintf("os:%s", name)
+ }
+ return fmt.Sprintf("os:%s@%s", name, version)
+}
+
func formatCPE(cpeString string) string {
c, err := cpe.NewAttributes(cpeString)
if err != nil {
diff --git a/syft/format/common/cyclonedxhelpers/to_format_model_test.go b/syft/format/common/cyclonedxhelpers/to_format_model_test.go
index 432643804..8ac465b90 100644
--- a/syft/format/common/cyclonedxhelpers/to_format_model_test.go
+++ b/syft/format/common/cyclonedxhelpers/to_format_model_test.go
@@ -11,6 +11,7 @@ import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers"
+ "github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
@@ -234,3 +235,89 @@ func Test_toBomDescriptor(t *testing.T) {
})
}
}
+
+func Test_toOsComponent(t *testing.T) {
+ tests := []struct {
+ name string
+ release linux.Release
+ expected cyclonedx.Component
+ }{
+ {
+ name: "basic os component",
+ release: linux.Release{
+ ID: "myLinux",
+ VersionID: "myVersion",
+ },
+ expected: cyclonedx.Component{
+ BOMRef: "os:myLinux@myVersion",
+ Type: cyclonedx.ComponentTypeOS,
+ Name: "myLinux",
+ Version: "myVersion",
+ SWID: &cyclonedx.SWID{
+ TagID: "myLinux",
+ Name: "myLinux",
+ Version: "myVersion",
+ },
+ Properties: &[]cyclonedx.Property{
+ {
+ Name: "syft:distro:id",
+ Value: "myLinux",
+ },
+ {
+ Name: "syft:distro:versionID",
+ Value: "myVersion",
+ },
+ },
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ gotSlice := toOSComponent(&test.release)
+ require.Len(t, gotSlice, 1)
+ got := gotSlice[0]
+ require.Equal(t, test.expected, got)
+ })
+ }
+}
+
+func Test_toOSBomRef(t *testing.T) {
+ tests := []struct {
+ name string
+ osName string
+ osVersion string
+ expected string
+ }{
+ {
+ name: "no name or version specified",
+ osName: "",
+ osVersion: "",
+ expected: "os:unknown",
+ },
+ {
+ name: "no version specified",
+ osName: "my-name",
+ osVersion: "",
+ expected: "os:my-name",
+ },
+ {
+ name: "no name specified",
+ osName: "",
+ osVersion: "my-version",
+ expected: "os:unknown",
+ },
+ {
+ name: "both name and version specified",
+ osName: "my-name",
+ osVersion: "my-version",
+ expected: "os:my-name@my-version",
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got := toOSBomRef(test.osName, test.osVersion)
+ require.Equal(t, test.expected, got)
+ })
+ }
+}
diff --git a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
index 4f1190ece..a39921ac8 100644
--- a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
+++ b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
@@ -91,6 +91,7 @@
]
},
{
+ "bom-ref":"redacted",
"type": "operating-system",
"name": "debian",
"version": "1.2.3",
diff --git a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
index 3b4844b02..5bbd1a51b 100644
--- a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
+++ b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
@@ -100,6 +100,7 @@
]
},
{
+ "bom-ref":"redacted",
"type": "operating-system",
"name": "debian",
"version": "1.2.3",
diff --git a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
index c56f6724b..9c52a354a 100644
--- a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
+++ b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
@@ -47,7 +47,7 @@
0
-
+
debian
1.2.3
debian
diff --git a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
index 8e0c8800f..c130be60d 100644
--- a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
+++ b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
@@ -50,7 +50,7 @@
0
-
+
debian
1.2.3
debian