From 0153b04ca5e0502dc810f2722cd9ae80c87b51cf Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 16:12:12 +0300 Subject: [PATCH 1/7] feat(build): add reproducible Windows code-signing step Wire a documented Authenticode signing path so the NSIS installer can be signed locally without signtool.exe (electron-builder needs the Windows SDK). scripts/sign-win.ps1 signs the installer + inner exe via any PFX (self-signed for pipeline verification, or a real CA cert via CSC_LINK/CSC_KEY_PASSWORD). electron-builder.yml documents the flow and keeps publisherName unset so auto-update keeps working on the unsigned build. build/omb-selfsigned.pfx is gitignored (local test only). --- .gitignore | 4 ++++ electron-builder.yml | 20 +++++++++--------- scripts/sign-win.ps1 | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 scripts/sign-win.ps1 diff --git a/.gitignore b/.gitignore index f915aeedb..e7e601a72 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ release cloudflare/composio-broker/worker-configuration.d.ts .claude/worktrees/ .vercel/ + +# local self-signed test cert (not for production) +build/omb-selfsigned.pfx +>>>>>>> 8f1cea4 (feat(build): add reproducible Windows code-signing step) diff --git a/electron-builder.yml b/electron-builder.yml index d7935ef24..f6b0a6216 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -108,15 +108,17 @@ win: - target: zip arch: x64 icon: build/icon.ico - # No signing config yet (would go under win.signtoolOptions or - # win.azureSignOptions — eb 26 nests it, there is no top-level - # win.certificateFile). The installer is unsigned, so SmartScreen shows - # "unknown publisher" on first run (README says so). verifyUpdateCodeSignature - # defaults true, but electron-updater skips the check when app-update.yml - # carries no publisherName — so auto-update still works today. Do NOT set - # publisherName without actually signing, or every update is rejected as - # untrusted; and once signed, keep the cert subject stable (or list both old - # and new in publisherName) or you strand already-installed users. + # Signing: the build is unsigned by default (electron-builder needs signtool.exe + # from the Windows SDK, which this repo's CI provides). To produce a SIGNED + # Windows build locally, run scripts/sign-win.ps1 after `pnpm package:win` + # (uses build/omb-selfsigned.pfx for pipeline verification) or supply a real + # Authenticode cert via CSC_LINK/CSC_KEY_PASSWORD + win.certificateFile and + # install signtool. A self-signed cert still trips SmartScreen ("unknown + # publisher") — it only proves the signing pipeline works; for a clean install + # use a CA-issued cert (SSL.com/Sectigo/EV). publisherName stays unset on + # purpose: electron-updater skips signature verification when app-update.yml + # has no publisherName, so auto-update keeps working today. Do NOT set + # publisherName without a REAL cert, or every update is rejected as untrusted. nsis: oneClick: true diff --git a/scripts/sign-win.ps1 b/scripts/sign-win.ps1 new file mode 100644 index 000000000..47e1fa39d --- /dev/null +++ b/scripts/sign-win.ps1 @@ -0,0 +1,48 @@ +# sign-win.ps1 — sign the built Windows artifacts with Authenticode. +# +# Usage (after `pnpm package:win`): +# pwsh scripts/sign-win.ps1 +# pwsh scripts/sign-win.ps1 -Pfx build/omb-selfsigned.pfx -Password omb-test-2026 +# +# For a REAL cert: pass -Pfx -Password (or set env +# OMB_CERT_FILE / OMB_PFX_PASSWORD). Requires the cert's private key. +# +# NOTE: a self-signed cert only proves the pipeline works. It still shows +# "unknown publisher" on machines that don't trust its root. Add the root to +# Trusted Root CAs (CurrentUser) once to validate locally: +# $pfx = New-Object Security.Cryptography.X509Certificates.X509Certificate2($Pfx, $Password) +# $s = New-Object Security.Cryptography.X509Certificates.X509Store('Root','CurrentUser'); $s.Open('ReadWrite'); $s.Add($pfx); $s.Close() + +param( + [string]$Pfx = $env:OMB_CERT_FILE, + [string]$Password = $env:OMB_PFX_PASSWORD, + [string]$Thumbprint, + [string]$TimestampServer = 'http://timestamp.digicert.com' +) + +$ErrorActionPreference = 'Stop' + +if (-not $Pfx) { $Pfx = Join-Path $PSScriptRoot '..\build\omb-selfsigned.pfx' } +if (-not $Password) { $Password = 'omb-test-2026' } + +$root = Resolve-Path (Join-Path $PSScriptRoot '..') +$files = @( + (Join-Path $root 'release\OpenMausBot-0.1.27-setup.exe'), + (Join-Path $root 'release\win-unpacked\OpenMausBot.exe') +) + +if ($Thumbprint) { + $cert = Get-ChildItem "cert:\CurrentUser\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } + if (-not $cert) { throw "cert with thumbprint $Thumbprint not in CurrentUser\My" } +} else { + $sec = ConvertTo-SecureString -String $Password -Force -AsPlainText + $cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2($Pfx, $sec, 'PersistKeySet') +} + +foreach ($f in $files) { + if (-not (Test-Path $f)) { Write-Warning "skip (missing): $f"; continue } + $r = Set-AuthenticodeSignature -FilePath $f -Certificate $cert -TimestampServer $TimestampServer + Write-Host ("{0,-60} {1}" -f $f, $r.Status) + if ($r.Status -ne 'Valid') { throw ("signing failed: " + $r.StatusMessage) } +} +Write-Host "done." From f422ae08464853aa1dfa61f146394d3768cfdbd6 Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 16:42:32 +0300 Subject: [PATCH 2/7] docs: add CHANGELOG for 0.1.27 (Windows build fix + code-signing step) --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..83d2b3e9e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to OpenMausBot are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project +adheres to [Conventional Commits](https://www.conventionalcommits.org/). + +## [Unreleased] + +## [0.1.27] + +### Fixed +- **Windows build: extract Android Platform Tools under git-bash.** `scripts/prepare-android-tools.mjs` + now tries `unzip` and falls back to `tar`, and normalizes absolute Windows paths to + MSYS form (`C:\x` → `/c/x`) so `pnpm build:android-tools` / `pnpm package:win` + succeed on a stock git-bash install instead of failing with + `tar: Cannot connect to C: resolve failed`. ([#317](https://github.com/milind-soni/OpenMausBot/pull/317)) + +### Added +- **Reproducible Windows code-signing step.** `scripts/sign-win.ps1` signs the NSIS + installer and the inner `OpenMausBot.exe` with any PFX — a self-signed cert + (`build/omb-selfsigned.pfx`, gitignored) for pipeline verification, or a real + CA-issued Authenticode cert supplied via `CSC_LINK`/`CSC_KEY_PASSWORD`. + `electron-builder.yml` documents the flow and intentionally keeps `publisherName` + unset so auto-update keeps working on the unsigned build. + Note: a self-signed cert validates only on machines that trust its root; a + CA-issued cert is still required for a SmartScreen-clean install. + +[Unreleased]: https://github.com/milind-soni/OpenMausBot/compare/v0.1.27...HEAD +[0.1.27]: https://github.com/milind-soni/OpenMausBot/releases/tag/v0.1.27 From ace2eb56644c23030b182de8208198dd8d502224 Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 18:14:58 +0300 Subject: [PATCH 3/7] docs: document Windows code-signing step and add package:win:signed script --- README.md | 19 +++++++++++++++++++ package.json | 1 + 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 4a7dfee59..b601010ed 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,25 @@ pnpm package:win # Windows installer + zip → release/ pnpm package:linux # Ubuntu x64 .deb + AppImage → release/ ``` +#### Code-signing the Windows build + +`pnpm package:win` produces an **unsigned** installer (SmartScreen flags it as +"unknown publisher"). Sign it with Authenticode after the build: + +```sh +# self-signed test cert (pipeline verification only — still trips SmartScreen) +pwsh scripts/sign-win.ps1 + +# real CA-issued cert (clean SmartScreen install) +CSC_LINK=path/to/cert.pfx CSC_KEY_PASSWORD=**** pwsh scripts/sign-win.ps1 +``` + +`scripts/sign-win.ps1` signs both `release/OpenMausBot--setup.exe` and the +inner `OpenMausBot.exe` with an RFC-3161 timestamp (DigiCert), so the signature +outlives the cert. A self-signed cert validates only on machines that trust its +root; for a SmartScreen-clean install use a CA-issued Authenticode certificate +and set `win.certificateFile` / `CSC_*` in `electron-builder.yml`. + ### Routines and webhook triggers Routines can run once or on selected weekdays, using either a MAUS's configured model/computer or the diff --git a/package.json b/package.json index 29d66525e..80292f749 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "package:prepare": "pnpm build && pnpm build:server && pnpm build:companion && pnpm build:updater && pnpm build:android-tools", "package:mac": "pnpm package:prepare && pnpm build:speech && pnpm build:cua && electron-builder --mac --publish never", "package:win": "pnpm package:prepare && electron-builder --win --publish never", + "package:win:signed": "pnpm package:win && pwsh scripts/sign-win.ps1", "package:linux": "pnpm package:prepare && pnpm build:cua:linux && electron-builder --linux --x64 --publish never", "package:linux:offline": "pnpm package:prepare && pnpm build:cua:linux:offline && electron-builder --linux --x64 --publish never", "smoke:linux-package": "node scripts/run-linux-package-smoke.mjs", From 78130b8f4fc8fd9f60b416ebdd377941f37c0871 Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 18:17:15 +0300 Subject: [PATCH 4/7] ci(win): optionally Authenticode-sign the build when CSC_LINK is set package-win.yml now signs release/OpenMausBot--setup.exe + the inner OpenMausBot.exe via scripts/sign-win.ps1, gated on secrets.CSC_LINK so the unsigned-by-default path is untouched. sign-win.ps1 now resolves the version from package.json instead of hardcoding 0.1.27, so it stays correct across releases. --- .github/workflows/package-win.yml | 11 +++++++++++ scripts/sign-win.ps1 | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-win.yml b/.github/workflows/package-win.yml index 5f659683d..cd76966df 100644 --- a/.github/workflows/package-win.yml +++ b/.github/workflows/package-win.yml @@ -42,6 +42,17 @@ jobs: shell: bash run: rm -rf dist dist-server release - run: pnpm package:win + # Optional Authenticode signing. Runs only when a cert is supplied via + # secrets.CSC_LINK (the same var electron-builder would use). Without it + # the build stays unsigned — the verify step below still rejects a + # publisherName on an unsigned build, so auto-update keeps working. + - name: sign the Windows build (optional) + if: ${{ secrets.CSC_LINK != '' }} + shell: bash + env: + OMB_CERT_FILE: ${{ secrets.CSC_LINK }} + OMB_PFX_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + run: pwsh scripts/sign-win.ps1 - name: report what was built shell: bash run: | diff --git a/scripts/sign-win.ps1 b/scripts/sign-win.ps1 index 47e1fa39d..c4fbc08b7 100644 --- a/scripts/sign-win.ps1 +++ b/scripts/sign-win.ps1 @@ -26,8 +26,10 @@ if (-not $Pfx) { $Pfx = Join-Path $PSScriptRoot '..\build\omb-selfsigned.pfx' } if (-not $Password) { $Password = 'omb-test-2026' } $root = Resolve-Path (Join-Path $PSScriptRoot '..') +$ver = (node -p "require('./package.json').version" 2>$null) +if (-not $ver) { $ver = '0.1.27' } # fallback if node unavailable $files = @( - (Join-Path $root 'release\OpenMausBot-0.1.27-setup.exe'), + (Join-Path $root "release\OpenMausBot-$ver-setup.exe"), (Join-Path $root 'release\win-unpacked\OpenMausBot.exe') ) From 4e5128ad59b62e644ae686b73bceb7a04236271c Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 18:47:35 +0300 Subject: [PATCH 5/7] Add environment variable for CSC_LINK and update condition --- .github/workflows/package-win.yml | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/.github/workflows/package-win.yml b/.github/workflows/package-win.yml index cd76966df..5a63b07f8 100644 --- a/.github/workflows/package-win.yml +++ b/.github/workflows/package-win.yml @@ -25,6 +25,8 @@ jobs: name: NSIS installer runs-on: windows-latest timeout-minutes: 30 + env: + HAS_CSC: ${{ secrets.CSC_LINK != '' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -47,7 +49,7 @@ jobs: # the build stays unsigned — the verify step below still rejects a # publisherName on an unsigned build, so auto-update keeps working. - name: sign the Windows build (optional) - if: ${{ secrets.CSC_LINK != '' }} + if: ${{ env.HAS_CSC == 'true' }} shell: bash env: OMB_CERT_FILE: ${{ secrets.CSC_LINK }} @@ -98,27 +100,3 @@ jobs: res=release/win-unpacked/resources HOME="$RUNNER_TEMP/omb-smoke" USERPROFILE="$RUNNER_TEMP/omb-smoke" \ OMB_PORT=21987 node "$res/server/index.js" > "$RUNNER_TEMP/server.log" 2>&1 & - pid=$! - for _ in $(seq 1 90); do - if curl -fsS --max-time 2 http://127.0.0.1:21987/api/health >/dev/null 2>&1; then - echo "packaged server answered /api/health ✓" - kill $pid 2>/dev/null || true - exit 0 - fi - kill -0 $pid 2>/dev/null || break - sleep 1 - done - echo "::error::the packaged server never served /api/health" - cat "$RUNNER_TEMP/server.log" || true - kill $pid 2>/dev/null || true - exit 1 - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: windows-installer - path: | - release/*.exe - release/*.exe.blockmap - release/latest.yml - if-no-files-found: error - retention-days: 14 From 23662b265c4ffb20dfc7202fb199295b85e85fb9 Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 18:48:38 +0300 Subject: [PATCH 6/7] Fix path handling and improve error checking in sign script --- scripts/sign-win.ps1 | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/scripts/sign-win.ps1 b/scripts/sign-win.ps1 index c4fbc08b7..15dbc9000 100644 --- a/scripts/sign-win.ps1 +++ b/scripts/sign-win.ps1 @@ -22,16 +22,24 @@ param( $ErrorActionPreference = 'Stop' -if (-not $Pfx) { $Pfx = Join-Path $PSScriptRoot '..\build\omb-selfsigned.pfx' } +if (-not $Pfx) { $Pfx = Join-Path $PSScriptRoot '..uild\omb-selfsigned.pfx' } if (-not $Password) { $Password = 'omb-test-2026' } $root = Resolve-Path (Join-Path $PSScriptRoot '..') $ver = (node -p "require('./package.json').version" 2>$null) -if (-not $ver) { $ver = '0.1.27' } # fallback if node unavailable -$files = @( - (Join-Path $root "release\OpenMausBot-$ver-setup.exe"), - (Join-Path $root 'release\win-unpacked\OpenMausBot.exe') -) +if (-not $ver) { throw "Could not read package version from package.json" } + +$installerPath = Join-Path $root "release\OpenMausBot-$ver-setup.exe" +$unpackedAppPath = Join-Path $root 'release\win-unpacked\OpenMausBot.exe' + +$files = @($installerPath, $unpackedAppPath) + +# Throw immediately if any of the required files are missing before signing +foreach ($f in $files) { + if (-not (Test-Path $f)) { + throw "Missing required build artifact for signing: $f" + } +} if ($Thumbprint) { $cert = Get-ChildItem "cert:\CurrentUser\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } @@ -42,7 +50,6 @@ if ($Thumbprint) { } foreach ($f in $files) { - if (-not (Test-Path $f)) { Write-Warning "skip (missing): $f"; continue } $r = Set-AuthenticodeSignature -FilePath $f -Certificate $cert -TimestampServer $TimestampServer Write-Host ("{0,-60} {1}" -f $f, $r.Status) if ($r.Status -ne 'Valid') { throw ("signing failed: " + $r.StatusMessage) } From c18374be4a9473044405f2fbc22481de11e95344 Mon Sep 17 00:00:00 2001 From: Ansygroup Date: Fri, 21 Aug 2026 18:51:49 +0300 Subject: [PATCH 7/7] ci(win): restore upload-artifact and server smoke step dropped by edits The branch edits left package-win.yml truncated: the 'start the packaged server' health check lost its loop/kill/exit and the upload-artifact step was removed entirely, so the workflow would never produce the installer artifact. Restore both; keep the HAS_CSC-gate signing step. --- .github/workflows/package-win.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/package-win.yml b/.github/workflows/package-win.yml index 5a63b07f8..f8c43e1ad 100644 --- a/.github/workflows/package-win.yml +++ b/.github/workflows/package-win.yml @@ -100,3 +100,27 @@ jobs: res=release/win-unpacked/resources HOME="$RUNNER_TEMP/omb-smoke" USERPROFILE="$RUNNER_TEMP/omb-smoke" \ OMB_PORT=21987 node "$res/server/index.js" > "$RUNNER_TEMP/server.log" 2>&1 & + pid=$! + for _ in $(seq 1 90); do + if curl -fsS --max-time 2 http://127.0.0.1:21987/api/health >/dev/null 2>&1; then + echo "packaged server answered /api/health ✓" + kill $pid 2>/dev/null || true + exit 0 + fi + kill -0 $pid 2>/dev/null || break + sleep 1 + done + echo "::error::the packaged server never served /api/health" + cat "$RUNNER_TEMP/server.log" || true + kill $pid 2>/dev/null || true + exit 1 + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: windows-installer + path: | + release/*.exe + release/*.exe.blockmap + release/latest.yml + if-no-files-found: error + retention-days: 14