Notarized, Approved, and Dead on Double-Click
A Pier 2.0.0 postmortem: one restricted entitlement, added to remove a keychain prompt, sailed through notarization — and got the app SIGKILLed on every user's Mac. Rolled back in 2.0.1 the same night.
Pier 2.0.0 had a production incident on launch night: download, install, double-click — “Pier can’t be opened.” Not a crash. The system killed it before launch. The 2.0.1 fix shipped within the hour. This is the full chain: how a change meant to improve the experience walked through notarization and blew up on users’ desktops.
Prologue: that keychain authorization prompt
Pier stores its license state in the keychain (harder to tamper with than UserDefaults, survives delete-and-reinstall). During the 1.3.x promo we got reports of fresh installs greeting users with a macOS authorization sheet: “Pier wants to use confidential information stored in your keychain.”
For a menu-bar tool you downloaded thirty seconds ago, that prompt is poison: no trust established, and the first thing you do is ask for “confidential information.” The stopgap was to skip the keychain entirely during the promo (the free flag short-circuited all reads and writes). But 2.0 needed real trial/activation state — the keychain was unavoidable, so it had to be fixed properly.
The proper fix, and how it exploded
The sanctioned way to kill that sheet on macOS is switching to the iOS-style data protection keychain: add a keychain-access-groups entitlement (the access group is Team ID + bundle id) and SecItemAdd stops triggering authorization prompts. Verified on the dev machine; packaged, signed, notarized, shipped.
The catch: keychain-access-groups is a restricted entitlement. Restricted entitlements require an embedded provisioning profile proving “Apple allows this signing identity to use this capability.” App Store builds carry a profile by nature; a Developer ID dmg does not. The build was signed with the entitlement but without an embedded.provisionprofile, so AMFI (Apple Mobile File Integrity) failed the load-time check and delivered a SIGKILL — the process never reached main.
The most damning link in the chain: notarization waves this straight through. Notarization checks for malware and signature integrity; it does not validate that your entitlements match your distribution method. A build guaranteed to die on every user’s machine completed the entire release pipeline carrying Apple’s notarization ticket.
Why the dev machine never caught it
Nothing on the development path exercises this check: swift build runs the binary without AMFI’s entitlement validation, and a debug build’s signing context differs from a Developer ID distribution package. The only action that would have exposed the bug: mount the notarized dmg, drag to Applications, and double-click like a user. That step wasn’t on the release checklist.
Fix and follow-up
Three things happened that night:
- Rollback: drop the entitlement, return to the standard login keychain, ship 2.0.1. The authorization sheet gets solved another way (a fresh install writing its own items doesn’t prompt; the real-world impact was smaller than feared);
- A tombstone: a comment now sits at the top of
Keychain.swiftexplaining exactly why not to switch to the data protection keychain, and what the correct path is (create a Developer ID provisioning profile in the Apple Developer portal and embed it at packaging time) — so a future me doesn’t helpfully reintroduce the bug; - A process change: the release skill gained a “launch verification” step. The one-line version: notarized ≠ launches. The notarized artifact must walk the full user path — mount, install, double-click.
Takeaways
- Notarization is a security review, not an availability review. It guarantees there’s no malware in the package; it does not guarantee your app survives its first exec on a user’s machine;
- A restricted entitlement is a capability with an eligibility check. Before adding one, answer: does my distribution method (App Store / Developer ID / unsigned) qualify, and is the credential (profile) actually embedded?
- The last item on every release checklist is “install it once.” All the green lights from building, signing, and notarizing combined do not substitute for one double-click in a clean environment;
- Write the lesson next to the code. Incident memory fades and nobody rereads
git log, but the next person tempted to “optimize” the keychain will openKeychain.swift— where the comment is waiting.
2.0.0 lived in production for less than an hour. If you downloaded it: just download again — and sorry about that.
Comments