Skip to content

ci: attest build provenance for the release artifacts - #39

Merged
z33b0t merged 1 commit into
mainfrom
ci/build-provenance
Jul 29, 2026
Merged

ci: attest build provenance for the release artifacts#39
z33b0t merged 1 commit into
mainfrom
ci/build-provenance

Conversation

@z33b0t

Copy link
Copy Markdown
Contributor

Adds actions/attest-build-provenance (pinned to 0f67c3f / v4.1.1, per #4) to both release jobs, with per-job id-token: write + attestations: write.

gh attestation verify Blurt-macOS.zip --repo lightware-dev/blurt

Scoped to release tags only — a PR build isn't published, so there's nothing to attest. The version-less Blurt-<platform>.zip copies don't need their own statement: they're byte-identical and attestations are keyed by digest, not filename.

One caveat I want on the record

This attests the bytes this run built. On a re-run where the release already carries an earlier attempt's asset, the upload step deliberately leaves the published file untouched (no --clobber, per #5) — and a macOS rebuild is never byte-identical, since codesign embeds a fresh timestamp and stapling a newly-issued ticket. The attestation would then cover a digest that isn't the one published, and gh attestation verify on the downloaded file would fail.

It's the same trap the cask-bump step already documents and guards against. I've noted it in a comment rather than engineered around it, because the fix (attest the published bytes by re-downloading) costs a download on every release to handle a case that means "delete the asset and re-run clean" anyway. Flagging it so it's a known edge rather than a surprise.

README correction included

While adding the verification instructions I hit a claim that's plainly false:

Grab the latest signed build from the Releases page

Windows releases have never been signed (#9). That's the same misreading #9 is about — it wasn't only the workflow implying it, the docs said it outright. Now corrected to state builds are unsigned, SmartScreen will warn, and the attestation is the cryptographic link that does exist.

Verification

YAML parses and step ordering is confirmed; the action SHA resolves to the v4.1.1 tag. Not executed — the step only runs on a v* tag, so the first real exercise is the next release. Worth checking the run produces the attestation and that gh attestation verify passes against the published asset before trusting it.

Closes#24

The release path produced a notarized macOS zip, a Windows zip and a sha256 in
the Homebrew cask, none of it tied back to the run or commit that built it. A
checksum proves the file didn't change in transit; anyone who can write to the
release (or the tap) can publish a consistent zip-plus-checksum pair.
Notarization proves Apple saw the binary and that it carries our Developer ID —
it says nothing about which source tree it was built from.
Adds actions/attest-build-provenance (pinned to a SHA, per #4) to both release
jobs, publishing a signed SLSA statement that binds the artifact digest to the
repo, workflow and commit:
gh attestation verify Blurt-macOS.zip --repo lightware-dev/blurt
Scoped to release tags — attestation is about what we publish, and a PR build
isn't. The version-less Blurt-<platform>.zip copies need no separate statement:
they're byte-identical and attestations are keyed by digest, not filename.
Per-job permissions widen to id-token: write + attestations: write.
This matters more on the Windows side, where releases are unsigned (#9), so the
attestation is currently the only cryptographic link between a download and this
repository. It is not a substitute for signing, and neither defends against repo
write access: an attacker who can push a tag gets CI to attest their payload.
What both defend is a release asset replaced *without* a code push.
Also fixes a README claim while adding the verification instructions: the
Windows section said "grab the latest signed build", which has never been true —
exactly the misreading #9 is about, and it was in the docs as well as the
workflow.
Closes#24
@z33b0t

Copy link
Copy Markdown
ContributorAuthor

@z33b0t ready to merge. All 8 checks green, including the macOS and Windows builds (this PR touches both workflows).

Merge after #37 — both change the release jobs, and #37 reworks the publish step these sit next to.

Two things worth reading before you merge:

  1. The attestation covers this run's bytes. On a re-run where the release already carries an earlier attempt's asset, the upload step deliberately leaves the published file alone (no --clobber), and a macOS rebuild is never byte-identical — fresh codesign timestamp, new notarization ticket. The attestation would then reference a digest that isn't the published one, and gh attestation verify would fail against the download. Same trap the cask bump already documents. I noted it in a comment rather than engineering around it; the fix means re-downloading on every release to handle a case that means "delete the asset and re-run clean" anyway.
  2. README correction included. The Windows section said "grab the latest signed build" — never true (Windows releases are unsigned — the sign step in windows.yml is dead code #9). It wasn't only the workflow implying releases are signed; the docs stated it. Now corrected, with the attestation described as the cryptographic link that does exist.

Not executed anywhere — the step only fires on a v* tag. First real exercise is the next release, so check the run emits the attestation and that gh attestation verify passes against the published asset before relying on it.

@z33b0t

Copy link
Copy Markdown
ContributorAuthor

@z33b0t Green and ready, but I can't merge this one — my gh token has repo but not workflow scope, and this PR modifies a file under .github/workflows/:

GraphQL: refusing to allow an OAuth App to create or update workflow
`.github/workflows/…` without `workflow` scope (mergePullRequest)

Either merge it yourself, or grant the scope and I'll finish:

gh auth refresh -h github.com -s workflow

(Oddly, #30 and #37 modified workflows and merged fine before this started refusing — so the restriction isn't applying consistently. Worth knowing in case you see it again.)

@z33b0t
z33b0t merged commit 74a6f9c into mainJul 29, 2026
8 checks passed
@z33b0t
z33b0t deleted the ci/build-provenance branch July 29, 2026 16:45
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release artifacts carry no build provenance attestation

1 participant

@z33b0t