How privacy protection is enforced through the Attribution Chain

When you’re running an app in Mojave, and that app tries to access privacy-protected data or services, the process is fairly simple in that there are normally only two players: the app and TCC, which controls access to those protected resources. I have already explained how that works in terms of user controls and applications.

What is potentially more complex and confusing is when there are others involved too. In this article, I’m going to look at running a simple command in Terminal, to list the contents of ~/Library/Calendars, as an example.


Terminal version 2.9 in Mojave 10.14 (inspected here using Taccy) makes no claims to be able to access private data or services through its entitlements, nor does it contain any usage statements which would be displayed when prompting a user for consent. Unless you add it (or its tools) to the Full Disk Access list in Privacy, it and all the commands you run within it will be denied access to protected folders and data. So entering the command
ls ~/Library/Calendars
will engage TCC’s full might.

What follows here are some short edited excerpts from the Mojave unified log. Each entry consists of the clock time in blue, the subsystem in red (invariably, and the eventMessage in black.



Early log entries after entering that command show the TCC subsystem,, already looking at the Attribution Chain. Access is being called by the ls command,, from the responsible app Terminal. The service being requested is for Full Disk Access (kTCCServiceSystemPolicyAllFiles).



However, Full Disk Access is not something that a command or app can obtain through the normal consent dialog system (only the user can add items to that list), and in any case the most specific request would instead be for access to the Calendar (kTCCServiceCalendar). So the request actually made is for Calendar access by, on behalf of the tool ls.



TCC’s attention turns now to head of the Attribution Chain, the app Terminal. Set in its database is the rule that Terminal is allowed to generate consent dialogs without requiring usage information strings. This is a special case: if you’re using a third-party app instead, then it will be expected to comply with the normal rules.

TCC therefore decides to prompt for Calendar access from Terminal, on behalf of the command ls. It confirms that the app has no usage string which needs to be inserted into the consent dialog, and checks the SDK against which was built, although in this case Terminal is a special case.

If this was a third-party app, TCC would discover that the app was built against the 10.14 SDK (in hex 0a0e00), and would therefore require that a usage string was present. As it cannot find one here, that would normally result in the request being automatically refused, and no dialog would be displayed. But as this is Terminal, a special case, TCC proceeds to display the consent dialog.



In this case, I refused consent. The request is therefore refused, and my command ls returns a refusal error.

I then closed, and added it to the Full Disk Access list in Privacy. Note that this will only take effect the next time that app is opened; if you add an app to a Privacy list while that app is still running, don’t expect to see any change in its behaviour until that app is closed and opened again.



When I then entered the same command, the log exchange was far more brief. TCC builds the same Attribution Chain, tracing the request from the ls command to, which its database says is allowed. TCC therefore gives its approval for the access request, and the command completes as expected.

In this case, when running a command from within the bash shell inside Terminal, the command has an Attribution Chain which takes it not to bash, but to the Terminal app itself. In more complex cases, such as LaunchAgents, which don’t involve a GUI app like Terminal, the Attribution Chain can be harder to see. You should then consider each step in the calling chain, and try giving those Full Disk Access:

  1. the tool itself, here ls,
  2. any script calling the tool,
  3. the shell running the script or tool,
  4. any app running the script or tool.

Work from most to least specific, trying the command tool first, any script calling it, the shell running the script such as bash, or wrapper app. Thanks to the work of Channing Walton here, we now know that to get scripts running properly in a LaunchAgent, you may have to add the shell explicitly to the Property List defining that LaunchAgent, and add that shell command to the Full Disk Access list in Privacy.

Thus, instead of the ProgramArguments for the LaunchAgent being
for example, you may have to specify them as

and then drag and drop the bash command from /bin into the Full Disk Access list in Privacy.

It is possible to add a shell script file to the Full Disk Access list, but whether that serves any useful purpose is doubtful: it all depends whether TCC sees that in the Attribution Chain.

To discover where in that Chain a request for protected data or services is failing, the simplest solution is to browse the log, looking at messages from the subsystem, whose entries should lead you to a solution, and deciding what to add the the Full Disk Access list.