Repository navigation
feat(release): signed, SHA-pinned musl release with build provenance - #126
Conversation
Rewrite release.yml so a v* tag produces a real, verifiable artefact: - build: checkout, dtolnay/rust-toolchain at 1.85.0 (the workspace MSRV) with the x86_64-unknown-linux-musl target, musl-tools, then `cargo build --locked --release -p squabble-cli --target x86_64-unknown-linux-musl`. Stage dist/squabble-x86_64-linux-musl and dist/SHA256SUMS (bare names), fail if either is missing or empty, and upload dist/ as one artifact. - release: needs build; job permissions are exactly contents: write, id-token: write, attestations: write. Download dist/, refuse an empty or incomplete dist/, attest build provenance for dist/* with no skip condition, then publish dist/* with generated release notes. - Drop the git-cliff changelog job and the language auto-detect stub. - Top-level permissions are contents: read only. Every uses: is pinned by full commit SHA with the tag in a comment. actions.lock is edited by hand for the release.yml entry only: its list now names the six SHA refs, three SHA-keyed dependency records are added (download-artifact, attest-build-provenance with its nested actions/attest, softprops/action-gh-release), and the two tag-keyed records that only release.yml used (attest-build-provenance@v4.2.2, softprops/action-gh-release@v3.0.3) are pruned. The lock stays transitively closed. Justfile release-tag now creates a signed tag (git tag -s) so the Immutable-Tags required_signatures rule does not depend on local tag.gpgsign config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bTuGfwCcvjrmNFejydTML
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details⏰ Context from checks skipped due to timeout. (10)
|
| Layer / File(s) | Summary |
|---|---|
Create signed release tag Justfile |
release-tag now creates a signed Git tag. |
Build and verify release asset .github/workflows/release.yml |
The workflow uses Rust 1.85.0 to build the x86_64-unknown-linux-musl binary. It stages the binary and SHA256SUMS, verifies them, and uploads the artifact. |
Validate and publish release .github/workflows/release.yml |
The release job downloads and validates the build artifact, attests provenance, and uploads the files to the GitHub Release. The changelog job was removed. Release notes are generated, and tags containing -rc, -beta or -alpha remain prereleases. |
Priority: ⬇️ Low
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Feature
Merge Risk
Merge Risk: ⚪ Minimal · up to 98535
The release pipeline is ready for normal checks before merge; no concrete release-blocking issue was found.
-
Security Architecture Review
Security architecture risk: 🔵 Low · up to
98535The pipeline strengthens artifact checks and separates build permissions from publication authority. Remaining uncertainty concerns enforcement of trusted, immutable release tags and recovery after interrupted publication. No security bypass has been demonstrated.
Retained concerns
- Low · security · inferred: Newly published, attested executable assets depend on repository-side release-tag authorization whose enforcement is unavailable. The workflow's lack of signature validation predates this PR, but the previously dormant binary-publication and attestation outputs are now active. This is an unresolved trust dependency, not a verified unsigned-tag bypass.
-
Security review details
Security Blast Radius
- inferred — The visible authority covers repository release publication and provenance issuance. A compromised accepted release source could affect consumers executing the resulting CLI. No downstream deployment, tenant access, additional credentials, or consumer rollout is established by this PR's evidence.
Security Findings and Attack Paths
- inferred — No retained security finding is supplied. The deferred candidate concerns an actor able to push a matching tag having unauthorized source built, attested, and published if remote release authorization is insufficient. Whether such a tag is accepted, and whether the actor would be unauthorized under repository policy, remain unresolved; unsigned-tag reachability is not verified.
Trust Boundaries and Controls
- inferred — Checksum checks protect the declared artifact-transfer consistency boundary, and job permissions separate build execution from publication authority. Because the binary and manifest are produced together, matching checksums do not independently authorize the source. Provenance publication likewise does not substitute for trusted release-tag admission.
Resilience and Maintainability Implications
- inferred — Declared missing-file, checksum, and attestation failures stop progression before the release action. This supports fail-closed normal ordering, but does not prove transactional publication or safe reconciliation of old assets and attestations after interrupted uploads or reruns. No inconsistent published state was demonstrated.
Hardening Proposals
- proposed — Establish and document the effective approved-publisher and immutable-tag policy before relying on release provenance as an authorization signal. If repository rules do not enforce the intended signed-tag policy, add an appropriate admission control. Separately validate interrupted-publication and rerun recovery against the pinned actions.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
I signed the tag with careful paws,
Then built one binary, checked its laws.
A checksum guards the files in line,
Provenance joins the release design.
This rabbit hops as tags take flight!
Comment @coderabbitai help to get the list of available commands.
|



Summary
PR 1 of 2 for a signed, pinned release pipeline. When a
v*tag is pushed,release.ymlwill build a staticsquabblebinary forx86_64-unknown-linux-muslusing the workspace MSRV toolchain (1.85.0). It publishes the binary with aSHA256SUMSfile and a Sigstore build-provenance attestation for both files. The Justfile'srelease-tagnow creates a signed tag. The crate version onmainis 0.1.0.Sequencing. No tag is created or pushed by this PR.
v0.1.0; tagging is the owner's call, and tags here are immutable.release.ymlruns and publishessquabble-x86_64-linux-muslandSHA256SUMS, with an attestation.verify-satisfied). It pins the real sha256 from that release and checks it withgh attestation verify.Changes
.github/workflows/release.ymlis rewritten in block YAML (not KYAML). The SPDX header is kept.push: tags: ['v*']. Top-level permissions arecontents: readonly.buildjob:persist-credentials: falsedtolnay/rust-toolchainwithtoolchain: 1.85.0andtargets: x86_64-unknown-linux-muslsudo apt-get install -y musl-toolscargo build --locked --release -p squabble-cli --target x86_64-unknown-linux-musldist/squabble-x86_64-linux-muslanddist/SHA256SUMS, runningsha256suminsidedist/so the names are baresha256sum -cupload-artifactwithif-no-files-found: errorreleasejob:needs: build. Its permissions are exactlycontents: write,id-token: writeandattestations: write.download-artifact, then a guard that fails on an empty or incompletedist/attest-build-provenancewithsubject-path: 'dist/*', with no skip conditionsoftprops/action-gh-releasewithfiles: dist/*,generate_release_notes: trueandfail_on_unmatched_files: truechangelogjob, and the language auto-detect build stub.uses:is pinned to a full commit SHA, with its tag in a trailing comment..github/workflows/actions.lockis edited by hand. Only therelease.ymlentry and the records it alone used are touched.gh actions-lockwas not run in rewrite/fix mode.release.ymllist now names the six SHA refs.dependencies:records:actions/download-artifact@3e5f45b2…actions/attest-build-provenance@4d101475…, with its nestedactions/attest@508db95…, which was already recordedsoftprops/action-gh-release@efb35369…release.ymlused:actions/attest-build-provenance@v4.2.2andsoftprops/action-gh-release@v3.0.3.Justfilerelease-tag:git tag -abecomesgit tag -s. TheImmutable-Tagsruleset has arequired_signaturesrule. The recipe now produces a signed tag whatever the localtag.gpgsignsetting is; on this machinetag.gpgsign=truewas already set, so the behaviour here does not change.Type of change
squabblechanges.📌 New pins
Head SHA:
9853570e107db3f4c5aec5b3266ed2c9e9703902actions/checkout3d3c42e5aac5ba805825da76410c181273ba90b1dtolnay/rust-toolchain6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772actions/upload-artifact043fb46d1a93c77aae656e7c1c64a875d1fc6a0aactions/download-artifact3e5f45b2cfb9172054b4087a40e8e0b5a5461e7cactions/attest-build-provenance4d101475d8b20a2381f78447822ac1eab6504dd8actions/attest508db95dd578ae2727ebd6217d5ba78e4fbda05dsoftprops/action-gh-releaseefb35369e0ad2afab669f228072c1b0d510eae64e598afbe…)Each SHA was checked with
git ls-remoteagainst the upstream tag.dtolnay/rust-toolchain@6c977a6cis an untaggedmastercommit from 2026-08-04, the merge of #182, and an ancestor of the movingv1branch. It is the SHA this repo's lock already records for the standards reusable workflows. It predates upstream's input-hardening change (83d0610, "Safely handle action inputs"). The inputs here are constant strings, so that change does not apply to this workflow.No other lockfile records, container digests or Cargo.lock entries change.
RSR Quality Checklist
Required
cargo +1.85.0 test --locked --workspacegives 279 passed, 0 failed (123 + 64 + 59 + 33).actionlintis clean onrelease.yml.editorconfig-checkerfails onJustfilelines 210–216 (indentation). That failure is already onorigin/main: the same check ongit show origin/main:Justfilefails identically. This PR changes only line 533.lint-workflows(Workflow Security Linter) red is pre-existing; see "CI on this head" under Testing.unsafeblocks without// SAFETY:: N/A, no Rust changed.release.yml(line 1, kept).actions.lockkeeps the machine-generated header it already had, andJustfilekeeps its existing header..envfiles. Pre-commitgitleaksanddetect-private-keypassed.As Applicable
STATE.a2ml: N/A. A2ML is retired estate-wide and is not created or edited here.ECOSYSTEM.a2ml: N/A, same reason.META.a2ml: N/A, same reason.release.ymlheader comment; user-facing install docs land with PR 2.TOPOLOGY.md: N/A, no architecture change.CHANGELOG: not updated. The release now usesgenerate_release_notes: true, and the git-cliff job is gone.Testing
How has this been verified?
rustup toolchain install 1.85.0 --profile minimal --target x86_64-unknown-linux-musl(into the estate tools rustup)cargo +1.85.0 build --locked --release -p squabble-cli --target x86_64-unknown-linux-muslELF 64-bit LSB pie executable, x86-64, static-pie linked, 2,343,240 bytes.squabble --versionprintedsquabble 0.1.0cargo tree --locked -p squabble-cli --target x86_64-unknown-linux-musl -e normal,buildring,cc,*-sys) is in the default tree, which is why the musl link works here withoutmusl-gcccargo metadata --locked(rust-versionof every package)wasip2declares 1.87.0 but is not in the musl default tree, and the 1.85.0 build above succeededcargo +1.85.0 test --locked --workspacebash scripts/check-lock-sync.shgh actions-lock --no-fixactionlint .github/workflows/release.yml(1.7.7)pre-commit run --files <the 3 files>bash standards/.githooks/docstring-scan.sh --worktree --checkgit log -1 --show-signatureGood "git" signature … ED25519Positive controls. Each one shows that a check above can return a failure.
softprops@efb35369record makescheck-lock-sync.shexit 1.commit:digest on thedownload-artifactSHA record makesgh actions-lock --no-fixexit 2.needs: buidlmakesactionlintexit 1.gh actions-lock --no-fixexited 0 on the dangling-edge plant. That is the blind spot documented incheck-lock-sync.sh;check-lock-sync.shis the gate that catches it.The two pre-commit hooks that did not pass:
editorconfig-checkerfails onJustfilelines 210–216. This is pre-existing onorigin/main(see the checklist above), and this PR changes only line 533.validate-k9could not run. Its hook repo,https://github.com/hyperpolymath/k9-pre-commit, does not resolve:git fetchreports "Repository not found", and GraphQLrepository(...)returns NOT_FOUND. As a result,pre-commit runaborts before any hook runs. Every other hook was run from a scratch copy of.pre-commit-config.yamlwith only that repo block removed. No.k9files are changed in this PR.Not verified locally, and only provable by the first tag run:
apt-get install musl-toolson the runner. It is not needed for the current default dependency tree, but is kept for any future C dependency.actions/download-artifactreading a same-run artifact withoutactions: read. The README at3e5f45b2scopes default access to the current run.softprops/action-gh-releasecreating the release.CI on this head (
9853570e)Read from
commits/9853570e…/check-runswith--paginate(52 runs),commits/9853570e…/statuses(plural, deduped by context), RESTpulls/126/reviews, GraphQLreviewThreadsandrules/branches/main.main:deletion,non_fast_forward,required_status_checks. The only required context isscan / gitleaks, and it is success.automerge,rust-ci / Cargo audit (security),rust-ci / llvm-cov line coverage; skipped satisfies, but they carry no evidence), 2 failure (bothlint-workflows, one run per trigger).CodeRabbitandCodeac analyze resultsas legacy statuses. Codeac says "1 errors and 3 warnings" in asuccessstate, which is the same text it gives onmain@a4cd088.refs/pull/126/merge(Hypatia and CodeQL analyses of merge commit76836fa6) are a subset of the 46 open alerts onmain, so this PR introduces 0 alerts. That includes the pre-existing Hypatia 🔧 CodeRabbit CI Fix: Fix failing Dogfood Gate K9 contracts and Groove manifest checks #64 and Feat/green polarity classifier #76, which fire on the case-folded duplicateSwatinem/rust-cache/swatinem/rust-cache@6323deb1record already present inmain'sactions.lock. This PR does not touch that record.lint-workflowsis red, and it is pre-existing.Check SHA-Pinned Actions;Check SPDX HeadersandCheck Permissions Declarationpass.main(run 36649482714 atb12657f5, whose.github/workflows/is identical toa4cd088; 39 findings). This head has 36 findings, all in files this PR does not change. The PR removes 3 of them, the tag-pinnedrelease.ymlrefs.uses:keys insideactions.lockitself. That is a linter false positive, andmainhas the same 5.lint-workflowsis deferred to lint-workflows: Check SHA-Pinned Actions red on main (31 tag-pinned refs + 5 actions.lock false positives) #127 (acceptance: the step is green onmain, the 31 refs are SHA-pinned and lock-covered, and the linter no longer readsactions.lock).Screenshots
N/A. Terminal results are in the Testing table above.
🤖 Generated with Claude Code
https://claude.ai/code/session_015bTuGfwCcvjrmNFejydTML