Skip to content

Publish sha256 alongside the release binary and verify on install (closes #12, #1) - #25

Merged
dolph merged 1 commit into
mainfrom
claude/release-checksums
Apr 27, 2026
Merged

Publish sha256 alongside the release binary and verify on install (closes #12, #1)#25
dolph merged 1 commit into
mainfrom
claude/release-checksums

Conversation

@dolph

@dolph dolph commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Closes #12.
Closes #1.

Summary

Publishes a sha256 of the release binary as a release asset, and has install.sh (and the README quickstart) verify the binary against that checksum before doing anything with the bytes. Also fixes the long-standing curl bug from #1curl -L -o https://... was missing its filename, so the documented install path didn't actually work.

This is Tier A from the issue body. Tier B (.goreleaser.yml for cross-compile + Cosign signatures) is a strictly bigger scope conversation that pairs with #15; left as a follow-up.

Changes

.github/workflows/go.yml

New step before the release upload, gated on the same tag predicate, plus the new file in files::

- name: Generate checksum
  if: startsWith(github.ref, 'refs/tags/v')
  run: sha256sum ussher > ussher.sha256

- name: Release
  uses: softprops/action-gh-release@v1
  if: startsWith(github.ref, 'refs/tags/v')
  with:
    body_path: README.md
    files: |
      ussher
      ussher.sha256

install.sh

Download block rewritten end-to-end:

if ! [ -f ussher ]; then
    curl -fLO https://github.com/dolph/ussher/releases/latest/download/ussher
    curl -fLO https://github.com/dolph/ussher/releases/latest/download/ussher.sha256
    sha256sum -c ussher.sha256
fi

This single block fixes three things:

The verification only runs when install.sh just downloaded the binary — if a local ussher already exists (the ./build.sh && ./install.sh build-from-source flow), the script trusts it and skips both downloads. That keeps the local-build flow working without a sidecar .sha256 file. The trust model there is "you built it yourself", which is fine.

README.md

The "Recommended installation & usage" step 1 now shows the parallel curl + sha256sum -c commands for adopters who skip install.sh and follow the README manually. The "don't proceed past this step on a mismatch" instruction is called out explicitly.

Test plan

  • shellcheck install.sh build.sh passes (the existing CI job from Add shellcheck to CI (refs #1) #6/Simplify shellcheck discovery to .sh only (review feedback from #6) #7 will also catch this on the PR).
  • Local dry-run of the new sequence: ./build.sh produces ussher; sha256sum ussher > ussher.sha256 followed by sha256sum -c ussher.sha256 round-trips cleanly.
  • ./build.sh still green; tests pass; coverage unchanged at 39.5%.
  • CI shellcheck + build jobs both green on the PR.
  • (post-merge) First tag push produces both ussher and ussher.sha256 as release assets; the README quickstart steps run end-to-end against that release.

Out of scope (follow-ups)

https://claude.ai/code/session_013HnepY8MhhxrJJjE5ysW47


Generated by Claude Code

Closes #12.
Closes #1.

The release step previously uploaded a single file — the ussher binary —
with no checksum, no signature, and no SLSA / sigstore attestation. The
documented install path then fetched those bytes and `chmod +x`'d them
with no verification. For a binary that becomes every adopting host's
`AuthorizedKeysCommand`, that's the highest-impact gap in the release
pipeline: any compromise of the release artifact reaches every adopter
silently.

The release workflow now generates `ussher.sha256` after the build and
includes it in the release upload alongside the binary. install.sh
downloads both files with `curl -fLO` (also fixes the long-standing
bug from #1 where `-o` was missing its filename and the URL was being
consumed as the output path) and runs `sha256sum -c ussher.sha256`
before doing anything with the bytes — `set -e` at the top of the
script aborts the install on any mismatch. `--fail` ensures a 4xx/5xx
response from GitHub doesn't silently produce an HTML error page that
gets installed.

Verification only runs when ussher.sh just downloaded the binary; if a
local `ussher` already exists in cwd (the build-from-source flow), the
script trusts it and skips both downloads. That keeps `./build.sh &&
./install.sh` working without a sha256 sidecar file.

The README quickstart grows a parallel block showing the curl + verify
steps for adopters who skip install.sh and follow the documentation
manually. Don't-proceed-on-mismatch is called out explicitly there.

Local dry run: `./build.sh` produces `ussher`; `sha256sum ussher >
ussher.sha256` followed by `sha256sum -c ussher.sha256` round-trips
cleanly. shellcheck is green on install.sh.

Out of scope, deferred to a fuller follow-up: replacing the bespoke
build/release with `.goreleaser.yml` for cross-compiled binaries (#15)
and Cosign keyless signatures for SLSA-style provenance. Tier A here
is a strict improvement and doesn't preclude that work — goreleaser
will replace these lines wholesale anyway.
@dolph
dolph merged commit 60317bd into main Apr 27, 2026
4 checks passed
@dolph
dolph deleted the claude/release-checksums branch April 27, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants