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
2. Per-release steps
3. Verification before publishing
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.
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.
1. One-time setup
project.yml:yaml DEVELOPMENT_TEAM: XXXXXXXXXX CODE_SIGN_IDENTITY: "Developer ID Application" ENABLE_HARDENED_RUNTIME: YES # already setPRODUCT_BUNDLE_IDENTIFIER— existing installs would lose their Keychain entry.ExportOptions.plistwithmethod = developer-idand the team IDbash xcrun notarytool store-credentials "IPArchive-NOTARY" \ --apple-id "<apple-id>" --team-id "XXXXXXXXXX" --password "xxxx-xxxx-xxxx-xxxx"2. Per-release steps
bash xcodegen generate xcodebuild -project IPAHelper.xcodeproj -scheme IPAHelper \ -configuration Release -archivePath build/IPArchive.xcarchive archivebash xcodebuild -exportArchive -archivePath build/IPArchive.xcarchive \ -exportOptionsPlist ExportOptions.plist -exportPath build/exportdittobash ditto -c -k --keepParent build/export/IPAHelper.app build/IPArchive.zipbash xcrun notarytool submit build/IPArchive.zip --keychain-profile "IPArchive-NOTARY" --wait.appbash xcrun stapler staple build/export/IPAHelper.appbash ditto -c -k --keepParent build/export/IPAHelper.app build/IPArchive-<version>.zip3. Verification before publishing
xcrun stapler validate build/export/IPAHelper.app→ The validate action worked!spctl -a -vvv -t exec build/export/IPAHelper.app→source=Notarized Developer IDcodesign -dv --verbose=4 build/export/IPAHelper.app→flagscontainsruntimecom.apple.quarantine), and confirm it opens on double-click with no dialog4. Known pitfalls
zip -rget-task-allowentitlement → notarization is rejected.app--timestampcodesignmust 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
.p12plus its password,DEVELOPMENT_TEAM, the Apple ID, and the app-specific password.scripts/release.sh) vs..github/workflows/release.yml