Investigating TCC Permission Abuse on macOS
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.
TL;DR. Attackers on macOS need TCC permissions for most of what they want: Full Disk Access to read user data, Accessibility, Screen Recording and Input Monitoring to watch and drive the session, and Automation of Finder or System Events to act as the user. Each path leaves a characteristic row in TCC.db: which client, which service, how it was decided, and when. This article maps the common techniques to what you will find, and to the artifacts to pivot to next. The examples come from the fictional FIN-MBP-03 sample in TCC Parser.
Keep perspective: nearly every row described below also occurs on clean Macs. A finding is a question to answer, not an answer.
The techniques and their traces
| Technique | What it looks like in TCC.db | Where |
|---|---|---|
| Full Disk Access to a terminal or interpreter | kTCCServiceSystemPolicyAllFiles, client com.apple.Terminal, /bin/bash, /usr/bin/osascript and similar, auth_value = 2 | System DB |
| Surveillance capabilities | kTCCServiceAccessibility, kTCCServiceScreenCapture, kTCCServiceListenEvent, kTCCServicePostEvent | System DB |
| Automation of Finder, System Events or Terminal | kTCCServiceAppleEvents, target com.apple.finder, com.apple.systemevents, com.apple.Terminal | Per-user DB |
| Path-based or ad-hoc clients | client_type = 1, path in /tmp, /private/var/tmp, /Users/Shared, a hidden dot-folder or Downloads, csreq cdhash-only | Both |
| Folder and volume access for staging | kTCCServiceSystemPolicyDocumentsFolder, ...DesktopFolder, ...DownloadsFolder, ...RemovableVolumes | Per-user DB |
| Cleanup | Rows removed with tccutil reset, visible only in the WAL or older copies | Both |
| Direct database edits | Rows with no matching tccd activity, odd reason codes or requirements | Depends on protections |
"System DB" and "per-user DB" reflect where these services usually live; the split has moved between releases, so query both.
Full Disk Access to terminals and interpreters
The shortest path to user data is to get Full Disk Access for something that runs arbitrary commands. Permissions are usually attributed to the responsible app, so a grant to Terminal covers every script, binary and one-liner launched from it. Look for FDA held by com.apple.Terminal, third-party terminals, or path-based interpreters, and read auth_reason: a user-driven reason (commonly 2 user consent or 3 user set) means someone clicked.
In the sample, Full Disk Access to com.apple.Terminal was granted at 10:11:37 UTC, six minutes into the intrusion window.
Accessibility, Screen Recording, Input Monitoring
These are the surveillance and control services. Accessibility lets a program click and type in other apps, including approving dialogs; Screen Recording sees the screen; Input Monitoring observes keystrokes; Post Event injects input. Malware typically requests them from a helper binary, sometimes after getting the user to approve a convincing prompt.
A denied row is evidence too. In the sample, the helper /Users/dana.whitlock/Library/Caches/.sync/sync-helper was granted Accessibility at 10:19:05 and was denied Screen Recording at 10:24:40. The denial shows the request happened, even though it failed.
On Sequoia and later, check ~/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist: far-future reminder dates there indicate someone suppressed the periodic Screen Recording reminder.
Automation of Finder, System Events and Terminal
Apple Events automation lets one app send commands to another. Control of Finder means copying and moving files as the user; control of System Events means scripting UI and system actions; control of Terminal means running commands through an app that may hold Full Disk Access. These rows live in the user database, with the target in indirect_object_identifier and its code requirement in indirect_object_code_identity.
In the sample, Terminal was allowed to control System Events at 10:14:02 and Finder at 10:16:48, both in dana.whitlock's database.
Path-based and ad-hoc signed clients
Legitimate apps are almost always recorded by bundle ID with a requirement tied to Apple or a Team ID. A client recorded by absolute path (client_type 1) in a temporary, shared or hidden folder, with a csreq that is only a cdhash, is a small, unsigned or ad-hoc signed program that someone placed and ran. That describes plenty of developer tools too, so check the path, the file, and how it arrived. Details in decoding csreq code requirements.
Staging data: folders and removable volumes
Grants for Documents, Desktop, Downloads and removable volumes to a terminal or helper, within minutes of each other, sketch the collection phase. In the sample: Documents and Desktop for Terminal around 10:18, Downloads for the sync-helper at 10:21:44, and removable volumes for Terminal at 10:38:55, which the story ties to a USB volume named "EXFIL".
Cleanup with tccutil
An intruder who knows about TCC may remove grants on the way out with tccutil reset. The row disappears from the database, but earlier copies of its page can stay in TCC.db-wal or freed pages. In the sample, the helper's Accessibility grant is gone from the current state and recovered from the WAL; the story places the removal at 10:47:12 UTC. See recovering removed TCC grants from the WAL.
Direct edits of TCC.db
System Integrity Protection prevents modification of the system database, even by root. With SIP disabled, a root process can write to it directly. The per-user database is protected by TCC rather than SIP, and public research has shown ways to tamper with it in the past. Signs to check, with care: rows whose last_modified has no matching com.apple.TCC activity in the unified log, reason codes that do not fit how the grant should have been made, and missing or odd code requirements. Record the SIP status of the Mac (from csrutil status on a live system, or from your triage tool) in the case notes.
MDM and PPPC
A malicious or unexpected PPPC profile can pre-grant many services without a prompt. It cannot silently grant camera, microphone or Screen Recording. Rows with auth_reason commonly read as MDM policy (6), a policy_id, or an entry in MDMOverrides.plist should match a profile your MDM team recognizes. In the sample, com.example.edr.agent holds Full Disk Access from a PPPC profile, which is expected for an EDR agent.
Building the timeline
Put the rows in order and look at gaps and bursts. The sample, in UTC on 2026-09-14:
| Time | Database | Event |
|---|---|---|
| 10:11:37 | System | Full Disk Access allowed for Terminal |
| 10:14:02 | dana.whitlock | Terminal may control System Events |
| 10:16:48 | dana.whitlock | Terminal may control Finder |
| ~10:18 | dana.whitlock | Documents and Desktop access for Terminal |
| 10:19:05 | System | Accessibility allowed for the path-based sync-helper |
| 10:21:44 | dana.whitlock | Downloads access for the sync-helper |
| 10:24:40 | System | Screen Recording denied for the sync-helper |
| 10:38:55 | dana.whitlock | Removable-volume access for Terminal |
| 10:47:12 | System | Accessibility row for the sync-helper removed (WAL only) |
In TCC Parser, Findings raises a burst of permission changes from 10:11:37 to 10:24:40, followed by a 14-minute gap before 10:38:55. A time range of 10:05 to 10:50 (the Use the sample's incident window button on the Findings tab) scopes every tab to the intrusion.
Where to pivot next
TCC.db tells you what was possible. Other artifacts tell you what happened:
| Question | Artifact |
|---|---|
| Who requested what, and which process was responsible? | Unified log, subsystem == "com.apple.TCC" |
| How did the helper arrive? | Quarantine events database, download history, file system timestamps |
| How does it persist? | LaunchAgents and LaunchDaemons (~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons) and login items |
| What was run in Terminal? | Shell history (~/.zsh_history, ~/.zsh_sessions/) |
| Was a permission changed and removed? | EDR telemetry; Endpoint Security TCC modification events from macOS 15.4 |
| What left the Mac? | Browser history, removable-media evidence, network logs |
In the sample story, those pivots find a quarantined download tools.zip from https://files.example/…, a fake LaunchAgent com.example.updater.plist, files staged in ~/Library/Caches/.sync/ and a visit to transfer.example. None of those are in TCC.db; they are what the TCC rows point you to.
A starting query for the unified log, on a collected log archive or a live system:
log show --info --predicate 'subsystem == "com.apple.TCC"' \
--start "2026-09-14 10:00:00" --end "2026-09-14 11:00:00"
Message formats vary by release and some values may be redacted as <private>. Retention is limited, so collect logs early.
Writing it up
- State capabilities, not actions: "Terminal held Full Disk Access from 10:11:37 UTC", then cite the artifact that shows use.
- Cite the database and account for every row, and say when a row was recovered from the WAL.
- Hedge
auth_reasoninterpretations as community-documented. - Note your own collection-time grants so they are not mistaken for attacker activity.
FAQ
Is Full Disk Access for Terminal a sign of compromise?
Not by itself. Developers and administrators grant it routinely. It becomes significant when the grant time lines up with other suspicious activity, when the user's role does not need it, or when it is followed by Automation, folder or removable-volume grants for the same app.
Can malware grant itself TCC permissions?
Normally a user prompt, an MDM policy or a system decision is required. Techniques to get there include tricking the user into approving a prompt, borrowing the permissions of an app that already has them, and editing the database directly where protections allow it, for example the system database when SIP is disabled.
Where do I find evidence that a permission was used?
Not in TCC.db, which only stores decisions. Look at the unified log (subsystem com.apple.TCC for requests), process execution evidence, shell history, file system timestamps and EDR telemetry.
How can I tell an MDM-granted permission from a user-granted one?
Check auth_reason (6 is commonly read as MDM policy), policy_id and the policies table, and MDMOverrides.plist. Confirm with the installed configuration profiles. A PPPC profile you cannot explain is a finding.