Quick answer: The download is not damaged. macOS Gatekeeper says this about any app that arrived through a browser and is unsigned, ad-hoc signed, or signed in a way that no longer validates, with no notarization ticket to fall back on. Players can get past it with xattr -cr /path/to/Game.app in Terminal. You make it go away for good by signing the build with a Developer ID certificate, notarizing it with notarytool and stapling the ticket before you zip it.

This is the single most common “won’t open on Mac” report an indie game gets, and it almost always comes from itch.io, a Discord download or a press build rather than Steam. The player double-clicks the game, gets told it is damaged and should go in the Trash, re-downloads it twice, then leaves a comment or a refund. Here is what the dialog actually means, the one line you can give players today, and how to sign and notarize a Godot, Unity, Unreal or GameMaker build so nobody sees it again.

The exact error

The player sees a system dialog, not anything from your game:

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

If the game shipped as a disk image the last sentence reads “You should eject the disk image” instead. Either way the only buttons are Move to Trash and Cancel. There is no Open Anyway, and unlike the gentler “cannot be opened because the developer cannot be verified” dialog, right-clicking the app and choosing Open does not help. The wording is what sends players off to re-download: the file is fine, Gatekeeper simply refused to run it.

What causes it

Two things have to be true at once. The app must carry the com.apple.quarantine extended attribute, which every browser attaches to a download and which Archive Utility copies onto everything it unpacks from a quarantined zip. And Gatekeeper must be unable to find a signature it trusts on it. That second part happens for one of these reasons:

  1. The build is unsigned or ad-hoc signed. This is the default output of a Godot export without a certificate configured, of a Unity build straight out of the editor, and of most CI pipelines that never ran codesign with a real identity. On current macOS versions an ad-hoc signature plus quarantine produces the “damaged” dialog rather than the “unidentified developer” one.
  2. The app is signed with a Developer ID but was never notarized. This usually gives “cannot be verified”, but if the signature was made without the hardened runtime or has since been invalidated you can land on “damaged” instead.
  3. The signature no longer validates. Something changed inside the bundle after signing: a patched binary, a config file edited by a launcher, resources that were stripped when the app was zipped on Windows or Linux, or a _CodeSignature folder that did not survive a copy. Gatekeeper treats a signed-then-modified app as tampered.
  4. The certificate was revoked or has expired and the build was not stapled. Rare, but it hits every player at once.

The reason this shows up on itch.io, Discord and press keys but almost never on Steam is quarantine, not signing. The Steam client and the itch.io desktop app write game files to disk themselves, so nothing gets the attribute and Gatekeeper never looks. A zip fetched from a store page in Safari or Chrome is quarantined, and so is every file inside it.

How to tell which case you are in

Download your own build the way a player would, through a browser, unzip it and run these in Terminal:

xattr -p com.apple.quarantine MyGame.app
codesign --verify --deep --strict --verbose=2 MyGame.app
spctl -a -vv -t exec MyGame.app

The first line prints a value if the quarantine attribute is present, which it will be. The second tells you whether the signature validates; “code object is not signed at all” or “invalid signature” points at cause 1 or 3. The third is Gatekeeper’s own verdict. rejected (no usable signature) means unsigned or ad-hoc, source=Unnotarized Developer ID means you signed but skipped notarization, and accepted source=Notarized Developer ID is what you want to see.

Fix 1: the workaround to give players now

Put this in your store page, pinned Discord message and support replies. It removes the quarantine attribute, after which Gatekeeper no longer inspects the app:

xattr -cr /Applications/MyGame.app

Tell players to open Terminal (Spotlight, type “Terminal”), paste the command with the real path to the app, and press Return. Dragging the app onto the Terminal window after typing xattr -cr fills in the path for them. -c clears extended attributes and -r applies it to everything inside the bundle, which matters because the executable and the resources inside are quarantined individually.

Be honest with yourself about how many players will do this. Opening Terminal is a hard stop for a lot of people, and the dialog has already told them the file is broken. Treat the workaround as triage and the next section as the fix.

Fix 2: sign and notarize the build

You need an Apple Developer Program membership (99 US dollars a year), a Developer ID Application certificate installed in your keychain from the developer portal, and a Mac with Xcode or the Command Line Tools. The whole pipeline is four commands.

1. Sign with the hardened runtime. Save the entitlements your engine needs as MyGame.entitlements (see the engine notes below), then:

codesign --deep --force --options runtime --timestamp \
  --entitlements MyGame.entitlements \
  --sign "Developer ID Application: Your Studio (TEAMID)" MyGame.app

--options runtime enables the hardened runtime, which notarization requires; --timestamp adds the secure timestamp that keeps the signature valid after the certificate expires. Apple prefers nested code to be signed individually from the inside out, but --deep handles the typical game bundle fine. If notarization later complains about a specific dylib or helper, sign that file on its own first and then the app.

2. Store your notarization credentials once. Generate an app-specific password at appleid.apple.com, then:

xcrun notarytool store-credentials "notary" \
  --apple-id you@studio.com --team-id TEAMID --password app-specific-password

3. Submit and wait. Zip the app for upload and let notarytool block until Apple answers, which is usually a few minutes:

ditto -c -k --keepParent MyGame.app MyGame-notarize.zip
xcrun notarytool submit MyGame-notarize.zip --keychain-profile "notary" --wait

If the status is Invalid, run xcrun notarytool log <submission-id> --keychain-profile "notary". The log names the exact file and the rule it broke; nine times out of ten it is a binary that was not signed with the hardened runtime or a missing entitlement.

4. Staple the ticket to the app. This is the step people skip. Without it Gatekeeper has to reach Apple’s servers to check the ticket, and a player who is offline, behind a strict firewall or on a flaky connection gets the error anyway:

xcrun stapler staple MyGame.app
xcrun stapler validate MyGame.app

Staple the .app itself (or a .dmg), never the upload zip. Then build the zip you actually distribute from the stapled app, as described in Fix 3.

Engine specifics: Godot, Unity, Unreal, GameMaker

Godot 4. The macOS export preset has Codesign and Notarization sections. Leaving Codesign on the built-in ad-hoc option is exactly what produces this dialog. Switch it to Xcode codesign on a Mac, or to rcodesign on Windows and Linux, and fill in your Developer ID identity, team ID and API key or Apple ID credentials. Godot can then sign, notarize and staple as part of the export, and its default entitlements (allow JIT, allow unsigned executable memory, allow dyld environment variables, disable library validation) already match what the engine needs. If you add GDExtensions, confirm each native library is signed too.

Unity. A standalone macOS build is unsigned. Sign it with the command above and an entitlements file that disables library validation and allows the executable memory that the scripting backend relies on; Unity’s own macOS distribution page lists the exact keys for your version and backend, and Mono builds need more of them than IL2CPP builds. Building an Xcode project instead of an app lets you sign and notarize from Xcode’s Organizer, which some teams find easier to keep in CI.

Unreal Engine. Package for Mac as usual, then sign the resulting .app with the command above. Sign the whole bundle after any post-package step that touches files inside it (config edits, pak patches, launcher injection), because every one of those invalidates an earlier signature. Then notarize and staple.

GameMaker. The macOS export signs with the team identifier set in the macOS Game Options when you build on, or through, a Mac. Verify the output with spctl as in the diagnosis step; if it comes back as unnotarized, run the notarize and staple steps on the exported app before you zip it.

Fix 3: package it without breaking the signature

A correctly signed app can still arrive “damaged” if the zip strips what the signature covers. Zip tools on Windows and many Linux defaults drop symlinks, extended attributes and the executable bit, which either breaks the signature or produces the different “The application can’t be opened” error. On a Mac, build the distribution zip with:

ditto -c -k --sequesterRsrc --keepParent MyGame.app MyGame-macOS.zip

Finder’s Compress produces an equivalent archive. Godot’s exporter writes a correct zip from any host OS. If you must zip on Windows or Linux, use zip -ry so symlinks are stored as symlinks, and check the executable bit on Contents/MacOS/MyGame after a round trip. A .dmg built with hdiutil sidesteps all of this and can itself be notarized and stapled.

Do not touch the app after stapling. Renaming the bundle is fine; editing anything inside it is not.

How to confirm it works

Testing on your own Mac proves nothing, because the files there were never quarantined. Upload the final zip, download it through a browser, unzip it and run the three diagnosis commands. spctl should say accepted with source=Notarized Developer ID, and double-clicking should open the game with at most the standard “downloaded from the internet” prompt. Repeat the check offline to confirm the ticket is stapled rather than fetched. Keep this round trip in your release checklist; it takes five minutes and catches the broken-zip case every time.

Hearing about the Mac players you lose

Gatekeeper refuses the app before a single line of your code runs, so no in-game crash reporter will ever see this one. What you can control is how fast you find out. Bugnet’s public bug tracker gives Mac players a place to see that “damaged and can’t be opened” is a known issue with a posted workaround, which turns a refund into a Terminal command. And once players are past Gatekeeper, the Godot, Unity and Unreal SDKs file the Mac-only crashes that follow, with the macOS version, chip and recent log lines attached, so you see whether your Apple silicon build is actually healthy instead of guessing from reviews.

Frequently asked questions

Why does macOS say my game is damaged when the download is fine?

The file is not damaged. Gatekeeper shows that message when an app downloaded from the internet is unsigned, ad-hoc signed or has a signature that no longer validates, and it has no notarization ticket. Re-downloading changes nothing; signing and notarizing the build does.

How can a player open a game that macOS says is damaged?

Open Terminal and run xattr -cr followed by the path to the .app, for example xattr -cr /Applications/MyGame.app. That removes the quarantine attribute the browser attached to the download. Right-click, Open and the Open Anyway button do not work for this particular dialog.

Why do Steam players not see the damaged dialog but itch.io players do?

Gatekeeper only checks files that carry the com.apple.quarantine attribute, which browsers add to downloads. The Steam client and the itch.io desktop app write game files directly, so nothing is quarantined. A zip downloaded from a store page in a browser is quarantined, and so is everything unpacked from it.

Do I need a paid Apple Developer account to fix this permanently?

Yes. Notarization requires a Developer ID Application certificate, which comes with the Apple Developer Program at 99 US dollars a year. Without it the best you can do is ad-hoc sign the build and publish the xattr workaround for players.

Download your own Mac build through a browser before every release. Your Mac never quarantines the files you built on it, so it is the one machine that cannot show you this bug.