Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/attestation/crafter/materials/csaf.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023-2025 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -146,10 +146,10 @@ func (i *CSAFCrafter) injectAnnotations(m *api.Attestation_Material, documentMap
if tracking, ok := documentMap["tracking"].(map[string]any); ok {
if generator, ok := tracking["generator"].(map[string]any); ok {
if engine, ok := generator["engine"].(map[string]any); ok {
if name, ok := engine["name"].(string); ok {
if name, ok := engine["name"].(string); ok && name != "" {
m.Annotations[AnnotationToolNameKey] = name
}
if version, ok := engine["version"].(string); ok {
if version, ok := engine["version"].(string); ok && version != "" {
Comment thread
jiparis marked this conversation as resolved.
m.Annotations[AnnotationToolVersionKey] = version
}
}
Expand Down
27 changes: 21 additions & 6 deletions pkg/attestation/crafter/materials/csaf_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2024-2025 The Chainloop Authors.
// Copyright 2024-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -99,11 +99,13 @@ func TestNewCSAFCrafter(t *testing.T) {

func TestCSAFCraft(t *testing.T) {
testCases := []struct {
name string
filePath string
wantErr string
digest string
schema *contractAPI.CraftingSchema_Material
name string
filePath string
wantErr string
digest string
schema *contractAPI.CraftingSchema_Material
annotations map[string]string
absentAnnotations []string
}{
{
name: "non-expected json file",
Expand Down Expand Up @@ -167,6 +169,10 @@ func TestCSAFCraft(t *testing.T) {
Name: "test",
Type: contractAPI.CraftingSchema_Material_CSAF_VEX,
},
annotations: map[string]string{
"chainloop.material.tool.name": "Secvisogram",
"chainloop.material.tool.version": "1.11.0",
},
},
{
name: "2.0 security advisory",
Expand Down Expand Up @@ -252,6 +258,15 @@ func TestCSAFCraft(t *testing.T) {
assert.Equal(&attestationApi.Attestation_Material_Artifact{
Id: "test", Digest: tc.digest, Name: strings.Split(tc.filePath, "/")[2],
}, got.GetArtifact())

for k, v := range tc.annotations {
assert.Equal(v, got.Annotations[k])
}

for _, k := range tc.absentAnnotations {
_, exists := got.Annotations[k]
assert.False(exists, "annotation %q should not be present", k)
}
})
}
}
18 changes: 13 additions & 5 deletions pkg/attestation/crafter/materials/cyclonedxjson.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2024-2025 The Chainloop Authors.
// Copyright 2024-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -214,8 +214,12 @@ func (i *CyclonedxJSONCrafter) extractMetadata(m *api.Attestation_Material, meta

// Maintain backward compatibility - keep legacy keys for the first tool
if len(tools) > 0 {
m.Annotations[AnnotationToolNameKey] = tools[0].Name
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
if tools[0].Name != "" {
m.Annotations[AnnotationToolNameKey] = tools[0].Name
}
if tools[0].Version != "" {
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
}
}

case *cyclonedxMetadataV15:
Expand All @@ -232,8 +236,12 @@ func (i *CyclonedxJSONCrafter) extractMetadata(m *api.Attestation_Material, meta

// Maintain backward compatibility - keep legacy keys for the first tool
if len(tools) > 0 {
m.Annotations[AnnotationToolNameKey] = tools[0].Name
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
if tools[0].Name != "" {
m.Annotations[AnnotationToolNameKey] = tools[0].Name
}
if tools[0].Version != "" {
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
}
}

default:
Expand Down
36 changes: 35 additions & 1 deletion pkg/attestation/crafter/materials/cyclonedxjson_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023-2025 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -74,6 +74,7 @@ func TestCyclonedxJSONCraft(t *testing.T) {
wantMainComponentKind string
wantMainComponentVersion string
annotations map[string]string
absentAnnotations []string
}{
{
name: "invalid path",
Expand Down Expand Up @@ -156,6 +157,34 @@ func TestCyclonedxJSONCraft(t *testing.T) {
"chainloop.material.sbom.vulnerabilities_report": "true",
},
},
{
name: "1.4 version with empty tool version",
filePath: "./testdata/sbom.cyclonedx-1.4-empty-tool-version.json",
wantDigest: "sha256:f0b15f1ae6cadb9f14e0071f14888bcbae03686580f8fc8dc0c838e6e96aaf0e",
wantFilename: "sbom.cyclonedx-1.4-empty-tool-version.json",
wantMainComponent: "test-app",
wantMainComponentKind: "application",
wantMainComponentVersion: "1.0.0",
annotations: map[string]string{
"chainloop.material.tool.name": "Hub",
"chainloop.material.tools": `["Hub"]`,
},
absentAnnotations: []string{"chainloop.material.tool.version"},
},
{
name: "1.5 version with empty tool version",
filePath: "./testdata/sbom.cyclonedx-1.5-empty-tool-version.json",
wantDigest: "sha256:4091c3b42ec5d368365f5b6fba80dd30cc3ddf84f795096e738b02f8e2716ce1",
wantFilename: "sbom.cyclonedx-1.5-empty-tool-version.json",
wantMainComponent: "test-app",
wantMainComponentKind: "application",
wantMainComponentVersion: "1.0.0",
annotations: map[string]string{
"chainloop.material.tool.name": "Hub",
"chainloop.material.tools": `["Hub"]`,
},
absentAnnotations: []string{"chainloop.material.tool.version"},
},
{
name: "1.5 version with multiple tools",
filePath: "./testdata/sbom.cyclonedx-1.5-multiple-tools.json",
Expand Down Expand Up @@ -221,6 +250,11 @@ func TestCyclonedxJSONCraft(t *testing.T) {
ast.Equal(v, got.Annotations[k])
}
}

for _, k := range tc.absentAnnotations {
_, exists := got.Annotations[k]
ast.False(exists, "annotation %q should not be present", k)
}
})
}
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/attestation/crafter/materials/sarif.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023-2025 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -69,8 +69,10 @@ func (i *SARIFCrafter) injectAnnotations(m *api.Attestation_Material, doc *sarif
if len(doc.Runs) > 0 {
// assuming vendor from first run.
m.Annotations = make(map[string]string)
m.Annotations[AnnotationToolNameKey] = doc.Runs[0].Tool.Driver.Name
if doc.Runs[0].Tool.Driver.Version != nil {
if doc.Runs[0].Tool.Driver.Name != "" {
m.Annotations[AnnotationToolNameKey] = doc.Runs[0].Tool.Driver.Name
}
if doc.Runs[0].Tool.Driver.Version != nil && *doc.Runs[0].Tool.Driver.Version != "" {
m.Annotations[AnnotationToolVersionKey] = *doc.Runs[0].Tool.Driver.Version
}
}
Expand Down
10 changes: 7 additions & 3 deletions pkg/attestation/crafter/materials/spdxjson.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023-2025 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -91,7 +91,11 @@ func (i *SPDXJSONCrafter) injectAnnotations(m *api.Attestation_Material, doc *sp

// Maintain backward compatibility - keep legacy keys for the first tool
if len(tools) > 0 {
m.Annotations[AnnotationToolNameKey] = tools[0].Name
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
if tools[0].Name != "" {
Comment thread
jiparis marked this conversation as resolved.
m.Annotations[AnnotationToolNameKey] = tools[0].Name
}
if tools[0].Version != "" {
m.Annotations[AnnotationToolVersionKey] = tools[0].Version
}
}
}
41 changes: 34 additions & 7 deletions pkg/attestation/crafter/materials/spdxjson_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023-2025 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -66,12 +66,13 @@ func TestNewSPDXJSONCrafter(t *testing.T) {

func TestSPDXJSONCraft(t *testing.T) {
testCases := []struct {
name string
filePath string
wantErr string
wantDigest string
wantFilename string
annotations map[string]string
name string
filePath string
wantErr string
wantDigest string
wantFilename string
annotations map[string]string
absentAnnotations []string
}{
{
name: "invalid sbom format",
Expand Down Expand Up @@ -99,6 +100,27 @@ func TestSPDXJSONCraft(t *testing.T) {
"chainloop.material.tools": `["syft@0.73.0"]`,
},
},
{
name: "tool with empty name",
filePath: "./testdata/sbom-spdx-empty-tool-name.json",
wantDigest: "sha256:bbc5c2f78c7b05f2e80295e1bf827f24623394dafcd56a4a14f0591b835d28fa",
wantFilename: "sbom-spdx-empty-tool-name.json",
absentAnnotations: []string{
"chainloop.material.tool.name",
"chainloop.material.tool.version",
},
},
{
name: "tool without version",
filePath: "./testdata/sbom-spdx-no-tool-version.json",
wantDigest: "sha256:c502fcd2944cc7aadf718af910a242cca0702387c27e740229d6e35cd1ccd52e",
wantFilename: "sbom-spdx-no-tool-version.json",
annotations: map[string]string{
"chainloop.material.tool.name": "spdxgen",
"chainloop.material.tools": `["spdxgen"]`,
},
absentAnnotations: []string{"chainloop.material.tool.version"},
},
{
name: "multiple tools",
filePath: "./testdata/sbom-spdx-multiple-tools.json",
Expand Down Expand Up @@ -155,6 +177,11 @@ func TestSPDXJSONCraft(t *testing.T) {
assert.Equal(v, got.Annotations[k])
}
}

for _, k := range tc.absentAnnotations {
_, exists := got.Annotations[k]
assert.False(exists, "annotation %q should not be present", k)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-empty-tool-name",
"documentNamespace": "https://example.com/test/empty-tool-name",
"creationInfo": {
"licenseListVersion": "3.20",
"creators": [
"Organization: Example Corp",
"Tool: "
],
"created": "2024-01-01T10:00:00Z"
},
"packages": [
{
"name": "example-package",
"SPDXID": "SPDXRef-Package-example",
"versionInfo": "1.0.0",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"copyrightText": "NOASSERTION"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-no-tool-version",
"documentNamespace": "https://example.com/test/no-tool-version",
"creationInfo": {
"licenseListVersion": "3.20",
"creators": [
"Organization: Example Corp",
"Tool: spdxgen"
],
"created": "2024-01-01T10:00:00Z"
},
"packages": [
{
"name": "example-package",
"SPDXID": "SPDXRef-Package-example",
"versionInfo": "1.0.0",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"copyrightText": "NOASSERTION"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2025-09-28T07:00:46Z",
"tools": [
{
"name": "Hub",
"version": ""
}
],
"component": {
"bom-ref": "test-component",
"type": "application",
"name": "test-app",
"version": "1.0.0"
}
},
"components": [
{
"bom-ref": "pkg:golang/example.com/test@v1.0.0",
"type": "library",
"name": "example.com/test",
"version": "v1.0.0",
"purl": "pkg:golang/example.com/test@v1.0.0"
}
]
}
Loading
Loading