From fcf07086b98c01284b394dadf8a37529870f05bb Mon Sep 17 00:00:00 2001 From: Alex Kantor Date: Thu, 17 Sep 2026 16:14:34 +0100 Subject: [PATCH] fix(ci): scan the image by digest and platform, not by tag The SBOM was generated from IMAGE:TAG. Syft resolves a multi-platform tag to whichever platform the runner is, and names that image. The artifact attested to Kosli is the index digest, so the SBOM's subject was never the artifact, and on the live attestation it was a digest that does not exist in the registry at all. Pinning the index digest and naming the platform makes the subject the index's entry for that platform, whatever the runner is. The step then asserts that, so the build fails rather than attesting an SBOM about something else. The SBOM still covers one platform of two. That is recorded on the attestation as sbom_platform rather than left to be inferred. verified: ran the step extracted from the YAML against ghcr, from an arm64 host. Pinned to the index with --platform linux/amd64 it produced subject 0a222bfc..., the index's amd64 entry, and passed. The same index with --platform linux/arm64 produced d7f84a11..., its arm64 entry. Scanning the tag on this host produced the arm64 digest, which is the defect. mutation: drop --platform -> the subject becomes the host's platform and the comparison fails by name. Tamper the subject in the file -> fails by name. Point DIGEST at a single manifest -> the index check fails by name. Remove the comparison and use the wrong platform -> exit 0, so the comparison is the only thing catching it. search: grep -rn container-sbom over the repo finds main-flow-template.yml, release-flow-template.yml and this step; no policy reads it, and no other step generates an SBOM from a tag. Snyk's container test still uses the tag, which is a scan and not an attested document. Co-Authored-By: Claude Opus 5 --- .github/workflows/docker.yml | 40 ++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b0c865e97..95eb26afe 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -207,6 +207,9 @@ jobs: artifact-metadata: write env: RESULTS_FILE: smoke-test-results.json + # Syft scans one platform. The image is multi-platform, so the SBOM covers + # this one only, and the attestation records which. + SBOM_PLATFORM: linux/amd64 steps: - name: Harden Runner @@ -289,13 +292,37 @@ jobs: subject-digest: ${{ steps.docker_build.outputs.digest }} push-to-registry: true + - name: Install syft + uses: anchore/sbom-action/download-syft@v0 + - name: Generate SBOM for the docker image - uses: anchore/sbom-action@v0 - with: - image: ${{ env.IMAGE }}:${{ inputs.tag }} - format: 'spdx-json' - output-file: 'sbom.spdx.json' - upload-artifact: false + env: + IMAGE: ${{ env.IMAGE }} + DIGEST: ${{ steps.docker_build.outputs.digest }} + run: | + syft -q -o spdx-json --platform "${SBOM_PLATFORM}" \ + "registry:${IMAGE}@${DIGEST}" > sbom.spdx.json + + # Syft names the platform image it scanned, never the index ${DIGEST} + # points at, so the subject has to be that platform's entry in it. + expected=$(docker buildx imagetools inspect "${IMAGE}@${DIGEST}" --raw \ + | jq -r --arg p "${SBOM_PLATFORM}" ' + .manifests[]? + | select((.platform.os + "/" + .platform.architecture) == $p) + | .digest') + subject=$(jq -r ' + (.relationships[] | select(.relationshipType == "DESCRIBES") | .relatedSpdxElement) as $root + | .packages[] | select(.SPDXID == $root) + | .checksums[]? | select(.algorithm == "SHA256") | .checksumValue + ' sbom.spdx.json) + + if [ -z "${expected}" ]; then + echo "::error::${IMAGE}@${DIGEST} is not a multi-platform index listing ${SBOM_PLATFORM}"; exit 1 + fi + if [ "sha256:${subject}" != "${expected}" ]; then + echo "::error::sbom.spdx.json describes sha256:${subject}, not the ${SBOM_PLATFORM} image ${expected}" + exit 1 + fi - name: Attest SBOM to Github uses: actions/attest@v4.2.2 @@ -333,6 +360,7 @@ jobs: --name container-sbom --fingerprint ${{ env.FINGERPRINT }} --sbom-file sbom.spdx.json + --annotate sbom_platform=${{ env.SBOM_PLATFORM }} --org ${{ inputs.kosli_org }} - name: Run Snyk Container Test to scan the Docker image for vulnerabilities