Verifiable releases: checksums + signatures + the reproducible-build claim
- Type: feature (release engineering / supply chain)
- Track: A (core — owns the build + the self-host gate)
- Status: blocked on [[decide-release-signing-key-custody]] for STEP 4 only — steps 1-3 landed 2026-08-31.
- Owner: frankS
- Related: [[feature-web-track-w-bootstrap]] (the download page consumes this)
Why (this is the REAL anti-impersonation defense)
A clone of the website harms nobody by existing. What harms people is a trojaned pxx
binary served from a page that looks like ours. Source secrecy does nothing against that —
verifiability does:
- A phishing clone can copy our page. It cannot make its binary match a hash published somewhere it doesn't control.
- So: releases and checksums live in the PUBLIC repo (GitHub Releases), installers point at the canonical GitHub URL, and the checksums are published where a clone site cannot rewrite them. Publicness is the security property here, not a liability.
The claim only we can make
We have a byte-identical self-host fixed-point gate. That means we can credibly say: build it yourself and get the same bytes. A reproducible build is an impersonation defense no clone can match, and it is nearly free for us — the gate already proves determinism. Nobody shipping a compiler gets to say this as easily as we do; say it loudly on the download page.
Scope
- SHA256SUMS for every release artifact, generated by the release job, committed/attached to the GitHub Release.
- Sign the checksum file (minisign or GPG; minisign is one line and matches the age philosophy — small key, printable, no GPG swamp). Publish the public key in the repo AND on the site.
- Reproducible-build doc: exact steps to rebuild a release from source and diff the bytes against the published artifact. Wire it to the existing self-host fixed-point machinery.
- Download page copy (Track W/D): canonical URL, the checksums, the "verify it yourself" instructions, the public key. Make verification the default path, not a footnote.
- Installer/bootstrap scripts fetch from the canonical GitHub Releases URL and verify the checksum before executing anything.
Acceptance
A published release carries SHA256SUMS + a valid signature; the documented rebuild procedure
reproduces the artifact byte-for-byte on a clean machine; the download page shows the verify
steps.
Log
- 2026-08-24 — blocked on [[decide-release-signing-key-custody]]. Step 2 needs a
private key an agent must not generate or hold, so this ticket sat at the head
of Track A's ready queue where nobody could finish it. Steps 1 and 3 turn out
to be mostly DONE already and were not noticed:
tools/release.shwrites aMANIFEST.sha256over every prebuilt binary, attaches it to the Release as its own asset, and ships aselfcheck.shthat rebuilds each binary on the downloader's host and diffs it against that manifest — the reproducible-build claim is machinery, not copy. What is genuinely missing without a key: aSHA256SUMSover the published TARBALL (today you must extract before you can verify anything) and the verify-it-yourself prose. Both are listed in the decide ticket as the first things to do once it is answered. - 2026-07-12 — opened, out of the website/impersonation discussion. The insight worth keeping: the anti-impersonation argument points toward the download pipeline being public and verifiable, not private.
UNBLOCKED 2026-08-25 — steps 1-3 are agent work; only the signature waits
[[decide-release-signing-key-custody]] was answered in part on 2026-08-25 (see
devdocs/progress/decided/README-agent-decisions.md). The blocked-by edge is
removed because it was gating the whole ticket on the one step that needs a
human.
Do, in this order:
SHA256SUMSfor the published tarball. TodayMANIFEST.sha256covers the prebuilt binaries, so a downloader can only verify after extracting. No key involved.- The reproducible-build doc, wired to the existing
selfcheck.sh— which already rebuilds each binary on the downloader's host and diffs against the manifest. The claim is machinery, not copy; say so. - Download-page copy (Track W/D). Claims discipline applies: this is reproducibility of our own build. Write "rebuilds byte-for-byte from source on your host and matches the published manifest" — never anything that could be read as a gcc comparison. See CLAIMS in CLAUDE.md.
- The minisign signature step — BLOCKED ON THE OWNER. The tool is preselected (minisign); what is missing is who holds the private key and where it lives. Do not generate a key to unblock this, and do not substitute another tool to route around it.
Steps 1-3 carry most of the anti-impersonation value the ticket argues for: a clone site cannot make its binary match a hash published in a repo it does not control, and cannot make it rebuild byte-for-byte at all.
Steps 1-3 done (frankS, 2026-08-31). Step 4 is still the owner's.
1. SHA256SUMS over the published tarball — done
tools/release.sh writes dist/SHA256SUMS over pxx-<tag>.tar.gz right after
the archive is created, and gh release create now attaches it alongside the
tarball and MANIFEST.sha256. Until now the only published hash was
MANIFEST.sha256, which lives inside the bundle — so verifying anything
meant first unpacking the thing you had not verified.
The two files answer two different questions and both ship:
| file | question | where |
|---|---|---|
SHA256SUMS |
is this the archive we published? | Release asset, checkable before extracting |
MANIFEST.sha256 |
do these binaries rebuild here? | inside the bundle, driven by selfcheck.sh |
Measured, not assumed — build_dist run for real (all four host targets):
==> build: dist/pxx-v0.0.0-test + pxx-v0.0.0-test.tar.gz + SHA256SUMS
72ad97c43a7e... pxx-v0.0.0-test.tar.gz
$ sha256sum -c SHA256SUMS
pxx-v0.0.0-test.tar.gz: OK
Negative control, because a checksum that cannot fail is not a checksum:
appending one byte to the tarball makes sha256sum -c report FAILED, and
restoring it makes it pass again. Run.
2. The reproducible-build doc — done, and one claim deliberately NOT made
In the generated RELEASE.md (ships in every bundle) and in
docs/install/index.md. Both say what selfcheck.sh actually does — recompile
each prebuilt binary on the downloader's host from the source in the same bundle
and diff against the manifest — and both state it as determinism of our own
build, never as a comparison with another compiler's output (CLAIMS discipline).
The tarball is NOT byte-reproducible and the docs say so. gzip records an
mtime and the binaries are built fresh, so two runs of build_dist differ. That
is exactly why both files exist: SHA256SUMS pins the artifact, selfcheck.sh
proves its contents. Writing "reproducible release" unqualified would have been
false in the one place it would be checked.
3. Download-page copy — the doc half is done; the website half is W's
docs/install/index.md gained a "What a release will carry, and how to check
it" section. It opens by saying no release has been cut yet — verified
locally, not assumed: git tag has no version tags and
devdocs/release-notes/CODENAMES is empty. So the section describes what a
release will carry rather than implying there is something to download, which is
the same trap the existing "no binary release channel yet" sentence was already
avoiding. The website copy is Track W's, in the separate repo, and is not
touched here.
4. minisign signature — UNCHANGED, still blocked on the owner
Not attempted. No key generated, no substitute tool. Until a signature exists,
SHA256SUMS is only as trustworthy as the repository serving it — which the
install doc now says in as many words rather than letting the checksum imply
more than it carries.
So this ticket is done except for step 4, which no agent may do. Leaving it
open on that one step is what put it at the head of Track A's ready queue where
nobody could finish it — the situation the 2026-08-25 note already flagged. It
moves to blocked/ on decide-release-signing-key-custody rather than staying
ready, so the queue stops offering three finished steps and one impossible one.