Compare commits

...
Author SHA1 Message Date
Keith Zantow 7ea37792f1 chore: add a test
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-02-25 10:26:45 -05:00
Keith Zantow 823723f510 fix: typed nil hashes in cyclonedx
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-02-24 20:46:46 -05:00
2 changed files with 41 additions and 22 deletions
@@ -70,27 +70,16 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
continue
}
metadata = &fileMetadata
// Digests
var digests []file.Digest
if digestsForLocation, exists := artifacts.FileDigests[coordinate]; exists {
digests = digestsForLocation
}
cdxHashes := digestsToHashes(digests)
components = append(components, cyclonedx.Component{
BOMRef: string(coordinate.ID()),
Type: cyclonedx.ComponentTypeFile,
Name: metadata.Path,
Hashes: &cdxHashes,
Hashes: slicePtr(digestsToHashes(artifacts.FileDigests[coordinate])),
})
}
cdxBOM.Components = &components
cdxBOM.Components = slicePtr(components)
dependencies := toDependencies(s.Relationships)
if len(dependencies) > 0 {
cdxBOM.Dependencies = &dependencies
}
cdxBOM.Dependencies = slicePtr(toDependencies(s.Relationships))
return cdxBOM
}
@@ -277,14 +266,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
func toBomProperties(srcMetadata source.Description) *[]cyclonedx.Property {
metadata, ok := srcMetadata.Metadata.(source.ImageMetadata)
if ok {
props := helpers.EncodeProperties(metadata.Labels, "syft:image:labels")
// return nil if props is nil to avoid creating a pointer to a nil slice,
// which results in a null JSON value that does not comply with the CycloneDX schema.
// https://github.com/anchore/grype/issues/1759
if props == nil {
return nil
}
return &props
return slicePtr(helpers.EncodeProperties(metadata.Labels, "syft:image:labels"))
}
return nil
}
@@ -344,3 +326,14 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
return nil
}
// Every slice pointer needs to either be a valid slice or nil,
// otherwise we will create a pointer that is not nil but references
// a nil slice, which results in a `null` JSON value that does not
// comply with the CycloneDX schema. See: https://github.com/anchore/grype/issues/1759
func slicePtr[T any](values []T) *[]T {
if len(values) == 0 {
return nil
}
return &values
}
+26
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/internal/cyclonedxutil"
"github.com/anchore/syft/syft/format/internal/testutil"
"github.com/anchore/syft/syft/pkg"
@@ -27,6 +28,31 @@ func getEncoder(t testing.TB) sbom.FormatEncoder {
return enc
}
func Test_noTypedNils(t *testing.T) {
s := sbom.SBOM{
Artifacts: sbom.Artifacts{
FileMetadata: map[file.Coordinates]file.Metadata{},
FileDigests: map[file.Coordinates][]file.Digest{},
},
}
c := file.NewCoordinates("/dev/null", "123")
s.Artifacts.FileMetadata[c] = file.Metadata{
Path: "/dev/null",
}
s.Artifacts.FileDigests[c] = []file.Digest{}
enc, err := NewFormatEncoderWithConfig(EncoderConfig{
Version: "1.6",
Pretty: true,
})
require.NoError(t, err)
contents := bytes.Buffer{}
err = enc.Encode(&contents, s)
require.NoError(t, err)
require.NotContains(t, contents.String(), `"hashes": null`)
}
func TestPrettyOutput(t *testing.T) {
run := func(opt bool) string {
enc, err := NewFormatEncoderWithConfig(EncoderConfig{