ci(release): add Azure Trusted Signing for Windows builds (#805)

* feat: Add Sentry environment variables to build process in CI workflows

- Integrated SENTRY_DSN, SENTRY_TRACES_SAMPLE_RATE, and SENTRY_PROFILES_SAMPLE_RATE as environment variables in the build steps of both beta-release.yml and release.yml workflows.
- This enhancement ensures that Sentry monitoring is properly configured during application builds across different platforms (macOS, Windows, Linux).

This change improves error tracking and performance monitoring capabilities for the application.

* ci(release): add Azure Trusted Signing for Windows builds

Integrate Azure Trusted Signing to sign Windows executables during
release and beta-release workflows. This removes SmartScreen warnings
for users downloading Auto-Claude on Windows.

- Add OIDC authentication with Azure (no client secret needed)
- Sign .exe files after packaging using azure/trusted-signing-action
- Use North Europe endpoint (neu.codesigning.azure.net)
- Conditionally skip signing if Azure credentials not configured

Required GitHub secrets: AZURE_TENANT_ID, AZURE_CLIENT_ID,
AZURE_SUBSCRIPTION_ID, AZURE_SIGNING_ACCOUNT, AZURE_CERTIFICATE_PROFILE

* fix(ci): move AZURE_CLIENT_ID to job-level env for condition evaluation

- Move AZURE_CLIENT_ID from step-level to job-level env block so it's
  available when GitHub Actions evaluates step-level `if:` conditions
- Update azure/trusted-signing-action from v0.5.1 to v0.5.11
- Remove redundant step-level env blocks

Fixes conditional checks that were always evaluating to false because
step-level env vars aren't processed until after if conditions are evaluated.

Co-authored-by: CodeRabbit <coderabbit@users.noreply.github.com>
Co-authored-by: Cursor Bot <cursor@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ci): use base64 encoding for SHA512 checksums in latest.yml

- Use System.Security.Cryptography.SHA512 to compute hash bytes
- Convert hash to base64 (electron-builder expected format) instead of hex
- Update regex pattern to match base64 characters [A-Za-z0-9+/=]
- Add -NoNewline to Set-Content to preserve YAML formatting

Fixes auto-update checksum verification that was broken because
Get-FileHash outputs hex while electron-updater expects base64.

Co-authored-by: CodeRabbit <coderabbit@users.noreply.github.com>
Co-authored-by: Cursor Bot <cursor@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ci): add signing verification and use HTTPS for timestamp server

- Add signature verification step using Get-AuthenticodeSignature
  - Fails build if signing fails silently (prevents unsigned releases)
  - Logs certificate subject, issuer, and thumbprint on success
- Change timestamp server from HTTP to HTTPS for better security

Addresses remaining feedback from Auto Claude PR Review:
- NEW-005/NEW-006: Missing verification that signing succeeded
- NEW-001/NEW-002: Timestamp server uses unencrypted HTTP

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ci): add error handling and multi-exe support to checksum regeneration

- Add $ErrorActionPreference = "Stop" for strict error handling
- Fail build if no exe files found in dist folder
- Fail build if latest.yml not found
- Fail build if checksum replacement didn't change content (regex mismatch)
- Log all exe files found and their hashes for debugging
- Show clear error messages with ::error:: prefix for GitHub Actions

Addresses NF-003/NF-004 (multiple exe handling) and NF-005/NF-006 (error handling)
from Auto Claude PR Review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-01-08 20:17:14 +01:00
committed by GitHub
parent 63e142ae59
commit 204588493b
2 changed files with 238 additions and 4 deletions
+119 -2
View File
@@ -265,6 +265,12 @@ jobs:
build-windows:
needs: create-tag
runs-on: windows-latest
permissions:
id-token: write # Required for OIDC authentication with Azure
contents: read
env:
# Job-level env so AZURE_CLIENT_ID is available for step-level if conditions
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
steps:
- uses: actions/checkout@v4
with:
@@ -325,12 +331,123 @@ jobs:
cd apps/frontend && npm run package:win -- --config.extraMetadata.version="$VERSION"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.WIN_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
# Disable electron-builder's built-in signing (we use Azure Trusted Signing instead)
CSC_IDENTITY_AUTO_DISCOVERY: false
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
- name: Azure Login (OIDC)
if: env.AZURE_CLIENT_ID != ''
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows executable with Azure Trusted Signing
if: env.AZURE_CLIENT_ID != ''
uses: azure/trusted-signing-action@v0.5.11
with:
endpoint: https://neu.codesigning.azure.net/
trusted-signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE }}
files-folder: apps/frontend/dist
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: https://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Verify Windows executable is signed
if: env.AZURE_CLIENT_ID != ''
shell: pwsh
run: |
cd apps/frontend/dist
$exeFile = Get-ChildItem -Filter "*.exe" | Select-Object -First 1
if ($exeFile) {
Write-Host "Verifying signature on $($exeFile.Name)..."
$sig = Get-AuthenticodeSignature -FilePath $exeFile.FullName
if ($sig.Status -ne 'Valid') {
Write-Host "::error::Signature verification failed: $($sig.Status)"
Write-Host "::error::Status Message: $($sig.StatusMessage)"
exit 1
}
Write-Host "✅ Signature verified successfully"
Write-Host " Subject: $($sig.SignerCertificate.Subject)"
Write-Host " Issuer: $($sig.SignerCertificate.Issuer)"
Write-Host " Thumbprint: $($sig.SignerCertificate.Thumbprint)"
} else {
Write-Host "::error::No .exe file found to verify"
exit 1
}
- name: Regenerate checksums after signing
if: env.AZURE_CLIENT_ID != ''
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
cd apps/frontend/dist
# Find the installer exe (electron-builder names it with "Setup" or just the app name)
# electron-builder produces one installer exe per build
$exeFiles = Get-ChildItem -Filter "*.exe"
if ($exeFiles.Count -eq 0) {
Write-Host "::error::No .exe files found in dist folder"
exit 1
}
Write-Host "Found $($exeFiles.Count) exe file(s): $($exeFiles.Name -join ', ')"
$ymlFile = "latest.yml"
if (-not (Test-Path $ymlFile)) {
Write-Host "::error::$ymlFile not found - cannot update checksums"
exit 1
}
$content = Get-Content $ymlFile -Raw
$originalContent = $content
# Process each exe file and update its hash in latest.yml
foreach ($exeFile in $exeFiles) {
Write-Host "Processing $($exeFile.Name)..."
# Compute SHA512 hash and convert to base64 (electron-builder format)
$bytes = [System.IO.File]::ReadAllBytes($exeFile.FullName)
$sha512 = [System.Security.Cryptography.SHA512]::Create()
$hashBytes = $sha512.ComputeHash($bytes)
$hash = [System.Convert]::ToBase64String($hashBytes)
$size = $exeFile.Length
Write-Host " Hash: $hash"
Write-Host " Size: $size"
}
# For electron-builder, latest.yml has a single file entry for the installer
# Update the sha512 and size for the primary exe (first one, typically the installer)
$primaryExe = $exeFiles | Select-Object -First 1
$bytes = [System.IO.File]::ReadAllBytes($primaryExe.FullName)
$sha512 = [System.Security.Cryptography.SHA512]::Create()
$hashBytes = $sha512.ComputeHash($bytes)
$hash = [System.Convert]::ToBase64String($hashBytes)
$size = $primaryExe.Length
# Update sha512 hash (base64 pattern: alphanumeric, +, /, =)
$content = $content -replace 'sha512: [A-Za-z0-9+/=]+', "sha512: $hash"
# Update size
$content = $content -replace 'size: \d+', "size: $size"
if ($content -eq $originalContent) {
Write-Host "::error::Checksum replacement failed - content unchanged. Check if latest.yml format has changed."
exit 1
}
Set-Content -Path $ymlFile -Value $content -NoNewline
Write-Host "✅ Updated $ymlFile with new base64 hash and size for $($primaryExe.Name)"
- name: Skip signing notice
if: env.AZURE_CLIENT_ID == ''
run: echo "::warning::Windows signing skipped - AZURE_CLIENT_ID not configured. The .exe will be unsigned."
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
+119 -2
View File
@@ -207,6 +207,12 @@ jobs:
build-windows:
runs-on: windows-latest
permissions:
id-token: write # Required for OIDC authentication with Azure
contents: read
env:
# Job-level env so AZURE_CLIENT_ID is available for step-level if conditions
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
steps:
- uses: actions/checkout@v4
@@ -261,12 +267,123 @@ jobs:
run: cd apps/frontend && npm run package:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.WIN_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
# Disable electron-builder's built-in signing (we use Azure Trusted Signing instead)
CSC_IDENTITY_AUTO_DISCOVERY: false
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
- name: Azure Login (OIDC)
if: env.AZURE_CLIENT_ID != ''
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows executable with Azure Trusted Signing
if: env.AZURE_CLIENT_ID != ''
uses: azure/trusted-signing-action@v0.5.11
with:
endpoint: https://neu.codesigning.azure.net/
trusted-signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE }}
files-folder: apps/frontend/dist
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: https://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Verify Windows executable is signed
if: env.AZURE_CLIENT_ID != ''
shell: pwsh
run: |
cd apps/frontend/dist
$exeFile = Get-ChildItem -Filter "*.exe" | Select-Object -First 1
if ($exeFile) {
Write-Host "Verifying signature on $($exeFile.Name)..."
$sig = Get-AuthenticodeSignature -FilePath $exeFile.FullName
if ($sig.Status -ne 'Valid') {
Write-Host "::error::Signature verification failed: $($sig.Status)"
Write-Host "::error::Status Message: $($sig.StatusMessage)"
exit 1
}
Write-Host "✅ Signature verified successfully"
Write-Host " Subject: $($sig.SignerCertificate.Subject)"
Write-Host " Issuer: $($sig.SignerCertificate.Issuer)"
Write-Host " Thumbprint: $($sig.SignerCertificate.Thumbprint)"
} else {
Write-Host "::error::No .exe file found to verify"
exit 1
}
- name: Regenerate checksums after signing
if: env.AZURE_CLIENT_ID != ''
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
cd apps/frontend/dist
# Find the installer exe (electron-builder names it with "Setup" or just the app name)
# electron-builder produces one installer exe per build
$exeFiles = Get-ChildItem -Filter "*.exe"
if ($exeFiles.Count -eq 0) {
Write-Host "::error::No .exe files found in dist folder"
exit 1
}
Write-Host "Found $($exeFiles.Count) exe file(s): $($exeFiles.Name -join ', ')"
$ymlFile = "latest.yml"
if (-not (Test-Path $ymlFile)) {
Write-Host "::error::$ymlFile not found - cannot update checksums"
exit 1
}
$content = Get-Content $ymlFile -Raw
$originalContent = $content
# Process each exe file and update its hash in latest.yml
foreach ($exeFile in $exeFiles) {
Write-Host "Processing $($exeFile.Name)..."
# Compute SHA512 hash and convert to base64 (electron-builder format)
$bytes = [System.IO.File]::ReadAllBytes($exeFile.FullName)
$sha512 = [System.Security.Cryptography.SHA512]::Create()
$hashBytes = $sha512.ComputeHash($bytes)
$hash = [System.Convert]::ToBase64String($hashBytes)
$size = $exeFile.Length
Write-Host " Hash: $hash"
Write-Host " Size: $size"
}
# For electron-builder, latest.yml has a single file entry for the installer
# Update the sha512 and size for the primary exe (first one, typically the installer)
$primaryExe = $exeFiles | Select-Object -First 1
$bytes = [System.IO.File]::ReadAllBytes($primaryExe.FullName)
$sha512 = [System.Security.Cryptography.SHA512]::Create()
$hashBytes = $sha512.ComputeHash($bytes)
$hash = [System.Convert]::ToBase64String($hashBytes)
$size = $primaryExe.Length
# Update sha512 hash (base64 pattern: alphanumeric, +, /, =)
$content = $content -replace 'sha512: [A-Za-z0-9+/=]+', "sha512: $hash"
# Update size
$content = $content -replace 'size: \d+', "size: $size"
if ($content -eq $originalContent) {
Write-Host "::error::Checksum replacement failed - content unchanged. Check if latest.yml format has changed."
exit 1
}
Set-Content -Path $ymlFile -Value $content -NoNewline
Write-Host "✅ Updated $ymlFile with new base64 hash and size for $($primaryExe.Name)"
- name: Skip signing notice
if: env.AZURE_CLIENT_ID == ''
run: echo "::warning::Windows signing skipped - AZURE_CLIENT_ID not configured. The .exe will be unsigned."
- name: Upload artifacts
uses: actions/upload-artifact@v4
with: