diff --git a/Changelog.md b/Changelog.md index 765ac8a..5d9de2c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,15 @@ # Changelog +## v1.122.3 + +Closes the second self-update path, and corrects a packaging claim. + +- **`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. +- **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. +- **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. + +No module or CLI action changes (81 modules, 201 actions). + ## v1.122.2 Packaging fix for the v1.122.1 release. diff --git a/Header.ps1 b/Header.ps1 index d761a88..41a3ed8 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.122.2 + 1.122.3 .LAST UPDATED 07/28/2026 diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index 295e19b..dd0a1fa 100644 --- a/Modules/00-Initialization.ps1 +++ b/Modules/00-Initialization.ps1 @@ -233,7 +233,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) { if (-not $script:ModuleRoot -and $script:ScriptPath) { $script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath) } -$script:ScriptVersion = "1.122.2" +$script:ScriptVersion = "1.122.3" $script:ScriptStartTime = Get-Date # Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe. diff --git a/Modules/50-EntryPoint.ps1 b/Modules/50-EntryPoint.ps1 index eb6c317..d0253f1 100644 --- a/Modules/50-EntryPoint.ps1 +++ b/Modules/50-EntryPoint.ps1 @@ -1078,7 +1078,15 @@ function Invoke-CLIAction { } Write-OutputColor " SHA256 verified." -color "Success" } else { - Write-OutputColor " WARNING: No SHA256 in release body — skipping verification." -color "Warning" + # Fail closed, matching Install-RackStack.ps1's bootstrap policy: refusal is the + # default outcome of any verification failure, including a missing manifest entry. + # This replaces the running EXE, so an update whose integrity cannot be + # established must not be installed. (The interactive updater in 35-Utilities + # had the same fail-open shape and was fixed in v1.122.1.) + Remove-Item -LiteralPath $stageDir -Recurse -Force -ErrorAction SilentlyContinue + Write-OutputColor " No SHA256 for RackStack.exe in the release body — refusing to install an unverified update." -color "Error" + Write-OutputColor " Download and verify manually from: https://github.com/TheAbider/RackStack/releases/tag/$latestTag" -color "Info" + [Environment]::Exit(1) } # If a previous .old still exists (user ran UpdateSelf twice without launching in between), diff --git a/README.md b/README.md index 26d0313..c443cae 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5424 structural tests + 5426 structural tests Pester 312 tests SLSA Level 3

diff --git a/RackStack.ps1 b/RackStack.ps1 index 91e70eb..0c69c06 100644 --- a/RackStack.ps1 +++ b/RackStack.ps1 @@ -13,7 +13,7 @@ Environment-specific settings are configured via rackstack.config.json (a legacy defaults.json is still read). .VERSION - 1.122.2 + 1.122.3 .NOTES - Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing) - Must be run as Administrator diff --git a/RackStack.psd1 b/RackStack.psd1 index f8664d3..70354f8 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'RackStack.psm1' - ModuleVersion = '1.122.2' + ModuleVersion = '1.122.3' GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91' Author = 'TheAbider' CompanyName = 'TheAbider' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 7bf5b81..5343871 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Automated Test Runner for RackStack v1.122.2 + Automated Test Runner for RackStack v1.122.3 .DESCRIPTION Comprehensive non-interactive test suite covering: @@ -10333,8 +10333,25 @@ try { Write-TestResult "Update: Get-ReleaseAssetHash function exists" ($utilContent204 -match 'function\s+Get-ReleaseAssetHash\b') Write-TestResult "Update: hash lookup is delegated, not inlined" ($utilContent204 -match '\$expectedHash\s*=\s*Get-ReleaseAssetHash\s') Write-TestResult "Update: asset selection normalizes space/dot" ($utilContent204 -match '\$assetKey\s*=\s*\$assetName\s*-replace\s*''\[\\s\.\]'',\s*''\.''') - # The old fail-open text must never come back. - Write-TestResult "Update: no 'skipping verification' fail-open path" (-not ($utilContent204 -match 'skipping verification')) + # The old fail-open text must never come back — REPO-WIDE, not just in this module. + # Scoping this to 35-Utilities was a mistake: it passed while an identical fail-open + # survived in 50-EntryPoint's UpdateSelf action, and only an end-to-end check against the + # shipped monolithic caught it. Any code path that replaces a running binary or script must + # refuse an update it cannot verify, so assert across every module. + $failOpenHits204 = @( + Get-ChildItem -Path $modulesPath -Filter '*.ps1' -File | + Where-Object { (Get-Content $_.FullName -Raw) -match 'skipping verification' } | + ForEach-Object { $_.Name } + ) + Write-TestResult "Update: no 'skipping verification' fail-open path in ANY module" ` + ($failOpenHits204.Count -eq 0) $(if ($failOpenHits204.Count) { "found in: $($failOpenHits204 -join ', ')" } else { "" }) + + # Both self-update implementations must refuse rather than warn-and-continue. + $entryContent204 = Get-Content "$modulesPath\50-EntryPoint.ps1" -Raw + Write-TestResult "Update: UpdateSelf refuses an unverified EXE" ` + ($entryContent204 -match 'refusing to install an unverified update') + Write-TestResult "Update: UpdateSelf exits rather than continuing" ` + ($entryContent204 -match 'refusing to install an unverified update[\s\S]{0,400}\[Environment\]::Exit\(1\)') Write-TestResult "Update: missing hash refuses the install" ($utilContent204 -match 'refusing to install an unverified update') # A refusal must actually return, not just print. Write-TestResult "Update: refusal path returns before install" ($utilContent204 -match 'refusing to install an unverified update[\s\S]{0,400}\breturn\b') diff --git a/dist/scoop/rackstack.json b/dist/scoop/rackstack.json index f950688..de9db75 100644 --- a/dist/scoop/rackstack.json +++ b/dist/scoop/rackstack.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", "version": "0.0.0", - "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.", + "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.", "homepage": "https://github.com/TheAbider/RackStack", "license": "MIT", "url": "https://github.com/TheAbider/RackStack/releases/download/v0.0.0/RackStack.exe",