Skip to content

Make UpdateSelf refuse an update it cannot verify - #94

Merged
TheAbider merged 1 commit into
masterfrom
fix/updateself-fail-closed
Jul 28, 2026
Merged

Make UpdateSelf refuse an update it cannot verify#94
TheAbider merged 1 commit into
masterfrom
fix/updateself-fail-closed

Conversation

@TheAbider

Copy link
Copy Markdown
Owner

The second fail-open path

v1.122.1 fixed the interactive updater (Install-ScriptUpdate in 35-Utilities). The UpdateSelf CLI action in 50-EntryPoint is a separate implementation and kept the same shape:

} else {
    Write-OutputColor "  WARNING: No SHA256 in release body — skipping verification." -color "Warning"
}

It then renames the running EXE aside and copies the downloaded one into place. An update whose integrity could not be established was installed with a warning.

It now refuses and exits, matching the policy Install-RackStack.ps1 already applies to the bootstrap download.

Severity is low and I want to be accurate about it. The RackStack.exe manifest line has always been present in release bodies, so this closed a latent gap, not a live exposure. Someone able to omit the hash from a release body already controls the release. But v1.122.1's changelog said "an update whose hash cannot be found is refused rather than installed with a warning" — true of the interactive updater, false of UpdateSelf. The claim was broader than what shipped; this makes it true.

How it was found, and the test lesson

Not by review — by verifying the shipped v1.122.2 monolithic instead of the module that was edited. Section 204 asserted the fail-open string was absent from 35-Utilities, which passed while an identical path survived in 50-EntryPoint.

A test scoped to the file you just changed only proves you changed that file. The assertion is now repo-wide:

$failOpenHits204 = @(Get-ChildItem -Path $modulesPath -Filter '*.ps1' -File |
    Where-Object { (Get-Content $_.FullName -Raw) -match 'skipping verification' } | ...)

Verified by reintroducing the string in 50-EntryPoint and confirming the suite goes red, rather than trusting that it passes for the right reason.

Audit of the remaining sites

Every download-then-execute path was checked, not just the one that failed:

Path Status
Install-RackStack.ps1 bootstrap Correct — fails closed, explicit -AllowUnverified opt-out
77-WindowsAdminCenter MSI install Correct — re-verifies Authenticode at apply time (TOCTOU-aware)
35-Utilities Install-ScriptUpdate Fixed in v1.122.1
50-EntryPoint UpdateSelf Fixed here

Packaging correction

dist/scoop/rackstack.json described RackStack as shipping "a code-signed EXE". It is not Authenticode-signed — that is precisely why ML antivirus engines flag it, as the new antivirus guide explains. Cosign signing is supply-chain attestation, not code signing, and readers will not draw that distinction. The description now states what is true: Sigstore cosign signatures and SLSA Level 3 build provenance.

This was the only tracked file carrying the claim; README, SECURITY.md, and the antivirus guide all state the opposite correctly.

Verification

  • 5426/5426 structural tests pass, 0 failures
  • PSScriptAnalyzer: 0 findings on 50-EntryPoint, 35-Utilities, Run-Tests
  • Pre-release validation: 38 passed, RackStack.psd1 ModuleVersion matches (1.122.3)
  • Monolithic rebuilt, parse-checked, BOMs intact

The two remaining Validate-Release failures are the pre-existing stale changelog-convention checks, unchanged from v1.122.0.

v1.122.1 fixed the interactive updater in 35-Utilities, but the UpdateSelf
CLI action in 50-EntryPoint is a separate implementation and kept the same
fail-open shape: when no SHA-256 for RackStack.exe appeared in the release
body it warned and replaced the running executable anyway.

It now refuses and exits, matching the policy Install-RackStack.ps1 already
applies to the bootstrap download. In practice the hash has always been
present, so this closes a latent gap rather than a live exposure - but
v1.122.1's notes described the fix more broadly than what shipped, and this
makes that description accurate.

Found by verifying the shipped v1.122.2 monolithic rather than the module
that was edited. The section 204 assertion was scoped to 35-Utilities, so it
passed while this second path survived. That assertion is now repo-wide
across every module, and verified by reintroducing the fail-open string in
50-EntryPoint and confirming the suite goes red.

Also audited the remaining download-then-execute sites: Install-RackStack.ps1
fails closed with an explicit -AllowUnverified opt-out, and
77-WindowsAdminCenter re-verifies its MSI signature at apply time. UpdateSelf
was the only outstanding one.

Separately, the Scoop manifest described the EXE as code-signed. It is not
Authenticode-signed - that is the reason antivirus engines flag it - so the
description now states what is true: Sigstore cosign signatures and SLSA
Level 3 build provenance.
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TheAbider
TheAbider merged commit d40bcbc into master Jul 28, 2026
7 checks passed
@TheAbider
TheAbider deleted the fix/updateself-fail-closed branch July 28, 2026 10:57
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