Locara

macOS Notarization + Sparkle

What it is: The two-part operational reality of shipping a Mac app outside the App Store. Notarization is Apple’s mandatory automated malware-scanning service, required for default Gatekeeper acceptance since macOS Catalina (Feb 2020). Sparkle is the canonical open-source app-update framework used by ~every serious indie Mac app. Together they define the publish-and-update path for Locara’s distribution. Status: Notarization is mature, automated, ~minutes-not-days; full requirement enforcement since February 3, 2020. Sparkle 2 (with EdDSA signing, sandbox support, channels, phased rollouts) is the current stable line, MIT-licensed, community-maintained. Most relevant to Locara: This is the operational layer Locara has to get right week one. Locara’s “apps signed and notarized by Locara CI” pitch only works if you understand how notarization actually behaves in production, and the update mechanism users expect is Sparkle-shaped.

Background

Apple announced notarization and the Hardened Runtime at WWDC in early June 2018 with a one-year runway for developers. macOS Mojave (10.14) added support; macOS Catalina (10.15, late 2019) made notarization required for default Gatekeeper acceptance, with full enforcement landing February 3, 2020. From that date, every newly-built Mac app distributed outside the Mac App Store must be:

  1. Code-signed with a valid Developer ID certificate (requires $99/year Apple Developer Program membership).
  2. Built with the Hardened Runtime enabled.
  3. Submitted to Apple’s notary service (xcrun notarytool, the modern tool; legacy was altool).
  4. Stapled with the resulting notarization ticket so Gatekeeper can verify offline.

Apple’s notary service runs automated malware/static-analysis checks. Notarization is not editorial — there’s no human reviewing for content. It typically completes in minutes. Rejections are mostly for missing Hardened Runtime, expired certs, unsigned helpers, prohibited APIs, or detected malware.

Sparkle is the open-source app-update framework that fills the “how does the user get a new version” gap for non-App-Store apps. Originally created by Andy Matuschak in 2006, it’s now maintained by a community team (notably Kornel Lesiński / “pornel” and Mayur Pawashe). Sparkle 2 (released around 2021–22) is the current stable line, with EdDSA (ed25519) signature verification of update payloads, support for sandboxed apps, beta channels, and phased rollouts. Sparkle is MIT-licensed.

Together: developer signs and notarizes the app once, ships it with a Sparkle integration, hosts an appcast.xml feed somewhere. Sparkle polls the feed, downloads new builds, verifies signatures, prompts the user to install.

Key design decisions — Notarization

  • Automated, not editorial. No human reviewer; checks run in minutes. Rejection reasons are technical, not subjective.
  • Hardened Runtime is mandatory. Restricts dynamic code loading (no JIT without entitlements), library validation (signed dylibs only), DYLD environment manipulation, and unsigned executables.
  • Per-version notarization. Every build needs its own ticket; updates trigger re-notarization.
  • Tickets can be stapled offline. Gatekeeper checks the stapled ticket without network access; useful for air-gapped distribution.
  • Apple can revoke certificates. A compromised Developer ID can be revoked by Apple; users with that app installed will see Gatekeeper warnings on launch.
  • Costs $99/year for Developer Program membership. No per-app fee.
  • Notarization differs from App Store review. Notarization checks for malware/sanity; App Store review checks against Apple’s full guidelines. Apps distributed outside the App Store only need notarization.

Key design decisions — Sparkle

  • Appcast feed. XML/RSS describing available updates: version, download URL, signature, release notes URL.
  • EdDSA (ed25519) signature verification of update payloads. Replaced legacy DSA in Sparkle 2; private signing key is the single point of trust for updates.
  • XPC-isolated installer (Sparkle 2). The main app process requests an update; a separate XPC service handles the actual install. Privilege separation; cleaner sandboxing story.
  • Channels for beta updates. Apps can offer “stable” and “beta” channels, users opt in.
  • Phased rollouts. Updates roll out to a percentage of users over time, allowing early detection of regressions.
  • Delta updates. Sparkle supports binary-diff updates so existing users only download changed files.
  • Critical and major flags. Updates can be marked critical (force prompt) or major (require user re-consent).
  • No code in your app required for basic use. Drop the framework in, configure the appcast URL, ship.
  • Open source, MIT licensed, community-maintained.

What worked

  • Notarization raised the floor without requiring App Store distribution. Most Mac malware doesn’t pass notarization; users get a meaningful baseline of safety even from third-party downloads.
  • The dev experience is OK once configured. xcrun notarytool submit ... --wait plus a Sparkle setup is the standard recipe; well-documented.
  • EdDSA signing means stolen-server attacks can’t push malware. Even if an attacker takes over the appcast hosting, they can’t sign a payload without the private key. (Sparkle 1’s MITM-on-HTTP-feeds was the cautionary lesson here.)
  • Sparkle is essentially universal among indie Mac apps. If a non-App-Store Mac app updates itself, it almost certainly uses Sparkle.
  • Stapled tickets enable offline distribution. Air-gapped enterprise deployments work.
  • Channels + phased rollouts are operationally serious. Real production apps use beta channels and phased rollouts to catch regressions early.

What failed / criticisms

  • First-time setup is paper cuts on top of paper cuts. Apple Developer Program enrollment, certificates, provisioning profiles, entitlement plists, Hardened Runtime configuration, JIT exclusions, notarization scripts — each step is documented but the cumulative friction filters out hobbyists.
  • $99/year is real friction for hobbyist publishers. Not a barrier for serious devs, real for one-off projects.
  • Notarization rejections are often opaque. Generic error messages; debugging requires log inspection and trial-and-error.
  • Hardened Runtime breaks legitimate apps. Electron apps, debuggers, JIT-using languages, plugin systems all need exemption entitlements (or refactoring).
  • Apple can revoke certs. Rare but real; a revoked Developer ID breaks every app signed with it. Recovery requires re-signing all apps with a new cert.
  • Sparkle 1’s HTTP-feed history. Pre-2016 Sparkle had MITM vulnerabilities (insecure HTTP feeds, weak signing). Fixed in Sparkle 2 but the legacy concern lingers.
  • Sparkle setup has its own footguns. Lost private signing keys mean you can’t push updates without forcing every user to manually reinstall. Key management is real ops work.
  • Notarization revocation is a continuous-monitoring concern. Apps stapled with a compromised certificate need re-notarization with a new cert; orchestrating this for thousands of installs is operationally non-trivial.
  • iOS path is completely different. None of this transfers — iOS has TestFlight + App Store, no Sparkle equivalent, much heavier review.

Specific learnings for Locara

  1. Locara apps are notarized by Locara, not by individual developers. This is a real wedge. Developers submit signed code to Locara CI; Locara signs and notarizes with its Developer ID. Solves the $99/dev friction, the cert-management complexity, and the trust problem in one move. This is the Homebrew-bottles model applied to Mac apps.
  2. Hardened Runtime + entitlements is the bottom layer of Locara’s capability enforcement. macOS App Sandbox handles syscalls; Locara handles its own primitives (model loading, tool execution); Hardened Runtime handles dynamic code. Defense in depth across three layers.
  3. The Hardened Runtime “no dynamic code loading” rule perfectly aligns with Locara’s “no runtime fetched-and-executed code” rule (cribbed from Apple App Store’s self-contained rule). Get this for free; don’t fight it.
  4. Sparkle is the update mechanism Locara must integrate with — or replace. Most Mac users expect Sparkle-style update prompts. Locara’s runtime can be the Sparkle host across all installed apps: it polls a Locara-hosted appcast per app, downloads, verifies signature, prompts the user. Centralized over having 1,000 apps each rolling their own.
  5. Sparkle 2’s XPC-isolated installer model is the right pattern. Don’t have the app’s main process do the install. Locara’s update flow should mirror this — privilege-separated installer, fresh process for the install step.
  6. Re-prompt on capability changes. If an app update changes the manifest’s declared capabilities, Locara must require user re-consent before applying. This is not built into Sparkle; Locara has to layer it. Most-cited App Store attack vector (compromised-update gains capabilities) is closed by this.
  7. All Locara appcasts must be HTTPS + signed. Sparkle 1’s MITM history is the cautionary tale. Don’t repeat it.
  8. Plan for cert revocation as an operational reality. If Locara’s Developer ID is revoked or compromised, every app stops launching cleanly. Have a key-rotation plan documented and rehearsed before it’s needed.
  9. EdDSA (ed25519) is the right signing primitive across Locara’s stack. Sparkle uses it for updates; Sigstore uses it; modern best practice. Don’t use RSA or DSA for new things.
  10. Channels + phased rollouts should be Locara-runtime features, not per-app concerns. Beta channels, phased rollouts of new app versions, critical-update marking — these are runtime concerns, not framework concerns. The runtime knows when to push, when to wait, when to alert the user.
  11. iOS will look completely different. Plan for the v2 (iOS) path being App-Store-shaped, with Locara as a wrapper that lives within Apple’s review process. Don’t promise Sparkle-style sideload-and-update on iOS — it’s structurally impossible.

References