Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 6 additions & 42 deletions .github/workflows/desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ jobs:
environment: windows-signing
env:
MAPLE_WINDOWS_AUTHENTICODE_SUBJECT: ${{ secrets.AZURE_ARTIFACT_SIGNING_EXPECTED_SUBJECT }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_ENDPOINT: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4
with:
Expand Down Expand Up @@ -280,26 +283,9 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Sign Windows app executable
uses: Azure/artifact-signing-action@c0ae2c1d0c1847ab81ac0ab8521bee597cfedd30 # was v2
with:
endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files: ${{ github.workspace }}\frontend\src-tauri\target\release\maple.exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
- name: Install Artifact Signing module
shell: pwsh
run: ./scripts/ci/install-windows-artifact-signing.ps1

- name: Bundle Windows installer
shell: bash
Expand All @@ -312,28 +298,6 @@ jobs:
source scripts/ci/_common.sh
setup_exe="$(windows_release_setup_exe_required)"
echo "MAPLE_WINDOWS_SETUP_EXE_REL=$(repo_relative_path "${setup_exe}")" >> "${GITHUB_ENV}"
echo "MAPLE_WINDOWS_SETUP_EXE_WIN=$(to_windows_path "${setup_exe}")" >> "${GITHUB_ENV}"

- name: Sign Windows installer
uses: Azure/artifact-signing-action@c0ae2c1d0c1847ab81ac0ab8521bee597cfedd30 # was v2
with:
endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files: ${{ env.MAPLE_WINDOWS_SETUP_EXE_WIN }}
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true

- name: Finalize Windows updater signatures and checksums
shell: bash
Expand Down
48 changes: 6 additions & 42 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ jobs:
environment: windows-signing
env:
MAPLE_WINDOWS_AUTHENTICODE_SUBJECT: ${{ secrets.AZURE_ARTIFACT_SIGNING_EXPECTED_SUBJECT }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_ENDPOINT: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
MAPLE_WINDOWS_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
permissions:
contents: write
id-token: write
Expand Down Expand Up @@ -227,26 +230,9 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Sign Windows app executable
uses: Azure/artifact-signing-action@c0ae2c1d0c1847ab81ac0ab8521bee597cfedd30 # was v2
with:
endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files: ${{ github.workspace }}\frontend\src-tauri\target\release\maple.exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
- name: Install Artifact Signing module
shell: pwsh
run: ./scripts/ci/install-windows-artifact-signing.ps1

- name: Bundle Windows installer
shell: bash
Expand All @@ -259,28 +245,6 @@ jobs:
source scripts/ci/_common.sh
setup_exe="$(windows_release_setup_exe_required)"
echo "MAPLE_WINDOWS_SETUP_EXE_REL=$(repo_relative_path "${setup_exe}")" >> "${GITHUB_ENV}"
echo "MAPLE_WINDOWS_SETUP_EXE_WIN=$(to_windows_path "${setup_exe}")" >> "${GITHUB_ENV}"

- name: Sign Windows installer
uses: Azure/artifact-signing-action@c0ae2c1d0c1847ab81ac0ab8521bee597cfedd30 # was v2
with:
endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files: ${{ env.MAPLE_WINDOWS_SETUP_EXE_WIN }}
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true

- name: Finalize Windows updater signatures and checksums
shell: bash
Expand Down
22 changes: 20 additions & 2 deletions scripts/ci/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3548,13 +3548,31 @@ windows_tauri_release_build_config() {
}

windows_tauri_release_bundle_config() {
jq -cn '{
local sign_script

sign_script="$(to_windows_path "${REPO_ROOT}/scripts/ci/windows-artifact-sign.ps1")"

jq -cn --arg signScript "${sign_script}" '{
build: {
beforeBuildCommand: null
},
bundle: {
createUpdaterArtifacts: false,
targets: ["nsis"]
targets: ["nsis"],
windows: {
signCommand: {
cmd: "pwsh",
args: [
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
$signScript,
"%1"
]
}
}
}
}'
}
Expand Down
25 changes: 16 additions & 9 deletions scripts/ci/desktop-windows-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ build
stage pinned runtime DLLs, and compile maple.exe without bundling.

bundle
Verify maple.exe is Authenticode-signed, then generate the NSIS installer
from that signed app executable. The installer itself is not signed here.
Generate the NSIS installer. Tauri patches and signs maple.exe through
bundle.windows.signCommand during bundling, signs the installer, then restores
target/release/maple.exe to its original unsigned/unpatched bytes.

finalize
Verify Authenticode signatures on maple.exe and the NSIS installer, create
the final Tauri updater signature for the signed installer, and emit release
reproducibility manifests.
Verify Authenticode signatures on the NSIS installer, create the final Tauri
updater signature for the signed installer, and emit release reproducibility
manifests.
EOF
}

Expand Down Expand Up @@ -74,7 +75,10 @@ run_bundle_phase() {
configure_reproducible_build_metadata

app_exe="$(windows_release_app_exe)"
verify_windows_authenticode_signatures "${app_exe}"
if [ ! -f "${app_exe}" ]; then
echo "Windows app executable is missing before bundling: ${app_exe}" >&2
exit 1
fi

cd "${FRONTEND_DIR}"
remove_build_tree "${TAURI_DIR}/target/release/bundle/nsis"
Expand All @@ -83,21 +87,24 @@ run_bundle_phase() {
bun tauri bundle --verbose --bundles nsis --config "$(windows_tauri_release_bundle_config)"

setup_exe="$(windows_release_setup_exe_required)"
# Tauri restores target/release/maple.exe after bundling. The durable signed
# artifact at this point is the NSIS installer; installed-payload verification
# should inspect an extracted installer payload, not the restored build output.
verify_windows_authenticode_signatures "${setup_exe}"
print_file_hashes "${setup_exe}"
verify_frontend_dist_unchanged
}

run_finalize_phase() {
local app_exe setup_exe repro_dir
local setup_exe repro_dir
local windows_runtime_dlls=()

print_source_provenance
configure_reproducible_build_metadata
configure_tauri_updater_signing_key

app_exe="$(windows_release_app_exe)"
setup_exe="$(windows_release_setup_exe_required)"
verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
verify_windows_authenticode_signatures "${setup_exe}"
Comment on lines +93 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep verifying the patched app executable after bundling.

Line 89 and Line 103 now verify only the NSIS setup. Since the app exe is the artifact Tauri mutates and signCommand is expected to sign, verify ${app_exe} after bundling as well; otherwise a signed installer can ship an unsigned or wrong-subject installed executable.

Proposed fix
   setup_exe="$(windows_release_setup_exe_required)"
-  verify_windows_authenticode_signatures "${setup_exe}"
+  verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
   print_file_hashes "${setup_exe}"
   verify_frontend_dist_unchanged
 }
 
 run_finalize_phase() {
-  local setup_exe repro_dir
+  local app_exe setup_exe repro_dir
   local windows_runtime_dlls=()
 
   print_source_provenance
   configure_reproducible_build_metadata
   configure_tauri_updater_signing_key
 
+  app_exe="$(windows_release_app_exe)"
   setup_exe="$(windows_release_setup_exe_required)"
-  verify_windows_authenticode_signatures "${setup_exe}"
+  verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
verify_windows_authenticode_signatures "${setup_exe}"
print_file_hashes "${setup_exe}"
verify_frontend_dist_unchanged
}
run_finalize_phase() {
local app_exe setup_exe repro_dir
local setup_exe repro_dir
local windows_runtime_dlls=()
print_source_provenance
configure_reproducible_build_metadata
configure_tauri_updater_signing_key
app_exe="$(windows_release_app_exe)"
setup_exe="$(windows_release_setup_exe_required)"
verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
verify_windows_authenticode_signatures "${setup_exe}"
verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
print_file_hashes "${setup_exe}"
verify_frontend_dist_unchanged
}
run_finalize_phase() {
local app_exe setup_exe repro_dir
local windows_runtime_dlls=()
print_source_provenance
configure_reproducible_build_metadata
configure_tauri_updater_signing_key
app_exe="$(windows_release_app_exe)"
setup_exe="$(windows_release_setup_exe_required)"
verify_windows_authenticode_signatures "${app_exe}" "${setup_exe}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/desktop-windows-release.sh` around lines 89 - 103, Keep the
post-bundling verification for the patched app executable in addition to the
NSIS setup. In run_finalize_phase and the related bundling flow, locate the
app_exe artifact that Tauri mutates and add an Authenticode signature check for
it alongside verify_windows_authenticode_signatures used for setup_exe, so both
the installer and installed executable are validated after signCommand runs.

Comment on lines 98 to +107

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Authenticode verification scope narrowed from exe+installer to installer-only

The finalize phase previously verified Authenticode signatures on both maple.exe and the NSIS installer (scripts/ci/desktop-windows-release.sh:107). Now it only verifies the installer. This is intentional because Tauri restores target/release/maple.exe to its original unsigned/unpatched bytes after bundling. The signed exe only exists inside the NSIS installer payload. This is a correct behavioral change, but reviewers should confirm that Tauri's signCommand in Tauri 2.x does indeed restore the original exe and sign the NSIS installer itself (not just the embedded exe). If Tauri doesn't sign the NSIS installer via signCommand, the verify_windows_authenticode_signatures call in the bundle phase at scripts/ci/desktop-windows-release.sh:93 would fail, which provides a safety net.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


rm -f "${setup_exe}.sig"
sign_tauri_updater_artifacts "${setup_exe}"
Expand Down
55 changes: 55 additions & 0 deletions scripts/ci/install-windows-artifact-signing.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
$ErrorActionPreference = "Stop"
Set-StrictMode -Version 3.0

$moduleName = "ArtifactSigning"
$moduleVersion = "0.1.8"
$moduleSha256 = "3221344b8c627915d3870f23e80816f31a5d8c2bae1d7c0cdd6c9652f6c4e089"
$moduleUrl = "https://www.powershellgallery.com/api/v2/package/$moduleName/$moduleVersion"

if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) {
$baseTemp = [System.IO.Path]::GetTempPath()
} else {
$baseTemp = $env:RUNNER_TEMP
}

$moduleRoot = Join-Path $baseTemp "maple-powershell-modules"
$moduleDir = Join-Path $moduleRoot "$moduleName/$moduleVersion"
$downloadDir = Join-Path $baseTemp "maple-powershell-downloads"
$packagePath = Join-Path $downloadDir "$moduleName.$moduleVersion.nupkg"

New-Item -ItemType Directory -Force -Path $downloadDir | Out-Null
New-Item -ItemType Directory -Force -Path $moduleRoot | Out-Null

Invoke-WebRequest -Uri $moduleUrl -OutFile $packagePath -TimeoutSec 120

$actualSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $packagePath).Hash.ToLowerInvariant()
if ($actualSha256 -ne $moduleSha256) {
throw "$moduleName $moduleVersion hash mismatch. Expected $moduleSha256 but got $actualSha256."
}

if (Test-Path -LiteralPath $moduleDir) {
Remove-Item -LiteralPath $moduleDir -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $moduleDir | Out-Null
Expand-Archive -LiteralPath $packagePath -DestinationPath $moduleDir -Force

$manifestPath = Join-Path $moduleDir "$moduleName.psd1"
Import-Module -Name $manifestPath -Force -ErrorAction Stop
$loadedModule = Get-Module $moduleName | Where-Object { $_.Version -eq [version]$moduleVersion } | Select-Object -First 1
if (-not $loadedModule) {
throw "$moduleName $moduleVersion was not loaded from $manifestPath."
}
if (-not (Get-Command Invoke-ArtifactSigning -ErrorAction SilentlyContinue)) {
throw "Invoke-ArtifactSigning was not exported by $manifestPath."
}

if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_ENV)) {
"MAPLE_WINDOWS_ARTIFACT_SIGNING_MODULE_ROOT=$moduleRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
if ([string]::IsNullOrWhiteSpace($env:PSModulePath)) {
"PSModulePath=$moduleRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
} else {
"PSModulePath=$moduleRoot;$env:PSModulePath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
}
}

Write-Host "$moduleName $moduleVersion installed with verified SHA-256 $moduleSha256"
65 changes: 65 additions & 0 deletions scripts/ci/windows-artifact-sign.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
param(
[Parameter(Mandatory = $true)]
[string]$File
)

$ArtifactSigningVersion = "0.1.8"

$ErrorActionPreference = "Stop"
Set-StrictMode -Version 3.0

if (-not (Test-Path -LiteralPath $File -PathType Leaf)) {
throw "Windows artifact to sign was not found: $File"
}

$requiredEnv = @(
"MAPLE_WINDOWS_ARTIFACT_SIGNING_ENDPOINT",
"MAPLE_WINDOWS_ARTIFACT_SIGNING_ACCOUNT_NAME",
"MAPLE_WINDOWS_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME"
)

foreach ($name in $requiredEnv) {
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
throw "$name is required for Windows Artifact Signing."
}
}

$moduleRoot = $env:MAPLE_WINDOWS_ARTIFACT_SIGNING_MODULE_ROOT
if (-not [string]::IsNullOrWhiteSpace($moduleRoot)) {
$moduleManifest = Join-Path $moduleRoot "ArtifactSigning/$ArtifactSigningVersion/ArtifactSigning.psd1"
if (-not (Test-Path -LiteralPath $moduleManifest -PathType Leaf)) {
throw "ArtifactSigning module manifest was not found: $moduleManifest"
}
Import-Module -Name $moduleManifest -Force -ErrorAction Stop
} else {
Import-Module ArtifactSigning -RequiredVersion $ArtifactSigningVersion -ErrorAction Stop
}

$loadedModule = Get-Module ArtifactSigning | Where-Object { $_.Version -eq [version]$ArtifactSigningVersion } | Select-Object -First 1
if (-not $loadedModule) {
throw "ArtifactSigning $ArtifactSigningVersion was not loaded."
}

$params = @{
Endpoint = $env:MAPLE_WINDOWS_ARTIFACT_SIGNING_ENDPOINT
CodeSigningAccountName = $env:MAPLE_WINDOWS_ARTIFACT_SIGNING_ACCOUNT_NAME
CertificateProfileName = $env:MAPLE_WINDOWS_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME
Files = $File
FileDigest = "SHA256"
TimestampRfc3161 = "http://timestamp.acs.microsoft.com"
TimestampDigest = "SHA256"
ExcludeEnvironmentCredential = $true
ExcludeWorkloadIdentityCredential = $true
ExcludeManagedIdentityCredential = $true
ExcludeSharedTokenCacheCredential = $true
ExcludeVisualStudioCredential = $true
ExcludeVisualStudioCodeCredential = $true
ExcludeAzureCliCredential = $false
ExcludeAzurePowerShellCredential = $true
ExcludeAzureDeveloperCliCredential = $true
ExcludeInteractiveBrowserCredential = $true
}
Comment on lines +43 to +61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Module parameter names mapped from GitHub Action input names to PowerShell cmdlet parameters

The old GitHub Action used kebab-case input names (e.g., signing-account-name) while the new script uses PascalCase PowerShell cmdlet parameters (e.g., CodeSigningAccountName at scripts/ci/windows-artifact-sign.ps1:45). Note the semantic difference: the action input was signing-account-name but the cmdlet parameter is CodeSigningAccountName (not SigningAccountName). This mapping was presumably derived from the ArtifactSigning module's actual cmdlet signature. If the parameter name is incorrect, Invoke-ArtifactSigning would fail at runtime since $ErrorActionPreference = 'Stop' is set, so any mismatch would be caught immediately in CI. Still worth confirming the parameter names match version 0.1.8 of the module.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


Invoke-ArtifactSigning @params

Write-Host ("signed-windows-artifact {0}" -f (Split-Path -Leaf $File))
Loading