From f21e2ccafabc23ae0d28ad8bf071eef340fc9a60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:40:36 +0000 Subject: [PATCH 1/4] Initial plan From 939ffc4b9d0ac673baeffd08210259a37f08c929 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:50:56 +0000 Subject: [PATCH 2/4] Fix Windows assembly signing: sign app binaries before packaging into installers Agent-Logs-Url: https://github.com/PowerPlatformToolBox/desktop-app/sessions/1c00549d-1aa5-4784-be1b-0fc190cf013e Co-authored-by: Power-Maverick <36135520+Power-Maverick@users.noreply.github.com> --- .github/workflows/nightly-release.yml | 71 +++++++++++++-------------- .github/workflows/prod-release.yml | 71 +++++++++++++-------------- docs/azure-trusted-signing.md | 31 ++++++++++-- 3 files changed, 96 insertions(+), 77 deletions(-) diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index f7426a46..f565819e 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -45,9 +45,11 @@ jobs: - os: windows-latest config: buildScripts/electron-builder-win.json artifact_name: windows-x64-build + win_arch: x64 - os: windows-latest config: buildScripts/electron-builder-win-arm64.json artifact_name: windows-arm64-build + win_arch: arm64 - os: macos-latest config: buildScripts/electron-builder-mac.json artifact_name: macos-build @@ -107,9 +109,9 @@ jobs: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - - name: Package application (Windows) + - name: Build application directory (Windows) if: matrix.os == 'windows-latest' - run: node ./buildScripts/package.js --config=${{ matrix.config }} + run: pnpm exec electron-builder --config ${{ matrix.config }} --target=dir:${{ matrix.win_arch }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} @@ -120,7 +122,7 @@ jobs: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - - name: Sign Windows artifacts with Azure Trusted Signing + - name: Sign application binaries with Azure Trusted Signing (Windows) if: matrix.os == 'windows-latest' uses: azure/artifact-signing-action@v1 with: @@ -130,47 +132,44 @@ jobs: endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} - files-folder: ${{ github.workspace }}/build - files-folder-filter: exe,msi + files-folder: ${{ github.workspace }}/build/win-${{ matrix.win_arch }}-unpacked + files-folder-filter: exe,dll files-folder-recurse: true timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 description: Power Platform ToolBox (Insider) description-url: https://github.com/PowerPlatformToolBox/desktop-app - - name: Repackage portable ZIP with signed EXE (Windows) + - name: Package installers from signed directory (Windows) if: matrix.os == 'windows-latest' - shell: powershell - run: | - $buildDir = "${{ github.workspace }}/build" - $zipFiles = @(Get-ChildItem "$buildDir/*.zip" -ErrorAction SilentlyContinue) - - if ($zipFiles.Count -eq 0) { - Write-Host "No ZIP artifacts found; skipping repack." - exit 0 - } - - foreach ($zip in $zipFiles) { - Write-Host "Repacking ZIP: $($zip.Name)" - $tempDir = Join-Path $env:RUNNER_TEMP ([Guid]::NewGuid().ToString()) - New-Item -ItemType Directory -Path $tempDir | Out-Null - - Expand-Archive -Path $zip.FullName -DestinationPath $tempDir -Force - - $zipExeFiles = @(Get-ChildItem $tempDir -Recurse -Filter *.exe -ErrorAction SilentlyContinue) - foreach ($zipExe in $zipExeFiles) { - $signedExe = Get-ChildItem $buildDir -Recurse -Filter $zipExe.Name -ErrorAction SilentlyContinue | Select-Object -First 1 - if ($signedExe) { - Copy-Item $signedExe.FullName $zipExe.FullName -Force - Write-Host " Replaced $($zipExe.Name) with signed binary." - } else { - Write-Host " No signed match found for $($zipExe.Name)." - } - } + run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/win-${{ matrix.win_arch }}-unpacked + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - Remove-Item $zip.FullName -Force - Compress-Archive -Path (Join-Path $tempDir '*') -DestinationPath $zip.FullName -Force - } + - name: Sign Windows installers with Azure Trusted Signing + if: matrix.os == 'windows-latest' + uses: azure/artifact-signing-action@v1 + with: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} + signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} + certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} + files-folder: ${{ github.workspace }}/build + files-folder-filter: exe,msi + files-folder-recurse: false + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + description: Power Platform ToolBox (Insider) + description-url: https://github.com/PowerPlatformToolBox/desktop-app - name: Regenerate latest.yml with correct SHA256 hashes (Windows) if: matrix.os == 'windows-latest' diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 3113e1b8..b038bead 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -76,9 +76,11 @@ jobs: - os: windows-latest config: buildScripts/electron-builder-win.json artifact_name: windows-x64-release + win_arch: x64 - os: windows-latest config: buildScripts/electron-builder-win-arm64.json artifact_name: windows-arm64-release + win_arch: arm64 - os: macos-latest config: buildScripts/electron-builder-mac.json artifact_name: macos-release @@ -133,9 +135,9 @@ jobs: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - - name: Package application (Windows) + - name: Build application directory (Windows) if: matrix.os == 'windows-latest' - run: node ./buildScripts/package.js --config=${{ matrix.config }} + run: pnpm exec electron-builder --config ${{ matrix.config }} --target=dir:${{ matrix.win_arch }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} @@ -146,7 +148,7 @@ jobs: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - - name: Sign Windows artifacts with Azure Trusted Signing + - name: Sign application binaries with Azure Trusted Signing (Windows) if: matrix.os == 'windows-latest' uses: azure/artifact-signing-action@v1 with: @@ -156,47 +158,44 @@ jobs: endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} - files-folder: ${{ github.workspace }}/build - files-folder-filter: exe,msi + files-folder: ${{ github.workspace }}/build/win-${{ matrix.win_arch }}-unpacked + files-folder-filter: exe,dll files-folder-recurse: true timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 description: Power Platform ToolBox description-url: https://github.com/PowerPlatformToolBox/desktop-app - - name: Repackage portable ZIP with signed EXE (Windows) + - name: Package installers from signed directory (Windows) if: matrix.os == 'windows-latest' - shell: powershell - run: | - $buildDir = "${{ github.workspace }}/build" - $zipFiles = @(Get-ChildItem "$buildDir/*.zip" -ErrorAction SilentlyContinue) - - if ($zipFiles.Count -eq 0) { - Write-Host "No ZIP artifacts found; skipping repack." - exit 0 - } - - foreach ($zip in $zipFiles) { - Write-Host "Repacking ZIP: $($zip.Name)" - $tempDir = Join-Path $env:RUNNER_TEMP ([Guid]::NewGuid().ToString()) - New-Item -ItemType Directory -Path $tempDir | Out-Null - - Expand-Archive -Path $zip.FullName -DestinationPath $tempDir -Force - - $zipExeFiles = @(Get-ChildItem $tempDir -Recurse -Filter *.exe -ErrorAction SilentlyContinue) - foreach ($zipExe in $zipExeFiles) { - $signedExe = Get-ChildItem $buildDir -Recurse -Filter $zipExe.Name -ErrorAction SilentlyContinue | Select-Object -First 1 - if ($signedExe) { - Copy-Item $signedExe.FullName $zipExe.FullName -Force - Write-Host " Replaced $($zipExe.Name) with signed binary." - } else { - Write-Host " No signed match found for $($zipExe.Name)." - } - } + run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/win-${{ matrix.win_arch }}-unpacked + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - Remove-Item $zip.FullName -Force - Compress-Archive -Path (Join-Path $tempDir '*') -DestinationPath $zip.FullName -Force - } + - name: Sign Windows installers with Azure Trusted Signing + if: matrix.os == 'windows-latest' + uses: azure/artifact-signing-action@v1 + with: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} + signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} + certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} + files-folder: ${{ github.workspace }}/build + files-folder-filter: exe,msi + files-folder-recurse: false + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + description: Power Platform ToolBox + description-url: https://github.com/PowerPlatformToolBox/desktop-app - name: Regenerate latest.yml with correct SHA256 hashes (Windows) if: matrix.os == 'windows-latest' diff --git a/docs/azure-trusted-signing.md b/docs/azure-trusted-signing.md index 0e620bce..672b221d 100644 --- a/docs/azure-trusted-signing.md +++ b/docs/azure-trusted-signing.md @@ -25,16 +25,37 @@ This document explains how Windows artifacts generated by the production and nig ## Workflow behavior -- The jobs running on `windows-latest` in both `prod-release.yml` and `nightly-release.yml` execute the [`azure/artifact-signing-action@v1`](https://github.com/Azure/artifact-signing-action) step immediately after Electron Builder packages the installers. -- Every `.exe` and `.msi` produced under `build/` is signed in place. Portable `.zip` artifacts remain unsigned because the action does not yet support signing archives. The action handles hashing, timestamping, and certificate management by requesting a short-lived certificate from Azure Trusted Signing. -- The action fails if any secret is missing or Azure denies the signing request, which prevents unsigned installers from being uploaded or released. +The Windows signing process uses a **three-phase signing approach** to ensure every binary—both the application itself and the installer wrappers—carries a valid signature: + +### Phase 1 – Sign application binaries + +After building only the unpacked application directory (`build/win-{arch}-unpacked/`), the `azure/artifact-signing-action@v1` step signs all `.exe` and `.dll` files found recursively inside that directory. This includes: + +- `Power Platform ToolBox.exe` (the main application executable) +- All `.dll` files bundled with the application + +### Phase 2 – Package installers from signed binaries + +With the application binaries now signed, Electron Builder is invoked with `--prepackaged` pointing at the already-signed directory. This creates the NSIS installer (`.exe`), MSI (`.msi`), MSI-wrapped EXE (`msiWrapped`), and portable ZIP, all of which embed the **signed** application binaries. + +### Phase 3 – Sign installer packages + +A second `azure/artifact-signing-action@v1` step signs the top-level installer files in `build/` (non-recursive) so that the outer wrappers are also authenticated: + +- `Power-Platform-ToolBox-*-win.exe` (NSIS setup installer) +- `Power-Platform-ToolBox-*-win.msi` (MSI installer) +- `Power-Platform-ToolBox-*-Setup.exe` (msiWrapped EXE bootstrapper, x64 only) + +Portable `.zip` archives are not signed as packages (the action does not support archive signing), but they contain the signed application binaries from Phase 1 because the ZIP is assembled from the `--prepackaged` directory. + +The action fails if any secret is missing or Azure denies the signing request, which prevents unsigned installers from being uploaded or released. ## Testing the signing step 1. Configure the secrets above in the repository or organization settings. 2. Trigger the `Stable Release` or `Insider Pre-Release` workflow via the `workflow_dispatch` entry point (you can do this on a throwaway branch after updating the version and release notes). -3. Inspect the Windows job logs for the "Sign Windows artifacts with Azure Trusted Signing" step to verify that Azure returned a certificate and that all `.exe`/`.msi` files were processed. -4. Download the Windows artifacts and run `Get-AuthenticodeSignature` locally to validate the signature chain. +3. Inspect the Windows job logs for the "Sign application binaries with Azure Trusted Signing (Windows)" and "Sign Windows installers with Azure Trusted Signing" steps to verify that Azure returned a certificate and that all files were processed. +4. Download the Windows artifacts and run `Get-AuthenticodeSignature` locally to validate the signature chain on both the installer and the installed application executable. ## Operational guidance From 48ed9b7db676d93cfc9a6b52224029b09101bc9c Mon Sep 17 00:00:00 2001 From: Danish Naglekar <36135520+Power-Maverick@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:30:58 -0400 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/nightly-release.yml | 2 +- .github/workflows/prod-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index f565819e..eb55da23 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -111,7 +111,7 @@ jobs: - name: Build application directory (Windows) if: matrix.os == 'windows-latest' - run: pnpm exec electron-builder --config ${{ matrix.config }} --target=dir:${{ matrix.win_arch }} + run: pnpm exec electron-builder --config ${{ matrix.config }} --dir --${{ matrix.win_arch }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index b038bead..72f6a126 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -137,7 +137,7 @@ jobs: - name: Build application directory (Windows) if: matrix.os == 'windows-latest' - run: pnpm exec electron-builder --config ${{ matrix.config }} --target=dir:${{ matrix.win_arch }} + run: pnpm exec electron-builder --config ${{ matrix.config }} --dir ${{ matrix.win_arch == 'arm64' && '--arm64' || '--x64' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} From 8f02025e70a96c2a5d319bed70e644a359b00850 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 03:31:47 +0000 Subject: [PATCH 4/4] Fix unpacked dir path and --dir flag for Windows signing steps Agent-Logs-Url: https://github.com/PowerPlatformToolBox/desktop-app/sessions/41781746-20f4-4a83-949e-dec4147c6056 Co-authored-by: Power-Maverick <36135520+Power-Maverick@users.noreply.github.com> --- .github/workflows/nightly-release.yml | 6 ++++-- .github/workflows/prod-release.yml | 8 +++++--- docs/azure-trusted-signing.md | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index eb55da23..43a7f7cd 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -46,10 +46,12 @@ jobs: config: buildScripts/electron-builder-win.json artifact_name: windows-x64-build win_arch: x64 + win_unpacked_dir: win-unpacked - os: windows-latest config: buildScripts/electron-builder-win-arm64.json artifact_name: windows-arm64-build win_arch: arm64 + win_unpacked_dir: win-arm64-unpacked - os: macos-latest config: buildScripts/electron-builder-mac.json artifact_name: macos-build @@ -132,7 +134,7 @@ jobs: endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} - files-folder: ${{ github.workspace }}/build/win-${{ matrix.win_arch }}-unpacked + files-folder: ${{ github.workspace }}/build/${{ matrix.win_unpacked_dir }} files-folder-filter: exe,dll files-folder-recurse: true timestamp-rfc3161: http://timestamp.acs.microsoft.com @@ -142,7 +144,7 @@ jobs: - name: Package installers from signed directory (Windows) if: matrix.os == 'windows-latest' - run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/win-${{ matrix.win_arch }}-unpacked + run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/${{ matrix.win_unpacked_dir }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 72f6a126..59e4f621 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -77,10 +77,12 @@ jobs: config: buildScripts/electron-builder-win.json artifact_name: windows-x64-release win_arch: x64 + win_unpacked_dir: win-unpacked - os: windows-latest config: buildScripts/electron-builder-win-arm64.json artifact_name: windows-arm64-release win_arch: arm64 + win_unpacked_dir: win-arm64-unpacked - os: macos-latest config: buildScripts/electron-builder-mac.json artifact_name: macos-release @@ -137,7 +139,7 @@ jobs: - name: Build application directory (Windows) if: matrix.os == 'windows-latest' - run: pnpm exec electron-builder --config ${{ matrix.config }} --dir ${{ matrix.win_arch == 'arm64' && '--arm64' || '--x64' }} + run: pnpm exec electron-builder --config ${{ matrix.config }} --dir --${{ matrix.win_arch }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} @@ -158,7 +160,7 @@ jobs: endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_CERTIFICATE_PROFILE }} - files-folder: ${{ github.workspace }}/build/win-${{ matrix.win_arch }}-unpacked + files-folder: ${{ github.workspace }}/build/${{ matrix.win_unpacked_dir }} files-folder-filter: exe,dll files-folder-recurse: true timestamp-rfc3161: http://timestamp.acs.microsoft.com @@ -168,7 +170,7 @@ jobs: - name: Package installers from signed directory (Windows) if: matrix.os == 'windows-latest' - run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/win-${{ matrix.win_arch }}-unpacked + run: pnpm exec electron-builder --config ${{ matrix.config }} --prepackaged build/${{ matrix.win_unpacked_dir }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} diff --git a/docs/azure-trusted-signing.md b/docs/azure-trusted-signing.md index 672b221d..82cc9c2f 100644 --- a/docs/azure-trusted-signing.md +++ b/docs/azure-trusted-signing.md @@ -29,7 +29,7 @@ The Windows signing process uses a **three-phase signing approach** to ensure ev ### Phase 1 – Sign application binaries -After building only the unpacked application directory (`build/win-{arch}-unpacked/`), the `azure/artifact-signing-action@v1` step signs all `.exe` and `.dll` files found recursively inside that directory. This includes: +After building only the unpacked application directory (`build/win-unpacked/` for x64, `build/win-arm64-unpacked/` for arm64), the `azure/artifact-signing-action@v1` step signs all `.exe` and `.dll` files found recursively inside that directory. This includes: - `Power Platform ToolBox.exe` (the main application executable) - All `.dll` files bundled with the application