Skip to content

TCC.db Forensics: The Investigator's Guide

What the macOS TCC database records, what it proves and what it does not, the services that matter, and a repeatable workflow for reviewing TCC.db in a case.

Published on 9 min read

TL;DR. Transparency, Consent and Control (TCC) is the macOS framework that decides whether a program may use the camera, microphone, screen, input devices, accessibility APIs, protected folders, the whole disk, or other apps through Apple Events. The daemon tccd stores each decision as a row in a SQLite database, TCC.db: one for the system and one per user. Each row tells you which client, which service, allowed or denied, why, and when the row last changed. It is a permissions register, not a usage log. TCC Parser decodes these databases in your browser, including earlier row versions left in the WAL file; nothing is uploaded.

This guide is the entry point to the series. Each section links to a deeper article.

What TCC is and why investigators care

Most interesting things malware or an intruder wants to do on a Mac touch a TCC-protected resource: reading Mail, Messages or browser data (needs Full Disk Access), watching the screen (Screen Recording), logging keys (Input Monitoring), clicking through dialogs (Accessibility), or driving Finder and System Events to act on the user's behalf (Apple Events automation). When a program asks for one of those capabilities, tccd either prompts the user, applies a policy, or refuses, and records the outcome.

That makes TCC.db one of the places where intent becomes visible. A path-based helper in a hidden cache folder asking for Accessibility, or Terminal getting Full Disk Access minutes after a suspicious download, is the kind of thing the database captures well.

What a row can prove, and what it cannot

A row can showA row cannot show
That a client (bundle ID or absolute path) holds, or was refused, a serviceThat the capability was actually used, or how often
Whether the decision was allowed, denied, unknown or limited (auth_value)What was captured, read or typed
A reason code for the decision (auth_reason, community-documented)Who was physically at the keyboard
The code-signing requirement the client must satisfy (csreq)That the binary on disk today is the one that was approved
For Automation, which app is being controlledEvery earlier state of the row (only the latest, unless the WAL keeps more)
When the row last changed (last_modified, Unix seconds UTC)When the permission was first granted, if the row changed since

Two consequences shape every report. First, a grant is capability, not activity: write "held Full Disk Access from 10:11:37 UTC", not "read the user's mail". Second, absence is weak evidence: rows get deleted by tccutil reset, by resets in System Settings and when apps are removed. See recovering removed grants from the WAL.

Where the databases live

DatabasePathTypical content
System/Library/Application Support/com.apple.TCC/TCC.dbFull Disk Access, Accessibility, Screen Recording, Input Monitoring, Developer Tools, Endpoint Security clients
Per user~/Library/Application Support/com.apple.TCC/TCC.dbCamera, microphone, contacts, calendars, photos, Automation, Desktop/Documents/Downloads, removable and network volumes
MDM grants/Library/Application Support/com.apple.TCC/MDMOverrides.plistPPPC grants pushed by a management server

The "typical content" column is a guide: the split is by service and has moved between releases, so always query every copy. Reading any of these files on a live Mac needs Full Disk Access for the process doing the copy, and System Integrity Protection additionally protects the system database against modification, even by root. The companion files TCC.db-wal and TCC.db-shm matter too. Collection details and tool gotchas: TCC.db location and acquisition.

Reading a row

The table that matters is access. On Big Sur and later, the columns you will use most are:

ColumnWhat to read from it
serviceThe TCC service, e.g. kTCCServiceSystemPolicyAllFiles
clientBundle ID or absolute path of the program
client_type0 bundle ID, 1 absolute path (client_type)
auth_value0 denied, 1 unknown, 2 allowed, 3 limited
auth_reasonWhy (user consent, user set, MDM policy, and so on; community-documented)
csreqCompiled code requirement identifying the client
indirect_object_identifierFor Automation: the app being controlled
last_modifiedUnix epoch seconds, UTC

Mojave and Catalina used allowed and prompt_count instead of auth_value and auth_reason; Sonoma added pid, pid_version, boot_uuid and last_reminded. Always check .schema access on your copy. The full evolution, and the smaller tables around access, are in the access table across macOS versions.

The services that matter most

Service identifierShown in System Settings asWhy it matters
kTCCServiceSystemPolicyAllFilesFull Disk AccessReads protected user data: Mail, Messages, Safari, other TCC.db files
kTCCServiceAccessibilityAccessibilityDrives the UI: clicks, keystrokes, approving dialogs
kTCCServiceScreenCaptureScreen RecordingSees everything on screen
kTCCServiceListenEventInput MonitoringObserves keyboard and mouse events
kTCCServicePostEvent(synthetic input)Posts input events
kTCCServiceAppleEventsAutomationControls another app; target in indirect_object_identifier
kTCCServiceSystemPolicyDocumentsFolder and siblingsFiles and FoldersDesktop, Documents, Downloads
kTCCServiceSystemPolicyRemovableVolumesRemovable VolumesUSB drives and similar
kTCCServiceEndpointSecurityClient(Endpoint Security)Security agents; a new one deserves a look

Who decided: user, system or MDM

auth_reason is how you tell a user click from a policy. Commonly cited values include 2 user consent (answered a prompt), 3 user set (toggled in System Settings), 4 system set and 6 MDM policy. Apple does not document these integers; treat them as hypotheses and confirm on a test Mac of the same version or in the unified log before relying on them.

On managed Macs, grants pushed by a Privacy Preferences Policy Control (PPPC) profile are reflected in MDMOverrides.plist, and rows may reference the policies table through policy_id. A legitimate EDR agent with Full Disk Access from MDM is normal. An unexpected PPPC profile is itself a finding.

A repeatable workflow

  1. Collect the whole com.apple.TCC folder for the system and every user, with -wal and -shm, from a process that holds Full Disk Access (or from a mounted image). Hash what you collected.
  2. Check the schema of each database before querying: column sets differ by release.
  3. Review high-impact grants: Full Disk Access, Accessibility, Screen Recording, Input Monitoring, Post Event, Developer Tools, Endpoint Security. Ask who holds them and whether that fits the user's role.
  4. Look at path-based clients (client_type = 1) and clients in temporary, shared or hidden folders. Legitimate apps are almost always bundle IDs.
  5. Decode csreq. A requirement that is only a cdhash pins one exact build, which is typical of ad-hoc signed code. See decoding csreq.
  6. Read Automation rows in each user database: which app may control Finder, System Events or Terminal.
  7. Recover history from TCC.db-wal and freed pages: removed and changed grants.
  8. Build a timeline from last_modified and correlate with the unified log (subsystem == "com.apple.TCC"), quarantine events, launchd jobs and shell history.

The permission abuse investigation article walks through this on a realistic, fictional intrusion.

Tools

  • sqlite3 on a copy, with your own queries. Fine for spot checks; you decode blobs and reason codes yourself.
  • mac_apt TCC plugin: supports the Catalina and Big Sur-and-later layouts, from an image or in artifact-only mode.
  • Plaso macostcc plugin: expects the pre-Big Sur allowed/prompt_count columns, so it targets Mojave and Catalina databases.
  • Velociraptor MacOS.System.TCC: live queries across a fleet.
  • TCC Parser: drop the folder or a triage ZIP in the browser; it decodes every schema version, the code requirements and reason codes, flags notable rows, recovers WAL history and exports CSV, Timesketch CSV and JSON. The step-by-step is in analyze TCC.db in your browser.

Apple and macOS are trademarks of Apple Inc. TCC Parser is an independent tool and is not affiliated with or endorsed by Apple.

Pitfalls

  • Incomplete collection. Without Full Disk Access the copy fails or comes back empty. A missing TCC.db in a triage package is usually a collection problem.
  • Forgetting the WAL. Several collectors copy only TCC.db. Recent changes may still sit in TCC.db-wal.
  • Responsible process. Permissions are often attributed to the app that launched a tool (Terminal) rather than the script that used them. A grant to Terminal covers everything run from it.
  • Bundle ID versus binary. A bundle-ID row is honoured for any code that satisfies the stored requirement. Compare the requirement with the binary on disk.
  • Over-reading reason codes. auth_reason meanings are community-documented, not official.

FAQ

Does TCC.db show every time an app used the camera or read my files?

No. TCC.db stores standing decisions: which client was allowed or denied which service, and when that decision last changed. Individual uses are not recorded there. Per-request evidence is in the unified log under the com.apple.TCC subsystem, and usage history in other artifacts.

Is a missing row proof that a permission was never granted?

No. tccutil reset, a reset in System Settings or removal of the app deletes rows. Earlier versions of a row can survive for a while in TCC.db-wal or in freed pages, and older copies of the database may exist in APFS snapshots or Time Machine backups.

Which TCC database should I look at, the system one or the user one?

Both, every time. High-impact services such as Full Disk Access, Accessibility and Screen Recording are usually in the system database, while camera, microphone, Automation and folder access are usually in each user's database. The split is by service and has moved between releases, so query all copies.

What time zone is last_modified in?

last_modified is Unix epoch seconds, which is UTC. It is not Mac absolute time. The same applies to last_reminded (Sonoma and later) and expired_at in the expired table.

Related articles

Step by step: load macOS TCC.db files and their WAL into a free in-browser parser, read the findings, review permissions and history, and export.
Where the system and per-user TCC.db files live, why Full Disk Access and SIP matter, and how to collect them with the WAL using UAC, Aftermath or Velociraptor.
How attackers abuse macOS TCC permissions, what each technique leaves in TCC.db, and how to pivot to unified logs, launchd, quarantine and shell history.