feat(workflows): publish SLSA provenance + SBOM attestations on every release tag (closes #58) - #94
Conversation
… release tag Closes #58. Adds two attestation flavours to docker-build.yml: - BuildKit-native: provenance: mode=max + sbom: true on the build step overrides build-push-action's default-off-when-push-by-digest behaviour. BuildKit emits per-arch attestation manifests; imagetools create walks each index digest so descriptors survive the manifest-list assembly. - GitHub-Sigstore: anchore/sbom-action generates a CycloneDX SBOM from the merged image; actions/attest-build-provenance and actions/attest-sbom bind Sigstore-signed attestations to the merged manifest digest, surfaced via the GitHub Attestations API and Docker Hub's "Build attestations" badge. Permissions: top-level grows attestations: read so build/scan can verify; merge job overrides locally with id-token: write + attestations: write. The scan job now calls gh attestation verify twice (once per predicate type) before Trivy - a hard regression gate against silent attestation drops. Docs (deployment.md + observability.md) document the consumer verification commands and registry/Sigstore/GitHub-API storage surfaces. CLAUDE.md CI/CD table and Recent Changes index updated.
…sub-list The prior commit's lint-staged hook turned `SLSA + SBOM` (with a leading plus on a continuation line) into a `- SBOM` sub-bullet. Inline the bullet so the rendered IMPLEMENT.md preserves the intended sentence.
|
bot workflow 🔍 Code review complete — 5 files, +289/-116. SummaryPR #94 wires SLSA v1 build provenance and CycloneDX/SPDX SBOMs into every Docker image the release pipeline publishes, with a What was checked
Findings[minor]
|
|
bot workflow 🔎 Resolve iteration complete — 0 failing checks, 1 open comment threads (some may already be resolved). Resolve iteration — PR #94Iteration date: 2026-05-02 SummaryPR #94 ( CI statusNo failing checks at the start of this iteration. The follow-up commit
No fix attempts consumed (start state was already 0 fail; the polling loop simply waited for the re-runs of the post-push suite). Review comments
Commits pushed
OutstandingNothing blocks merge from a resolve perspective:
cost: $4.5778 · turns: 3 · duration: 55s |
The Sigstore-signed CycloneDX SBOM that `actions/attest-sbom@v4` binds to the merged manifest digest is generated by `anchore/sbom-action` (Syft), which scans the runner's native architecture only — so on `ubuntu-24.04` it covers amd64 packages, not arm64. Tighten the table copy in `deployment.md` and `observability.md` to acknowledge this and direct arm64 audits to the per-arch BuildKit-attached SPDX SBOM (which already covers both arches). Resolves PR #94 inline review. Co-authored-by: chrisleekr-bot[bot] <chrisleekr-bot[bot]@users.noreply.github.com>
Co-authored-by: chrisleekr-bot[bot] <chrisleekr-bot[bot]@users.noreply.github.com>
Co-authored-by: chrisleekr-bot[bot] <chrisleekr-bot[bot]@users.noreply.github.com>
|
bot workflow 🔍 Code review complete — 6 files, +315/-132. Review — PR #94 (
|
| push-to-registry: true | ||
|
|
||
| - name: Attest SBOM | ||
| uses: actions/attest-sbom@v4 |
There was a problem hiding this comment.
💭 Suggestion | 🟡 Minor
actions/attest-sbom@v4 is deprecated upstream — every release run surfaces a workflow warning.
The v4 ref of actions/attest-sbom (latest tag, what @v4 resolves to today) is a thin composite shim around actions/attest@v4.1.0 whose first step prints ::warning::actions/attest-sbom has been deprecated, please use actions/attest instead (see actions/attest-sbom/action.yml@v4). Functionality is preserved — the wrapper auto-detects CycloneDX vs SPDX and synthesises the predicate (CycloneDX → https://cyclonedx.org/bom, which matches the gh attestation verify predicate type at .github/workflows/docker-build.yml:343). But every release tag will silently raise a deprecation warning in the merge job, and Renovate will eventually try to flip this to a successor. actions/attest-build-provenance@v4 (line 286) is not deprecated, so this only affects the Attest SBOM step.
Suggested fix: Switch to actions/attest@v4 directly: pass predicate-path: sbom-${{ matrix.variant }}.cyclonedx.json + predicate-type: https://cyclonedx.org/bom (the constant the wrapper already maps to for CycloneDX). Same Sigstore bundle, same registry push, no deprecation warning. Alternatively keep the wrapper and accept the warning until upstream removes the shim — low-risk either way.
There was a problem hiding this comment.
Valid finding — actions/attest-sbom@v4 is deprecated upstream and surfaces a ::warning:: in every release run. The recommended swap to actions/attest@v4 is a workflow-only edit; this resolve agent is environment-policy-blocked from modifying files under .github/workflows/, so it has been left for the maintainer to land directly.
Confirmed against actions/attest-sbom/action.yml@v4: the composite shim wraps actions/attest@v4.1.0 and its first step prints ::warning::actions/attest-sbom has been deprecated, please use actions/attest instead. CycloneDX → https://cyclonedx.org/bom mapping is preserved in either path, so the verify step at .github/workflows/docker-build.yml:343 keeps working unchanged. Suggested edit at .github/workflows/docker-build.yml:292-298:
- name: Attest SBOM
uses: actions/attest@v4
with:
subject-name: ${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.inspect.outputs.digest }}
predicate-path: "sbom-${{ matrix.variant }}.cyclonedx.json"
predicate-type: https://cyclonedx.org/bom
push-to-registry: trueSame Sigstore bundle, same registry push, no deprecation warning. actions/attest-build-provenance@v4 (line 286) is not affected — only the SBOM step.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds SLSA v1 provenance and CycloneDX SBOM attestations to the Docker build pipeline. The workflow now generates attestations during per-arch builds, publishes them to the registry after manifest-list creation via Sigstore, and verifies both attestation types before running Trivy scans. ChangesSupply-Chain Attestations for Docker Releases
Sequence DiagramsequenceDiagram
actor Developer
participant BuildAction as docker/build-push-action
participant BuildKit as BuildKit
participant Registry as Docker Registry
participant MergeStep as Merge Job
participant Sigstore as Sigstore
participant ScanJob as Scan Job
Developer->>BuildAction: Push multi-arch build (provenance: mode=max, sbom: true)
BuildAction->>BuildKit: Build per-arch images with attestation enabled
BuildKit->>BuildKit: Generate SLSA v1 provenance + SPDX SBOM
BuildKit->>Registry: Push 4 leaf manifests + attestation descriptors
BuildAction->>MergeStep: Emit per-arch digests & attestation metadata
MergeStep->>Registry: Assemble manifest list from leaf digests
MergeStep->>Registry: Extract merged digest from manifest list
MergeStep->>MergeStep: Generate CycloneDX SBOM from merged digest
MergeStep->>Sigstore: Sign SLSA provenance + CycloneDX SBOM
Sigstore->>Registry: Publish two Sigstore attestations to image subject
ScanJob->>Registry: Fetch published image
ScanJob->>ScanJob: gh attestation verify (SLSA predicate)
ScanJob->>ScanJob: gh attestation verify (CycloneDX predicate)
alt Both attestations valid
ScanJob->>ScanJob: Run Trivy scan
ScanJob->>Registry: Upload SARIF results
else Missing attestation
ScanJob-->>Developer: ❌ Release gate fails
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The PR spans workflow configuration with conditional logic, shell script integration for digest extraction and SBOM generation, permission scoping changes, and coordinated documentation updates across five files. The changes are cohesive but require verification of BuildKit attestation propagation through the merge step, permission correctness, and consistency across docs. Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
bot workflow 🔍 Code review complete — 6 files, +315/-132. Review — PR #94 (
|
| gh attestation verify "$IMAGE_REF" --repo "$REPO" \ | ||
| --predicate-type https://slsa.dev/provenance/v1 | ||
| gh attestation verify "$IMAGE_REF" --repo "$REPO" \ | ||
| --predicate-type https://cyclonedx.org/bom |
There was a problem hiding this comment.
💭 Suggestion | 🟡 Minor
Regression gate is Sigstore-only — a future drop of provenance: mode=max / sbom: true would silently lose the per-arch BuildKit SBOM that arm64 audits depend on.
gh attestation verify at .github/workflows/docker-build.yml:340-343 queries GitHub's Attestations API for the Sigstore-signed bundles emitted by actions/attest-build-provenance + actions/attest-sbom. It does not inspect the BuildKit-attached vnd.docker.reference.type=attestation-manifest siblings on the per-arch leaf manifests. Those siblings are the only source the docs direct arm64 supply-chain audits to (docs/operate/deployment.md:97: "the only source for arm64 package coverage"), because the Sigstore CycloneDX flavour is amd64-only. A future refactor that drops provenance: mode=max or sbom: true from the build step (.github/workflows/docker-build.yml:168-169) would still leave the Sigstore attestations intact, so this verify step would pass — but every arm64 audit downstream would silently get nothing. The PR description ("any future regression that drops an attestation") and docs/operate/deployment.md:95 over-read what the gate actually protects.
Suggested fix: Add a sibling check that fails the workflow if BuildKit's per-arch attestations aren't published — e.g. assert docker buildx imagetools inspect <ref> --format '{{ json .SBOM }}' and '{{ json .Provenance }}' are non-empty for both linux/amd64 and linux/arm64. That closes the BuildKit half of the gate without depending on Sigstore.
There was a problem hiding this comment.
Valid finding — gh attestation verify covers Sigstore-signed predicates only and won't catch a regression that drops the BuildKit-attached attestations on per-arch leaf manifests. The recommended imagetools inspect sibling check is a workflow-only edit; this resolve agent is environment-policy-blocked from modifying files under .github/workflows/, so it has been left for the maintainer to land directly.
The asymmetry matters because docs/operate/deployment.md:97 directs arm64 supply-chain audits at the BuildKit-attached SPDX SBOM (the Sigstore CycloneDX flavour is amd64-only after the 0f7fe9e doc tightening). A future refactor that drops provenance: mode=max or sbom: true from .github/workflows/docker-build.yml:168-169 would still leave the Sigstore attestations intact, so the existing verify step at .github/workflows/docker-build.yml:340-343 would pass — but every arm64 audit downstream would silently get nothing. Suggested addition just before the existing Verify image attestations step (or as a new step in merge):
- name: Verify per-arch BuildKit attestations
env:
IMAGE_REF: ${{ env.IMAGE_NAME }}:${{ steps.imgtag.outputs.tag }}
run: |
set -euo pipefail
for arch in linux/amd64 linux/arm64; do
sbom=$(docker buildx imagetools inspect "$IMAGE_REF" \
--format '{{ json (index .SBOM "'"$arch"'") }}')
prov=$(docker buildx imagetools inspect "$IMAGE_REF" \
--format '{{ json (index .Provenance "'"$arch"'") }}')
if [[ -z "$sbom" || "$sbom" == "null" ]]; then
echo "ERROR: missing BuildKit SBOM on $arch" >&2; exit 1
fi
if [[ -z "$prov" || "$prov" == "null" ]]; then
echo "ERROR: missing BuildKit provenance on $arch" >&2; exit 1
fi
doneThat closes the BuildKit half of the gate without depending on Sigstore. Alternatively, if the maintainer prefers to keep the gate Sigstore-only, the docs at deployment.md:95 and the PR description should be tightened to scope the "any future regression that drops an attestation" claim to Sigstore predicates only.
Co-authored-by: chrisleekr-bot[bot] <chrisleekr-bot[bot]@users.noreply.github.com>
|
bot workflow 🔎 Resolve iteration complete — 0 failing checks, 3 open comment threads (some may already be resolved). Resolve iteration — PR #94Iteration date: 2026-05-02 (second resolve pass, post-review iteration #2) SummaryPR #94 ( CI statusNo failing checks at the start of this iteration. No commits pushed, so CI was not re-triggered — final state is the same all-green ledger from
Zero fix attempts consumed against the FIX_ATTEMPTS_CAP=3 budget. Review comments
Commits pushedNone this iteration. Prior iterations' commits remain in place:
OutstandingTwo maintainer-action items block "ready to merge" from a strict resolve-clean perspective. Both are valid review findings that this resolve agent classified Valid but couldn't action because they require editing
Other notes:
cost: $3.0063 · turns: 5 · duration: 65s |
Two open review threads on `.github/workflows/docker-build.yml` (`@v4` deprecation + Sigstore-only regression gate) classified Valid but environment-policy-blocked from this resolve agent's edit allowlist. Replies posted with exact maintainer-action diffs; threads left open. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# [1.9.0](v1.8.0...v1.9.0) (2026-05-03) ### Bug Fixes * **checkout:** fetch PR base branch so origin/<baseBranch> resolves (closes [#74](#74)) ([#96](#96)) ([71f83a6](71f83a6)) * **fetcher:** paginate GraphQL connections + MAX_FETCHED_* caps (closes [#66](#66)) ([#95](#95)) ([f728ecd](f728ecd)) * **triage:** accept note-only evidence; raise research max-turns to 200 ([#97](#97)) ([3b6036c](3b6036c)) * **workflow:** fix release.yml ([#98](#98)) ([cb43d69](cb43d69)) ### Features * **workflows:** publish SLSA provenance + SBOM attestations on every release tag (closes [#58](#58)) ([#94](#94)) ([95856bc](95856bc))
|
🎉 This PR is included in version 1.9.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Wires SLSA v1 build provenance and CycloneDX/SPDX SBOMs into every Docker image the release pipeline publishes, and adds a hard regression gate (
gh attestation verify× 2 predicates) that fails the workflow if either attestation is silently dropped by a future refactor ofdocker-build.yml. All edits are additive YAML + matching docs — noDockerfile.*,package.json, orsrc/change. Closes #58.Two attestation flavours land per release tag:
provenance: mode=max+sbom: trueon the build step overridesdocker/build-push-action's default-off-when-push-by-digestbehaviour. BuildKit emits per-arch attestation manifests;imagetools createwalks each index digest so the descriptors survive the manifest-list assembly without extra CLI plumbing.anchore/sbom-actiongenerates a CycloneDX SBOM from the merged image;actions/attest-build-provenance@v4andactions/attest-sbom@v4bind Sigstore-signed attestations to the merged manifest digest, surfaced via the GitHub Attestations API and Docker Hub's "Build attestations" badge.Permissions follow least-privilege: top-level grows only
attestations: read(so the scan job can verify), and the merge job overrides locally withid-token: write+attestations: write. Build / scan stay read-only.Changes
push-by-digestinteraction. (T1)imagetools createwalks the per-arch index digests now that BuildKit attaches attestation siblings. (T2)id-token: write+attestations: writeto the merge job only; add top-levelattestations: readfor verification. (T3)anchore/sbom-action@v0, and publish Sigstore-signed provenance + SBOM viaactions/attest-build-provenance@v4+actions/attest-sbom@v4. (T4)Verify image attestationsstep in the scan job that callsgh attestation verifyonce per predicate type (SLSA + CycloneDX) before Trivy. Each call fails the job if its predicate is missing — regression gate. (T5)gh attestation verifyanddocker buildx imagetools inspect) and the registry / Sigstore / GitHub-API storage matrix. (T7, T8)CLAUDE.mdCI/CD table row fordocker-build.ymland add20260502-supply-chain-attestationsto "Recent Changes". (T9)Files changed
.github/workflows/docker-build.yml· primary subject — adds BuildKit attestation flags, scoped permissions, merged-digest capture, SBOM generation + Sigstore-signed attestations, and thegh attestation verifyregression gate.docs/operate/deployment.md· new "Verifying image attestations" subsection under Build with consumer commands for both attestation flavours and both image variants.docs/operate/observability.md· new "Supply-chain attestations" section documenting the registry / Sigstore / GitHub-API storage matrix; cross-linksdeployment.md.CLAUDE.md· CI/CD table row + Recent Changes entry.IMPLEMENT.md· per-task verification report for the bot tracking comment.Commits
a24bc88·docs(implement): fix CLAUDE.md bullet that prettier flattened into a sub-list41ea01a·feat(workflows): publish SLSA provenance + SBOM attestations on every release tagTests run
bun run typecheck· pass (no output, exit 0)bun run lint· pass (0 errors, 289 pre-existing warnings — none new from this change)bun run format→bun run format:fix· pass after re-flowing two doc tablesactionlint .github/workflows/*.yml· pass (no output)bun run scripts/check-docs-citations.ts· passbun run scripts/check-docs-versions.ts· passmkdocs build --strict· pass (Documentation built in 0.58 seconds)bun test· 519 pass / 153 skip / 194 pre-existing fail (verified baseline bygit stash && bun test: same counts — failing suites need Postgres + Valkey frombun run dev:deps, unrelated to this YAML/Markdown change)Verification
provenance: mode=max+sbom: trueset ondocker/build-push-action@v7; inline comment cites thepush-by-digestdefault-off behaviour the issue called out and links the Docker multi-platform guide.imagetools createpreserved as-is; inline comment in the merge step explains why no CLI plumbing is needed (per-arch index digests now reference both image AND attestation manifests).permissions:grew onlyattestations: read; merge job overrides locally withid-token: write+attestations: write. Build / scan retain only read scopes.Create manifest list and push: digest capture (with regex validation),anchore/sbom-action@v0(CycloneDX),actions/attest-build-provenance@v4,actions/attest-sbom@v4. All four steps target the merged-image digest.gh attestation verifytwice (SLSA, CycloneDX) before Trivy; each fails the job if its predicate is absent.uses:returned zero matches: every workflow uses tag-only major-version pinning, with Renovate'sgithub-actionsgroup rule handling bumps. Pinned new actions tag-only at major version (@v4,@v0) to match repo posture and avoid Renovate-undo churn. Also bumpedattest-*from the plan's@v3to@v4since v4.x has been GA since 2026-02-26 and the repo otherwise tracks current majors (checkout@v6,build-push-action@v7,download-artifact@v8).docs/operate/deployment.mdanddocs/operate/observability.mdcarry consumer commands and storage matrix;mkdocs build --strictclean and the bespoke citation / version checks pass.CLAUDE.md.gh workflow run docker-build.ymlagainst a dev tag is the maintainer's call (it pushes a real image to Docker Hub and consumes attestations-API quota). All YAML / docs gates that can run locally pass.Security posture preserved
attestations: read(least required to verify).gh attestation verifyruns with the defaultsecrets.GITHUB_TOKEN— no PAT.env:-first pattern preserved on every new step.Related Issues
Test plan
bun run typecheckcleanbun run lintno new errorsSummary by CodeRabbit
New Features
Documentation