---
title: "The Permission Was Granted. The Data Use Still Needed a Policy."
description: "TCC can authorize a macOS process to reach a protected resource, but consent is not a standing business authorization. A defensible review follows the prompt, responsible code identity, returned capability, downstream use, retention, logging, and revocation."
date: 2026-09-05
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/posts/macos-tcc-consent-policy/
---

## The macOS consent boundary in 60 seconds

When a macOS application asks for the camera, microphone, contacts, calendars, automation, screen
recording, accessibility, or Full Disk Access, the platform may place a user or administrator
decision between the process and the protected resource. This family of privacy controls is commonly
described as TCC: Transparency, Consent, and Control.

That decision is important, but its meaning is narrow. It says that a particular responsible code
identity may access a class of protected resource under the platform's current rules. It does not say
that every feature in the application may use every returned record, that a plug-in may inherit the
same purpose, or that an upload, model prompt, export, or background workflow is authorized.

The testing mistake is to stop at the system prompt:

> The person approved resource access. The application must still authorize each data use.

A defensible review therefore follows the complete chain: **declared purpose → requesting binary →
system decision → resource handle → data transformation → external effect → retention → revocation**.
The interesting question is not only whether the prompt appears. It is whether the resulting
capability remains bound to the feature, account, destination, duration, and explanation the person
understood.

## Five controls, five different decisions

TCC is easiest to reason about when it is separated from adjacent controls.

| Control            | Decision it makes                                                    | Decision it does not make                                       |
| ------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------- |
| Code signing       | Which code identity is executing?                                    | Is this feature allowed to process this data?                   |
| App Sandbox        | Which resources can this executable reach?                           | Is every reachable object valid for the current workflow?       |
| Entitlements       | Which protected capability may the executable request?               | Did the person grant it, and is the later use appropriate?      |
| TCC and consent    | Did the person or administrator allow this protected-resource class? | May the app retain, combine, transmit, or repurpose the result? |
| Application policy | Which feature, account, destination, and state may consume the data? | Can it bypass the operating system's privacy enforcement?       |

Apple's guidance for [requesting access to protected
resources](https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources)
requires an accurate purpose string and describes access as denied by default until the person makes
a decision. Some capabilities also require a signed entitlement. Those are complementary gates, not
interchangeable ones.

For files, App Sandbox adds another vocabulary. A user-selected file or a security-scoped bookmark
can extend a sandboxed process's access to a specific resource. Apple's [sandbox file-access
documentation](https://developer.apple.com/documentation/security/accessing-files-from-the-macos-app-sandbox)
also notes that other access-control layers can still reject the operation. A reachable URL is a
capability. It is not proof that a parser, uploader, helper, or account transition will use it safely.

<figure class="diagram">
<svg viewBox="0 0 720 326" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A macOS data request passes through declaration, code identity, TCC decision, application use policy, bounded effect and retention or revocation gates.">
<defs><marker id="mtcp-a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow" /></marker></defs>
<rect x="16" y="24" width="122" height="52" rx="8" class="dg-box"/><text x="30" y="45" class="dg-label">feature intent</text><text x="30" y="62" class="dg-muted">WHY · WHEN</text>
<line x1="140" y1="50" x2="172" y2="50" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="176" y="24" width="134" height="52" rx="8" class="dg-box"/><text x="190" y="45" class="dg-label">code identity</text><text x="190" y="62" class="dg-muted">BINARY · SIGNING</text>
<line x1="312" y1="50" x2="344" y2="50" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="348" y="24" width="142" height="52" rx="8" class="dg-box-accent"/><text x="362" y="45" class="dg-label">TCC decision</text><text x="362" y="62" class="dg-accent">GRANT · DENY</text>
<line x1="492" y1="50" x2="524" y2="50" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="528" y="24" width="176" height="52" rx="8" class="dg-box-accent"/><text x="542" y="45" class="dg-label">resource capability</text><text x="542" y="62" class="dg-accent">HANDLE · STREAM</text>
<line x1="616" y1="78" x2="616" y2="116" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="528" y="120" width="176" height="54" rx="8" class="dg-box"/><text x="542" y="142" class="dg-label">use policy</text><text x="542" y="160" class="dg-muted">FEATURE · ACCOUNT</text>
<line x1="526" y1="147" x2="494" y2="147" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="344" y="120" width="146" height="54" rx="8" class="dg-box"/><text x="358" y="142" class="dg-label">bounded effect</text><text x="358" y="160" class="dg-muted">LOCAL · REMOTE</text>
<line x1="342" y1="147" x2="310" y2="147" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="150" y="120" width="156" height="54" rx="8" class="dg-box-accent"/><text x="164" y="142" class="dg-label">evidence</text><text x="164" y="160" class="dg-accent">OUTCOME · EFFECT</text>
<line x1="228" y1="176" x2="228" y2="214" class="dg-line" marker-end="url(#mtcp-a)"/>
<rect x="150" y="218" width="156" height="54" rx="8" class="dg-box"/><text x="164" y="240" class="dg-label">lifecycle</text><text x="164" y="258" class="dg-muted">RETAIN · REVOKE</text>
<text x="16" y="307" class="dg-crit">CONSENT IS SCOPED</text><text x="164" y="307" class="dg-mono">resource access ≠ unrestricted downstream data use</text>
</svg>
<figcaption>The system grant creates a capability. Application policy still has to bind that capability to a feature, account, destination, and lifecycle.</figcaption>
</figure>

## Build the privacy surface before touching a setting

On a personal or customer Mac, do not begin by dumping privacy databases or resetting decisions.
Start with the application artifact and product documentation. The goal is to understand what the
product declares without collecting the person's actual grants.

For every executable in the supplied app bundle, record:

- the exact code-signing identifier and designated requirement;
- sandbox and privacy-sensitive entitlements;
- usage-description keys and their localized purpose strings;
- linked frameworks and code paths that request protected resources;
- XPC services, login items, plug-ins, and extensions involved in the workflow;
- the feature state in which the request should occur;
- local consumers, remote destinations, retention rules, and deletion behavior.

Read-only artifact inspection can answer many of these questions:

```bash
codesign -d --entitlements :- CustomerApp.app
plutil -p CustomerApp.app/Contents/Info.plist
find CustomerApp.app/Contents -maxdepth 4 -type d -name '*.xpc' -print
```

These examples intentionally point to a customer-supplied test artifact. They do not justify
enumerating the user's installed applications or opening the per-user TCC database. A personal Mac
contains sensitive evidence about the owner's applications, habits, accounts, and prior decisions;
that information is not needed to explain this methodology.

Turn the static inventory into a feature matrix:

| Feature         | Responsible executable  | Protected class  | Prompt trigger             | Purpose shown          | Returned capability | Downstream effect    |
| --------------- | ----------------------- | ---------------- | -------------------------- | ---------------------- | ------------------- | -------------------- |
| Voice note      | exact client identity   | microphone       | explicit record action     | record this note       | audio stream        | local draft          |
| Import          | main app                | selected files   | open panel                 | user selection         | scoped URL          | local parser         |
| Automation      | XPC client and target   | Apple Events     | explicit integration setup | control named app      | event channel       | bounded command      |
| Support capture | system extension or app | screen recording | explicit support session   | capture visible screen | frame stream        | approved case upload |

The last column prevents a shallow review. Two products can request the same resource and create very
different risk: one transforms a local selection into an ephemeral preview; another sends continuous
data to a remote processor and retains it after logout.

## Test state transitions, not only the happy prompt

A useful privacy test has controlled states. Use a dedicated test account or disposable test Mac with
synthetic contacts, calendars, files, audio, and screen content. Never use the assessor's personal
records as convenient fixtures.

At minimum, exercise:

1. **Undetermined:** the feature should explain the action before the operating-system prompt and
   avoid access before a deliberate gesture.
2. **Denied:** the feature should fail closed, preserve unrelated functions, and offer a truthful
   recovery path without repeatedly coercing the person.
3. **Granted:** only the responsible component and requested workflow should receive the capability.
4. **Changed while running:** revocation should stop or bound active capture and produce an observable
   state change.
5. **Account transition:** logout, tenant switch, and role reduction should invalidate application
   authorization even if the system-level grant remains.
6. **Feature disabled:** local queues, bookmarks, caches, background jobs, and helper state should no
   longer continue the data use.

Apple documents `tccutil reset` for development testing, but that is a state-changing operation. Use
it only on the named test identity with an agreed recovery plan—not on a personal laptop merely to
make a prompt reappear. Record the exact precondition and expected state; a reset is setup, not proof
of a vulnerability.

## The dangerous gap is after access succeeds

Once an API returns data, platform consent may no longer distinguish the good and bad application
paths. The assessment should follow the object into parsers, shared containers, databases, logs,
analytics, XPC calls, cloud requests, exports, and model inputs.

Look for broken bindings:

- data approved for local preview becomes an automatic remote upload;
- a security-scoped bookmark remains usable after the project or account is removed;
- a helper with a broader grant accepts a path or operation from a weaker client;
- an extension places protected data in a shared container with unnecessary consumers;
- a denied or revoked state changes UI text but does not stop the active stream;
- a purpose string describes one feature while a background component performs another;
- diagnostic logs include filenames, contact values, message bodies, screen text, or stable user IDs.

The correct report does not say “TCC bypass” unless platform enforcement was actually bypassed.
Usually the finding is an application authorization, minimization, retention, or transparency failure
after legitimate access. Naming the failed boundary accurately makes the remediation both smaller and
stronger.

<figure class="diagram">
<svg viewBox="0 0 720 326" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A negative-control test compares an allowed feature and account with mismatched feature, account, destination and revoked consent paths that must all fail closed.">
<defs><marker id="mtcp-b" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow" /></marker></defs>
<rect x="18" y="22" width="160" height="54" rx="8" class="dg-box-accent"/><text x="32" y="44" class="dg-label">synthetic fixture</text><text x="32" y="62" class="dg-accent">NO PERSONAL DATA</text>
<line x1="180" y1="49" x2="218" y2="49" class="dg-line" marker-end="url(#mtcp-b)"/>
<rect x="222" y="22" width="166" height="54" rx="8" class="dg-box"/><text x="236" y="44" class="dg-label">expected context</text><text x="236" y="62" class="dg-muted">FEATURE · ACCOUNT</text>
<line x1="390" y1="49" x2="428" y2="49" class="dg-line" marker-end="url(#mtcp-b)"/>
<rect x="432" y="22" width="166" height="54" rx="8" class="dg-box-accent"/><text x="446" y="44" class="dg-label">allowed use</text><text x="446" y="62" class="dg-accent">ONE EFFECT</text>
<rect x="222" y="118" width="166" height="54" rx="8" class="dg-box"/><text x="236" y="140" class="dg-label">wrong feature</text><text x="236" y="158" class="dg-muted">DENY</text>
<rect x="222" y="190" width="166" height="54" rx="8" class="dg-box"/><text x="236" y="212" class="dg-label">wrong account</text><text x="236" y="230" class="dg-muted">DENY</text>
<rect x="432" y="118" width="166" height="54" rx="8" class="dg-box"/><text x="446" y="140" class="dg-label">wrong destination</text><text x="446" y="158" class="dg-muted">DENY</text>
<rect x="432" y="190" width="166" height="54" rx="8" class="dg-box-danger"/><text x="446" y="212" class="dg-label">consent revoked</text><text x="446" y="230" class="dg-danger">STOP · DISCARD</text>
<line x1="98" y1="78" x2="98" y2="268" class="dg-line"/><line x1="98" y1="145" x2="218" y2="145" class="dg-line" marker-end="url(#mtcp-b)"/><line x1="98" y1="217" x2="218" y2="217" class="dg-line" marker-end="url(#mtcp-b)"/><line x1="98" y1="145" x2="428" y2="145" class="dg-line" marker-end="url(#mtcp-b)"/><line x1="98" y1="217" x2="428" y2="217" class="dg-line" marker-end="url(#mtcp-b)"/>
<text x="18" y="308" class="dg-crit">PROOF REQUIRES CONTRAST</text><text x="206" y="308" class="dg-mono">one intended path plus denied and revoked controls</text>
</svg>
<figcaption>A granted test is incomplete. Change one authorization variable at a time and prove that mismatched or revoked contexts cannot produce the same effect.</figcaption>
</figure>

## Evidence matrix

| Claim                                      | Positive evidence                                                                            | Negative control                                           | What remains unproven                                     |
| ------------------------------------------ | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------- |
| The prompt belongs to the intended feature | Synthetic fixture, deliberate gesture, matching purpose string, requesting binary identified | Invoke unrelated feature; no prompt or access              | Every localization and legacy OS version                  |
| Denial fails closed                        | Protected API returns denied and no data-dependent effect occurs                             | Repeat action and restart helper                           | Third-party SDK behavior not instrumented                 |
| Grant is bound to the active account       | Intended account produces one expected local or remote effect                                | Switch account while platform grant remains; effect denied | Server-side retention outside test access                 |
| Revocation stops use                       | Active stream or bookmark access ends and UI state changes                                   | Reopen stale object or restart background component        | Future OS implementation changes                          |
| Logs preserve privacy                      | Synthetic marker correlates events while content remains redacted                            | Search approved diagnostic export for raw fixture value    | Logs outside the agreed collection window                 |
| Cleanup is complete                        | Test records, bookmarks, caches, queues, and helpers are removed                             | Relaunch and confirm fixture is not resumed                | Personal-device privacy state, deliberately not collected |

The matrix keeps observation and interpretation separate. A visible prompt proves that a request
occurred; it does not prove why a framework requested it. A successful API call proves reachability;
it does not prove appropriate retention. A missing log line proves neither absence nor denial unless
the expected telemetry path was independently validated.

## Remediation should preserve the platform boundary

Good fixes rarely involve weakening macOS privacy controls. They make the application use narrower
and more explainable:

- request access at the moment a person starts the named feature;
- make every purpose string specific, localized, and consistent with actual downstream use;
- keep protected processing in the least-privileged component that needs it;
- pass narrow results across XPC instead of raw data or reusable paths;
- bind operations to trusted account and feature state, not client-supplied identifiers;
- discard stale security-scoped bookmarks, queues, and caches on logout or revocation;
- make background capture visible and independently stoppable;
- log decisions and effect classes while keeping content and personal identifiers private;
- document the test state that proves denied and revoked paths remain safe.

TCC is not an application policy engine, and it should not be asked to become one. Its strength is
that it places the person and the operating system at the resource boundary. The product's strength
must come from preserving that intent after the resource crosses into application code.

## Sources

- Apple Developer Documentation, [Requesting access to protected resources](https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources)
- Apple Developer Documentation, [Resetting access to protected resources in macOS](https://developer.apple.com/documentation/xcode/resetting-access-to-protected-resources-in-macos)
- Apple Developer Documentation, [Accessing files from the macOS App Sandbox](https://developer.apple.com/documentation/security/accessing-files-from-the-macos-app-sandbox)
- Apple Developer Documentation, [Protecting user data with App Sandbox](https://developer.apple.com/documentation/security/protecting-user-data-with-app-sandbox)
- Apple Support, [Change Privacy & Security settings on Mac](https://support.apple.com/guide/mac-help/change-privacy-security-settings-on-mac-mchl211c911f/mac)

This is a source-reviewed assessment methodology. It does not claim a TCC bypass, inspect a personal
TCC database, or present synthetic controls as findings from a production application.