Skip to content

ExecutionPersistence

TCC.db: macOS Privacy Permission Grants

The macOS TCC databases record which apps were allowed or denied camera, microphone, screen, disk and automation access, and when that last changed.

Location
/Library/Application Support/com.apple.TCC/TCC.db
Proves
Which programs requested or were granted sensitive privacy permissions, by whom, and when the decision last changed
Timestamps
Unix epoch seconds (UTC) in last_modified
Access
Full Disk Access to read; system database also SIP-protected
Retention
Until the row is changed, reset (tccutil) or the app is removed
Collection
Aftermath, mac_apt, UAC, Velociraptor

What it is

Transparency, Consent and Control (TCC) is the macOS framework that gates access to privacy-sensitive resources: camera, microphone, screen capture, input monitoring, accessibility APIs, protected user folders, Full Disk Access and Apple Events automation. The daemon tccd makes the decisions and stores them in SQLite databases named TCC.db, one for the system and one per user.

Each row is a standing decision for a client (bundle ID or path) and a service. The database is a permissions register, not a usage log: it tells you what a program was allowed to do, not every time it did it.

Where it lives

ScopePathVersion notesProtection
System/Library/Application Support/com.apple.TCC/TCC.dbaccess schema changed in 11 Big Sur and 14 SonomaSIP + TCC, needs Full Disk Access to read
Per user~/Library/Application Support/com.apple.TCC/TCC.dbSame schema as systemTCC, needs Full Disk Access to read
MDM grants/Library/Application Support/com.apple.TCC/MDMOverrides.plistManaged MacsSIP-protected directory
Screen Recording reminder dates~/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist15 Sequoia+TCC-protected

Collect the WAL and SHM companions (TCC.db-wal, TCC.db-shm) with each database. Every account under /Users has its own per-user copy.

What it proves

  • A given app or binary holds, or was refused, Full Disk Access, Accessibility, Screen Recording, Input Monitoring, camera or microphone.
  • A path-based client (client_type = 1) such as an unsigned binary or script interpreter was granted a capability, which is unusual for legitimate software.
  • An Automation grant lets one app send Apple Events to another (target in indirect_object_identifier).
  • The decision came from the user, the system or an MDM policy (auth_reason, cross-checked with MDMOverrides.plist).
  • Approximate time of the latest change to a decision (last_modified).

It does not prove that the permission was actually used, how often, or what was captured. A missing row does not prove a permission was never granted: tccutil reset deletes rows.

Key fields

Table access (Big Sur and later):

ColumnMeaning
serviceService identifier, e.g. kTCCServiceSystemPolicyAllFiles (Full Disk Access), kTCCServiceAccessibility, kTCCServiceScreenCapture, kTCCServiceListenEvent, kTCCServiceAppleEvents
clientBundle ID or absolute path of the requesting program
client_type0 bundle ID, 1 absolute path
auth_value0 denied, 1 unknown, 2 allowed, 3 limited
auth_reasonCommunity-documented: 1 error, 2 user consent, 3 user set, 4 system set, 5 service policy, 6 MDM policy, 7 override policy, 8 missing usage string, 9 prompt timeout, 10 preflight unknown, 11 entitled, 12 app type policy
auth_versionRecord format version
csreqCode-signing requirement blob identifying the client
policy_idLink to an MDM policy, when present
indirect_object_identifier_type, indirect_object_identifier, indirect_object_code_identityTarget of the grant (Automation)
flagsAdditional flags
last_modifiedLast change to the row
pid, pid_version, boot_uuid, last_remindedAdded in macOS 14 Sonoma

On Mojave and Catalina the table used allowed and prompt_count instead of auth_value and auth_reason. Always run .schema access first.

Timestamps

last_modified is Unix epoch seconds, UTC, unlike most Apple databases that use Mac absolute time. It reflects the last write to that row (prompt answered, toggle flipped, policy applied), not the first grant.

SELECT service, client, auth_value, auth_reason,
       datetime(last_modified, 'unixepoch') AS last_modified_utc
FROM access ORDER BY last_modified DESC;

last_reminded (Sonoma and later) is also Unix epoch seconds; its schema default is strftime('%s','now'). File system birth and modification times of TCC.db (APFS, nanosecond precision) only tell you when the database changed, not which row.

Retention

Rows persist until the decision is changed, the client is reset with tccutil reset <service> [bundle-id], the user resets permissions in System Settings, or the database is rebuilt. There is no automatic ageing. Deleted rows may survive for a while in the WAL file or free pages, and older copies may exist in local APFS snapshots or Time Machine backups.

Collection

The collector process (Terminal, the agent, or the tool binary) must hold Full Disk Access on a live system, or the copy fails silently or comes back empty.

# Live, with FDA granted to Terminal
sudo ditto "/Library/Application Support/com.apple.TCC" ./case/tcc_system
for u in /Users/*; do
  [ -d "$u/Library/Application Support/com.apple.TCC" ] && \
  sudo ditto "$u/Library/Application Support/com.apple.TCC" "./case/tcc_$(basename "$u")"
done
  • Aftermath copies the system and per-user TCC.db and MDMOverrides.plist (run as root with FDA), but not TCC.db-wal: changes still in the WAL, and the older row versions it holds, are lost.
  • UAC collects both system and per-user TCC.db (files/system/tcc.yaml), but not TCC.db-wal or TCC.db-shm, with the same loss. Copy the companions yourself, as in the commands above.
  • Velociraptor MacOS.System.TCC reads both databases directly.
  • Dead-box: mount the Data volume read-only; TCC and SIP do not restrict reads from an analysis host.

Parsing

  • sqlite3 on a copy, with the queries above.
  • mac_apt TCC plugin: python mac_apt.py -o out E01 mac.E01 TCC.
  • Plaso macostcc SQLite plugin: log2timeline.py --parsers 'sqlite/macostcc' --storage-file tcc.plaso ./case.
  • Velociraptor MacOS.System.TCC artifact for fleet hunting.

High-risk review:

SELECT service, client, client_type, auth_value, auth_reason,
       datetime(last_modified,'unixepoch') AS changed_utc
FROM access
WHERE auth_value = 2
  AND (service IN ('kTCCServiceSystemPolicyAllFiles','kTCCServiceAccessibility',
                   'kTCCServiceScreenCapture','kTCCServiceListenEvent',
                   'kTCCServicePostEvent')
       OR client_type = 1);

TCC Parser decodes system and per-user TCC.db files with their -wal (including older row versions carved from it), MDMOverrides.plist and csreq blobs in the browser, and flags risky grants; nothing is uploaded.

Investigator tips

  • Grants to interpreters or terminals (/bin/bash, /usr/bin/osascript, com.apple.Terminal) cover everything launched through them. Pivot to shell history and launchd jobs.
  • A denied row (auth_value = 0) is still evidence of a request, often the first visible attempt by malware.
  • auth_reason = 6 points to MDM. Confirm with MDMOverrides.plist and sudo profiles show -type configuration; a rogue profile is a finding in itself.
  • tccd decisions are logged under subsystem com.apple.TCC in the unified logs, which is where per-request evidence lives.
  • On Sequoia, far-future dates in ScreenCaptureApprovals.plist indicate someone suppressed the periodic Screen Recording reminder.
  • Endpoint Security exposes TCC changes to EDR tools from macOS 15.4 (ES_EVENT_TYPE_NOTIFY_TCC_MODIFY); check your EDR telemetry for changes that the database no longer shows.
  • Compare csreq with the signature of the binary on disk: a bundle-ID row is honoured for any code that satisfies the stored requirement.
  • See the TCC database forensics guide for service lists and query patterns.

See also