From d070822d6f9d9a575c0fa168bdee4576b570353f Mon Sep 17 00:00:00 2001 From: TheAbider <51920546+TheAbider@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:11:10 -0700 Subject: [PATCH] v1.122.4: stamp the EXE with its own identity Every release through v1.122.3 shipped a binary whose CompanyName, ProductName, FileDescription and LegalCopyright were empty. Verified by reading the version resource straight out of the published v1.122.3 artifact, not inferred from the build script. Two costs. An empty version resource is a mild heuristic-AV signal, because legitimate software populates it and commodity malware often does not. More concretely, the build passes -RequireAdmin, so launching the tool raises a UAC prompt, and UAC displays FileDescription as the program name: users were being asked to elevate a blank. The values match the identity already published in RackStack.psd1, the Chocolatey nuspec and the Scoop manifest, so nothing new is asserted about who ships this. Run-Tests section 209 pins the whole build surface: the compiler stays pinned to an exact version, the ps2exe invocation keeps setting title, product, company, copyright and description, and the EXE's CompanyName must equal RackStack.psd1's so the published identity cannot drift apart across manifests. Section 209 applies the lesson from section 208: it asserts the Invoke-PS2EXE regex actually matched before trusting what the match did or did not contain, so renaming a nearby variable fails the section instead of silently passing it. Mutation-verified six ways, including breaking that anchor. Structural tests 5474 -> 5485. --- .github/workflows/ci.yml | 17 +++++++++- Changelog.md | 10 ++++++ Header.ps1 | 2 +- Modules/00-Initialization.ps1 | 2 +- README.md | 2 +- RackStack.ps1 | 2 +- RackStack.psd1 | 2 +- Tests/Run-Tests.ps1 | 61 ++++++++++++++++++++++++++++++++++- 8 files changed, 91 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee96845..3b2d9ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,7 +172,22 @@ jobs: $mono = "builds\RackStack v$ver.ps1" if (-not (Test-Path $mono)) { throw "Monolithic not found at $mono" } if (-not (Test-Path 'RackStack.ico')) { throw "RackStack.ico missing — required for compile" } - Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' -Version $ver -RequireAdmin -IconFile 'RackStack.ico' + # Populate the full version resource. Company/Product/Description + # were empty in every release through v1.122.3, which is both a mild + # heuristic-AV signal (legitimate software fills these in) and a real + # UX gap: -RequireAdmin raises a UAC prompt, and UAC displays + # FileDescription as the program name — so users were being asked to + # elevate a blank. Values match the identity already published in + # RackStack.psd1, the Chocolatey nuspec, and the Scoop manifest. + Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' ` + -Version $ver ` + -RequireAdmin ` + -IconFile 'RackStack.ico' ` + -title 'RackStack - Windows Server configuration toolkit' ` + -product 'RackStack' ` + -company 'TheAbider' ` + -copyright 'Copyright (c) 2026 TheAbider' ` + -description 'Menu-driven configuration and automation for Windows Server hosts.' $info = Get-Item 'builds\RackStack.exe' Write-Host "Compiled: $($info.FullName) ($([math]::Round($info.Length / 1MB, 2)) MB)" diff --git a/Changelog.md b/Changelog.md index 5d9de2c..6f31268 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,15 @@ # Changelog +## v1.122.4 + +Hardens what the tool will let you exclude from Defender, and fixes an executable that shipped without a name. + +- **RackStack refuses to exclude a script interpreter from Defender.** A process exclusion does not exempt one application -- it exempts every payload that process will ever execute, so excluding `powershell.exe` stops Defender inspecting all PowerShell on the host from then on. The custom *path* prompt has warned before excluding an operating-system directory since v1.98; the custom *process* prompt accepted anything. It now declines `powershell`, `pwsh`, `cmd`, `wscript`, `cscript`, `mshta`, `rundll32`, `regsvr32` and the .NET command-line hosts, however they are spelled, and points at a path exclusion scoped to the application's own folder instead. The recommended Hyper-V exclusions this tool has always applied were never affected. +- **The executable now carries its own identity.** Company, product, description and copyright were empty in every previous release. Windows shows the description as the program name in the elevation prompt, so users were being asked to approve a blank; it now names the tool. A complete version resource also reads as ordinary software to antivirus heuristics, which an empty one does not. +- **The build pins the compiler that produces the executable**, so a published binary cannot change without a corresponding change in the repository. + +No module or CLI action changes (81 modules, 201 actions). + ## v1.122.3 Closes the second self-update path, and corrects a packaging claim. diff --git a/Header.ps1 b/Header.ps1 index 41a3ed8..d2f4a39 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.122.3 + 1.122.4 .LAST UPDATED 07/28/2026 diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index dd0a1fa..3752548 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.3" +$script:ScriptVersion = "1.122.4" $script:ScriptStartTime = Get-Date # Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe. diff --git a/README.md b/README.md index 369882c..bed2376 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5474 structural tests + 5485 structural tests Pester 312 tests SLSA Level 3

diff --git a/RackStack.ps1 b/RackStack.ps1 index 0c69c06..c537a44 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.3 + 1.122.4 .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 70354f8..2309036 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'RackStack.psm1' - ModuleVersion = '1.122.3' + ModuleVersion = '1.122.4' GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91' Author = 'TheAbider' CompanyName = 'TheAbider' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 7f0b88d..8f1968a 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Automated Test Runner for RackStack v1.122.3 + Automated Test Runner for RackStack v1.122.4 .DESCRIPTION Comprehensive non-interactive test suite covering: @@ -10754,6 +10754,65 @@ catch { Write-TestResult "Defender Interpreter Guard Tests" $false $_.Exception.Message } +# ============================================================================ +# SECTION 209: BUILD METADATA INTEGRITY (what ps2exe stamps into the EXE) +# ============================================================================ +# Every release through v1.122.3 shipped a binary whose CompanyName, +# ProductName, FileDescription and LegalCopyright were EMPTY — verified by +# reading the version resource out of the published v1.122.3 artifact. Two +# costs: an empty version resource is a mild heuristic-AV signal because +# legitimate software populates it, and -RequireAdmin raises a UAC prompt that +# displays FileDescription as the program name, so users were asked to elevate +# a blank. +# +# The compiler version is pinned here too. ps2exe builds the binary that ships +# to users, so an unpinned Install-Module let the released artifact change +# without a commit — the exposure the SHA-pinning policy already closes for +# actions, including transitive ones. +Write-SectionHeader "SECTION 209: BUILD METADATA INTEGRITY" + +try { + $ciPath209 = Join-Path $script:ModuleRoot '.github\workflows\ci.yml' + if (Test-Path -LiteralPath $ciPath209) { + $ci209 = Get-Content -LiteralPath $ciPath209 -Raw + + # Compiler must be pinned to an exact version, never floating. + Write-TestResult "Build: ps2exe is pinned to an explicit version" ` + ([bool]($ci209 -match "\`$ps2exeVersion\s*=\s*'\d+\.\d+\.\d+'")) + Write-TestResult "Build: ps2exe install uses -RequiredVersion" ` + ([bool]($ci209 -match 'Install-Module ps2exe -RequiredVersion')) + Write-TestResult "Build: ps2exe install is not unpinned" ` + ([bool]($ci209 -notmatch 'Install-Module ps2exe -Force')) + + # The version resource must actually be populated. + $p2e209 = [regex]::Match($ci209, '(?s)Invoke-PS2EXE.*?(?=\r?\n\s*\$info\s*=)') + Write-TestResult "Build: Invoke-PS2EXE call is locatable" $p2e209.Success ` + "regex found no ps2exe invocation — the checks below would pass vacuously" + $call209 = $p2e209.Value + foreach ($flag in @('title', 'product', 'company', 'copyright', 'description')) { + Write-TestResult "Build: EXE metadata sets -$flag" ` + ($p2e209.Success -and $call209 -match "-$flag\s+'") + } + Write-TestResult "Build: EXE still stamps -Version" ` + ($p2e209.Success -and $call209 -match '-Version\s+\$ver') + + # One identity across every published surface. The EXE's CompanyName + # must agree with the Gallery manifest rather than drifting on its own. + $psd209 = Get-Content (Join-Path $script:ModuleRoot 'RackStack.psd1') -Raw + $psdCompany209 = [regex]::Match($psd209, "CompanyName\s*=\s*'([^']+)'").Groups[1].Value + $exeCompany209 = [regex]::Match($call209, "-company\s+'([^']+)'").Groups[1].Value + Write-TestResult "Build: EXE CompanyName matches RackStack.psd1 ('$psdCompany209')" ` + ($psdCompany209 -and $exeCompany209 -and $psdCompany209 -eq $exeCompany209) ` + "psd1='$psdCompany209' exe='$exeCompany209'" + } + else { + Write-TestResult "Build: metadata integrity" -Skipped -Message "no .github/workflows in this layout" + } +} +catch { + Write-TestResult "Build Metadata Integrity Tests" $false $_.Exception.Message +} + # ============================================================================ # FINAL SUMMARY # ============================================================================