Skip to content

fix(release): compare the signature states against the list that was signed - #21

Merged
donislawdev merged 1 commit into
mainfrom
fix/signing-check-compares-the-list-it-signed
Sep 10, 2026
Merged

fix(release): compare the signature states against the list that was signed#21
donislawdev merged 1 commit into
mainfrom
fix/signing-check-compares-the-list-it-signed

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

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:

$expected = @($OURS[$archive.Name] | ForEach-Object { $_ -replace '^[^/]+/', '' })

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. Stripping that folder off $OURS left nothing matching
anything.

Measured, not reasoned about

Extracting the real archives and running the same key computation:

archive $OURS matches stripped list matches
ChronoMock-win-x64.zip 7 of 7 0 of 7
chrono-cli-win.zip 4 of 4 0 of 4

Both archives were affected. The command line one failed first only because it
sorts first.

The fix

$OURS 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 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 failed
signing 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 latest pointing at the
tag. 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_ASSETS matches what the script uploads and the wait condition matches
what phase C uploads. Nothing else found, which is a reading and not a proof.

🤖 Generated with Claude Code

…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
donislawdev merged commit 21e2f5d into main Sep 10, 2026
8 checks passed
@donislawdev
donislawdev deleted the fix/signing-check-compares-the-list-it-signed branch September 10, 2026 17:25
Sign up for free to 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.

1 participant