fix(install.ps1): verify-blob must not inherit a stale exit code - #529
Conversation
`$LASTEXITCODE` persists from the previous command. `Test-CosignRuns` runs `cosign version` immediately before the verification, and presets 255 precisely so a binary that never starts cannot leave a stale 0 behind. The `verify-blob` call that actually GATES the install had no such preset. So a cosign shim that exits 0 on `version` and then no-ops on `verify-blob` leaves `$LASTEXITCODE` at 0, the `-ne 0` gate reads that as success, and the installer prints "cosign signature valid" and installs a binary nothing verified. That is RFC-0001 R8 defeated by a stale variable, one line from the guard that exists. Reproduced before fixing, driving the real block extracted from install.ps1 under pwsh with a no-op verifier: with fix -> REFUSED (LASTEXITCODE=255) without fix -> INSTALLED-UNVERIFIED (LASTEXITCODE=0) The regression assertion is a SOURCE check, and the limitation is stated rather than hidden: install.ps1 has NO behavioural coverage -- there is no pwsh or Pester anywhere in this repo's CI, which is why a signature gate that does not gate reached a promotion PR. Case 19 asserts the preset sits inside the `$sigDownloaded` block and before the invocation, by line number, so a preset elsewhere cannot satisfy it. It closes this hole; it does not make the Windows installer tested. Worth its own ticket. Mutation-proved: removing the preset reddens case 19 with the message naming the consequence. 34 passed / 0 failed; the mutation gives 33/1. One self-inflicted detail recorded because it is the repo's own failure class: the first version of the check grepped for `verify-blob` and matched the explanatory comment written directly above the call, so it failed on correct code. Anchored on the `& $cosign` invocation instead -- prose is not wiring. Found by Bugbot on release-train promotion PR cli#528 (High). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
left a comment
There was a problem hiding this comment.
The one-line fix in install.ps1 is right and the reasoning behind it is sound — $global:LASTEXITCODE = 255 before the verify-blob invocation is exactly the guard Test-CosignRuns already carries, and install.sh never had the hole because it branches on the command directly (if "$COSIGN_BIN" verify-blob ... then). No objection to the production change.
The problem is case 19, the part of this PR that is supposed to keep the hole closed. I ran it on this branch (34/0) and then mutated it, and it does not hold: commenting the preset out leaves case 19 green, and a whitespace variant of the same correct statement turns it red. Two comments inline, both on the same fragile text match; one suggestion fixes both.
…loc, #529) Both review findings were right, and both are the failure this PR is about -- left open on the side the PR did not anchor. A COMMENTED-OUT PRESET SATISFIED CASE 19. The check matched the substring `$global:LASTEXITCODE = 255` anywhere on a line, so `# $global:LASTEXITCODE = 255` passed it: the gate dead, the suite green. That is the likelier human mutation -- commenting the line out while debugging the installer -- and it was exactly the one not covered. The PR proved the DELETE mutation and missed this one. THE MIRROR IMAGE, ON THE SAME LINE. Hard-coded single spaces meant `$global:LASTEXITCODE=255` -- correct, equivalent PowerShell -- turned case 19 RED on a working gate, with a message asserting the installer would install unverified. A false alarm that names a supply-chain failure is worse than none. Anchoring the whole statement start-to-end, with flexible spacing, closes both. Suggestion taken as written from the review. AND THE `$` IS ESCAPED in the `if ($sigDownloaded)` grep, matching the two sibling patterns in the same block. In a POSIX BRE a `$` that is not at the end is undefined; an implementation treating it as an anchor matches nothing, `blk_line` comes back empty, and case 19 fails on correct code. Verified all three directions on this branch, reproducing the reviewer's results first: commented-out preset before 34/0 (escaped) -> after 33/1 (caught) no-space variant before 33/1 (false) -> after 34/0 (correct) preset deleted before 33/1 -> after 33/1 (still caught) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
left a comment
There was a problem hiding this comment.
Both findings fixed, and I reproduced all three directions on 372e868 rather than taking the commit message's numbers on trust:
M1 preset commented out -> 33 passed, 1 failed (caught)
M2 $global:LASTEXITCODE=255 -> 34 passed, 0 failed (no longer a false alarm)
M3 preset deleted -> 33 passed, 1 failed (still caught)
baseline -> 34 passed, 0 failed
The escaped \$ also resolves the portability half — grep -n 'if (\$sigDownloaded)' now returns line 329 under both /usr/bin/grep and the anchor-treating ugrep that made the old pattern match nothing. bash -n clean.
Anchoring the statement start-to-end is the right call over -F on the preset: it is the property that actually matters (the assignment is code, not prose), and it is what makes M1 and M2 come out opposite ways from the same edit.
One inherent consequence of the anchor, not worth another round: a trailing comment on the preset line ($global:LASTEXITCODE = 255 # no verdict yet) would now read as absent and turn case 19 red. That is the safe direction to fail, and loosening it is how the commented-out hole got in, so leaving it strict is the better trade.
The production one-liner in install.ps1 is unchanged from my first pass and still correct. The limitation the PR states up front stands and is worth its own ticket: this is a source assertion, and install.ps1 has no behavioural coverage — no pwsh, no Pester anywhere in CI — which is why a signature gate that did not gate reached a promotion PR in the first place.
Summary
$LASTEXITCODEpersists from the previous command.Test-CosignRunsrunscosign versionimmediately before verification and presets 255 precisely so abinary that never starts cannot leave a stale
0behind.The
verify-blobcall that actually gates the install had no such preset.So a cosign shim that exits
0onversionand then no-ops onverify-blobleaves$LASTEXITCODEat0, the-ne 0gate reads that as success, and the installerprints "cosign signature valid" and installs a binary nothing verified. RFC-0001
R8 defeated by a stale variable, one line from the guard that already exists.
Reproduced before fixing
Driving the real block extracted from
install.ps1underpwsh, with a verifierthat no-ops:
Type
fix
Test plan
Case 19 in
install-verify.shasserts the preset sits inside the$sigDownloadedblock and before the invocation, by line number — so a presetelsewhere in the file cannot satisfy it.
Mutation-proved: removing the preset reddens case 19 with a message naming the
consequence.
34 passed / 0 failed; under mutation33 / 1.A limitation worth stating rather than hiding
This is a source assertion.
install.ps1has no behavioural coverage —there is no
pwshor Pester anywhere in this repo's CI, which is why a signaturegate that does not gate reached a promotion PR. This closes the specific hole; it
does not make the Windows installer tested. That deserves its own ticket.
One self-inflicted detail, recorded because it is this repo's own failure class
The first version of the check grepped for
verify-bloband matched theexplanatory comment written directly above the call, so it failed on correct
code. Anchored on the
& $cosigninvocation instead — prose is not wiring.Provenance
Found by Bugbot on release-train promotion PR #528 (High). Per the staging-hop
policy, fixed on
develop. Unblocks the cli leg.Checklist
developNote
High Risk
Changes mandatory cosign verification on the Windows installer; a stale exit code could install binaries that were never signature-checked.
Overview
Fixes a Windows installer signature bypass where
cosign verify-blobcould be treated as successful without actually running.Test-CosignRunsalready sets$global:LASTEXITCODE = 255beforecosign versionso a no-op shim cannot leave a stale0. Theverify-blobcall that gates installation did not do the same. After a successfulversion, a shim that no-ops onverify-blobleft$LASTEXITCODEat0, so the installer could report a valid cosign signature and install an unverified binary (RFC-0001 R8 / cli#528).The PR adds the same 255 preset immediately before
& $cosign verify-blobinside the$sigDownloadedblock.Case 19 in
install-verify.shasserts that preset sits betweenif ($sigDownloaded)and the& $cosign verify-blobinvocation (line-order check; anchors on the invocation, not comment text). This is source-only coverage—there is still no pwsh/Pester behavioral test forinstall.ps1.Reviewed by Cursor Bugbot for commit 372e868. Bugbot is set up for automated code reviews on this repo. Configure here.