From e4ab9e9cedb203fd633f8aa182d1b932e1405079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C9=90=CA=87=C9=B9=C9=90=CA=8Ep=C4=B1=CA=8D=20=C9=A5=C4=B1?= =?UTF-8?q?=D7=9F=C9=90=C6=83?= Date: Thu, 17 Sep 2026 16:58:52 +0700 Subject: [PATCH 1/2] build: ad-hoc sign macOS builds rather than leave them unsigned Packaging never used a certificate, but it did not say so either: with no identity set, electron-builder auto-discovers whatever sits in the local keychain, so a build on a machine holding a Development certificate produced an app that only ran on that machine. When it found nothing it skipped signing altogether, which is worse than it sounds -- the bundle keeps the signature Electron shipped with, over resources electron-builder has since rewritten, and `codesign --verify` fails it with "code has no resources but signature indicates they must be present". Gatekeeper calls a quarantined copy of that "damaged and can't be opened", which right-click Open will not clear. `identity: "-"` signs ad-hoc instead: no certificate, no team, nothing Gatekeeper accepts -- but a signature that verifies, so a quarantined copy gets the ordinary unidentified-developer dialog that right-click Open does clear. `dist:mac` sets CSC_IDENTITY_AUTO_DISCOVERY=false as well, so a certificate in the keychain cannot quietly change what a local build produces; CI already passed it. Verified against a real `npm run dist:mac`: both architectures report flags=0x2(adhoc) with no team identifier, `codesign --verify --deep --strict` exits 0, the signature survives the zip round-trip, and the binary executes on arm64 once the quarantine flag is removed. Co-Authored-By: Claude Opus 5 --- electron-builder.yml | 11 +++++++++-- package.json | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index a7de408..27f0512 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -38,8 +38,15 @@ mac: - target: zip arch: [arm64, x64] darkModeSupport: true - # Unsigned local builds: without this macOS refuses to open the .app because - # the hardened runtime has no signature to validate. + # No Apple certificate is ever used. "-" is an ad-hoc signature: it carries no + # identity, so the app stays unsigned as far as Gatekeeper is concerned, but it + # is a *valid* one. Without it the bundle keeps Electron's own signature over + # resources we have since rewritten, and Gatekeeper calls a quarantined copy of + # that "damaged" — which right-click Open cannot clear, unlike an ad-hoc build. + identity: "-" + # The hardened runtime would validate that signature against an identity there + # isn't one of, and gatekeeperAssess would run the build through spctl, which + # by definition rejects anything unsigned. hardenedRuntime: false gatekeeperAssess: false extendInfo: diff --git a/package.json b/package.json index 4be4418..6cf6c6d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "smoke": "node scripts/build-smoke.mjs && node scripts/with-mongod.mjs electron dist/smoke/smoke.js", "pack": "npm run build && electron-builder --dir", "dist": "npm run build && electron-builder -mwl", - "dist:mac": "npm run build && electron-builder --mac", + "dist:mac": "npm run build && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac", "dist:win": "npm run build && electron-builder --win", "dist:linux": "npm run build && electron-builder --linux", "ui-check": "npm run build && node scripts/build-ui-check.mjs && node scripts/with-mongod.mjs electron dist/smoke/ui-check.js", From 089ff48ecb70249f8929b67c22efcf75520ff6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C9=90=CA=87=C9=B9=C9=90=CA=8Ep=C4=B1=CA=8D=20=C9=A5=C4=B1?= =?UTF-8?q?=D7=9F=C9=90=C6=83?= Date: Thu, 17 Sep 2026 16:59:00 +0700 Subject: [PATCH 2/2] docs: say what an unsigned macOS build asks of whoever receives it The old two lines said builds were unsigned and how to sign them, which left out the only part a recipient needs: macOS quarantines the app the moment it arrives by download, AirDrop, Slack or email, and an ad-hoc signature does not clear that. Right-click Open, or `xattr -dr com.apple.quarantine`, once per install. Also records why the build signs ad-hoc rather than not at all, with the `codesign --verify` error the previous arrangement produced, and completes the notarisation note: dropping `identity` is not enough, hardenedRuntime has to go back on for Apple to accept the upload. Co-Authored-By: Claude Opus 5 --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a392e2..df6887c 100644 --- a/README.md +++ b/README.md @@ -230,9 +230,47 @@ run, so releases cannot loop. For a MINOR or MAJOR release, bump the version yourself (`npm version minor --no-git-tag-version`) and push; the next patch bump continues from there. -macOS builds are unsigned by default. To sign and notarise, set -`CSC_LINK`/`CSC_KEY_PASSWORD` and the notarisation credentials before running -`npm run dist:mac`. +### Unsigned macOS builds + +macOS builds carry no Apple certificate. `electron-builder.yml` sets +`identity: "-"`, and `dist:mac` runs with `CSC_IDENTITY_AUTO_DISCOVERY=false`, so +a certificate sitting in your keychain is never picked up — a build on your +machine is byte-for-byte the same deal as a build in CI. + +`"-"` is an *ad-hoc* signature: no identity, no team, nothing Gatekeeper will +accept — but a structurally valid one. That is the part worth having. Skipping +signing altogether leaves the bundle carrying Electron's original signature over +resources electron-builder has since rewritten, which `codesign --verify` fails: + +``` +code has no resources but signature indicates they must be present +``` + +A quarantined app in that state is not merely refused — Gatekeeper reports it as +"damaged and can't be opened. You should move it to the Trash", and right-click → +*Open* will not get past it. With a valid ad-hoc signature the same app gets the +ordinary unidentified-developer dialog instead, which right-click → *Open* does +clear. + +So the `.app` runs anywhere, but macOS quarantines it the moment it arrives by +download, AirDrop, Slack or email, and an ad-hoc signature does not clear that +quarantine. Whoever you hand the build to has to say so once, either way: + +- **Finder** — right-click the app, choose *Open*, then *Open* in the dialog. + On macOS 15 and newer, open it normally first, then allow it under + *System Settings › Privacy & Security*. +- **Terminal** — strip the quarantine flag outright: + + ```bash + xattr -dr com.apple.quarantine "/Applications/Mongo Explorer.app" + ``` + +Either is needed only once per install, not per launch. + +To ship something that skips all of this, sign and notarise properly: set +`CSC_LINK`/`CSC_KEY_PASSWORD` and the notarisation credentials, drop the +`identity` line from `electron-builder.yml`, and set `hardenedRuntime: true` +(notarisation requires it) before running `npm run dist:mac`. ## Verifying