-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(desktop): codesign ripgrep and node binaries before tauri build #8518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
00215bb
e5f048f
580a4d5
d698e9d
fc8e44c
605bdf5
49a8d86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -281,16 +281,35 @@ jobs: | |
| env: | ||
| WINDOWS_CERTIFICATE: '${{ secrets.WINDOWS_CERTIFICATE }}' | ||
| WINDOWS_CERTIFICATE_PASSWORD: '${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}' | ||
| LEGACY_WIN_CSC_LINK: '${{ secrets.WIN_CSC_LINK }}' | ||
| LEGACY_WIN_CSC_KEY_PASSWORD: '${{ secrets.WIN_CSC_KEY_PASSWORD }}' | ||
| run: | | ||
| if (-not $env:WINDOWS_CERTIFICATE -or -not $env:WINDOWS_CERTIFICATE_PASSWORD) { | ||
| throw 'WINDOWS_CERTIFICATE and WINDOWS_CERTIFICATE_PASSWORD are required for published Windows releases.' | ||
| # Prefer the Tauri-style WINDOWS_CERTIFICATE pair; fall back to the | ||
| # legacy electron-builder WIN_CSC_LINK pair if only that exists. | ||
| $pfx = $null | ||
| $pfxPassword = $null | ||
| $primaryIncomplete = ([bool]$env:WINDOWS_CERTIFICATE) -ne ([bool]$env:WINDOWS_CERTIFICATE_PASSWORD) | ||
| $legacyIncomplete = ([bool]$env:LEGACY_WIN_CSC_LINK) -ne ([bool]$env:LEGACY_WIN_CSC_KEY_PASSWORD) | ||
| if ($primaryIncomplete -or $legacyIncomplete) { | ||
| throw 'Incomplete Windows signing configuration: provide a complete WINDOWS_CERTIFICATE/WINDOWS_CERTIFICATE_PASSWORD pair or WIN_CSC_LINK/WIN_CSC_KEY_PASSWORD pair.' | ||
| } | ||
| if ($env:WINDOWS_CERTIFICATE -and $env:WINDOWS_CERTIFICATE_PASSWORD) { | ||
| $pfx = $env:WINDOWS_CERTIFICATE | ||
| $pfxPassword = $env:WINDOWS_CERTIFICATE_PASSWORD | ||
| } elseif ($env:LEGACY_WIN_CSC_LINK -and $env:LEGACY_WIN_CSC_KEY_PASSWORD) { | ||
| $pfx = $env:LEGACY_WIN_CSC_LINK | ||
| $pfxPassword = $env:LEGACY_WIN_CSC_KEY_PASSWORD | ||
| } | ||
| if ($pfx) { | ||
| $path = Join-Path $env:RUNNER_TEMP 'qwen-code-desktop.pfx' | ||
| [IO.File]::WriteAllBytes($path, [Convert]::FromBase64String($pfx)) | ||
| $password = ConvertTo-SecureString $pfxPassword -AsPlainText -Force | ||
| $certificate = Import-PfxCertificate -FilePath $path -CertStoreLocation Cert:\CurrentUser\My -Password $password | ||
| $windowsConfig = @{ bundle = @{ windows = @{ certificateThumbprint = $certificate.Thumbprint } } } | ConvertTo-Json -Compress -Depth 3 | ||
| "WINDOWS_CONFIG=$windowsConfig" | Out-File -FilePath $env:GITHUB_ENV -Append | ||
| } else { | ||
| Write-Output "::warning::Windows signing certificate is not configured. Windows artifacts will be unsigned and may trigger SmartScreen warnings." | ||
| } | ||
|
yiliang114 marked this conversation as resolved.
|
||
| $path = Join-Path $env:RUNNER_TEMP 'qwen-code-desktop.pfx' | ||
| [IO.File]::WriteAllBytes($path, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)) | ||
| $password = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force | ||
| $certificate = Import-PfxCertificate -FilePath $path -CertStoreLocation Cert:\CurrentUser\My -Password $password | ||
| $windowsConfig = @{ bundle = @{ windows = @{ certificateThumbprint = $certificate.Thumbprint } } } | ConvertTo-Json -Compress -Depth 3 | ||
| "WINDOWS_CONFIG=$windowsConfig" | Out-File -FilePath $env:GITHUB_ENV -Append | ||
|
|
||
| - name: 'Prepare bundled runtime' | ||
| working-directory: 'packages/desktop-shell' | ||
|
|
@@ -308,6 +327,39 @@ jobs: | |
| working-directory: 'packages/desktop-shell' | ||
| run: 'npm run test:release' | ||
|
|
||
| - name: 'Sign bundled vendor binaries (macOS)' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Major] Signed macOS binaries invalidate the bundled
Suggested fix: regenerate |
||
| if: "runner.os == 'macOS' && inputs.dry_run == false" | ||
|
yiliang114 marked this conversation as resolved.
|
||
| working-directory: 'packages/desktop-shell' | ||
| shell: 'bash' | ||
| env: | ||
| APPLE_SIGNING_IDENTITY: '${{ env.APPLE_SIGNING_IDENTITY }}' | ||
| run: | | ||
| set -euo pipefail | ||
| # Sign all native macOS executables in the bundled runtime so | ||
| # notarization does not reject them. Tauri only signs the main | ||
| # app binary; resources like ripgrep and the Node.js runtime are | ||
| # embedded verbatim and must be signed beforehand. | ||
| runtime_dir="runtime/qwen-code" | ||
| # ripgrep vendor binaries | ||
| rg_dir="$runtime_dir/lib/vendor/ripgrep" | ||
| if [ -d "$rg_dir" ]; then | ||
|
yiliang114 marked this conversation as resolved.
|
||
| find "$rg_dir" -type f -name 'rg' -path '*-darwin/*' -exec \ | ||
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ | ||
| --options runtime --timestamp \ | ||
| --entitlements src-tauri/Entitlements.plist {} + | ||
| else | ||
| echo "::warning::Ripgrep vendor directory not found at $rg_dir; no ripgrep binaries signed." | ||
| fi | ||
| # Node.js runtime binary | ||
| node_bin="$runtime_dir/node/bin/node" | ||
| if [ -f "$node_bin" ]; then | ||
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ | ||
|
yiliang114 marked this conversation as resolved.
|
||
| --options runtime --timestamp \ | ||
| --entitlements src-tauri/Entitlements.plist "$node_bin" | ||
| else | ||
| echo "::warning::Node.js runtime binary not found at $node_bin; no Node.js binary signed." | ||
| fi | ||
|
|
||
| - name: 'Build desktop installers' | ||
| working-directory: 'packages/desktop-shell' | ||
| shell: 'bash' | ||
|
|
@@ -321,7 +373,7 @@ jobs: | |
| args=( ${{ matrix.tauri_args }} ) | ||
| if [ "$DRY_RUN" = 'true' ]; then | ||
| args+=(--no-sign) | ||
| elif [ "$RUNNER_OS" = 'Windows' ]; then | ||
| elif [ "$RUNNER_OS" = 'Windows' ] && [ -n "$WINDOWS_CONFIG" ]; then | ||
| args+=(--config "$WINDOWS_CONFIG") | ||
| fi | ||
| npm run tauri -- build "${args[@]}" | ||
|
|
@@ -338,10 +390,18 @@ jobs: | |
| - name: 'Verify Windows signature' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Minor] Windows signature verification only covers NSIS The step checks Consider iterating over all Windows installer artifacts produced by the build, or documenting that NSIS is the only attested format. |
||
| if: "runner.os == 'Windows' && inputs.dry_run == false" | ||
| shell: 'pwsh' | ||
| env: | ||
| WINDOWS_CONFIG: '${{ env.WINDOWS_CONFIG }}' | ||
| run: | | ||
| $installer = Get-ChildItem packages/desktop-shell/src-tauri/target/${{ matrix.rust_target }}/release/bundle/nsis/*.exe | Select-Object -First 1 | ||
| $signature = Get-AuthenticodeSignature $installer.FullName | ||
| if ($signature.Status -ne 'Valid') { throw "Invalid Authenticode signature: $($signature.Status)" } | ||
| if ($signature.Status -eq 'Valid') { | ||
| Write-Output "Windows installer is Authenticode-signed." | ||
| } elseif ($signature.Status -eq 'NotSigned' -and -not $env:WINDOWS_CONFIG) { | ||
| Write-Output "::warning::Windows installer is unsigned (no code signing certificate configured). SmartScreen will warn users on first install." | ||
| } else { | ||
| throw "Invalid Authenticode signature: $($signature.Status)" | ||
| } | ||
|
|
||
| - name: 'Create Electron bridge archive' | ||
| if: "runner.os == 'macOS' && inputs.electron_bridge" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,7 @@ try { | |
| testBootstrapBridgeConfiguration(); | ||
| testLegacyApplicationIdentity(); | ||
| testElectronBridgeWorkflow(); | ||
| testDesktopReleaseSigningWorkflow(); | ||
| testResolveLogRoot(); | ||
| testSliceNewLog(); | ||
| testUpdateManifest(path.join(root, 'manifest')); | ||
|
|
@@ -79,6 +80,65 @@ function testElectronBridgeWorkflow() { | |
| } | ||
| } | ||
|
|
||
| function testDesktopReleaseSigningWorkflow() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Minor] String-fragile workflow-contract tests
Consider parsing the workflow YAML once and asserting against the parsed AST, or at least isolating the literal fragments into a single source of truth. |
||
| const workflow = fs.readFileSync( | ||
| path.join(repoRoot, '.github', 'workflows', 'desktop-release.yml'), | ||
| 'utf8', | ||
| ); | ||
| const primaryIncomplete = | ||
| '$primaryIncomplete = ([bool]$env:WINDOWS_CERTIFICATE) -ne ' + | ||
| '([bool]$env:WINDOWS_CERTIFICATE_PASSWORD)'; | ||
| const legacyIncomplete = | ||
| '$legacyIncomplete = ([bool]$env:LEGACY_WIN_CSC_LINK) -ne ' + | ||
| '([bool]$env:LEGACY_WIN_CSC_KEY_PASSWORD)'; | ||
| assert.ok( | ||
| workflow.includes(primaryIncomplete), | ||
| 'Windows signing must fail closed when the primary certificate pair is incomplete', | ||
| ); | ||
| assert.ok( | ||
| workflow.includes(legacyIncomplete), | ||
| 'Windows signing must fail closed when the legacy certificate pair is incomplete', | ||
| ); | ||
| assert.ok( | ||
| workflow.includes( | ||
| 'elif [ "$RUNNER_OS" = \'Windows\' ] && [ -n "$WINDOWS_CONFIG" ]; then', | ||
| ), | ||
| 'Windows builds must only pass a Tauri config when signing config exists', | ||
| ); | ||
| assert.ok( | ||
| workflow.includes( | ||
| "$signature.Status -eq 'NotSigned' -and -not $env:WINDOWS_CONFIG", | ||
| ), | ||
| 'Unsigned Windows installers are only allowed when no signing config exists', | ||
| ); | ||
| assert.ok( | ||
| workflow.includes( | ||
| "--entitlements src-tauri/Entitlements.plist {} +", | ||
| ), | ||
| 'ripgrep codesign failures must fail the signing step', | ||
| ); | ||
| assert.match( | ||
| workflow, | ||
| /Ripgrep vendor directory not found at \$rg_dir/, | ||
| 'missing ripgrep binaries must be visible in release logs', | ||
| ); | ||
| assert.match( | ||
| workflow, | ||
| /Node\.js runtime binary not found at \$node_bin/, | ||
| 'missing Node.js runtime binary must be visible in release logs', | ||
| ); | ||
| assert.ok( | ||
| workflow.indexOf("name: 'Prepare bundled runtime'") < | ||
| workflow.indexOf("name: 'Sign bundled vendor binaries (macOS)'"), | ||
| 'vendor binaries must be signed after the runtime is prepared', | ||
| ); | ||
| assert.ok( | ||
| workflow.indexOf("name: 'Sign bundled vendor binaries (macOS)'") < | ||
| workflow.indexOf("name: 'Build desktop installers'"), | ||
| 'vendor binaries must be signed before Tauri builds installers', | ||
| ); | ||
| } | ||
|
|
||
| function testBootstrapBridgeConfiguration() { | ||
| assert.equal( | ||
| tauriConfig.app?.withGlobalTauri, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.