How to grant command line tools full disk access

Hello all,

I'm looking for clarification on the functionality of Full Disk Access (FDA) in macOS. To illustrate my case, consider the following simple example program:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    const char *filePath = "/Library/Preferences/com.apple.TimeMachine.plist";

    // Try to open the file
    FILE *file = fopen(filePath, "r");
    if (file == NULL) {
        // If there is an error opening the file, print the error and exit
        printf("Error opening file %s: %s\n", filePath, strerror(errno));
        return 1;
    }

    fclose(file);

    // If we reached here, the file was successfully opened
    printf("File %s opened successfully\n", filePath);

    return 0;
}

When this program is built and executed in Terminal.app with Terminal having FDA, the file opens successfully. Conversely, when FDA is revoked from Terminal and granted to the program, an error occurs due to insufficient privileges.

Interestingly, building and executing the program within Xcode, without Xcode having FDA, but granting FDA to the resulting binary (either debug or release), allows the file to open successfully. Which is what I would expect for the above case as well.

Running the same binary (with FDA enabled), which runs successfully within Xcode, in Terminal yields an error message.

So, I have the following questions based on these observations:

  1. Why does the program access the file successfully when run from within Xcode, despite Xcode lacking FDA?
  2. Why does the program fail to access the file when run from Terminal without FDA, even though the program itself has FDA?
  3. What is the precise relationship between a parent process and its child process concerning FDA?

These tests were conducted on macOS 14.5 with Xcode 15.4.

Thanks in advance!

Answered by DTS Engineer in 789812022

TCC has a the concept of finding the responsible code. I talk about this in some detail in On File System Permissions. The exact algorithm it uses for this is not documented, has changed in the past, and may well change in the future. Given that, I’m not able to answer your specific questions [1].

If your building a product for macOS then I’m happy to offer suggestions for how to structure it to work well with TCC, but now and in the future. If you’d like to go down that path, please post some details about what your product does.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well I could answer “These are all implementation details of the current algorithm.” but that’s not very helpful (-:

TCC has a the concept of finding the responsible code. I talk about this in some detail in On File System Permissions. The exact algorithm it uses for this is not documented, has changed in the past, and may well change in the future. Given that, I’m not able to answer your specific questions [1].

If your building a product for macOS then I’m happy to offer suggestions for how to structure it to work well with TCC, but now and in the future. If you’d like to go down that path, please post some details about what your product does.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well I could answer “These are all implementation details of the current algorithm.” but that’s not very helpful (-:

From a user's or developer's perspective this behavior is not comprehensible.

  • If I build a command line tool in Xcode and it lacks FDA approval, it doesn't work ✅
  • If I then add FDA for that binary and run it from Xcode it works ✅
  • but then it still can't be run from the terminal (if the terminal does not have FDA), what? 🚨 **Why is the FDA approval only honored when it is run by Xcode? **
  • How can I compile a program w/o Xcode and grant it FDA?

The fact that this kind of behavior is not clearly documented (and all the other missing important documentation) is a constant source of frustration for us developers.

Most command-line tools are written to for a specific use case. The common ones are:

  • Run by the user from Terminal, or over SSH

  • Spawned as a child proces (a helper tool) by an app

  • Run by launchd as a daemon or agent

Each of these has a fairly well-understood TCC story, and I’m happy to go into those details.

If you run the same tool in multiple scenarios, the story gets more muddled.

Why is the FDA approval only honored when it is run by Xcode?

Because Xcode runs the tool in a way that makes it clear to the system that Xcode is not the responsible code for this process.

How can I compile a program w/o Xcode and grant it FDA?

It’s not about how the tool is compiled [1], it’s about how it’s run. For example, if your run the tool as a launchd daemon then launchd is not considered the tool’s responsible code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Assuming your tool is signed with a stable code-signing identity. Without that, you’ll see weird behaviour.

Thanks, that's helpful.

Each of these has a fairly well-understood TCC story, and I’m happy to go into those details.

That would be much appreciated!

I initially encountered this problem when trying to use CLion by Jetbrains. When I try to develop a command line tool in CLion and run it there, my program does not get FDA unless I approve FDA for CLion - my actual program does not matter. While this seems to go against the least privilege approach, I can live with that.

  • What would Jetbrains need to do to behave like Xcode in this case?
  • Would the story differ if I put the command line tool in an app bundle?
That would be much appreciated!
  • Run by the user from Terminal — The tool’s responsible code is Terminal.

  • Run by the user … over SSH — The tool’s responsible code is the SSH server. You can set Full Disk Access on that using System Settings > General > Sharing > Remote Login > Allow full disk access for remote users.

  • Spawned as a child process (a helper tool) by an app — The system treats the app as the responsible code.

  • Run by launchd as a daemon or agent — If the daemon or agent was installed by SMAppService, that makes the app the responsible code. Otherwise, the daemon or agent should include AssociatedBundleIdentifiers in its launchd property list.

What would Jetbrains need to do to behave like Xcode in this case?

I don’t know.

It’s not uncommon for debuggers to follow a much harder path, and this is no exception. When you run a command-line tool from Xcode there’s a long chain between your tool and Xcode itself. For example, I just did this on my Mac (Xcode 15.4 on macOS 14.4.1) and got MyTool > debugserver > lldb-rpc-server > Xcode. I expect that one of these is breaking TCC’s responsible code search. Moreover, I suspect that’s deliberate, because we want the behaviour you’re seeing.

If you run your tool from the command-line debugger, lldb, what behaviour do you get?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello Quinn,

Thank you for your reply. In the interim, others have contributed, and my questions have largely been addressed. We do not have a specific issue or use case at the moment; rather, we were seeking clarification on the behavior as it was not entirely clear to us. It is crucial for us to understand the intended behavior of TCC.

Following your suggestion to wriker, I ran the above test program in LLDB from the command line. Interestingly, the program can open the file successfully if the binary has FDA, despite neither LLDB nor Terminal having FDA.

How to grant command line tools full disk access
 
 
Q