Browse Source

punk-runtime: dependency-free sha1 hashing in the powershell payload (Get-FileHash field failure)

Field report (jcross1, Windows PowerShell 5): Get-FileHash not
recognized during list -remote while Write-Host/Invoke-WebRequest
worked. Root cause class: Get-FileHash is a SCRIPT-defined function in
PS 5.1's Microsoft.PowerShell.Utility, resolved via PSModulePath
autoload - a damaged PSModulePath (pwsh7 path pollution, overridden env,
redirected user module dirs) removes it while the module's compiled
cmdlets keep working. Effects were wrong results, not just noise:
spurious UPDATE AVAILABLE rows (null local hash) and fail-closed fetch
verification.

New Get-PunkFileSha1 computes sha1 via .NET (SHA1.Create over a stream,
lowercase hex) at all three former Get-FileHash sites; works on any
PowerShell edition/state incl FIPS, and emits a one-time informational
note when the damaged condition is detected (chosen over
detect-and-offer-install/env-repair: nothing to install once the
dependency is gone, and persistent environment mutation from a fetch
tool is disproportionate). Bash payload already probes
sha1sum/shasum/sha1/openssl - unchanged. bin/AGENTS.md records the
no-script-module-cmdlets payload rule.

Verified: hash parity with Get-FileHash under powershell 5 and pwsh 7;
list -remote via the rewrapped .cmd (correct Same-version rows);
damaged-PS simulation (probe forced unresolvable): note printed once,
zero errors, hashing correct; scriptwrap roundtrip pin
runtimecmd_roundtrip PASS (payload + regenerated bin output committed
together). CHANGELOG bullet added under the in-flight 0.19.0 entry
(rides the user's pending release-notes commit).

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 6 days ago
parent
commit
ee3ed05923
  1. 8
      bin/AGENTS.md
  2. 47
      bin/punk-runtime.cmd
  3. 47
      src/scriptapps/bin/punk-runtime.ps1

8
bin/AGENTS.md

@ -101,6 +101,14 @@ The first argument to a built punk shell may be a dash-delimited package mode co
`bin/runtime/<platform>/` (retrieved from the punkbin artifact repo with sha1
verification against its `sha1sums.txt` - both the powershell payload on windows and the
bash payload on unix verify; a fetch whose checksum fails leaves only a `.tmp`).
Hashing is deliberately dependency-free in both payloads: the powershell payload computes
sha1 via .NET (`Get-PunkFileSha1`) rather than `Get-FileHash`, because Get-FileHash is a
script-defined function in Windows PowerShell 5.1 resolved through PSModulePath and
vanishes on machines with a damaged PSModulePath while compiled cmdlets still work
(field-observed; a one-time note flags the condition); the bash payload probes
sha1sum/shasum/sha1/openssl. Keep new payload code free of PS 5.1
script-module-defined cmdlets (`Get-FileHash`, `New-TemporaryFile`, `New-Guid`,
`Import-PowerShellDataFile`, ...) for the same reason.
`list -remote` compares local runtimes against the server's sha1sums (Same version /
UPDATE AVAILABLE / not-listed, plus remote-only entries; cached sha1sums.txt fallback
with a warning when the server is unreachable). It also surfaces the selection state:

47
bin/punk-runtime.cmd

@ -2513,6 +2513,41 @@ function Get-PunkRuntimeRootName {
}
return $name
}
#Dependency-free SHA1 (robustness fix 2026-07-23): Get-FileHash is a SCRIPT-defined
#function in Windows PowerShell 5.1's Microsoft.PowerShell.Utility, resolved via
#PSModulePath autoloading - on machines with a damaged PSModulePath (pwsh7 module-path
#pollution, overridden env vars, redirected user module dirs) it vanishes while the
#module's COMPILED cmdlets (Write-Host, Invoke-WebRequest) still work, observed in the
#field ("Get-FileHash ... not recognized" under PS 5). Hashing here goes through .NET
#directly so fetch verification and list -remote comparison work on any PowerShell
#edition/state, with a one-time informational note when the damage is present.
#Returns lowercase hex (sha1sums.txt convention; -eq/-ne comparisons are
#case-insensitive so case never matters for verification).
$script:punkFileHashNoteShown = $false
function Get-PunkFileSha1 {
param([string] $Path)
if (-not $script:punkFileHashNoteShown) {
$script:punkFileHashNoteShown = $true
if ($PSVersionTable.PSVersion.Major -le 5 -and -not (Get-Command Get-FileHash -ErrorAction SilentlyContinue)) {
Write-Host "note: Get-FileHash is unavailable in this Windows PowerShell (PSModulePath on this machine may be damaged) - punk-runtime uses built-in .NET hashing and is unaffected"
}
}
#Resolve-Path: .NET's current directory is not PowerShell's location, so hand
#[System.IO.File] an absolute provider path
$fullpath = (Resolve-Path -LiteralPath $Path).ProviderPath
$sha1 = [System.Security.Cryptography.SHA1]::Create()
try {
$fs = [System.IO.File]::OpenRead($fullpath)
try {
$hashbytes = $sha1.ComputeHash($fs)
} finally {
$fs.Dispose()
}
} finally {
$sha1.Dispose()
}
return (($hashbytes | ForEach-Object { $_.ToString("x2") }) -join "")
}
#G-103 artifact metadata: a runtime may carry a <rootname>.toml beside it (emitted
#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a
#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent.
@ -2787,8 +2822,8 @@ function psmain {
if (Test-Path -Path $output -PathType Leaf) {
Write-Host "Runtime already found at $output"
Write-Host "Checking sha1 checksum of local file versus sha1 of server file"
$file_sha1 = Get-FileHash -Path "$output" -Algorithm SHA1
if (${file_sha1}.Hash -ne $stored_sha1) {
$file_sha1 = Get-PunkFileSha1 -Path "$output"
if ($file_sha1 -ne $stored_sha1) {
Write-Host "$runtime on server has different sha1 hash - Download required"
$need_download = $true
}
@ -2812,8 +2847,8 @@ function psmain {
}
Write-Host "comparing sha1 checksum of downloaded file with data in sha1sums.txt"
Start-Sleep -Seconds 1 #REVIEW - give at least some time for windows to do its thing? (av filters?)
$newfile_sha1 = Get-FileHash -Path "${output}.tmp" -Algorithm SHA1
if (${newfile_sha1}.Hash -eq $stored_sha1) {
$newfile_sha1 = Get-PunkFileSha1 -Path "${output}.tmp"
if ($newfile_sha1 -eq $stored_sha1) {
Write-Host "sha1 checksum ok"
Move-Item -Path "${output}.tmp" -Destination "${output}" -Force
Write-Host "Runtime is available at ${output}"
@ -3045,8 +3080,8 @@ function psmain {
#local runtimes in the comparison
$dircontents = Get-PunkRuntimeCandidates $archfolder
foreach ($f in $dircontents) {
$local_sha1 = Get-FileHash -Path $(${f}.FullName) -Algorithm SHA1
$localdict[$f.Name] = ${local_sha1}.Hash
$local_sha1 = Get-PunkFileSha1 -Path $(${f}.FullName)
$localdict[$f.Name] = $local_sha1
}
}

47
src/scriptapps/bin/punk-runtime.ps1

@ -199,6 +199,41 @@ function Get-PunkRuntimeRootName {
}
return $name
}
#Dependency-free SHA1 (robustness fix 2026-07-23): Get-FileHash is a SCRIPT-defined
#function in Windows PowerShell 5.1's Microsoft.PowerShell.Utility, resolved via
#PSModulePath autoloading - on machines with a damaged PSModulePath (pwsh7 module-path
#pollution, overridden env vars, redirected user module dirs) it vanishes while the
#module's COMPILED cmdlets (Write-Host, Invoke-WebRequest) still work, observed in the
#field ("Get-FileHash ... not recognized" under PS 5). Hashing here goes through .NET
#directly so fetch verification and list -remote comparison work on any PowerShell
#edition/state, with a one-time informational note when the damage is present.
#Returns lowercase hex (sha1sums.txt convention; -eq/-ne comparisons are
#case-insensitive so case never matters for verification).
$script:punkFileHashNoteShown = $false
function Get-PunkFileSha1 {
param([string] $Path)
if (-not $script:punkFileHashNoteShown) {
$script:punkFileHashNoteShown = $true
if ($PSVersionTable.PSVersion.Major -le 5 -and -not (Get-Command Get-FileHash -ErrorAction SilentlyContinue)) {
Write-Host "note: Get-FileHash is unavailable in this Windows PowerShell (PSModulePath on this machine may be damaged) - punk-runtime uses built-in .NET hashing and is unaffected"
}
}
#Resolve-Path: .NET's current directory is not PowerShell's location, so hand
#[System.IO.File] an absolute provider path
$fullpath = (Resolve-Path -LiteralPath $Path).ProviderPath
$sha1 = [System.Security.Cryptography.SHA1]::Create()
try {
$fs = [System.IO.File]::OpenRead($fullpath)
try {
$hashbytes = $sha1.ComputeHash($fs)
} finally {
$fs.Dispose()
}
} finally {
$sha1.Dispose()
}
return (($hashbytes | ForEach-Object { $_.ToString("x2") }) -join "")
}
#G-103 artifact metadata: a runtime may carry a <rootname>.toml beside it (emitted
#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a
#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent.
@ -473,8 +508,8 @@ function psmain {
if (Test-Path -Path $output -PathType Leaf) {
Write-Host "Runtime already found at $output"
Write-Host "Checking sha1 checksum of local file versus sha1 of server file"
$file_sha1 = Get-FileHash -Path "$output" -Algorithm SHA1
if (${file_sha1}.Hash -ne $stored_sha1) {
$file_sha1 = Get-PunkFileSha1 -Path "$output"
if ($file_sha1 -ne $stored_sha1) {
Write-Host "$runtime on server has different sha1 hash - Download required"
$need_download = $true
}
@ -498,8 +533,8 @@ function psmain {
}
Write-Host "comparing sha1 checksum of downloaded file with data in sha1sums.txt"
Start-Sleep -Seconds 1 #REVIEW - give at least some time for windows to do its thing? (av filters?)
$newfile_sha1 = Get-FileHash -Path "${output}.tmp" -Algorithm SHA1
if (${newfile_sha1}.Hash -eq $stored_sha1) {
$newfile_sha1 = Get-PunkFileSha1 -Path "${output}.tmp"
if ($newfile_sha1 -eq $stored_sha1) {
Write-Host "sha1 checksum ok"
Move-Item -Path "${output}.tmp" -Destination "${output}" -Force
Write-Host "Runtime is available at ${output}"
@ -731,8 +766,8 @@ function psmain {
#local runtimes in the comparison
$dircontents = Get-PunkRuntimeCandidates $archfolder
foreach ($f in $dircontents) {
$local_sha1 = Get-FileHash -Path $(${f}.FullName) -Algorithm SHA1
$localdict[$f.Name] = ${local_sha1}.Hash
$local_sha1 = Get-PunkFileSha1 -Path $(${f}.FullName)
$localdict[$f.Name] = $local_sha1
}
}

Loading…
Cancel
Save