Skip to content

Code-sign and notarize macOS builds #7

Open

Description

@killerwolf

Downloaded builds currently trigger macOS Gatekeeper's "QuickToss.app is damaged and can't be opened" error on Apple Silicon, since the app isn't signed with a Developer ID or notarized (hardenedRuntime: true is set, but electron-builder skips signing entirely when no identity is found — confirmed in CI logs: skipped macOS application code signing ... 0 valid identities found).

This is a hard UX blocker for every new user, and it also puts the auto-update flow (#4 below, or wherever it lands) at risk — a downloaded update .zip carries the same quarantine flag as the initial download.

Current workaround

Users have to run xattr -cr /Applications/QuickToss.app manually after install.

Scope

  • Enroll in the Apple Developer Program if not already ($99/year)
  • Generate a "Developer ID Application" certificate, export as .p12
  • Add signing secrets to GitHub Actions: CSC_LINK, CSC_KEY_PASSWORD
  • Add notarization credentials: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD (or API key), APPLE_TEAM_ID
  • Set mac.notarize in package.json (or the newer electron-builder notarize config) and remove the gatekeeperAssess: false override once signing is verified
  • Confirm both x64 and arm64 builds launch cleanly on a fresh Mac without the xattr workaround
  • Verify the auto-update flow (download → quit-and-install) also works cleanly once signed

Activity

  1. killerwolf commented on Aug 20, 2026

    @killerwolf
    OwnerAuthor

    Confirmed with a real reproduction — this is now blocking auto-update, not just the first-launch Gatekeeper warning.

    Running the installed v1.1.0 from a terminal while it picked up v1.3.0:

    Update has already been downloaded to .../pending/QuickToss-1.3.0-arm64.zip
    Creating proxy server for native Squirrel.Mac ...
    Download completed to: file:///...ShipIt/update.ng4DQpC/...zip
    [Error: Code signature at URL file:///...ShipIt/update.ng4DQpC/QuickToss.app/ did not pass validation:
     le code n'a pas de ressources mais la signature indique qu'elles doivent être présentes] {
      code: -1,
      domain: 'SQRLCodeSignatureErrorDomain'
    }
    

    The download and checksum stages succeed — Squirrel.Mac rejects the bundle at the signature-validation step, right before swapping it in.

    Observed symptoms this explains:

    • arm64: the update toast vanished — the error event fires shortly after update-downloaded.
    • x64: the toast stayed but "Restart" did nothing — quitAndInstall() hands off to Squirrel, which fails the same validation.

    So auto-update cannot work at all until the app is signed with a Developer ID and notarized. There's no supported way to bypass Squirrel.Mac's signature check on macOS.

  2. killerwolf commented on Sep 10, 2026

    @killerwolf
    OwnerAuthor

    Still open and still the right ask, but the mechanics changed in 2.0 (ADR 0002) — worth recording so nobody picks this up against the old description.

    What no longer applies:

    • Squirrel.Mac and electron-updater are gone. The signature problems described here — Squirrel rejecting the downloaded bundle on arm64, electron-updater unable to read a signature for the running app on x64 — were properties of that updater, not of the app.
    • ~/Library/Logs/QuickToss/auto-updater.log no longer exists. There is no updater framework to log.
    • The hardened-runtime entitlements are gone. Signing needed com.apple.security.cs.allow-jit and allow-unsigned-executable-memory for V8, which are exactly the exceptions that weaken the hardened runtime. A Rust binary with no JavaScript engine needs neither, so notarization should be a cleaner proposition than it was.

    What still applies:

    • The app is signed ad-hoc (codesign --sign - in scripts/bundle.sh), not with a Developer ID, and isn't notarized. Users still need xattr -cr /Applications/QuickToss.app once.
    • In-place updates still can't be done safely, so the app only checks for a newer version and links to the release page.

    What's smaller now: the download went from ~110 MB to ~6 MB. Notarization uploads and staples a much smaller artifact, and if in-place updates ever land, the download is no longer the reason not to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions