Gatekeeper controls which applications can be executed in macOS by:
Checking for a valid Developer ID signature from Apple, so that malicious developers can be revoked if necessary.
Requiring apps to pass Apple’s notarization process, which scans software for suspicious content and security vulnerabilities.
Notarization & Developer ID
Notarization & Developer ID work together to validate the origin and safety of an app:
Developer ID: A unique identifier Apple provides to trusted developers, helping users recognize legitimate software sources.
Notarization: A process where Apple’s automated systems scan and approve apps, identifying malicious code or security flaws before they reach your Mac.
App Sandbox
The App Sandbox confines each application to a restricted environment:
Prevents an app from freely reading or modifying files outside its container.
Limits network access and system-level changes to minimize damage if the app is compromised.
Prompts for consent when an app wants to access things like your camera, microphone, or contacts.
Allows you to review and adjust permissions in System Settings at any time.
FileVault
FileVault encrypts your disk to protect data at rest:
Uses XTS-AES-128 encryption to secure the entire startup volume.
Helps prevent unauthorized access if your Mac is lost or stolen.
Keychain
Keychain securely stores credentials and secrets:
Encrypts your passwords, certificates, and private keys.
Relies on the system’s security frameworks to prevent unauthorized access.
The Accessibility Permission
Accessibility permission falls under the TCC framework. It opens your system to programmatic control for certain purposes.
Accessibility Examples
Examples of applications that need accessibility:
Automation utilities
Window management tools
Voice control applications
Screen readers for visually impaired users
Examples of application control:
Simulated keyboard and mouse input
Monitor user interactions with other applications
Access and manipulate content from windows and controls across applications
Applications with accessibility permission are also referred to as trusted. macOS requires explicit user permission to designate an app as trusted. This takes the form of a dialog that requires admin authentication, and a Settings panel where permissions can be revoked. Spoiler alert: there is no clean way around manual authentication.
Configuration
To be able to request accessibility permissions you need to
Declare an entitlement if you run on a hardened runtime.
Declare the reason you need accessibility in your Info.plist.
Include a provisioning profile if you plan to distribute in the App Store.
The code above will either present a system dialog, or redirect the user to the Accessibility panel at Settings > Privacy & Security > Accessibility. Such panel can also be open programmatically with:
Remove and add back the application to Accessibility.
Run the application from Xcode > Product > Perform Action > Run Without Building
You will be able to debug the application, but it is of course, very inconvenient to go through all this after each build.
TCC provides a utility (tccutil) that can reset permissions, but not assign them. The TCC database is a SQLite file on disk you can read, but writing requires disabling SIP, which opens the possibility to make a catastrophic mistake that damages your system.
Disabling SIP
Turn on the Mac and press ⌘R until you see the Apple logo or the recovery utilities.
Go to the Utilities menu at the top and select Terminal.
Type: csrutil disable
Restart the Mac.
Once restarted type crsutil status to verify it is disabled. To re-enable SIP repeat the steps above but run csrutil enable.
TCC.db
# clears accessibility for one appsudo tccutil reset Accessibility $PRODUCT_BUNDLE_IDENTIFIER# clears accessibility for all apps
tccutil reset Accessibility
TCC.db
# is my app authorized? 1=granted, 2=deniedsudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db""SELECT auth_value FROM access WHERE service='kTCCServiceAccessibility' AND client='dev.jano.orange';"# read rowsudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db""SELECT * FROM access WHERE service='kTCCServiceAccessibility' AND client='dev.jano.orange';"# write (if SIP is disabled)sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db""UPDATE access SET auth_value = 1 WHERE service = 'kTCCServiceAccessibility' AND client = 'dev.jano.orange';"# list clients allowed in my machinesudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db""SELECT client FROM access WHERE service='kTCCServiceAccessibility';"# show all services tracked theresudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db""SELECT DISTINCT service FROM access;"
The service, client, and auth_value are well known. The others are not documented, but more or less this is the unofficial table structure
Column
Example Value
Description
service
kTCCServiceAccessibility
The type of permission
client
dev.jano.orange
The bundle identifier
client_type
0
0 typically means bundled app
auth_value
2
2 usually indicates denied, 1 is allowed
auth_reason
4
Reason code for the authorization
auth_version
1
Version of the authorization
csreq
??
Code signing requirement
policy_id
null
Policy identifier
indirect_object_identifier
0
Used for file/folder access permissions
flags
UNUSED
Flag settings
last_modified
null
Last modification timestamp
pid
0
Process ID
expired_at
1736658020
Unix timestamp when permission expires
remote_pid
null
Remote process ID
responsibility_id
UNUSED
Responsibility identifier
temporary_grant
0
Whether this is a temporary permission
There is an utility in GitHub: tccprofile but didn’t look into it. There is another local database at ~/Library/Application\ Support/com.apple.TCC/TCC.db but it is not relevant.
Recommendations
(aka things to do to avoid problems)
Sign the application to make its identity clear to macOS. What I read online is that each build changes the app signature so permissions have to be requested again. But what I experienced is that signing with a Developer ID certificate is enough for the system to recognize it is the same app.
To sign your app
Go to Signing & Capabilities
Set a Team
There are several options here. I use a Developer ID certificate and a Developer ID profile tied to it.
Once signed, you can check the state of your app with codesign.
Copy the product to a stable location. What I read is that the default location of ~/Library/Developer/Xcode/DerivedData/ is hidden and changes between builds, which may confuse the system. But what I experienced is that the system recognizes the application regardless where it is generated.
FYI: changing the destination folder may cause problems if one of your dependencies declares a localization bundle of its own. If this is your case, you can still work around it and change the destination folder.
There are three ways to declare a custom location:
The Console.app is your friend –despite its unfriendly UI. You can spot the Accesibility permission being denied if you paste the following in the search field (top, right):