Apple Gatekeeper
Introduction
This is the first spin-off article from a previous post, Apple Defences. We talk about one of the cornerstones of Apple defence: Gatekeeper.
What is Gatekeeper
Gatekeeper is a security control implemented in macOS (and only in macOS — it is not present in other Apple operating systems such as iOS, iPadOS, or tvOS, which adopt far more restrictive, closed signing models).
Its purpose is to enforce code-signing verification and application provenance checks.
Every application — or, more precisely, every executable carrying a quarantine attribute — is verified before it is allowed to run.
At first glance, one might think Gatekeeper acts as a form of preventive anti-malware, but it is not, strictly speaking. It does not analyse runtime behaviour nor match against known malware signatures.
In reality, Gatekeeper is a pre-execution policy mechanism that decides whether a binary has sufficient trust or provenance (valid signature, Developer ID, notarisation, or App Store distribution) to run without user intervention.
It is therefore a preventive control operating at launch time, right when an executable attempts to start.
Part of Gatekeeper’s logic interfaces with File Quarantine, introduced in macOS 10.5 (Leopard) and improved in 10.6 (Snow Leopard).
For the reader’s context: File Quarantine (the com.apple.quarantine extended attribute) is the primary trigger that starts Gatekeeper’s assessment.
When a file is downloaded via Safari, Mail, AirDrop, Messages, or other applications that comply with the Quarantine API, macOS tags that file with the com.apple.quarantine attribute.
Upon first execution (or mount, in the case of disk images), the presence of that attribute triggers Gatekeeper’s assessment.
We will return to File Quarantine in a dedicated post.
Gatekeeper as an independent subsystem appears with OS X Mountain Lion (10.8), introducing the system-wide policy engine and its CLI tool spctl (security policy control).
The graphical interface — found under System Preferences ▸ Security & Privacy ▸ General with the options “App Store”, “App Store and identified developers”, and “Anywhere” — was also introduced in Mountain Lion.
Lion, for the record, already included the signing and quarantine primitives, but not yet Gatekeeper as we know it today.
Details of spctl
The spctl command (security policy control) is the command-line tool for querying and managing macOS’s security assessment subsystem (the “Gatekeeper policy engine”).
It does not handle filesystem permissions or ACLs — those belong to chmod, chown, ls -le, chmod +a, and similar utilities.
spctl instead manages policy rules and assessments: it validates signatures, anchors (Developer ID, Mac App Store), and any custom rules inserted into the policy database by an administrator.
Things not to do (key operations with spctl)
Note: syntax may vary slightly between macOS versions — always check
man spctlon the target system.
Enabling / disabling assessment globally
- Enable:
sudo spctl --global-enable
- Disable (historically
--master-disableappeared in many guides, but the documented form is--global-disable):
sudo spctl --global-disable
Warning: from macOS Sequoia (15) onwards, Apple has restricted the ability to disable Gatekeeper locally.
Full deactivation may now require an MDM configuration profile or confirmation through System Settings.
Do not assume that –global-disable will silence every prompt on modern systems.
Checking files or assessments
- Assess whether an app is allowed to execute:
spctl --assess --type execute -v /Applications/AppName.app # short alias spctl -a --type execute -v /Applications/AppName.app - For installer packages:
spctl --assess --type install -v /path/to/installer.pkg - For documents (open):
spctl --assess --type open -v /path/to/document
Managing rules (policy database)
- Add a rule to authorise an app (example badge):
sudo spctl --add --label "Trusted App" /path/to/App.app
This inserts a persistent entry into the policy database (useful for local testing or managed devices).
- Remove a rule:
sudo spctl --remove --label "Trusted App"
(Or remove by anchor / hash / path depending on the output you obtained.)
- Re-enable a previously disabled rule:
sudo spctl --enable --label "RuleName"
Listing and status
- List rules:
spctl --list
(Output can be minimal; use verbose or –raw for technical details.)
- Show assessment status:
spctl --status - Reset to defaults:
sudo spctl --reset-default sudo reboot # recommended to ensure full application of changes
Useful advanced options
--label: assigns a descriptive name to a rule for easier management.--path: explicitly marks the argument as a file path.--type: execute, install, or open to differentiate assessment types.-v /-vv: increases verbosity;--rawprovides low-level verdict and rule data.--assess(or-a): runs an assessment;--add /--removemodify the database.
Practical example (local testing workflow)
- Assess an app:
spctl --assess --type execute -v /path/to/App.app - Temporarily allow the app for testing:
sudo spctl --add --label "gtest" /path/to/App.app spctl --assess --type execute -v /path/to/App.app - Remove the rule afterwards:
sudo spctl --remove --label "gtest" - If something looks odd, inspect xattrs and logs:
xattr -l /path/to/App.app log stream --predicate 'subsystem == "com.apple.security.assessment" OR process == "syspolicyd"' --style compact
Things not to do / practical cautions
- Do not use
spctlto change permissions or ACLs — it is not the right tool. - Do not assume
--global-disableremoves every prompt — on modern systems it may not; proper fleet management should rely on MDM / configuration profiles. - Be careful with
--add— it is persistent and may open policy exceptions; manage such rules via MDM on production machines. - Do not confuse quarantine with assessment — removing
com.apple.quarantine(e.g.xattr -d com.apple.quarantine) merely removes the trigger; it does not make the file safe.
Gatekeeper evolution
- 10.8 (Mountain Lion): first implementation
- 10.9.5: fix for malformed app-bundle bypass
- 10.9.5: fix CVE-2014-8826 (malformed bundles — Patrick Wardle, naturally)
- 10.14.5: introduction of the notarisation requirement
- 10.14.5: fix CVE-2019-8656 (symlink bypass)
- 10.15: notarisation enforced by default
- 11.0: integration with Apple Silicon secure boot
Interaction with other controls
Gatekeeper and XProtect
Gatekeeper acts before execution, deciding whether an app may launch, based on:
- provenance (
xattr com.apple.quarantine) - code signature (
codesign) - Apple’s notarisation ticket (
.pdb) - local policy rules (
spctl, MDM)
Gatekeeper does not inspect file contents for malware or suspicious patterns.
Conceptually, it is a trust-enforcement mechanism, not a detection engine.
In short:
- If Gatekeeper approves, the executable may run.
- If it denies, the app is blocked (or the familiar prompt appears: “cannot be opened because it is from an unidentified developer”).
Once the app starts, XProtect steps in.
Gatekeeper and TCC
Temporal roles
- Gatekeeper operates before execution — it’s the bouncer at the door.
- TCC (Transparency, Consent and Control) comes into play after the app is running, when it attempts to access sensitive data or protected resources (Camera, Microphone, Calendar, Clipboard, Location, Filesystem, etc.).
In practice:
- Gatekeeper asks:
“Can I trust it enough to let it run?”
- TCC asks:
“Now that it’s running, shall I allow it to touch this?”
Points of contact and differences
Common ground: code signing and bundle ID
Both systems rely on the code signature and Bundle Identifier as a unique identity key.
That’s the first integration point:
- Gatekeeper validates that the signature is legitimate (Developer ID, App Store, notarisation).
- TCC stores user permissions bound to that signature and bundle ID.
If you re-compile the same source with a different signing key, Gatekeeper treats it as a new binary and TCC will not inherit the previous permissions.
Shared data model
Both persist their decisions within system databases:
- Gatekeeper → policy DB at
/var/db/SystemPolicyConfiguration/ - TCC → SQLite database at
/Library/Application Support/com.apple.TCC/TCC.db(plus user-level copies under~/Library/Application Support/com.apple.TCC/TCC.db)
Both are queried by syspolicyd, acting as the central dispatcher for system policy.
The hand-off
Once Gatekeeper approves execution, the app launches.
From that point:
- AMFI maintains code integrity in memory.
- When the app calls a sensitive API (e.g. AVFoundation for the camera, CoreLocation, AddressBook, FullDiskAccess, etc.), the system contacts tccd (Transparency, Consent and Control daemon).
- tccd verifies:
- the process identity and signature (already validated by Gatekeeper/AMFI)
- its record in the TCC database
- any relevant MDM policy
- previous user consent (granted or denied)
- If no valid consent exists, the system shows the familiar prompt: “Allow to access …?”
The resulting decision is stored in TCC.db, tied to that signed identity.
Complete flow (simplified)
![[Gatekeeper workflow.svg]]
Practical examples
- Scenario A: Unsigned app
- Gatekeeper blocks it before launch.
- TCC never comes into play.
- Scenario B: Signed and notarised app
- Gatekeeper allows execution.
- TCC, upon first camera access, prompts the user.
- The decision is stored in TCC.db with the binary’s hash and signature.
- Scenario C: Tampered app after approval
- AMFI blocks it (broken signature → invalid code).
- Consequently, TCC never receives a request — the process never gains API access.
Observing the integration in action
Monitor both mechanisms with log stream:
- Gatekeeper:
log stream --predicate 'subsystem == "com.apple.security.assessment"' --style compact - TCC (
tccddaemon):log stream --predicate 'process == "tccd"' --info - View current TCC entries:
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db \ "SELECT service, client, auth_value FROM access"
Security implications and combined behaviour
- Gatekeeper and TCC do not overlap: one controls who enters, the other what they do once inside.
- Their logical link is the code signature, used as a digital fingerprint of trust.
- An app may be perfectly notarised yet still lack access to any sensitive data until the user explicitly consents (TCC).
- Conversely, a local unsigned app may run if its quarantine flag is removed, but it will never pass TCC barriers for private data access — it’s silently denied.
Handy analogy
Gatekeeper: “You may enter the lab.” XProtect: “Walk through the metal detector.” AMFI: “I’ll check your badge integrity while you’re inside.” TCC: “But to use the instruments, I’ll need your explicit permission.”
Closing thoughts
Gatekeeper is often misunderstood — praised as an anti-malware engine by some, dismissed as a mere nuisance by others.
In reality, it is neither. It is a trust arbiter: a static pre-execution checkpoint that enforces Apple’s code-signing and notarisation model.
By design, Gatekeeper does not analyse or sandbox; it delegates content scanning to XProtect, integrity enforcement to AMFI, and privacy governance to TCC.
Together, these components form a layered defence model where trust is established before execution, integrity is preserved during execution, and privacy is protected after execution.
For researchers and defenders alike, understanding how these subsystems hand off control to one another is crucial.
When you know where each layer begins and ends, you know exactly where to look when something slips through.
In short: Gatekeeper lets it in, XProtect checks it, AMFI keeps it honest, and TCC makes it ask politely.
Want the deep dive?
If you’re a security researcher, incident responder, or part of a defensive team and you need the full technical details (labs, YARA sketches, telemetry tricks), email me at info@bytearchitect.io or DM me on X (@reveng3_org). I review legit requests personally and will share private analysis and artefacts to verified contacts only.
Prefer privacy-first contact? Tell me in the first message and I’ll share a PGP key.
Subscribe to The Byte Architect mailing list for release alerts and exclusive follow-ups.
Gabriel(e) Biondo
ByteArchitect · RevEng3 · Rusted Pieces · Sabbath Stones