diff --git a/syft/format/common/spdxhelpers/to_format_model.go b/syft/format/common/spdxhelpers/to_format_model.go index 4adf40e68..73a751555 100644 --- a/syft/format/common/spdxhelpers/to_format_model.go +++ b/syft/format/common/spdxhelpers/to_format_model.go @@ -628,6 +628,12 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) { comment = fmt.Sprintf("layerID: %s", coordinates.FileSystemID) } + relativePath, err := convertAbsoluteToRelative(coordinates.RealPath) + if err != nil { + log.Debugf("unable to convert relative path '%s' to absolute path: %s", coordinates.RealPath, err) + relativePath = coordinates.RealPath + } + results = append(results, &spdx.File{ FileSPDXIdentifier: toSPDXID(coordinates), FileComment: comment, @@ -635,7 +641,7 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) { LicenseConcluded: noAssertion, FileCopyrightText: noAssertion, Checksums: toFileChecksums(digests), - FileName: coordinates.RealPath, + FileName: relativePath, FileTypes: toFileTypes(metadata), LicenseInfoInFiles: []string{ // required in SPDX 2.2 helpers.NOASSERTION, @@ -833,3 +839,22 @@ func trimPatchVersion(semver string) string { } return semver } + +// spdx requires that the file name field is a relative filename +// with the root of the package archive or directory +func convertAbsoluteToRelative(absPath string) (string, error) { + // Ensure the absolute path is absolute (although it should already be) + if !path.IsAbs(absPath) { + // already relative + log.Debugf("%s is already relative", absPath) + return absPath, nil + } + + // we use "/" here given that we're converting absolute paths from root to relative + relPath, found := strings.CutPrefix(absPath, "/") + if !found { + return "", fmt.Errorf("error calculating relative path: %s", absPath) + } + + return relPath, nil +} diff --git a/syft/format/common/spdxhelpers/to_format_model_test.go b/syft/format/common/spdxhelpers/to_format_model_test.go index 36201bc85..315899b74 100644 --- a/syft/format/common/spdxhelpers/to_format_model_test.go +++ b/syft/format/common/spdxhelpers/to_format_model_test.go @@ -382,6 +382,51 @@ func Test_toPackageChecksums(t *testing.T) { } } +func Test_toFiles(t *testing.T) { + tests := []struct { + name string + in sbom.SBOM + want spdx.File + }{ + { + name: "File paths are converted to relative in final SPDX collection", + in: sbom.SBOM{ + Source: source.Description{ + Name: "alpine", + Version: "sha256:d34db33f", + Metadata: source.ImageMetadata{ + UserInput: "alpine:latest", + ManifestDigest: "sha256:d34db33f", + }, + }, + Artifacts: sbom.Artifacts{ + Packages: pkg.NewCollection(pkg.Package{ + Name: "pkg-1", + Version: "version-1", + }), + FileMetadata: map[file.Coordinates]file.Metadata{ + { + RealPath: "/some/path", + FileSystemID: "", + }: { + Path: "/some/path", + }, + }, + }, + }, + want: spdx.File{ + FileName: "some/path", + }, + }, + } + + for _, test := range tests { + files := toFiles(test.in) + got := files[0] + assert.Equal(t, test.want.FileName, got.FileName) + } +} + func Test_toFileTypes(t *testing.T) { tests := []struct { diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden index 8614a8c6d..8587d7bc0 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden @@ -59,7 +59,7 @@ ], "files": [ { - "fileName": "/some/file", + "fileName": "some/file", "SPDXID": "SPDXRef-File-some-file-2c5bc344430decac", "checksums": [ { diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 138166baa..fd11fcc8b 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -89,7 +89,7 @@ ], "files": [ { - "fileName": "/a1/f6", + "fileName": "a1/f6", "SPDXID": "SPDXRef-File-a1-f6-9c2f7510199b17f6", "fileTypes": [ "OTHER" @@ -107,7 +107,7 @@ "copyrightText": "NOASSERTION" }, { - "fileName": "/d1/f3", + "fileName": "d1/f3", "SPDXID": "SPDXRef-File-d1-f3-c6f5b29dca12661f", "fileTypes": [ "OTHER" @@ -125,7 +125,7 @@ "copyrightText": "NOASSERTION" }, { - "fileName": "/d2/f4", + "fileName": "d2/f4", "SPDXID": "SPDXRef-File-d2-f4-c641caa71518099f", "fileTypes": [ "OTHER" @@ -143,7 +143,7 @@ "copyrightText": "NOASSERTION" }, { - "fileName": "/f1", + "fileName": "f1", "SPDXID": "SPDXRef-File-f1-5265a4dde3edbf7c", "fileTypes": [ "OTHER" @@ -161,7 +161,7 @@ "copyrightText": "NOASSERTION" }, { - "fileName": "/f2", + "fileName": "f2", "SPDXID": "SPDXRef-File-f2-f9e49132a4b96ccd", "fileTypes": [ "OTHER" @@ -179,7 +179,7 @@ "copyrightText": "NOASSERTION" }, { - "fileName": "/z1/f5", + "fileName": "z1/f5", "SPDXID": "SPDXRef-File-z1-f5-839d99ee67d9d174", "fileTypes": [ "OTHER" diff --git a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index ac7a8585d..b7940ae72 100644 --- a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -10,7 +10,7 @@ Created: redacted ##### Unpackaged files -FileName: /a1/f6 +FileName: a1/f6 SPDXID: SPDXRef-File-a1-f6-9c2f7510199b17f6 FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000 @@ -18,7 +18,7 @@ LicenseConcluded: NOASSERTION LicenseInfoInFile: NOASSERTION FileCopyrightText: NOASSERTION -FileName: /d1/f3 +FileName: d1/f3 SPDXID: SPDXRef-File-d1-f3-c6f5b29dca12661f FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000 @@ -26,7 +26,7 @@ LicenseConcluded: NOASSERTION LicenseInfoInFile: NOASSERTION FileCopyrightText: NOASSERTION -FileName: /d2/f4 +FileName: d2/f4 SPDXID: SPDXRef-File-d2-f4-c641caa71518099f FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000 @@ -34,7 +34,7 @@ LicenseConcluded: NOASSERTION LicenseInfoInFile: NOASSERTION FileCopyrightText: NOASSERTION -FileName: /f1 +FileName: f1 SPDXID: SPDXRef-File-f1-5265a4dde3edbf7c FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000 @@ -42,7 +42,7 @@ LicenseConcluded: NOASSERTION LicenseInfoInFile: NOASSERTION FileCopyrightText: NOASSERTION -FileName: /f2 +FileName: f2 SPDXID: SPDXRef-File-f2-f9e49132a4b96ccd FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000 @@ -50,7 +50,7 @@ LicenseConcluded: NOASSERTION LicenseInfoInFile: NOASSERTION FileCopyrightText: NOASSERTION -FileName: /z1/f5 +FileName: z1/f5 SPDXID: SPDXRef-File-z1-f5-839d99ee67d9d174 FileType: OTHER FileChecksum: SHA1: 0000000000000000000000000000000000000000