🔒 Attest the five release binaries with GitHub build provenance - #404
Merged
Conversation
Each matrix job attests its compiled dist/${{ matrix.artifact }} between the
compile and the upload, so GitHub publishes build provenance for the exact
bytes it produced and an unattested binary never enters the artifact set the
release job downloads. `release` needs the whole build matrix, so a failed
attestation withholds the release.
npm keeps publishing as it does: trusted publishing already generates
provenance for these public packages, so `--provenance` stays off the command
line.
PR #404: 🔒 Attest the five release binaries with GitHub build provenance3 files, +217 / -5 Scope✅ PR scope looks good. Structural✅ No structural bloat detected. Slop✅ Slop indicators look low. Static Analysis✅ Oxlint found no issues. CorrectnessNo extraneous code patterns detected. |
taras
marked this pull request as ready for review
August 9, 2026 03:50
taras
enabled auto-merge (squash)
August 9, 2026 03:50
2 tasks
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.
Closes #100.
Why
A downloaded
xmdbinary can be checked againstchecksums.txt, but that onlyproves the file matches a list published beside it. Nothing binds the bytes to
this repository or to the workflow run that compiled them, so a consumer has no
way to tell a release artifact from a substituted one.
The npm side of #100 turned out to need no work: npm generates provenance
automatically for a public package published from a public repository over
Trusted Publishing/OIDC, which is exactly what
publish-one.ymlalready does.The issue was narrowed to the binaries before this PR.
What changes
Before:
release.ymlholds onlycontents: write. Five matrix jobs compile todist/${{ matrix.artifact }}and upload it. GitHub publishes no attestationfor any of them, and
gh attestation verifyon a released binary fails.After:
Each matrix job attests its compiled binary between the compile and the
upload. A successful tagged build leaves GitHub holding SLSA build provenance
for all five subjects, and a consumer can verify one:
The release assets themselves are byte-identical to before: same five binaries,
same names, same
checksums.txt, no attestation bundle attached.How it works
Placement is the whole guarantee. Before the compile there are no bytes to
attest; after the upload an unattested binary is already in the artifact set
releasedownloads. Becausereleasehasneeds: build, a failed attestationfails that matrix job and withholds the entire release rather than publishing a
partial or unattested set —
fail-fast: falsestill lets the other four targetsfinish so the failure is diagnosable, but it does not make
buildsuccessful.One shared matrix step covers all five platforms, so the five-target list is not
duplicated anywhere; the existing exact-five matrix assertion is what proves the
coverage.
Review guide
Start with:
.github/workflows/release.ymlThen review:
permissionsblock —contents: writepreserved,id-token: writeandattestations: writeadded.Attest ${{ matrix.artifact }}step and its position inbuild.scripts/tests/release-targets.test.ts— thegray-matterparse and the sixnew assertions.
specs/release-process-spec.md§1, §3, §4, §7.Look carefully at:
actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6isthe commit the
v4.2.2tag resolves to (verified against the GitHub API, notcopied from documentation). A mutable major tag here would put an unreviewed
action inside a job holding
id-token: write.What must stay true
checked by the exact-five matrix assertion plus the ordering assertion.
release.needs: buildwith nocontinue-on-errorand noifon the step; checked bycannot be softened into a warningandpublishes no binary once an attestation has failed.attests once for the whole matrix, at the reviewed pin.subject-pathis configured. A registry push, a renamed subject, or acustom predicate would quietly replace the default sign-and-publish behavior.
Checked by exact equality on the step's
with.How to verify it
The regressions parse
release.ymlas YAML throughgray-matterrather thanmatching text, so a comment naming the action cannot satisfy them. Each was run
against a deliberate mutation of the workflow to confirm it is not vacuous:
attestations: writeactions/attest@v4subject-path: dist/xmd-x86_64-apple-darwinpush-to-registry: truecontinue-on-error: trueif: always()release.needstopreflightRun locally:
deno task test scripts/tests/release-targets.test.ts scripts/tests/publish-workflow-membership.test.tsAlso run on the rebased head:
deno task lint,deno task check,deno task check:jsr,git diff --check— all clean.Scope
Included
release.yml.the failure gate, reruns, and consumer verification.
Intentionally unchanged
publish-one.ymland the npm command.--provenanceis deliberatelyabsent: Trusted Publishing already produces provenance for these public
packages, and the flag would add nothing. The spec now says so, so its absence
does not read as an oversight.
checksums.txtand the release assets. Checksums stay; attestation ispublished to GitHub's attestation API, not attached as a release file.
Risks and limitations
was created to test OIDC, so this PR's proof is structural: the workflow
parses to the intended shape, and the ordering and gating assertions hold.
What CI cannot show here is that GitHub's attestation API accepts the token —
that requires a
v*tag push.gh attestation verifyisthe acceptance check to run after the next release.
repository, and existing attestations do not affect later releases.
Scope confirmation