Signed GHCR image with SBOM and SLSA provenance for release tags - #866
Merged
Merged
Conversation
Updated GitHub Actions workflow to use newer versions of actions and added image signing step.
Updated the GitHub Actions workflow to include SBOM generation and verification steps.
Updated GitHub Actions workflow for Docker release. Changed trigger from branch to tag, updated action versions, and modified steps for versioning and SBOM verification.
Added workflow_dispatch input for Docker image version and removed unnecessary steps for building and pushing Docker images.
Removed unnecessary YAML code block delimiters from the workflow file.
Updated Docker image version description and validation regex for manual builds.
Updated GitHub Actions workflow to trigger on push to the 'test/ghcr-private' branch and removed commented-out sections for clarity.
Removed commented-out sections and simplified version determination steps.
Updated workflow to reflect GHCR naming and paths.
BuildKit attestations are unsigned, so cosign verify-attestation found none. Generate the SBOM with Syft and attest it with cosign, attest SLSA provenance with attest-build-provenance, verify against the exact workflow identity, pin actions to SHAs and scope permissions to the job.
Tags like 2.3.0 and 2.4.0-beta publish an image named after the tag; the test branch keeps :test. Pass GIT_COMMIT/GIT_BRANCH build args like the Docker Hub workflow, and skip the org storage record that needs artifact-metadata: write.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
SLSA verification does not enforce the claimed exact workflow ref and uses a vulnerable GitHub CLI verification path.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds a hardened GHCR release workflow for signed GrandNode images with SBOM and SLSA provenance.
Changes:
- Builds and publishes release-tagged GHCR images.
- Generates, signs, and verifies SBOM and provenance attestations.
- Pins actions and restricts workflow permissions.
| File | Description |
|---|---|
.github/workflows/docker-ghcr.yml |
Defines the complete GHCR release and verification pipeline. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
workflow_dispatch takes an optional tag input; the job checks out that tag, validates its format and labels the image with the commit actually built, so tags created before this workflow existed can be published.
Drop the test/ghcr-private branch trigger and the :test fallback; a run without a release tag now fails instead of publishing.
--signer-workflow omitted the ref and, in gh versions affected by GHSA-wjmr-j3rp-mh2g, was matched as a prefix. Use --cert-identity with the same workflow_ref identity as the cosign checks, and reject attestations from self-hosted runners.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Resolves: no issue
Type: feature
Issue
There is no signed, verifiable GrandNode image in GitHub Container Registry. The first version of
docker-ghcr.ymlbuilt and signed the image, then failed atVerify SBOM attestation:sbom: true/provenance: mode=maxinbuild-push-actionattach BuildKit attestations to the image index unsigned.cosign verify-attestationonly accepts cosign-signed attestations, so the SBOM and provenance checks could never pass.Solution
.github/workflows/docker-ghcr.ymlbuilds, pushes, signs and verifiesghcr.io/grandnode/grandnode2:X.Y.ZandX.Y.Z-*(e.g.2.3.0,2.4.0-beta) publish:<tag>; manual run with ataginput (Actions → GrandNode Docker GHCR → Run workflow) checks out and publishes any existing release tag, including tags created before this workflow existed. The tag is validated againstX.Y.Z[-suffix], and the image is labelled with the commit actually built.cosign attest --type spdxjsonand also uploaded as a run artifact.actions/attest-build-provenance, pushed to the registry.cosign verify,cosign verify-attestationandgh attestation verify, all pinned to the exact workflow identity (github.workflow_ref) instead of arefs/heads/.*regexp.{}at workflow level, and the job gets onlycontents: read,packages: write,id-token: writeandattestations: write;persist-credentials: false;envrather than being interpolated into scripts;concurrencygroup is set.GIT_COMMIT/GIT_BRANCHbuild args are passed the same way as in the Docker Hub workflow.GHCR has no OCI Referrers API, so signatures and attestations show up as extra untagged package versions next to a
sha256-<digest>tag. Do not prune untagged versions blindly, or verification of live images breaks.Package visibility (private) is a GHCR package setting, not part of this workflow.
Breaking changes
None. This adds a new workflow; the Docker Hub workflows are unchanged.
Testing
Build, signing and all three verifications were proven green on the PR branch (runs 36905293807, 36905967976, 36908671913) while it still had a temporary branch trigger publishing
:test; that trigger is removed, so only release tags are published.Manual tag build (works before merge, from the branch):
When the workflow is run manually from a branch, the cosign identity is that branch's workflow ref (e.g.
...docker-ghcr.yml@refs/heads/develop), not the tag's.Tag triggers only fire when the workflow file is in the tagged commit, so they can be checked only after merge:
2.4.1-betafromdevelop.ghcr.io/grandnode/grandnode2:2.4.1-beta.