diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index dd82e552773..77e1dbcb7f8 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -1132,6 +1132,45 @@ jobs: throw "Signed inner artifact did not round-trip cleanly ($($failures.Count) failures)." } + # Why this step exists: electron-builder's CopyElevateHelper re-copies a + # pristine elevate.exe from its download cache over resources\elevate.exe + # on EVERY nsis pack — including the --prepackaged rebuild below — which + # clobbered the SignPath signature in v1.4.129-rc.4. There is no supported + # way to disable just the copy, so we overwrite the cache's copy with our + # signed one (identical bytes plus signature) so the clobber becomes a + # no-op. Known quirk: the cache persists across releases via actions/cache, + # so later runs may see elevate.exe as already signed and skip staging it — + # that is fine (the signature is timestamped) and the evidence gate checks + # elevate.exe in the shipped installer unconditionally. If this ever causes + # trouble, delete this step; the only effect is elevate.exe shipping + # unsigned again, which the evidence gate will flag. + - name: Replace cached elevate.exe with the signed copy + id: sign-elevate-cache + if: matrix.platform == 'win' && steps.restore-signed-inner.outcome == 'success' + continue-on-error: true + shell: pwsh + run: | + $signed = 'dist/win-unpacked/resources/elevate.exe' + if (-not (Test-Path $signed)) { + Write-Host '::warning::No elevate.exe in win-unpacked resources; nothing to protect from the rebuild clobber.' + exit 0 + } + $signature = Get-AuthenticodeSignature -FilePath $signed + $subject = if ($null -eq $signature.SignerCertificate) { '' } else { $signature.SignerCertificate.Subject } + if ($signature.Status -ne 'Valid' -or $subject -notlike '*CN=SignPath Foundation*') { + Write-Host "::warning::win-unpacked elevate.exe is not SignPath-signed ($($signature.Status), $subject); skipping cache swap." + exit 0 + } + $cached = @(Get-ChildItem "$env:LOCALAPPDATA\electron-builder\Cache\nsis" -Recurse -Filter elevate.exe -ErrorAction SilentlyContinue) + if ($cached.Count -eq 0) { + Write-Host '::warning::No cached elevate.exe found (electron-builder cache layout changed?); the rebuild will pack the unsigned copy and the evidence gate will flag it.' + exit 0 + } + foreach ($file in $cached) { + Copy-Item -Path $signed -Destination $file.FullName -Force + Write-Host "Replaced $($file.FullName) with the SignPath-signed copy." + } + - name: Rebuild NSIS installer from signed unpacked app id: rebuild-nsis-signed if: matrix.platform == 'win' && steps.restore-signed-inner.outcome == 'success' @@ -1325,7 +1364,16 @@ jobs: & $7za x 'dist/orca-windows-setup.exe' '-oinner-evidence-extract' -y | Out-Null $root = Resolve-Path 'inner-evidence-extract' - foreach ($relative in Get-Content 'inner-signing-list.txt') { + # Why elevate.exe is always appended: staging skips already-signed + # files, and the persisted electron-builder cache can carry a + # previously signed elevate.exe — so it may be absent from the list + # in some runs, yet it is the file most at risk of losing its + # signature in the NSIS rebuild. Verify it in every release. + $targets = @(Get-Content 'inner-signing-list.txt') + if ($targets -notcontains 'resources\elevate.exe') { + $targets += 'resources\elevate.exe' + } + foreach ($relative in $targets) { $path = Join-Path $root $relative if (-not (Test-Path $path)) { $failures.Add("missing from installer payload: $relative") @@ -1348,7 +1396,7 @@ jobs: if ($required) { throw $message } Write-Host "::warning::$message Fail-open until ORCA_WINDOWS_INNER_SIGNATURE_REQUIRED is 'true'." } else { - Write-Host "All $((Get-Content 'inner-signing-list.txt').Count) inner binaries in the shipped installer are signed by SignPath Foundation." + Write-Host "All $($targets.Count) inner binaries in the shipped installer are signed by SignPath Foundation." } } catch { if ($required) { throw } diff --git a/config/scripts/package-electron-runtime-contract.test.mjs b/config/scripts/package-electron-runtime-contract.test.mjs index 2a3eb906f4e..5e70cf68f42 100644 --- a/config/scripts/package-electron-runtime-contract.test.mjs +++ b/config/scripts/package-electron-runtime-contract.test.mjs @@ -254,6 +254,7 @@ describe('Electron runtime package contract', () => { 'Notify Slack that inner-binary signing is waiting for approval', 'Download signed inner binaries from SignPath', 'Restore signed inner binaries into unpacked app', + 'Replace cached elevate.exe with the signed copy', 'Rebuild NSIS installer from signed unpacked app' ] for (const stepName of innerChainStepNames) {