From c6299f8b856ee794e5c4fed3194398a52ed90a45 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:24:42 -0700 Subject: [PATCH] Improve Windows release signing reliability (#6292) Co-authored-by: Orca --- .github/workflows/release-cut.yml | 57 ++++++++++++++++--- ...package-electron-runtime-contract.test.mjs | 56 ++++++++++++++++++ 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index 26de7544dd2..11d09a2e576 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -917,6 +917,56 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install SignPath PowerShell module + if: matrix.platform == 'win' + shell: pwsh + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + + $trimChars = [char[]]@([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + $documentsRoot = [System.IO.Path]::GetFullPath([Environment]::GetFolderPath('MyDocuments')).TrimEnd($trimChars) + $currentUserModuleRoot = $env:PSModulePath -split [System.IO.Path]::PathSeparator | + Where-Object { + if ([string]::IsNullOrWhiteSpace($_)) { + $false + } else { + $candidate = [System.IO.Path]::GetFullPath($_).TrimEnd($trimChars) + $candidate.StartsWith($documentsRoot, [System.StringComparison]::OrdinalIgnoreCase) + } + } | + Select-Object -First 1 + + if ([string]::IsNullOrWhiteSpace($currentUserModuleRoot)) { + throw 'Unable to resolve the current-user PowerShell module root from PSModulePath.' + } + + $signPathModulePath = Join-Path -Path $currentUserModuleRoot -ChildPath 'SignPath' + + for ($attempt = 1; $attempt -le 3; $attempt++) { + if ($attempt -eq 2) { + Start-Sleep -Seconds 15 + } elseif ($attempt -eq 3) { + Start-Sleep -Seconds 30 + } + + try { + Install-Module -Name SignPath -Repository PSGallery -MinimumVersion 4.0.0 -MaximumVersion 4.999.999 -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop + Import-Module SignPath -ErrorAction Stop + Get-Command -Name Get-SignedArtifact -Module SignPath -ErrorAction Stop + break + } catch { + if ($attempt -eq 3) { + throw + } + + Write-Warning "SignPath PowerShell module preflight attempt $attempt failed: $_" + if (Test-Path -LiteralPath $signPathModulePath) { + Write-Warning "Removing current-user SignPath module directory before retry: $signPathModulePath" + Remove-Item -LiteralPath $signPathModulePath -Recurse -Force + } + } + } + - name: Upload unsigned Windows installer for SignPath if: matrix.platform == 'win' id: upload-unsigned-windows-installer @@ -977,13 +1027,6 @@ jobs: Invoke-RestMethod -Method Post -Uri $env:SLACK_WEBHOOK_URL -ContentType 'application/json' -Body $payload - - name: Install SignPath PowerShell module - if: matrix.platform == 'win' - shell: pwsh - run: | - Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - Install-Module -Name SignPath -MinimumVersion 4.0.0 -MaximumVersion 4.999.999 -Scope CurrentUser -Force - - name: Download signed Windows installer from SignPath if: matrix.platform == 'win' shell: pwsh diff --git a/config/scripts/package-electron-runtime-contract.test.mjs b/config/scripts/package-electron-runtime-contract.test.mjs index 54c7314706d..af09d347de1 100644 --- a/config/scripts/package-electron-runtime-contract.test.mjs +++ b/config/scripts/package-electron-runtime-contract.test.mjs @@ -86,6 +86,62 @@ describe('Electron runtime package contract', () => { ) }) + it('preflights SignPath module install before Windows signing side effects', () => { + const releaseWorkflow = readFileSync( + join(projectDir, '.github/workflows/release-cut.yml'), + 'utf8' + ) + const parsedWorkflow = parse(releaseWorkflow) + const steps = parsedWorkflow.jobs.build.steps + const stepNames = steps.map((step) => step.name) + const installStepIndexes = stepNames.flatMap((name, index) => + name === 'Install SignPath PowerShell module' ? [index] : [] + ) + const buildIndex = stepNames.indexOf('Build Windows release artifacts') + const uploadIndex = stepNames.indexOf('Upload unsigned Windows installer for SignPath') + const downloadIndex = stepNames.indexOf('Download signed Windows installer from SignPath') + + expect(installStepIndexes).toEqual([buildIndex + 1]) + expect(installStepIndexes[0]).toBeLessThan(uploadIndex) + + const uploadThroughDownloadScript = steps + .slice(uploadIndex, downloadIndex + 1) + .map((step) => step.run ?? '') + .join('\n') + + expect(uploadThroughDownloadScript).not.toContain('Install-Module -Name SignPath') + + const installStep = steps[installStepIndexes[0]] + const installRun = installStep.run + const sleepSeconds = [...installRun.matchAll(/Start-Sleep -Seconds (\d+)/g)].map( + ([, seconds]) => seconds + ) + + expect(installStep.if).toBe("matrix.platform == 'win'") + expect(installStep.shell).toBe('pwsh') + expect(installRun).toContain('Set-PSRepository -Name PSGallery -InstallationPolicy Trusted') + expect(installRun).toMatch(/\$env:PSModulePath -split \[System\.IO\.Path\]::PathSeparator/) + expect(installRun).toContain( + "$signPathModulePath = Join-Path -Path $currentUserModuleRoot -ChildPath 'SignPath'" + ) + expect(installRun).toMatch(/for \(\$attempt = 1; \$attempt -le 3; \$attempt\+\+\)/) + expect(sleepSeconds).toEqual(['15', '30']) + expect(installRun).toContain( + 'Install-Module -Name SignPath -Repository PSGallery -MinimumVersion 4.0.0 -MaximumVersion 4.999.999 -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop' + ) + expect(installRun).toContain('Import-Module SignPath') + expect(installRun).toContain( + 'Get-Command -Name Get-SignedArtifact -Module SignPath -ErrorAction Stop' + ) + expect(installRun).toContain('Remove-Item -LiteralPath $signPathModulePath -Recurse -Force') + expect(installRun).not.toContain('SignPath*') + expect(installRun.indexOf('if ($attempt -eq 3)')).toBeLessThan( + installRun.indexOf('Remove-Item -LiteralPath $signPathModulePath') + ) + expect(installRun).toMatch(/if \(\$attempt -eq 3\) {\s+throw\s+}/) + expect(installRun).not.toMatch(/throw\s+\$_/) + }) + it('publishes both Linux release matrix entries', () => { const releaseWorkflow = readFileSync( join(projectDir, '.github/workflows/release-cut.yml'),