Skip to content

Commit d40bcbc

Browse files
authored
Make UpdateSelf refuse an update it cannot verify (#94)
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.
1 parent b10aa61 commit d40bcbc

9 files changed

Lines changed: 45 additions & 10 deletions

File tree

Changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## v1.122.3
4+
5+
Closes the second self-update path, and corrects a packaging claim.
6+
7+
- **`UpdateSelf` now refuses an update it cannot verify.** v1.122.1 fixed the interactive updater, but the `UpdateSelf` CLI action is a separate implementation and kept the same fail-open shape: if 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 applied. 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 actually shipped, and this makes that description true.
8+
- **The test suite now asserts no module has a fail-open verification path**, rather than checking the one module that was edited. The narrower check passed while this second path survived; only verifying the shipped artifact caught it.
9+
- **The Scoop manifest no longer claims the EXE is code-signed.** It is not Authenticode-signed -- that is precisely why antivirus engines flag it. The description now states what is actually true: Sigstore cosign signatures and SLSA Level 3 build provenance.
10+
11+
No module or CLI action changes (81 modules, 201 actions).
12+
313
## v1.122.2
414

515
Packaging fix for the v1.122.1 release.

Header.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.122.2
33+
1.122.3
3434
.LAST UPDATED
3535
07/28/2026
3636

Modules/00-Initialization.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
233233
if (-not $script:ModuleRoot -and $script:ScriptPath) {
234234
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
235235
}
236-
$script:ScriptVersion = "1.122.2"
236+
$script:ScriptVersion = "1.122.3"
237237
$script:ScriptStartTime = Get-Date
238238

239239
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.

Modules/50-EntryPoint.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,15 @@ function Invoke-CLIAction {
10781078
}
10791079
Write-OutputColor " SHA256 verified." -color "Success"
10801080
} else {
1081-
Write-OutputColor " WARNING: No SHA256 in release body — skipping verification." -color "Warning"
1081+
# Fail closed, matching Install-RackStack.ps1's bootstrap policy: refusal is the
1082+
# default outcome of any verification failure, including a missing manifest entry.
1083+
# This replaces the running EXE, so an update whose integrity cannot be
1084+
# established must not be installed. (The interactive updater in 35-Utilities
1085+
# had the same fail-open shape and was fixed in v1.122.1.)
1086+
Remove-Item -LiteralPath $stageDir -Recurse -Force -ErrorAction SilentlyContinue
1087+
Write-OutputColor " No SHA256 for RackStack.exe in the release body — refusing to install an unverified update." -color "Error"
1088+
Write-OutputColor " Download and verify manually from: https://github.com/TheAbider/RackStack/releases/tag/$latestTag" -color "Info"
1089+
[Environment]::Exit(1)
10821090
}
10831091

10841092
# If a previous .old still exists (user ran UpdateSelf twice without launching in between),

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<a href="https://www.bestpractices.dev/projects/12921"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12921/badge"></a>
2929
<a href="https://codecov.io/gh/TheAbider/RackStack"><img alt="codecov" src="https://codecov.io/gh/TheAbider/RackStack/branch/master/graph/badge.svg"></a>
3030
<img alt="PSScriptAnalyzer 0 errors" src="https://img.shields.io/badge/PSScriptAnalyzer-0%20errors-brightgreen">
31-
<img alt="5424 structural tests" src="https://img.shields.io/badge/structural%20tests-5424-brightgreen">
31+
<img alt="5426 structural tests" src="https://img.shields.io/badge/structural%20tests-5426-brightgreen">
3232
<img alt="Pester 312 tests" src="https://img.shields.io/badge/Pester-312%20tests-brightgreen">
3333
<img alt="SLSA Level 3" src="https://slsa.dev/images/gh-badge-level3.svg">
3434
</p>

RackStack.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Environment-specific settings are configured via rackstack.config.json (a legacy defaults.json is still read).
1414
1515
.VERSION
16-
1.122.2
16+
1.122.3
1717
.NOTES
1818
- Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing)
1919
- Must be run as Administrator

RackStack.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@{
22
RootModule = 'RackStack.psm1'
3-
ModuleVersion = '1.122.2'
3+
ModuleVersion = '1.122.3'
44
GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91'
55
Author = 'TheAbider'
66
CompanyName = 'TheAbider'

Tests/Run-Tests.ps1

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<#
22
.SYNOPSIS
3-
Automated Test Runner for RackStack v1.122.2
3+
Automated Test Runner for RackStack v1.122.3
44

55
.DESCRIPTION
66
Comprehensive non-interactive test suite covering:
@@ -10333,8 +10333,25 @@ try {
1033310333
Write-TestResult "Update: Get-ReleaseAssetHash function exists" ($utilContent204 -match 'function\s+Get-ReleaseAssetHash\b')
1033410334
Write-TestResult "Update: hash lookup is delegated, not inlined" ($utilContent204 -match '\$expectedHash\s*=\s*Get-ReleaseAssetHash\s')
1033510335
Write-TestResult "Update: asset selection normalizes space/dot" ($utilContent204 -match '\$assetKey\s*=\s*\$assetName\s*-replace\s*''\[\\s\.\]'',\s*''\.''')
10336-
# The old fail-open text must never come back.
10337-
Write-TestResult "Update: no 'skipping verification' fail-open path" (-not ($utilContent204 -match 'skipping verification'))
10336+
# The old fail-open text must never come back — REPO-WIDE, not just in this module.
10337+
# Scoping this to 35-Utilities was a mistake: it passed while an identical fail-open
10338+
# survived in 50-EntryPoint's UpdateSelf action, and only an end-to-end check against the
10339+
# shipped monolithic caught it. Any code path that replaces a running binary or script must
10340+
# refuse an update it cannot verify, so assert across every module.
10341+
$failOpenHits204 = @(
10342+
Get-ChildItem -Path $modulesPath -Filter '*.ps1' -File |
10343+
Where-Object { (Get-Content $_.FullName -Raw) -match 'skipping verification' } |
10344+
ForEach-Object { $_.Name }
10345+
)
10346+
Write-TestResult "Update: no 'skipping verification' fail-open path in ANY module" `
10347+
($failOpenHits204.Count -eq 0) $(if ($failOpenHits204.Count) { "found in: $($failOpenHits204 -join ', ')" } else { "" })
10348+
10349+
# Both self-update implementations must refuse rather than warn-and-continue.
10350+
$entryContent204 = Get-Content "$modulesPath\50-EntryPoint.ps1" -Raw
10351+
Write-TestResult "Update: UpdateSelf refuses an unverified EXE" `
10352+
($entryContent204 -match 'refusing to install an unverified update')
10353+
Write-TestResult "Update: UpdateSelf exits rather than continuing" `
10354+
($entryContent204 -match 'refusing to install an unverified update[\s\S]{0,400}\[Environment\]::Exit\(1\)')
1033810355
Write-TestResult "Update: missing hash refuses the install" ($utilContent204 -match 'refusing to install an unverified update')
1033910356
# A refusal must actually return, not just print.
1034010357
Write-TestResult "Update: refusal path returns before install" ($utilContent204 -match 'refusing to install an unverified update[\s\S]{0,400}\breturn\b')

dist/scoop/rackstack.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json",
33
"version": "0.0.0",
4-
"description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a code-signed EXE plus a PowerShell Gallery wrapper module exposing 201 structured CLI actions.",
4+
"description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a standalone EXE with Sigstore cosign signatures and SLSA Level 3 build provenance, plus a PowerShell Gallery wrapper module exposing 201 structured CLI actions.",
55
"homepage": "https://github.com/TheAbider/RackStack",
66
"license": "MIT",
77
"url": "https://github.com/TheAbider/RackStack/releases/download/v0.0.0/RackStack.exe",

0 commit comments

Comments
 (0)