Skip to content

Ship a signed + notarized macOS release (no Gatekeeper warning on download) #1

Description

@yanyin1986

Goal: ship a downloadable macOS build that opens on a clean Mac without the "IPArchive is damaged and can't be opened. You should move it to the Trash." or "unidentified developer" dialogs.

That requires all three of: Developer ID signing → notarization → stapling. Missing any one of them brings the dialog back.

Prerequisite: an Apple Developer Program membership ($99/yr). A Developer ID certificate cannot be issued without it, and without that certificate notarization is impossible. Ad-hoc signing plus asking users to run xattr -dr com.apple.quarantine is exactly the experience this issue exists to avoid.

1. One-time setup

  • Join the Apple Developer Program
  • Create a Developer ID Application certificate (Xcode → Settings → Accounts). Note: not "Apple Distribution", which is for the Mac App Store.
  • Add the signing settings to project.yml:
    yaml DEVELOPMENT_TEAM: XXXXXXXXXX CODE_SIGN_IDENTITY: "Developer ID Application" ENABLE_HARDENED_RUNTIME: YES # already set
    ⚠️ Do not change PRODUCT_BUNDLE_IDENTIFIER — existing installs would lose their Keychain entry.
  • Add ExportOptions.plist with method = developer-id and the team ID
  • Store notarization credentials (requires an app-specific password from appleid.apple.com):
    bash xcrun notarytool store-credentials "IPArchive-NOTARY" \ --apple-id "<apple-id>" --team-id "XXXXXXXXXX" --password "xxxx-xxxx-xxxx-xxxx"

2. Per-release steps

  • Archive in Release configuration
    bash xcodegen generate xcodebuild -project IPAHelper.xcodeproj -scheme IPAHelper \ -configuration Release -archivePath build/IPArchive.xcarchive archive
  • Export with Developer ID
    bash xcodebuild -exportArchive -archivePath build/IPArchive.xcarchive \ -exportOptionsPlist ExportOptions.plist -exportPath build/export
  • Package for submission — must use ditto
    bash ditto -c -k --keepParent build/export/IPAHelper.app build/IPArchive.zip
  • Notarize
    bash xcrun notarytool submit build/IPArchive.zip --keychain-profile "IPArchive-NOTARY" --wait
  • Staple the ticket to the .app
    bash xcrun stapler staple build/export/IPAHelper.app
  • Re-package the stapled app — this is the artifact users download
    bash ditto -c -k --keepParent build/export/IPAHelper.app build/IPArchive-<version>.zip
  • Attach to a GitHub Release

3. Verification before publishing

  • xcrun stapler validate build/export/IPAHelper.appThe validate action worked!
  • spctl -a -vvv -t exec build/export/IPAHelper.appsource=Notarized Developer ID
  • codesign -dv --verbose=4 build/export/IPAHelper.appflags contains runtime
  • End-to-end: upload the zip, download it back through a browser (so it carries com.apple.quarantine), and confirm it opens on double-click with no dialog

4. Known pitfalls

Pitfall Consequence
Packaging with Finder "Compress" or zip -r Loses symlinks and extended attributes → "damaged, move to Trash" — the exact dialog this issue is about
Notarizing but not stapling Dialog reappears when the user is offline or Apple's service is slow
Building in Debug configuration Carries the get-task-allow entitlement → notarization is rejected
Shipping a DMG but only stapling the .app The DMG itself must also be signed, notarized and stapled
Missing --timestamp Notarization rejected (xcodebuild's export adds it automatically; manual codesign must pass it explicitly)

5. Follow-up: automate in CI

GitHub Actions provides free standard macOS runners for public repositories, so the whole flow above can run on tag push: build → notarize → create the GitHub Release → upload the artifact.

Secrets required: the Developer ID certificate as base64-encoded .p12 plus its password, DEVELOPMENT_TEAM, the Apple ID, and the app-specific password.

  • Decide: local release script (scripts/release.sh) vs. .github/workflows/release.yml

Activity

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions