diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 18042dc..85d3e1c 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -464,12 +464,51 @@ try { # pass. Only cosign actually completing can bring it back to 0. That is # the whole guarantee behind RFC-0001 R8, and it was one line away. $global:LASTEXITCODE = 255 - & $cosign verify-blob ` - --certificate-identity-regexp "https://github.com/$GitHubRepo/.github/workflows/release.yml@refs/tags/v.*" ` - --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' ` - --certificate (Join-Path $tmpDir "$binaryFile.cert") ` - --signature (Join-Path $tmpDir "$binaryFile.sig") ` - (Join-Path $tmpDir $binaryFile) 2>$null + # AND THE STDERR WINDOW, WITHOUT WHICH SUCCESS ITSELF IS FATAL. + # + # cosign writes "Verified OK" to STDERR on the SUCCESS path. Windows + # PowerShell 5.1 turns any native-command stderr into a + # NativeCommandError record, and this script runs under + # `$ErrorActionPreference = 'Stop'` (line 27) -- so the record is + # TERMINATING and the installer dies at the moment verification + # passes. `2>$null` does not save it: 5.1 raises the record before + # the redirection applies. Measured on a real Windows 11 box against + # the published v0.10.17 installer: + # + # Verifying cosign signature... + # cosign.exe : Verified OK + # + & $cosign verify-blob ` + # + FullyQualifiedErrorId : NativeCommandError + # + # ...and nothing was installed. So `tracebloc client` cannot be + # installed on Windows at all, and tracebloc/client's installer + # Step 4 fails with it, which drops Step 5 to manual sign-in and + # Step 6 to the hand-typed credential prompt. + # + # INVISIBLE ON POWERSHELL 7, which is why it survived: pwsh 7 does + # not make error records out of native stderr, so it passes there + # and fails on the Windows default. + # + # THIS FILE ALREADY HAD THE FIX, 230 LINES UP. `Test-CosignRuns` + # opens exactly this window for `cosign version` and says why. The + # idiom was simply never applied to the call that matters. + # + # NOT piped to Out-Null: `$LASTEXITCODE` is the entire verdict here + # and piping a native command through a cmdlet is a documented way + # to lose it. The window makes the record non-terminating; `2>$null` + # still discards the text. + $prevEap = $ErrorActionPreference + try { + $ErrorActionPreference = 'Continue' + & $cosign verify-blob ` + --certificate-identity-regexp "https://github.com/$GitHubRepo/.github/workflows/release.yml@refs/tags/v.*" ` + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' ` + --certificate (Join-Path $tmpDir "$binaryFile.cert") ` + --signature (Join-Path $tmpDir "$binaryFile.sig") ` + (Join-Path $tmpDir $binaryFile) 2>$null + } finally { + $ErrorActionPreference = $prevEap + } if ($LASTEXITCODE -ne 0) { # No TRACEBLOC_ALLOW_UNVERIFIED branch here, deliberately. # Verification RAN and said no. diff --git a/scripts/tests/install-ps1-verify.sh b/scripts/tests/install-ps1-verify.sh index 671c99c..893610d 100755 --- a/scripts/tests/install-ps1-verify.sh +++ b/scripts/tests/install-ps1-verify.sh @@ -134,6 +134,33 @@ else bad 'a post-cosign artifact fetch is missing its size cap' fi + +# ── 5d. every native call sits in a stderr window (cli, Windows PowerShell 5.1) +# THE SUCCESS PATH WAS FATAL. cosign writes "Verified OK" to STDERR when a +# signature VERIFIES. Windows PowerShell 5.1 turns native-command stderr into a +# NativeCommandError record, and this installer runs under +# `$ErrorActionPreference = 'Stop'`, so that record TERMINATES the run at the +# moment verification succeeds. `2>$null` does not prevent it -- 5.1 raises the +# record before the redirection applies. Measured on a real Windows 11 box +# against the published v0.10.17 installer: "cosign.exe : Verified OK" followed +# by NativeCommandError, and nothing installed. +# +# Invisible on PowerShell 7, which does not make error records out of native +# stderr -- so it passes wherever it is tested and fails on the Windows default. +# +# THE INVARIANT, and it catches the next native call someone adds: every `& $...` +# invocation must sit inside an `$ErrorActionPreference = 'Continue'` window. +# There are exactly two (cosign version, cosign verify-blob) and there must be at +# least as many windows as calls. +native_calls=$(grep -cE '^[[:space:]]*&[[:space:]]*\$' "$INSTALLER" || true) +eap_windows=$(grep -cF "ErrorActionPreference = 'Continue'" "$INSTALLER" || true) +if [ "$native_calls" -eq 0 ]; then + bad 'no native invocation found at all -- this guard would pass vacuously' +elif [ "$eap_windows" -ge "$native_calls" ]; then + ok "every native invocation ($native_calls) sits in a stderr window ($eap_windows)" +else + bad "a native invocation runs outside an ErrorActionPreference window ($native_calls calls, $eap_windows windows) -- cosign's 'Verified OK' on stderr will terminate the install under Windows PowerShell 5.1" +fi # ── 6. behavioural tier ───────────────────────────────────────────────────── # pwsh is preinstalled on GitHub-hosted ubuntu runners. If it is missing we # cannot tell whether the helpers behave, and "cannot tell" is a finding, not a