From 41de49ec1d0a4cc0cf93f24feb4560ce2896f9cc Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 29 Jun 2026 11:15:37 +0200 Subject: [PATCH] feat(publish-image): Attest SPDX SBOM alongside CycloneDX --- publish-image/README.md | 7 ++++--- publish-image/action.yaml | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/publish-image/README.md b/publish-image/README.md index 2d58070..68351c6 100644 --- a/publish-image/README.md +++ b/publish-image/README.md @@ -9,9 +9,10 @@ following work: `image-repository`. 2. Push the container image to the specified registry. 3. Sign the container image (which pushes the signature to the specified registry). -4. Generate an SBOM via a syft scan. -5. Attest an image with the SBOM as a predicate (which pushes the attestation to the specified - registry). +4. Generate a CycloneDX SBOM via a syft scan and merge its components. +5. Convert the merged CycloneDX SBOM to SPDX (normalising deprecated license ids). +6. Attest the image with both the CycloneDX and SPDX SBOMs as predicates (which pushes the + attestations to the specified registry). ## Inputs and Outputs diff --git a/publish-image/action.yaml b/publish-image/action.yaml index c3cf0e8..923d9a4 100644 --- a/publish-image/action.yaml +++ b/publish-image/action.yaml @@ -193,3 +193,25 @@ runs: --predicate sbom.merged.json \ --type cyclonedx \ "${IMAGE_REPOSITORY_DIGEST}" + + # Derive an SPDX SBOM from the merged CycloneDX SBOM. We only generate + # CycloneDX, SPDX is produced by conversion so both formats describe the + # same merged, deduplicated inventory. + syft convert sbom.merged.json --output spdx-json=sbom.spdx.raw.json + + # Normalise deprecated SPDX license identifiers that strict validators and + # some consumers reject. syft still emits the deprecated standalone form + # "GPL-2.0-with-classpath-exception"; rewrite it to the valid WITH + # expression. Extend the `fix` chain if further deprecated ids show up. + jq ' + def fix: gsub("GPL-2.0-with-classpath-exception"; "GPL-2.0-only WITH Classpath-exception-2.0"); + (.. | .licenseDeclared? // empty) |= (if type == "string" then fix else . end) + | (.. | .licenseConcluded? // empty) |= (if type == "string" then fix else . end) + ' sbom.spdx.raw.json > sbom.spdx.json + + # Attest the SPDX SBOM to the image + "$GITHUB_ACTION_PATH/../.scripts/actions/retry.sh" cosign attest \ + --yes \ + --predicate sbom.spdx.json \ + --type spdxjson \ + "${IMAGE_REPOSITORY_DIGEST}"