If you've ever opened System Settings → Privacy & Security on your Mac and thought "great, I know exactly which apps have access to what" — I have bad news. That settings pane doesn't tell you the whole story. In some cases, it's actively misleading.
I spent a frustrating afternoon debugging why a backup script kept failing with permission errors, even though I'd clearly granted it Full Disk Access. That rabbit hole led me into the internals of macOS's TCC system, and what I found wasn't reassuring.
The Problem: What You See Isn't What You Get
macOS manages privacy permissions through a system called TCC — Transparency, Consent, and Control. It's the thing that pops up asking "Would you like to allow Terminal to access your Documents folder?"
The Privacy & Security pane in System Settings is supposed to be the single source of truth for these permissions. But it's not. Here's why:
- The UI can show apps as having permissions they don't actually have at the system level
- Apps can have permissions that don't show up in the UI at all
- Toggles you flip in the UI sometimes don't take effect until a reboot (or ever)
- There are multiple TCC databases, and the UI doesn't clearly reflect which one you're looking at
If you're a developer building tools that depend on these permissions — screen recording, accessibility, automation — this matters a lot.
Root Cause: The TCC Database Architecture
macOS actually maintains multiple TCC databases:
# User-level TCC database
~/Library/Application Support/com.apple.TCC/TCC.db
# System-level TCC database (requires root/SIP considerations)
/Library/Application Support/com.apple.TCC/TCC.db
The system-level database is protected by SIP (System Integrity Protection) and managed by the tccd daemon. The user-level one handles per-user consent. When you toggle something in System Settings, it writes to one of these — but the UI reads from both, and sometimes the state gets out of sync.
The access table in each database tracks entries with fields like service (the permission type), client (the app's bundle ID), and auth_value (whether it's allowed or denied). When these databases disagree, or when stale entries linger, the UI can show a state that doesn't match reality.
Step-by-Step: How to Actually Audit Your TCC Permissions
Don't trust the GUI. Here's how to see what's really going on.
1. Query the User-Level TCC Database Directly
# List all permissions in your user TCC database
# auth_value: 0 = denied, 2 = allowed
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db \
"SELECT service, client, auth_value, auth_reason FROM access ORDER BY service;"
This shows you the raw data. Compare it against what System Settings displays — you might be surprised at the discrepancies.
2. Check the System-Level Database
# Requires elevated privileges
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db \
"SELECT service, client, auth_value FROM access ORDER BY service;"
Note: on recent versions of macOS with SIP enabled, direct access to the system TCC database may be restricted even with sudo. If you get a permission error, that's SIP doing its job.
3. Use tccutil for Resets
Apple provides tccutil, though it's limited. If a permission is stuck in a bad state, resetting it can force a fresh consent prompt:
# Reset a specific service for a specific app
tccutil reset AppleEvents com.example.myapp
# Nuclear option: reset ALL permissions for a service
# Be careful — this resets permissions for ALL apps
tccutil reset Camera
# Reset everything (you probably don't want this)
tccutil reset All
After a reset, the app will need to request permission again on its next launch.
4. Monitor TCC Decisions in Real Time
When debugging whether your app is actually getting the permission it needs:
# Stream TCC-related log messages
log stream --predicate 'subsystem == "com.apple.TCC"' --level debug
This is incredibly useful. You can see exactly when tccd grants or denies a permission, what the requesting app's code signature looks like, and why a decision was made. I use this constantly when testing apps that need accessibility or automation permissions.
5. Validate Code Signing
One common cause of TCC confusion: the app's code signature changed. TCC ties permissions to an app's code signing identity. If you rebuild your development app or the bundle ID shifts, TCC sees it as a different app, even if the name is the same.
# Check an app's code signing identity
codesign -dv --verbose=4 /Applications/YourApp.app 2>&1 | grep "TeamIdentifier\|Identifier"
If the identifier in the TCC database doesn't match the app's current signature, the permission effectively doesn't apply.
Why This Keeps Happening
A few architectural reasons this is a persistent pain point:
-
System Settings is a GUI wrapper, not the authority. The
tccddaemon is the actual gatekeeper. The UI is just a viewer with write access — and viewers can lie. - Stale entries persist. Uninstalled apps, renamed bundles, and re-signed binaries leave orphan rows in the database. The UI may still show these.
-
MDM and profiles add another layer. If your Mac is managed (common for work machines), configuration profiles can silently grant or restrict permissions that don't appear in the user-facing UI at all. Check with
profiles listif things seem off. - SIP protects the system database from inspection. This is good for security but means you can't always verify the full picture without disabling protections you shouldn't disable.
Prevention: Building Apps That Handle TCC Gracefully
If you're a developer shipping macOS apps, don't assume the happy path:
-
Always check permissions at runtime, don't cache the result from a previous launch. Use the appropriate framework APIs (like
AVCaptureDevice.authorizationStatus(for:)for camera) rather than trying to read TCC directly. - Handle denial gracefully. Your app might show as "allowed" in System Settings but still get denied at the system level. Code defensively.
-
Log the actual TCC response. When users report permission issues, having the real
tccddecision in your logs saves hours of back-and-forth. - Document the reset procedure for your users. Include steps to reset your app's specific TCC entries when things go sideways, because they will.
The Bigger Picture
This isn't really a bug — it's a consequence of Apple layering security mechanisms over multiple macOS releases without fully unifying the user-facing experience. TCC has grown from a simple consent system into a complex multi-database, multi-policy architecture. The GUI hasn't kept pace.
As developers, we can't fix Apple's settings UI. But we can stop treating it as authoritative. Query the databases directly, monitor tccd in real time, and build your apps to handle the messy reality of macOS permissions.
The terminal doesn't lie. System Settings sometimes does.
Top comments (0)