中文 English

Your Mac App Is Not Broken: Gatekeeper May Just Distrust an Unsigned Tool

Published: 2026-06-01
macOS Gatekeeper codesign xattr Code Signing Troubleshooting Developer Tools Security

The short version

When a macOS utility refuses to launch after being downloaded, the first conclusion should not be “the app is broken.” A very common cause is a combination of two facts: the app still has the com.apple.quarantine extended attribute that marks it as downloaded from the internet, and the app bundle does not have a usable signature that Gatekeeper can trust. For first launch, “downloaded from the web + no usable signature” is enough for macOS to block it.

For a tool whose source you understand and trust, the smallest local repair is straightforward: inspect the quarantine attribute, verify the signing state, apply a local ad-hoc signature to that one app bundle, remove quarantine from that one app, and then verify the result. The point is not to turn off macOS security globally. The point is to fix one known local tool while keeping the safety boundary clear.

This post is based on a real local troubleshooting session, but all private details have been removed. There are no real internal addresses, usernames, tokens, private download locations, hostnames, or machine-specific paths. The examples use placeholders such as /Applications/<App>.app and <App>. The goal is to share a repeatable diagnostic method, not to expose an environment.

Abstract hero image for macOS Gatekeeper blocking an unsigned tool

Figure 1: A custom hero image for this article. The visible symptom is “the app will not open,” but the useful evidence usually lives in quarantine metadata, code signing, and Gatekeeper assessment.

1. Background: why downloaded macOS apps are treated carefully

Power users often install tools that do not come from the Mac App Store. Sometimes the tool is a .dmg, sometimes it is a .zip, and sometimes it is just a prebuilt .app copied into Applications. When the app is opened for the first time, macOS may show a warning that the developer cannot be verified, that the app is damaged, or that the app cannot be opened.

This behavior is not random. Gatekeeper exists to evaluate software that arrives from outside trusted installation channels. On modern macOS, the best path for externally distributed software is Developer ID signing plus notarization. macOS also tracks how a file arrived on the machine through quarantine metadata. A browser, mail client, chat client, or download manager can attach that metadata to downloaded files.

In other words, macOS does not only ask whether a file is executable. It asks several more specific questions:

  1. Did this app come from the internet or another external source?
  2. Does it have a verifiable code signature?
  3. Is the signature tied to a trusted developer identity?
  4. Has it been notarized by Apple?
  5. Has the user explicitly allowed this app to run?

If the answers are weak, especially when the app is both quarantined and unsigned, Gatekeeper is expected to block first launch. That does not automatically prove the app is malicious. It also does not always mean the binary is corrupt. It means macOS does not have enough trust evidence.

2. Symptom: the app exists, but macOS refuses to run it

There are several common symptoms.

The first is the familiar dialog saying macOS cannot verify the developer. That is the most direct Gatekeeper clue. Many users see it and assume the only choices are deleting the app or disabling security.

The second is the more confusing “the app is damaged and can’t be opened” message. It sounds like the archive was corrupted, but in some cases it is still a Gatekeeper or quarantine problem rather than a broken binary.

The third is a quiet failure: the Dock icon appears briefly, Finder gives no useful feedback, and the process does not stay running. At that point, repeated double-clicking is not a diagnostic method. The command line gives clearer evidence.

The first useful commands are:

xattr -lr "/Applications/<App>.app"
codesign --verify --deep --strict --verbose=4 "/Applications/<App>.app"
spctl --assess --type execute --verbose=4 "/Applications/<App>.app"

They answer three different questions:

  1. xattr: does the app bundle or any file inside it still carry com.apple.quarantine?
  2. codesign: does the bundle have a self-consistent code signature?
  3. spctl: would the system policy accept it as executable software?

If the output contains lines like these, the direction is clear:

com.apple.quarantine: ...
code object is not signed at all
rejected
source=no usable signature

That is not a mystery. The app is quarantined, it does not have a usable signature, and Gatekeeper rejects it.

Diagnosis flow from symptom to root cause

Figure 2: Check quarantine first, then code signing, then system policy. The vague symptom becomes a concrete root cause.

3. Three concepts that should not be mixed together

The fastest way to make this problem messy is to use “Gatekeeper,” “signature,” and “quarantine” as if they were the same thing. They are related, but they describe different layers.

3.1 Quarantine: where the file came from

com.apple.quarantine is an extended attribute. It tells macOS that a file came from an external source and should be treated carefully on first launch. Browsers, mail clients, chat clients, and download tools can attach it.

Inspect it with:

xattr -lr "/Applications/<App>.app"

If the output includes com.apple.quarantine, the app is on the first-run security path. That attribute is not a bug. It is a safety signal.

3.2 Code signing: whether the code object is signed and consistent

codesign verifies the signing state of a code object. An app bundle may have a Developer ID signature, an ad-hoc signature, a broken signature, or no signature at all.

Check it with:

codesign --verify --deep --strict --verbose=4 "/Applications/<App>.app"

If the output says code object is not signed at all, the app is unsigned. This is different from “the signature is expired” or “the signature is not trusted.” Different signing errors have different fixes.

3.3 Gatekeeper and spctl: whether system policy accepts the app

spctl asks the system policy layer whether the app should be accepted:

spctl --assess --type execute --verbose=4 "/Applications/<App>.app"

If it returns rejected or no usable signature, Gatekeeper policy does not accept this app as trusted external software. That does not necessarily mean the binary cannot run in every possible context. It means the policy layer is rejecting it.

4. Root cause: quarantine plus no usable signature

The root cause can be stated precisely:

This was a downloaded .app bundle with quarantine metadata and no usable code signature, so Gatekeeper blocked first launch.

That is more accurate than saying “the app was unsigned.” An unsigned app without quarantine may behave differently. A quarantined app with a valid Developer ID signature and notarization may pass. The failure comes from the combination.

This combination is common:

  1. A developer wraps a command-line binary in a simple .app bundle.
  2. The release is not signed with a Developer ID certificate.
  3. A user downloads it through a browser, so macOS adds quarantine metadata.
  4. On first launch, Gatekeeper cannot find a trusted signature.
  5. macOS blocks the app, and the user experiences it as a launch failure.

This is why reinstalling the app, moving it to another folder, changing execute bits, or disabling global security can be the wrong response. Those actions may change the symptom, but they do not explain the cause.

5. The local fix: repair one app, not the whole system

The following workflow is for software whose origin you already trust. Do not copy these commands for unknown software. If you do not know where the app came from, stop and verify the source first.

5.1 Set the target path

APP="/Applications/<App>.app"

Confirm that it exists:

test -d "$APP" && echo "app exists"

5.2 Inspect quarantine metadata

xattr -lr "$APP"

If com.apple.quarantine appears, the app is still marked as downloaded software.

5.3 Inspect signing state

codesign --verify --deep --strict --verbose=4 "$APP"

If the bundle is unsigned, you may see code object is not signed at all.

5.4 Apply a local ad-hoc signature

codesign --force --deep --sign - "$APP"

The --sign - form means ad-hoc signing. It does not require an Apple developer certificate. It does not create a Developer ID signature. It simply gives this local code object a signature structure that codesign can verify.

5.5 Remove quarantine from this app bundle

xattr -dr com.apple.quarantine "$APP"

This removes the quarantine attribute from this app bundle only. It does not disable Gatekeeper globally and does not affect other apps.

5.6 Verify and launch

codesign --verify --deep --strict --verbose=4 "$APP"
xattr -lr "$APP" | grep quarantine || echo "no quarantine"
open "$APP"

If codesign reports that the app is valid on disk and satisfies its designated requirement, and quarantine no longer appears, the app should be able to launch locally.

Command checklist for local repair

Figure 3: The order matters. Gather evidence, sign locally, remove quarantine, then verify.

6. Why spctl may still reject the app

One confusing detail is that spctl --assess may still say rejected after ad-hoc signing.

That is expected in many cases. An ad-hoc signature is not a Developer ID signature. It does not prove the app came from an Apple-recognized developer, and it does not prove the app has been notarized.

The distinction matters:

  1. codesign --sign - creates a local ad-hoc signature.
  2. xattr -dr com.apple.quarantine removes the first-run download quarantine marker for this app.
  3. Developer ID signing plus notarization is the correct path for public distribution.

The local repair is not trying to make the app equivalent to notarized software. It is making a known local tool runnable on your machine after you have made a trust decision.

Boundary between local repair and official distribution signing

Figure 4: Ad-hoc signing is useful for local use. It should not be presented as a public distribution trust model.

7. When not to use this method

This workflow is not a universal bypass. Avoid it when:

  1. You do not know the software source.
  2. The publisher, checksum, or release channel cannot be verified.
  3. The app asks for an administrator password, installs privileged helpers, adds login items, or touches sensitive directories.
  4. The app will handle private keys, tokens, browser data, wallets, or production configuration.
  5. You plan to redistribute the repaired app to other users.

If you publish macOS software, the correct solution is not to tell users to remove quarantine. Use Developer ID signing, notarization, checksums, release notes, and a trustworthy download channel. Apple’s documentation is clear that externally distributed macOS software should follow the formal signing and notarization path.

8. Q&A

Q1: Can I just click “Open Anyway” in System Settings?

Sometimes, yes. That is reasonable for a one-off app whose source you understand. The command-line workflow is more explicit because it shows what macOS is objecting to and what you changed.

Q2: Does chmod +x fix this?

Only if the real problem is missing execute permission on the internal binary. If the failure is caused by quarantine or signing policy, chmod +x does not address the root cause.

Q3: Is sudo spctl --master-disable simpler?

It is broader, not better. Disabling Gatekeeper globally affects more than one app. For a single trusted local tool, a targeted repair is usually the better engineering choice.

Q4: Is ad-hoc signing safe?

Ad-hoc signing is not a safety certification. It only gives the local code object a signature structure. The actual trust decision still depends on the software source, integrity, behavior, and permissions.

Q5: Will I need to repeat this after updating the app?

Possibly. A new download or replacement bundle may have new quarantine metadata and a different signing state. Re-run the diagnostic commands instead of blindly reusing the fix.

Q6: Why avoid real app names, internal paths, and private addresses in a public post?

Troubleshooting methods are useful without exposing operational details. Real paths, internal addresses, account names, private app names, and download sources can become attack clues. Placeholders are enough.

9. Summary

When a downloaded macOS app will not open, the app is not always broken. The common chain is: quarantine marks it as downloaded software, codesign cannot find a usable signature, and Gatekeeper rejects first launch.

The practical troubleshooting order is:

  1. Use xattr to inspect quarantine.
  2. Use codesign to inspect signing state.
  3. Use spctl to inspect system policy assessment.
  4. If the source is trusted, ad-hoc sign only this app.
  5. Remove quarantine only from this app.
  6. Verify and launch.

This is not about bypassing security blindly. It is about understanding which layer is blocking the app and making the smallest local change that matches the evidence. For local trusted tools, that is often enough. For software distribution, Developer ID signing and notarization remain the right answer.

References