“App Is Damaged and Can’t Be Opened”: The Fix for Mac Developers

A user downloads your Mac app, opens it, and macOS says:

“MyApp.app” is damaged and can’t be opened. You should move it to the Trash.

Many articles about this alert tell the user to run xattr -cr in Terminal. At best, that hides the problem on one Mac. If your users see this alert, your release is broken, and only you can fix it. This post shows how to reproduce the alert, the three causes on the developer side, and the fix for each. It covers one step of distributing a macOS app outside the App Store.

What the alert means

The download is usually not corrupt. Gatekeeper checks every app that arrives with the quarantine flag, and this alert means the code signature of the app is broken. Apple's support page says it appears when "macOS detects that software has been modified or damaged".

Two things follow from that:

  • The app can still work on your Mac. Your build never got the quarantine flag. In our tests, Gatekeeper did not block a broken app without the flag, unless the app was notarized. Apple notes that tools like curl and scp do not set the flag either.
  • It is not a notarization problem. A correctly signed app that is not notarized gets a different alert: “MyApp.app” Not Opened, with the text Apple could not verify “MyApp.app” is free of malware. An app with no signature at all gets that alert too.

Apple also says that the exact wording of Gatekeeper alerts changes between macOS releases. The alert texts in this post are from macOS 27.2 on Apple silicon. The checks below do not depend on the wording.

Reproduce it first

Test the file that your users get, the way they get it:

  1. Upload the release to its real download URL.
  2. Download it with Safari, so that it gets the quarantine flag. Check the flag with xattr -p com.apple.quarantine MyApp.dmg.
  3. Open it on a Mac that has never seen your app. Gatekeeper caches its results, so Apple recommends a fresh machine for each test. A virtual machine with a snapshot is the easy way.

Then run these commands on the downloaded copy, not on your build output:

codesign -v -vvv --strict --deep MyApp.app
syspolicy_check distribution MyApp.app

syspolicy_check needs macOS 14 or later. On older systems, use spctl -a -t exec -vvv MyApp.app. The codesign output tells you which of the three causes you have.

Cause 1: the bundle was never signed

codesign -dvv MyApp.app

If the output has flags=0x20002(adhoc,linker-signed) and Sealed Resources=none, this is your cause.

Apple silicon does not run native code that has no signature, so the linker gives every arm64 executable an ad-hoc signature. That signature covers the executable, not the bundle. It is enough to run the app on the Mac that built it. It is not enough for Gatekeeper: after a download, the app is "damaged". codesign -v says code has no resources but signature indicates they must be present. Projects that ship builds without a Developer ID signature report this alert, for example Ardour and IPTVnator.

An ad-hoc signature for the whole bundle is a different case. codesign -s - makes one, and Apple's engineers say that "Sign to Run Locally" is Xcode's name for it. In our test, that app got the “MyApp.app” Not Opened alert, not this one. The fix is the same.

Fix: sign with a Developer ID Application certificate, with the hardened runtime and a secure timestamp, then notarize. This needs a paid Apple Developer Program membership. See Code Signing and Notarization.

Cause 2: the bundle changed after you signed it

codesign reports:

MyApp.app: a sealed resource is missing or invalid
file added: MyApp.app/Contents/Resources/config.json

The signature seals the files in the bundle. A file added, file modified or file missing line names a file that changed after signing. A changed Info.plist gives invalid Info.plist (plist or signature have been modified). For a notarized app, syspolicy_check names the file too: File added after outer app bundle was codesigned. For an app that is not notarized, it only reports the missing ticket. Typical sources are a build script that runs after the signing step, or a release script that edits Info.plist or copies a file into the exported app.

Fix: make signing the last step that touches the bundle. If you must change the app after export, sign it again from the inside out, and notarize the new build.

Cause 3: non-ASCII file names in the bundle

codesign again reports a sealed resource is missing or invalid, with the same name in two lines:

MyApp.app: a sealed resource is missing or invalid
file added: MyApp.app/Contents/Resources/café.png
file missing: MyApp.app/Contents/Resources/café.png

A file name like café.png has two valid Unicode forms, precomposed and decomposed. If the name changes from one form to the other on the way to the user, the file is the same, but its name no longer matches the signature. In our tests, an HFS+ disk image (hdiutil create -fs HFS+) and tar changed a precomposed name to the decomposed form. ditto, cp -R, and zip -ry with unzip kept the name. Apple's engineers report the same break for a copy in Finder and for the Compress command in Finder. They call this Apple's bug, and they also say: "the only good solution is to stick to ASCII file names within your bundle."

Find the files:

find MyApp.app | LC_ALL=C grep '[^ -~]'

Fix: rename them to ASCII, including localized resources and files inside embedded frameworks, then sign and notarize again.

Not this alert, but close: zip -r breaks the bundle

A ZIP file made with plain zip -r also breaks the signature. codesign reports:

MyApp.app: bundle format is ambiguous (could be app or framework)
In subcomponent: MyApp.app/Contents/Frameworks/Sparkle.framework

Frameworks contain symbolic links such as Versions/Current. Plain zip -r does not store symbolic links: it stores a copy of the target in their place. The unzipped framework is no longer a valid bundle, and the signature fails. We tested this with our own notarized app that embeds one framework, and with a notarized app that embeds Sparkle. We unpacked each archive with ditto -x -k. The zip -r archives failed codesign verification. The zip -ry and ditto -c -k --keepParent archives passed. After zip -r, our notarized app got the “MyApp.app” Not Opened alert, not the "damaged" alert, with and without a stapled ticket.

Fix: create the archive with the command from Apple's packaging guide:

ditto -c -k --keepParent MyApp.app MyApp.zip

A signed disk image is the better container. You can sign a DMG and staple the notarization ticket to it. You cannot do either with a ZIP file.

Do not tell users to run xattr

xattr -cr MyApp.app removes the quarantine flag. In our tests, that opened a broken app only when the app was not notarized. A notarized app with a broken seal got the same alert again, without the flag. Even when the app opens, the release is still broken. Every new user hits the same alert, most of them will not open Terminal, and those who do learn to switch off a security check for your app. Ship a fixed build.

Check every release

Before you announce a release, download it with Safari on a clean Mac or VM and open it. For a release script, add codesign -v --strict --deep MyApp.app. It exits with a non-zero status for each of the three causes. syspolicy_check distribution MyApp.app also checks notarization, but it fails for an app from a DMG when the ticket is stapled only to the DMG.

Skip the manual steps

Amore signs the app with your Developer ID, notarizes it, and packs it into a DMG as part of one command. We tested the DMG step with amore create-dmg: the DMG came out signed, notarized and stapled, and an app with cause 1 stopped the command with a notarization error. For cause 3, keep the file names in your bundle ASCII. Set it up in Code Signing and Notarization, then release with the CLI or GitHub Actions. For other signing and notary failures, see Notarization Errors.

Sources