Gatekeeper runs this check automatically the first time you open a downloaded app, and most of the time you never see the details — a dialog appears, you click through it, done. This lab is about seeing what Gatekeeper saw: which developer signed the app, whether that signature is intact, whether Apple’s notary service checked it, and what it is allowed to do once it runs. Every command here ships with Xcode’s command-line tools (xcode-select --install if you do not have them yet), and every one of them is read-only — you are inspecting, not changing, the app.
The signature itself: codesign -dvvv
codesign is Apple’s tool for creating and inspecting code signatures. The -d flag displays information about the signed code at a path, and per its man page, "Increasing levels of verbosity produce more output" — so -dvvv (display, three levels of verbose) gets you the full picture in one command:
codesign -dvvv /Applications/Example.app
# example output, trimmed to the fields that matter
Executable=/Applications/Example.app/Contents/MacOS/Example
Identifier=com.example.app
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=... flags=0x10000(runtime) hashes=...
Signature size=4741
Authority=Developer ID Application: Example Software LLC (ABCDE12345)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Team Identifier=ABCDE12345
Runtime Version=14.0.0Four fields to read every time. Authority is the certificate chain: a normal, external app should end in Apple Root CA by way of Developer ID Certification Authority, with the top line naming the developer. Team Identifier is Apple’s ten-character ID for the developer account — the value to compare across apps you believe come from the same company, since it does not change between their releases. flags=0x10000(runtime) means the hardened runtime is enabled, a set of additional restrictions (like resisting code injection into the process) that Apple requires for notarization. And the absence of any Authority line at all — just Signature=adhoc — means the app is unsigned or self-signed with no chain to Apple whatsoever.
Does the signature still match the files: --verify --deep --strict
A signature is a promise about a specific set of bytes at signing time. --verify checks whether that promise still holds — per the man page, it confirms "that the code at those path(s) is signed, that the signature is valid, and that all sealed components are unaltered." Two extra flags matter for an app bundle, which is a directory full of nested resources, frameworks and helper executables, not a single file:
codesign --verify --deep --strict --verbose=2 /Applications/Example.app
/Applications/Example.app: valid on disk
/Applications/Example.app: satisfies its Designated Requirement--deep matters because, per the man page, verification of nested content is by default "limited to a shallow investigation that may not detect changes to the nested code" — deep mode recursively verifies every embedded framework and helper tool, not just the outer bundle. --strict adds extra checks Apple considers important enough to not be on by default, including that any symlink inside the bundle "point[s] to sealed files inside its bundle," rejecting one that points outside the app or to something unsealed — a known trick for smuggling an unsigned payload into an otherwise legitimately signed bundle. If either check fails, you will see code failed to satisfy specified code requirement or a note naming exactly which nested item does not match what was originally sealed — read that line, it names the file.
Reading the entitlements
Entitlements are the specific permissions an app’s signature grants it — camera access, the ability to reach the network unsandboxed, disabling library validation, and so on. codesign -d --entitlements - extracts them; per the man page, "Embedded entitlement data will be extracted likewise and written to" the given path, and - means standard output:
codesign -d --entitlements - /Applications/Example.app
# example output, trimmed
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>Most entitlements are unremarkable and match what the app obviously needs — a video-calling app requesting camera access is not a finding. The one worth pausing on is disable-library-validation: it means the app will load code from outside its own signed bundle, which is a normal need for some plugin-based software and a wider door than most apps require. It is a detail to note, not an automatic red flag, and it is exactly the kind of detail you cannot see without asking.
Did Apple’s notary service actually check it: spctl and stapler
A valid signature only proves an app has not been altered since a developer signed it — it says nothing about whether Apple has looked at it. That is what notarization adds. Apple’s own documentation describes the automated notary service as scanning "your software for malicious components, checks for code-signing issues, and returns the results to you quickly." When it passes, "the notary service generates a ticket for you to staple to your software; the notary service also publishes that ticket online where Gatekeeper can find it."
spctl --assess is the practical way to ask Gatekeeper’s own policy engine for its verdict rather than inferring it yourself. Per its man page, --assess "perform[s] an assessment on the files given," and -v / --verbose, repeated for more detail, is described simply as requesting "more verbose output":
spctl --assess -vv /Applications/Example.app
/Applications/Example.app: accepted
source=Notarized Developer IDsource=Notarized Developer ID is the result you want to see — it means Gatekeeper found a valid Developer ID signature and a notarization ticket, whether that ticket is stapled to the app or was found online. A result of source=Unnotarized Developer ID means the app is signed but Apple has not (or not yet) notarized it, and a flat rejected means Gatekeeper would block it from launching in its default configuration.
To check the staple specifically, xcrun stapler validate looks for the ticket physically attached to the app rather than asking Gatekeeper to look it up online — useful for confirming an app will still assess correctly with no internet connection at all:
xcrun stapler validate /Applications/Example.app
Processing: /Applications/Example.app
The validate action worked!The quarantine flag: where the first-run check comes from
Apple’s Platform Security guide explains that "Gatekeeper also tracks the provenance of files written by downloaded software" and "requests user approval before opening downloaded software for the first time." The mechanism behind that is an extended attribute, com.apple.quarantine, that Safari, Mail and other apps attach to anything they save from the network. xattr -l lists it, and per the man page, this option "causes both the attribute names and corresponding values to be displayed":
xattr -l ~/Downloads/Example.dmg
com.apple.quarantine: 0081;65e1a2b3;Safari;No output at all means the file carries no quarantine flag — either it was created locally, arrived by a path that does not set the attribute (some archive tools and package managers skip it), or the flag was manually removed with xattr -d com.apple.quarantine. That last case is worth knowing about for a different reason: it is a documented way people bypass Gatekeeper’s first-run check entirely, on files they trust or think they trust, and it is worth being deliberate about rather than pasting from an unfamiliar set of terminal instructions.
A worked comparison: two apps claiming the same developer
A concrete way to use all of this together: say you have two copies of an app that both claim to be from the same company, one from the developer’s own site and one from a link someone sent you. Run codesign -dvvv on both and compare the Team Identifier line — it is a ten-character string tied to a specific Apple Developer account, and unlike a display name or a bundle identifier, it is not something a second party can casually reproduce without access to that account’s signing certificate. If the two copies show different Team Identifiers, you are not looking at two builds of the same app; you are looking at two different signers, one of whom is not who the download claimed. Follow that with spctl --assess -vv on the suspicious copy — a mismatched or absent notarization source on the copy that claims to be identical to a notarized original is the confirmation, not just a clue.
Reading the whole picture, and the actual red flags
- No
Authoritychain at all, or a chain that does not end in Apple Root CA — the app has no accountable developer identity behind it. codesign --verifyfails, especially with a message naming a specific nested file — something inside the bundle changed after it was signed.spctl --assessreturnsrejected, or the Team Identifier incodesign -dvvvdoes not match the developer you expect for that product.- A quarantine flag that was clearly stripped on a file you did not download yourself, or that arrived through an unusual channel (a script, an email attachment renamed to look like something else).
- Entitlements that are wide open — full disk access, disabled library validation, unrestricted network access — on an app whose stated purpose does not obviously need them.
None of these checks look at what the app actually does once it is running and connected to the network — that is a different kind of question, answered by watching its traffic rather than its signature. A clean signature and a valid notarization ticket are a real, meaningful floor: they say Apple has seen this exact set of bytes and found no code-signing issues at the time of the check. They are the beginning of trusting an app, not the end of it.
How FireAI and HisnLabs fit in
A clean signature and a stapled ticket say an app hasn’t been tampered with since Apple checked it — they say nothing about what it connects to afterward, which is the question FireAI’s per-app rules and on-device review are built to keep answering, signature by signature, connection by connection.
FireAI is HisnLabs’ own product: an on-device AI firewall for Mac. It shows every connection your apps make, in plain language, and lets you decide what leaves your Mac — its AI runs locally, so your traffic is never sent to us or anyone else. HisnLabs’ security research team is the group that keeps that decision-making accurate: cataloguing which domains are ordinary telemetry versus a real product, tracking the country and network behind a connection, and training the on-device model (its Autopilot feature) on real traffic patterns, all without any of it leaving your Mac.
You can read the technical decisions behind it, or try FireAI for 17 days, at FireAI, by HisnLabs.
