Code Signing, Notarization, and Gatekeeper on macOS
How macOS verifies that an application hasn't been tampered with and hasn't been flagged as malware, before it's ever allowed to launch.
Every time you open a downloaded application on macOS for the first time, three distinct, independent checks have already run before the app’s first line of code executes: is it signed, has it been notarized, and does Gatekeeper allow it to run given where it came from. Conflating these three is common, but they answer different questions and fail in different ways.
Code signing: has this binary been tampered with
Code signing attaches a cryptographic signature to an executable, tied to a Developer ID certificate issued by Apple, covering the binary and its bundled resources. The signature doesn’t say anything about whether the app is trustworthy in intent — only that its contents match exactly what the signer produced, unmodified since signing.
codesign --sign "Developer ID Application: Daniel Cosenza" MyApp.app
codesign --verify --verbose MyApp.app
codesign --display --verbose=4 MyApp.app
A signature also records an entitlements list (specific permissions the app is requesting, like camera access or a specific sandbox exception) and a designated requirement — a formal expression of what a valid signature for this app must look like, which is what lets the system detect a modified or resigned binary rather than just an unsigned one.
codesign -d --entitlements :- MyApp.app
Notarization: has Apple scanned this for known malware
Notarization is a separate, later step: after building and signing an app, a developer uploads it to Apple’s notary service, which scans it for known malware signatures and policy violations. If it passes, Apple returns a ticket that gets stapled to the app — an offline-verifiable proof that this exact binary was scanned and came back clean, checkable even without a network connection at launch time.
xcrun notarytool submit MyApp.zip --keychain-profile "AC_PASSWORD" --wait
xcrun stapler staple MyApp.app
Notarization is not a review of the app’s functionality or a guarantee of trustworthiness in any broad sense — it’s specifically a malware and policy scan, run automatically, distinct from the manual App Store review process that only applies to apps distributed through the App Store itself.
Gatekeeper: should this specific launch be allowed
Gatekeeper is the runtime policy engine that actually decides whether to let a given app launch, based on its signature, notarization status, and — critically — the quarantine attribute attached to files downloaded from the internet.
xattr -l ~/Downloads/MyApp.app
# com.apple.quarantine: 0181;...;Safari;...
When you double-click a freshly downloaded, quarantined app for the first time, Gatekeeper checks its signature and notarization ticket and shows the “are you sure you want to open this” dialog specifically because of that quarantine flag — not because the app is unsigned. Once you approve it, macOS records that decision so you’re not prompted again for the same binary.
spctl --assess --verbose MyApp.app
spctl --status
spctl --assess runs the exact same evaluation Gatekeeper performs at launch time, which makes it the right tool for diagnosing “why won’t this open” independent of actually double-clicking the app.
What removing the quarantine flag actually does (and its risk)
xattr -d com.apple.quarantine MyApp.app removes the flag that triggers Gatekeeper’s first-launch check entirely — a common workaround for developer-signed-but-not-notarized internal tools, but one that bypasses the safety check quarantine exists to trigger, and should only be used when you’re confident about the binary’s origin, since it disables exactly the mechanism designed to give you a second look before running something downloaded from the internet.
xattr -d com.apple.quarantine MyApp.app
The three checks working together
Put together, the full picture on first launch is: quarantine (was this downloaded from the internet, should Gatekeeper even bother checking) triggers Gatekeeper, which checks the code signature (is this binary intact and attributable to a specific Developer ID) and looks for a stapled notarization ticket (did Apple’s automated scan flag anything). Any one of these failing produces a different, specific error — an invalid signature says the binary was tampered with; a missing notarization ticket (for a Developer-ID-signed app distributed outside the App Store) triggers a stronger warning; a quarantine flag with no issues at all just triggers the routine “are you sure” prompt. Knowing which of the three actually failed is the difference between debugging a real problem and just disabling a safety mechanism that was working exactly as designed.