fix(release): compare the signature states against the list that was signed - #21
Merged
Merged
Conversation
…signed Phase B refused to go on, naming all four of our own binaries in the command line archive as files "signing changed that it should not have touched". They are exactly the files it had just signed, on purpose, a few lines earlier. The check rebuilt its comparison list instead of reusing the one it signed from. Get-SignatureStates keys relative to the directory the archive was expanded INTO, and each archive carries its own top folder, so its keys look like `chrono-cli/chrono.exe`. The check stripped that folder off $OURS before comparing, and then nothing matched anything. Measured on the real archives rather than reasoned about: $OURS matches 7 of 7 keys in the window archive and 4 of 4 in the command line one, and the stripped list matches 0 of 7 and 0 of 4. Both archives were affected. The command line one failed first only because it sorts first. $OURS is now used verbatim. That the shape is right is not an opinion here - Assert-Path resolves every entry of the same list against the same directory thirty lines above, so if the shape were wrong the script would already have stopped there. A dry run could not have caught this: the whole comparison block is inside `if (-not $DryRun)`. Worth saying because the obvious lesson from a failed signing run is "should have dry run first", and here it is the wrong lesson. The rest of phase B was read through for the same class of fault while the card was out. EXPECTED_ASSETS matches what the script uploads, and the wait condition for the attestation bundles matches what phase C uploads. Nothing else found, which is a reading and not a proof - phase B has still never run past this point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
donislawdev
deleted the
fix/signing-check-compares-the-list-it-signed
branch
September 10, 2026 17:25
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.
Phase B of the v0.2.0 release refused to go on, naming all four of our own
binaries in the command line archive as files "signing changed that it should
not have touched". They are exactly the files it had just signed, on purpose, a
few lines earlier.
The fault
The check rebuilt its comparison list instead of reusing the one it signed from:
Get-SignatureStateskeys relative to the directory the archive was expandedINTO, and each archive carries its own top folder, so its keys look like
chrono-cli/chrono.exe. Stripping that folder off$OURSleft nothing matchinganything.
Measured, not reasoned about
Extracting the real archives and running the same key computation:
$OURSmatchesChronoMock-win-x64.zipchrono-cli-win.zipBoth archives were affected. The command line one failed first only because it
sorts first.
The fix
$OURSverbatim. That the shape is right is not an opinion here:Assert-Pathresolves every entry of the same list against the same directory thirty lines
above, so a wrong shape would have stopped the script there.
A dry run could not have caught this - the whole comparison block is inside
if (-not $DryRun). Worth saying, because the obvious lesson from a failedsigning run is "should have dry run first", and here that is the wrong lesson.
Verified by the thing itself
Phase B was re-run with this fix and completed. v0.2.0 is published, signed and
verified: seven assets on the release page including both attestation bundles,
and phase D passed every step on the published page - the expected assets, the
checksums, the README verification commands executed as written, every binary
signed by the pinned certificate and timestamped, and
latestpointing at thetag. That is the full four-phase ritual running end to end for the first time.
The rest of phase B was read through for the same class of fault.
EXPECTED_ASSETSmatches what the script uploads and the wait condition matcheswhat phase C uploads. Nothing else found, which is a reading and not a proof.
🤖 Generated with Claude Code