16

After upgrading to Mojave, my rsync based backup script run via a launch agent in ~/Library/LaunchAgents, could no longer read some directories in ~/Library.

1
  • 2
    What you are running up against is not System Integrity Protection. Were you running into System Integrity Protection, the solution—and the only solution, outside of changing your script—would be to disable System Integrity Protection. Commented Nov 21, 2018 at 1:00

10 Answers 10

10

I've spent a few weeks trying to sort this out. I agree that the currently accepted answer is not really a solution -- it's not much better than just disabling SIP altogether.

My solution is a totally hacky workaround, but doesn't require whitelisting bash entirely. Update: Slightly less hacky workaround below.

Update 20190226: As detailed in the Restic issue linked below, this seems to have stopped working. The original binaries I created with this method continue to work without errors for some strange reason, but I can't give access to new binaries directly using this method.

Overview

  1. Package a standard MacOS application that runs a script (a bash script in my case, which in turn sets up an environment and runs a separate binary that requires FDA)
  2. Add that application to FDA
  3. Run the application by opening the .app (either by double clicking via GUI, or open /path/to/MyApp.app but not by directly using the executable stored in e.g. MyApp.app/Contents/Resources/)

For Step 1, you can easily package your script as an app by using tools built into MacOS such as Automator.app or Script Editor, or Platypus also works.

Example contents of such an app might be a simple AppleScript (in Script Editor) such as:

on run
    do shell script "/usr/local/bin/bash /path/to/myscript.sh"
end run

From Script Editor, use the dropdown in the save menu to save as an application, then CLOSE SCRIPT EDITOR.

Add this app to Full Disk Access through System Preferences -> Security & Privacy.

NB: If you didn't save and close Script Editor prior to adding to FDA as instructed, it seems that some kind of invisible process (automated background saves?) will change something (some kind of timestamp or hash?) that is required for Full Disk Access, which can some intermittent errors that were a major headache to figure out. So if you didn't do this, remove your app from FDA, save and close Script Editor, then add back to FDA.

For your LaunchAgent, use something like:

<string>/usr/bin/open</string>
<string>/path/to/MyApp.app</string>

Root access

If your backup script needs root access (e.g. to back up root-owned 0600 files), you're in for another set of hacky workarounds, since /usr/bin/open won't seem to run anything as root (even if you specify the UserName key in in a root-owned /Library/LaunchDaemons/ plist. (I'd be happy to open this as a separate question if appropriate, since I think the below workaround leaves much to be desired.)

One option for this is to add with administrator privileges in your AppleScript, but this requires manually typing in your password, defeating the purpose of automated backup. My current workaround is to:

  1. sudo visudo and give my unprivileged user the ability to run my backup script as sudo with NOPASSWD: (I also specify the hash of the script to hopefully improve security, e.g. myuser ALL=(ALL) NOPASSWD: sha256:hashgoeshere /path/to/myscript.sh)
  2. sudo chown root myscript.sh
  3. sudo chmod 0740 myscript.sh (4 so it can still be added to VCS)
  4. Change the AppleScript to do shell script "sudo -n /path/to/myscript.sh" and resave as MyApp.app
  5. Add MyApp.app to FDA
  6. Change my launchd script to open /path/to/MyApp.app
  7. Reload launchd script with launchctl and test to make sure it seems to be working

Further reading / details:

UPDATE:

After some preliminary testing, it looks like a slightly less hacky workaround is to compile a binary (using a compiled language) that calls your bash script. Add that binary to FDA and it seems to work. Add to the root-owned /Library/LaunchDaemons plist, and you have a way to call it from root without all the craziness above.

Example script in Go:

// Runrestic provides a binary to run my restic backup script in MacOS Mojave with Full Disk Access
package main

import (
    "log"
    "os"
    "os/exec"
    "path/filepath"
)

func main() {
    ex, err := os.Executable()
    if err != nil {
        log.Fatal(err)
    }
    dir := filepath.Dir(ex)
    script := filepath.Join(dir, "restic-backup.sh")
    cmd := exec.Command("/usr/local/bin/bash", script)
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

For security, I sudo chown root and sudo chmod 0700 the resulting binary before adding to Full Disk Access (although admittedly an attacker could just change the bash script that this calls if it were left unprotected).

5
  • 1
    Thanks for spending the time to work through this. I am going to mark as the 'correct' answer because it's safer than mine. Commented Nov 21, 2018 at 17:46
  • This is the most helpful answer/article I could find on the topic, however it's been last updated 1 year ago. Have there been any improvements in workflow since then? Commented Apr 8, 2020 at 2:32
  • 1
    @Superman.Lopez I am still using the same process. There is a little more discussion in the restic issue I linked, but nothing that has substantially changed my process.
    – n8henrie
    Commented Apr 8, 2020 at 2:35
  • 2
    I just found out that LaunchControl has a helper tool to run launchd scripts with fda permissions. For my usage this is by far the fastest and easiest way to manage the permissions. I highly recommend the LaunchControl application, and it's reasonably priced. Commented Apr 13, 2020 at 2:57
  • 1
    Another potential workaround is to run the script via passwordless ssh to your own user/host
    – gingerlime
    Commented Nov 8, 2020 at 9:57
7

The currently accepted answer is a bit hard to follow with all of its updates. Here's a short summary of what currently does and doesn't work, plus a new tip.

Adding scripts or binaries to the "Full Disk Access" list no longer works. The only thing that works is adding an actual macOS app. As Channing mentions, Automator.app, Script Editor, or Platypus can be used to create one.

Tip: the whitelist applies to any script or binary inside the app's directory. So, you don't have to launch the app directly. In fact, the app itself is completely irrelevant -- it simply acts as a container for whitelisting. You can copy your script to an arbitrary app, whitelist that app, then run your script. The only caveat is that you have to remove and re-add the app to the whitelist every time you change your script or binary within it.

1
  • I just tested this theory and I confirm it works: created a dummy macOS .app which contains a shell script. Authorized app for full disk access and invoke the embedded shell script in a LaunchAgent and I confirm that the shell script has full disk access.
    – yan
    Commented Apr 18, 2021 at 18:48
6

I solved this as follows as follows:

Allow Bash to have Full Disk Access

  1. Open Preferences
  2. Go to Security & Preferences
  3. Select Full Disk Access in the list on the left
  4. Click the lock to make changes
  5. Click the + button on the list on the right
  6. Navigate to the root of your HD
  7. Press CMD+Shift+. to show all the hidden items
  8. Select /bin/bash
  9. Quit Preferences
  10. Restart the mac (I am not sure if this is really necessary)

Run your script correctly

The mistake I made was that the launch agent ran the script like this:

<key>ProgramArguments</key>
<array>
    <string>/Users/channing/bin/backup.sh</string>
</array>

Do this instead

<key>ProgramArguments</key>
<array>
    <string>/bin/bash</string>
    <string>/Users/channing/bin/backup.sh</string>
</array>

Restart your agent:

launchctl unload ~/Library/LaunchAgents/backup.plist
launchctl load ~/Library/LaunchAgents/backup.plist

Rejoice.

8
  • 4
    That's not a "solution", That punched a great big hole in the protections. It doesn't just let the script work, it lets anything that can get a shell prompt do whatever it wants. Commented Oct 2, 2018 at 17:40
  • 3
    @MarcWilson is correct. This solution is like carpet bombing a city because you want to renovate your kitchen.
    – Allan
    Commented Oct 2, 2018 at 20:04
  • 2
    To backup my home directory with rsync periodically. I also backup with Backblaze. Time Machine has failed once too often for me to trust it (corruption). Commented Oct 4, 2018 at 10:48
  • 3
    We need a way to trust a script. Commented Oct 5, 2018 at 6:24
  • 2
    @MarcWilson As far as I and other searchers are concerned, this is a solution. FDA is not a well-documented "feature" -- and with critical file operations failing in the meantime, this is a fine solution until we can figure out what's going on.
    – Matt
    Commented Oct 20, 2018 at 3:21
6

A new way to address this: LaunchControl now ships with a helper called fdautil that you can whitelist, and then run commands using fdautil exec. It will only allow commands you've whitelisted via LaunchControl or fdautil set.

There's a bit of info about it at https://www.soma-zone.com/LaunchControl/FAQ.html, and more details in the app's help window.

2
  • interesting work around, it seem you can use fdautil without paying for the license. But you do need to give it full disk access... its probably better to package your own script. Commented Apr 5, 2021 at 14:49
  • I think this is the right path. It lets you manage an "allowed list", so more granular control. I think this concept of an authorization proxy is the right approach, and it would be cool to see a dedicated tool to do this that's not bundled the way fdautil is. Commented Jul 1, 2023 at 16:33
5

Had the same issue on macOS Catalina trying to schedule Borg backups.

I created an app using "Script Editor" that runs /usr/local/bin/borg-backup.sh using zsh.

do shell script "zsh /usr/local/bin/borg-backup.sh"

I then exported the app to /Applications/borg-backup.app clicking "File" then "Export..." choosing "Application" for "File Format".

Finally, I updated ~/Library/LaunchAgents/local.borg-backup.plist.

<key>ProgramArguments</key>
<array>
  <string>open</string>
  <string>/Applications/borg-backup.app</string>
</array>

The first time the launch agent ran, a prompt asked me to grant borg-backup.app access to ~/Documents.

1
  • This works great, but won't allow you to run your script as root unfortunately. The accepted answer mentions this.
    – gingerlime
    Commented Nov 8, 2020 at 9:56
3

I did some testing and can't seem to give any normal† program full disk access anymore. I wrote a minimal shell script [1], a minimal binary calling that shell script [2], and a binary trying to access a secure location [3]. I then gave all these scripts/executables Full Disk Access, and also to /bin/sh for good measure. Calling any of these directly via the shell gives me an error.

I then stumbled across an Apple dev forums discussion about The Rules for Full Disk Access. It looks like you need an app bundle to give Full Disk Access permissions, which explains why granting full disk access to a terminal app enables that app to call ls ~/Library/Mail successfully.

However, it does not explain why you can grant access to /bin/bash and then use that in your launchd.plist file to have full disk access in your shell script.

† simple binary, not an App Bundle living in /Applications

[1] /Users/me/access-test.sh:

#!/bin/sh
ls /Users/me/Library/Mail

[2] /Users/me/access-test.c:

#include <unistd.h>

int main(int argc, char *const argv[]) {
    const char *file = "/Users/me/access-test.sh";
    return execvp(file, argv);
}

[3] /Users/me/access-test-2.c:

#include <stdio.h>
#include <dirent.h>

int main(void) {
    DIR *dp;
    struct dirent *ep;

    dp = opendir("/Users/me/Library/Mail");
    if (dp == NULL) {
        perror("Couldn't open directory");
        return 1;
    } else {
        while ((ep = readdir(dp))) {
            puts(ep->d_name);
        }
        closedir (dp);
    }
}
0
2

I faced a similar problem. For now it seems that I managed to get my backup.sh script to work properly through launchd by removing the .sh extension and making the script executable

mv backup.sh backup
chmod +x backup

Then I can add the script through the Full Disk Access dialog. From the .plist file I call /path/to/backup directly (i.e. without /bin/bash). Don't forget to add #!/bin/bash at the beginning of your script and to reload the .plist file.

2
  • Interesting - I'll try that. Commented Feb 13, 2019 at 10:45
  • This doesn't work for me. I'm able to add the script to the Full Disk Access list as you describe, but when running it via launchd, I still get Operation not permitted errors. This is on Mojave, 10.14.5.
    – user13569
    Commented Jul 1, 2019 at 6:25
0

I am having the same issue with some copies that I trigger through launchd that use ditto. Trying some tests too see what I can minimally add into Full Disk Access to get it to work. For bash scripts I'm wondering if you might be able to create an AppleScript application and use that to grant the access and call the script. It may give you a way to grant access to that specific application and avoid granting access to all things that you might run through bash

0

If you're not committed to using LaunchAgent, a simpler option is to run your script from your crontab, and add /usr/sbin/cron to System PreferencesSecurity & PrivacyPrivacy tab → Full Disk Access:

Screenshot of Security & Privacy preference pane

On macOS 12.1, this gives my backup script (run from cron) full access to everything in my user's home directory.

I believe this is still secure because commands executed by cron have to be added by you (the crontab files in /usr/lib/cron/tabs/ are protected by SIP). When I edit my crontab (via crontab -e) the OS prompts me if I give permission for the changes to be applied.

If you want to execute your backup script manually, in the terminal, you may have to add Terminal.app (or iTerm.app et al) to the Full Disk Access pane. This seems less secure because any command run in the terminal would then have full access to your disk (I would appreciate if anyone can confirm this).

0

https://github.com/sveinbjornt/Platypus is a way to turn a script into an app that you can give permissions to

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .