Skip to content

fix(install.ps1): verify-blob must not inherit a stale exit code - #529

Merged
LukasWodka merged 2 commits into
developfrom
fix/2141-verify-blob-exit-preset
Aug 18, 2026
Merged

fix(install.ps1): verify-blob must not inherit a stale exit code#529
LukasWodka merged 2 commits into
developfrom
fix/2141-verify-blob-exit-preset

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

$LASTEXITCODE persists from the previous command. Test-CosignRuns runs
cosign version immediately before 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. 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.ps1 under pwsh, with a verifier
that no-ops:

with fix     -> REFUSED               (LASTEXITCODE=255)
without fix  -> INSTALLED-UNVERIFIED  (LASTEXITCODE=0)

Type

fix

Test plan

Case 19 in install-verify.sh asserts the preset sits inside the
$sigDownloaded block and before the invocation, by line number — so a preset
elsewhere 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 mutation 33 / 1.

A limitation worth stating rather than hiding

This is a source assertion. 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. 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-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.

Provenance

Found by Bugbot on release-train promotion PR #528 (High). Per the staging-hop
policy, fixed on develop. Unblocks the cli leg.

Checklist

  • Targets develop
  • Tests added and mutation-proved
  • No secrets or customer data

Note

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-blob could be treated as successful without actually running.

Test-CosignRuns already sets $global:LASTEXITCODE = 255 before cosign version so a no-op shim cannot leave a stale 0. The verify-blob call that gates installation did not do the same. After a successful version, a shim that no-ops on verify-blob left $LASTEXITCODE at 0, 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-blob inside the $sigDownloaded block.

Case 19 in install-verify.sh asserts that preset sits between if ($sigDownloaded) and the & $cosign verify-blob invocation (line-order check; anchors on the invocation, not comment text). This is source-only coverage—there is still no pwsh/Pester behavioral test for install.ps1.

Reviewed by Cursor Bugbot for commit 372e868. Bugbot is set up for automated code reviews on this repo. Configure here.

`$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>
@LukasWodka LukasWodka self-assigned this Aug 18, 2026
@LukasWodka
LukasWodka requested a review from saadqbal August 18, 2026 11:20

@shujaatTracebloc shujaatTracebloc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread scripts/tests/install-verify.sh Outdated
Comment thread scripts/tests/install-verify.sh Outdated
…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 shujaatTracebloc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@LukasWodka
LukasWodka merged commit df1daa9 into develop Aug 18, 2026
26 checks passed
@LukasWodka
LukasWodka deleted the fix/2141-verify-blob-exit-preset branch August 18, 2026 14:42
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.

2 participants