Windows builds are now signed with Azure Artifact Signing, for both releases (build-app.yml) and PR previews (build-app-preview.yml). Signing covers the app binary, the MSI, the NSIS installer and its uninstaller.
How
scripts/windows-signing-setup.ps1 (once per job):
Downloads Microsoft.ArtifactSigning.Client 1.0.128 and a job-local .NET 10.0.12 runtime, each pinned by hash. Nothing is installed on WindowsBuilder.
Writes the dlib's metadata.json, which excludes every credential type except EnvironmentCredential, so a SYSTEM job never waits on an interactive browser sign-in.
Exports TAURI_CONFIG with bundle.windows.signCommand.
scripts/windows-sign.ps1: the sign command. It runs signtool sign /dlib with SHA-256 and the Microsoft timestamp server, with retries. Credentials come only from the environment and are never on a command line.
scripts/windows-verify-signatures.ps1: a new "Verify signatures" step. It fails the job unless the app .exe, the .msi and the NSIS .exe all have a valid, timestamped signature.
Secrets used
AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, ARTIFACT_SIGNING_ENDPOINT, ARTIFACT_SIGNING_ACCOUNT_NAME, ARTIFACT_SIGNING_PROFILE_NAME. A missing secret fails the job before the build starts.
Build VM findings
These come from read-only inspection through the Proxmox guest agent:
WindowsBuilder (VM 110) runs both Windows runners in host mode as SYSTEM.
It has signtool 10.0.26100 but no .NET, hence the job-local runtime.
The signing files live in the workspace, not %TEMP%: the NSIS uninstaller is signed from 32-bit makensis, and WOW64 redirects SYSTEM's %TEMP% for 32-bit processes.
Testing
This PR's own preview build is the end-to-end test: it runs on the real VM with the real secrets.
Note: merging touches build-app.yml, so it triggers a release build. That will be the first signed release.
Windows builds are now signed with **Azure Artifact Signing**, for both releases (`build-app.yml`) and PR previews (`build-app-preview.yml`). Signing covers the app binary, the MSI, the NSIS installer and its uninstaller.
## How
- **`scripts/windows-signing-setup.ps1`** (once per job):
- Downloads `Microsoft.ArtifactSigning.Client` 1.0.128 and a job-local .NET 10.0.12 runtime, each pinned by hash. Nothing is installed on `WindowsBuilder`.
- Writes the dlib's `metadata.json`, which excludes every credential type except `EnvironmentCredential`, so a SYSTEM job never waits on an interactive browser sign-in.
- Exports `TAURI_CONFIG` with `bundle.windows.signCommand`.
- **`scripts/windows-sign.ps1`:** the sign command. It runs `signtool sign /dlib` with SHA-256 and the Microsoft timestamp server, with retries. Credentials come only from the environment and are never on a command line.
- **`scripts/windows-verify-signatures.ps1`:** a new "Verify signatures" step. It fails the job unless the app `.exe`, the `.msi` and the NSIS `.exe` all have a valid, timestamped signature.
## Secrets used
`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `ARTIFACT_SIGNING_ENDPOINT`, `ARTIFACT_SIGNING_ACCOUNT_NAME`, `ARTIFACT_SIGNING_PROFILE_NAME`. A missing secret fails the job before the build starts.
## Build VM findings
These come from read-only inspection through the Proxmox guest agent:
- `WindowsBuilder` (VM 110) runs both Windows runners in host mode as SYSTEM.
- It has `signtool` 10.0.26100 but no .NET, hence the job-local runtime.
- The signing files live in the workspace, not `%TEMP%`: the NSIS uninstaller is signed from 32-bit `makensis`, and WOW64 redirects SYSTEM's `%TEMP%` for 32-bit processes.
## Testing
This PR's own preview build is the end-to-end test: it runs on the real VM with the real secrets.
**Note:** merging touches `build-app.yml`, so it triggers a release build. That will be the first signed release.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Releases and PR previews now sign the app binary, the MSI, the NSIS
installer and its uninstaller. "Verify signatures" fails the job on any
unsigned or untimestamped .exe/.msi, so an unsigned installer can't ship
quietly.
- windows-signing-setup.ps1 fetches Microsoft.ArtifactSigning.Client 1.0.128
and a job-local .NET 10.0.12 runtime, each pinned by hash. Nothing is
installed on the build VM. It also writes the dlib metadata and exports
TAURI_CONFIG with bundle.windows.signCommand.
- windows-sign.ps1 runs the installed signtool with /dlib, SHA-256 and the
Microsoft timestamp server, with retries. Credentials come only from the
AZURE_* environment. The metadata excludes every credential type except
EnvironmentCredential, because InteractiveBrowserCredential would hang a
job running as SYSTEM.
- The signing files go in the workspace, not %TEMP%, because the uninstaller
is signed from 32-bit makensis and WOW64 redirects SYSTEM's %TEMP%.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first signing run built nothing signed, and "Verify signatures" failed
it as intended. The Tauri 2 CLI never reads TAURI_CONFIG. It only sets that
variable for tauri-build, so the sign command was dropped silently, just as
the inline beforeBuildCommand override had been for as long as the Windows
jobs have set it. The setup now writes a config file, and the build passes
it with `cargo tauri build --config`.
Review follow-ups:
- The NSIS uninstaller is written to %TEMP% and signed from 32-bit makensis.
SYSTEM's %TEMP% sits under System32, which WOW64 redirects for makensis but
not for the x64 signtool, so they would disagree about where the file is.
%TEMP% and %TMP% now point into the workspace. makensis ignores the sign
command's exit code for the uninstaller, so windows-sign.ps1 logs every
file it signs, and the verify step requires a logged signature under that
temp directory.
- The signing client is pinned by SHA-512, so the pin can be checked against
nuget.org's published packageHash.
- tauri-cli on Windows is pinned to =2.11.0 --locked, the @tauri-apps/cli
version the Linux and macOS jobs run from the lockfile, instead of "^2".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Artifact Signing is metered at about 1000 signatures a month, and there
were 67 Windows builds last month.
- windows-sign.ps1 signs only what reaches users: the app binary, the MSI,
the NSIS installer and the uninstaller. It skips the WiX extension DLLs
and NSIS plugins that Tauri also offers, and any file that is already
validly signed. That is 4 signatures per release.
- Previews are no longer signed, so the preview workflow no longer
references the signing secrets. A PR's workflow runs the PR's own code.
The inline TAURI_CONFIG there, which the v2 CLI never read, becomes a
real `--config`.
- Tauri reports a failed sign command only as "failed to run powershell".
The script now keeps a transcript, signtool /debug included, and the
release job prints it on failure.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first fully signed run (signer CN=OLS Digital LLC) signed the app
binary, the MSI, the NSIS installer and the uninstaller, and skipped the
seven build-time DLLs. It still failed verification on
target\release\triple-c.exe. Tauri patches that file with bundle-type
information before each bundle, signs it, packages it, and patches it
again, so the loose copy is unsigned by design and is not what ships.
Verification now:
- checks the installers themselves;
- unpacks the MSI with `msiexec /a` and checks the binaries inside it;
- for NSIS, which can't be unpacked that way, requires the signing log to
show that the app binary and the uninstaller were signed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jknapp
merged commit f8e3ec1150 into main2026-09-24 02:09:49 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Windows builds are now signed with Azure Artifact Signing, for both releases (
build-app.yml) and PR previews (build-app-preview.yml). Signing covers the app binary, the MSI, the NSIS installer and its uninstaller.How
scripts/windows-signing-setup.ps1(once per job):Microsoft.ArtifactSigning.Client1.0.128 and a job-local .NET 10.0.12 runtime, each pinned by hash. Nothing is installed onWindowsBuilder.metadata.json, which excludes every credential type exceptEnvironmentCredential, so a SYSTEM job never waits on an interactive browser sign-in.TAURI_CONFIGwithbundle.windows.signCommand.scripts/windows-sign.ps1: the sign command. It runssigntool sign /dlibwith SHA-256 and the Microsoft timestamp server, with retries. Credentials come only from the environment and are never on a command line.scripts/windows-verify-signatures.ps1: a new "Verify signatures" step. It fails the job unless the app.exe, the.msiand the NSIS.exeall have a valid, timestamped signature.Secrets used
AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,ARTIFACT_SIGNING_ENDPOINT,ARTIFACT_SIGNING_ACCOUNT_NAME,ARTIFACT_SIGNING_PROFILE_NAME. A missing secret fails the job before the build starts.Build VM findings
These come from read-only inspection through the Proxmox guest agent:
WindowsBuilder(VM 110) runs both Windows runners in host mode as SYSTEM.signtool10.0.26100 but no .NET, hence the job-local runtime.%TEMP%: the NSIS uninstaller is signed from 32-bitmakensis, and WOW64 redirects SYSTEM's%TEMP%for 32-bit processes.Testing
This PR's own preview build is the end-to-end test: it runs on the real VM with the real secrets.
Note: merging touches
build-app.yml, so it triggers a release build. That will be the first signed release.🤖 Generated with Claude Code