How to give file&folder access to an unknown sourced app?

I'm developing my own app in Unity so it's so far an unknown sourced app, which requires access to the file system since it reads and writes data. My app doesn't have the installation process. Double-clicking on the filename.app will directly open and run it.

The first issue is the entitlement. I've added the following entitlement: (https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_all )
  • com.apple.security.files.all (which is deprecated according to the doc)

  • com.apple.security.files.user-selected.read-write

  • com.apple.security.files.downloads.read-write

But it only works in Downloads/ folder. The app popped up this error when I put the .app file outside the Downloads/ folder (for example Documents/): unauthorizedaccessexception: access to the path is denied. This must be something to do with the entitlement.

The second issue is the privacy settings.
The first time when I run it, the dialog asking for permissions will pop up, which is fine. But if I delete the app and download it again from the Internet (e.g. Google Drive) for simulating the reinstalling procedure in the Downloads/ folder, the error mentioned above will appear again.

Here're what I've tried yet none of them worked:
  • Allow apps downloaded from "Anywhere" in the "Security & Privacy" setting;

  • Grant "Full Disk Access" to the app;

  • Change it to read&write for everyone in "Get Info".

The last annoying issue is that if I delete the app from "Files&Folders" in the Privacy settings, there'll be no way to restore it. The dialog won't pop-up again and the data access will thus be denied.

Any help with all these? Thanks a lot in advance!
Answered by DTS Engineer in 675310022
OK, there’s a lot to unpack here. You wrote:

I didn't use Xcode. The app is built directly from Unity.

That’s fine. macOS’s file system permissions model applies to all apps regardless of how the app was built. Sometimes you run into issues because your third-party tool constructs the app weirdly (yes, I’m looking at you Java!) but there’s nothing about Xcode that makes it fundamentally special.

is sharing the .app through Google Drive link regarded as "Software
distribution outside the Mac App Store"?

No. The key issue regarding app distribution is quarantine. When you download an app using Safari it quarantines it. When you launch a quarantined app, the system invokes Gatekeeper. See the Safely open apps on your Mac (from Apple Support) for more about this technology.

Quarantine in an opt-in system; the app doing the download controls whether something is quarantine. I don’t use Google Drive so I don’t know if it applies quarantine. It’s easy to check in Terminal though:

Code Block
% xattr /path/to/your.app


If this lists a com.apple.quarantine attribute, the app is quarantined.

If you user runs a quarantined app then one of two things happens:
  • If the app comes from the Mac App Store then Gatekeeper… actually, I’m not 100% sure what the process is in this case but it doesn’t matter to you so let’s move on.

  • If the app is distributed independently, it must be signed with a Developer ID signing identity and also notarised.

However, neither of these apply to you because you’re not distributing the app to other folks. In such circumstances the best approach is to make sure that your app doesn’t get quarantined.

Fortunately that seems like the case here because, if Gatekeeper were blocking your app, you’d get a big alert telling you that, not a file system permissions problem.

Also it needs to read a data file, which is exactly in the same folder
where the app is located.

This is your fundamental problem. Mac apps simply don’t work this way. Consider what happens for a normal app, installed in the Applications directory:
  1. User A logs in.

  2. And configures your app by modifying /Applications/DataToBeRead.txt.

  3. And then logs out.

  4. Now user B logs in.

  5. Whoops, they have user A’s configuration.

Worse yet, if user B is not an Administrator they won’t be able to change that configuration.

Mac apps are required to store their configuration elsewhere. Exactly how you do that depends on the specifics of your app. Before I go into details, however, I want to point you to my On File System Permissions post. I’m going to use a lot of terms from that post, so read it through first.

The reason why this fails in your specific case is that the Downloads directory is subject to MAC. So, if the app is in the Downloads directory and it tries to read DataToBeRead.txt from there, MAC kicks in and blocks the read.

The solution isn’t to try and get around MAC but rather to put the configuration file in a place that’s more in line with platform norms. How you do that depends on your target users:
  • If this is just a quick hack with a limited number of users, store the file in the Application Support directory (or a directory that you create within that directory) and then tell your users how to locate that.

  • If you plan to deploy this to a wider audience then things get more complex. I recommend that you adopt one of the two Mac app paradigms, namely, build a document-based app (think TextEdit) or a shoebox app (think Photos). In a document-based app the user is in complete control over where documents are saved. In a shoebox app you choose a default location (like the Application Support directory) and then, optionally, let the user override that. To see what I mean, look at the UI you get when you launch Photos while holding down the Option key.



A few more notes…

Don’t hard code paths. Rather, use FileManager to locate these various directories. For example, this code:

Code Block
let appSupportDir = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
print(appSupportDir.path)
// -> /Users/quinn/Library/Application Support


The Finder hides ~/Library by default. If you want to access it, choose Go > Go to Folder and enter ~/Library.

The locations you get back from FileManager depend on whether your app is sandboxed. The above example was for a non-sandboxed app. If it were sandboxed the last line would look something like this:

Code Block
// -> /Users/quinn/Library/Containers/com.example.apple-samplecode.MySandboxedApp/Data/Library/Application Support


Sandboxed apps are not allowed to access standard locations (like ~/Library/Application Support) without express user permission, so File Manager redirects all such references to within the app’s container.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
I’m struggling to understand the context of this question. You wrote:

My app doesn't have the installation process. Double-clicking on the
filename.app will directly open and run it.

suggesting that your product is a standalone app. However, you also wrote:

The app popped up this error when I put the .app file outside the
Downloads folder (for example Documents):
unauthorizedaccessexception: access to the path is denied.

which suggests that you’re having problems accessing a file outside of your app’s bundle (apps always have access to files within their own bundle). So, in the failing case, your app is in ~/Documents/MyApp.app, right? But where is the file you’re trying to access?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Hi eskimo, thanks for your reply!

Yes this is a standalone app. Basically what I did is to upload it to Google Drive because I want to share it with someone else without touching the App Store. But when I downloaded it again, it doesn't work even in Downloads/ (which works before I download from Google Drive, namely the local version built directly from Unity). The app will create new text files and write some data, that's why the app requires the disk access. Also it needs to read a data file, which is exactly in the same folder where the app is located.

I'm new to MacOS development, so maybe I used wrong words. After building, the app is wrapped up as MyApp.app. Every time I moved MyApp.app together with the datafile. That is,

/Folder/MyApp.app
/Folder/DataToBeRead.txt


So if the Folder/ is Downloads/, and the app is working correctly only in the local version. That'll be weird if it doesn't work after I download it just because of the file reading, since the location of the data file didn't change. What's more weird is that even I granted full disk permission to the downloaded version in the system preferences, it still doesn't work.
Forgot to mention the following details:
  1. I didn't use Xcode. The app is built directly from Unity.

  2. The version of MacOS is Mac Big Sur 11.3, and Xcode is 12.5.

  3. The app also writes to the same folder where it's located.

  4. All other cases don't work except for this one. Namely if Folder/ is not Downloads/, or the app is the downloaded version, the data access issue will appear.

So if the Folder/ is Downloads/, the app is working correctly only in the local version.

So after searching for a while, I got this question: is sharing the .app through Google Drive link regarded as "Software distribution outside the Mac App Store"? I found in this webpage (https://developer.apple.com/support/compare-memberships/), it seems that if I sign in with my Apple ID, I can't distribute the app outside the Mac App Store. I'm not sure if this is part of the reasons why the data access is denied.
Accepted Answer
OK, there’s a lot to unpack here. You wrote:

I didn't use Xcode. The app is built directly from Unity.

That’s fine. macOS’s file system permissions model applies to all apps regardless of how the app was built. Sometimes you run into issues because your third-party tool constructs the app weirdly (yes, I’m looking at you Java!) but there’s nothing about Xcode that makes it fundamentally special.

is sharing the .app through Google Drive link regarded as "Software
distribution outside the Mac App Store"?

No. The key issue regarding app distribution is quarantine. When you download an app using Safari it quarantines it. When you launch a quarantined app, the system invokes Gatekeeper. See the Safely open apps on your Mac (from Apple Support) for more about this technology.

Quarantine in an opt-in system; the app doing the download controls whether something is quarantine. I don’t use Google Drive so I don’t know if it applies quarantine. It’s easy to check in Terminal though:

Code Block
% xattr /path/to/your.app


If this lists a com.apple.quarantine attribute, the app is quarantined.

If you user runs a quarantined app then one of two things happens:
  • If the app comes from the Mac App Store then Gatekeeper… actually, I’m not 100% sure what the process is in this case but it doesn’t matter to you so let’s move on.

  • If the app is distributed independently, it must be signed with a Developer ID signing identity and also notarised.

However, neither of these apply to you because you’re not distributing the app to other folks. In such circumstances the best approach is to make sure that your app doesn’t get quarantined.

Fortunately that seems like the case here because, if Gatekeeper were blocking your app, you’d get a big alert telling you that, not a file system permissions problem.

Also it needs to read a data file, which is exactly in the same folder
where the app is located.

This is your fundamental problem. Mac apps simply don’t work this way. Consider what happens for a normal app, installed in the Applications directory:
  1. User A logs in.

  2. And configures your app by modifying /Applications/DataToBeRead.txt.

  3. And then logs out.

  4. Now user B logs in.

  5. Whoops, they have user A’s configuration.

Worse yet, if user B is not an Administrator they won’t be able to change that configuration.

Mac apps are required to store their configuration elsewhere. Exactly how you do that depends on the specifics of your app. Before I go into details, however, I want to point you to my On File System Permissions post. I’m going to use a lot of terms from that post, so read it through first.

The reason why this fails in your specific case is that the Downloads directory is subject to MAC. So, if the app is in the Downloads directory and it tries to read DataToBeRead.txt from there, MAC kicks in and blocks the read.

The solution isn’t to try and get around MAC but rather to put the configuration file in a place that’s more in line with platform norms. How you do that depends on your target users:
  • If this is just a quick hack with a limited number of users, store the file in the Application Support directory (or a directory that you create within that directory) and then tell your users how to locate that.

  • If you plan to deploy this to a wider audience then things get more complex. I recommend that you adopt one of the two Mac app paradigms, namely, build a document-based app (think TextEdit) or a shoebox app (think Photos). In a document-based app the user is in complete control over where documents are saved. In a shoebox app you choose a default location (like the Application Support directory) and then, optionally, let the user override that. To see what I mean, look at the UI you get when you launch Photos while holding down the Option key.



A few more notes…

Don’t hard code paths. Rather, use FileManager to locate these various directories. For example, this code:

Code Block
let appSupportDir = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
print(appSupportDir.path)
// -> /Users/quinn/Library/Application Support


The Finder hides ~/Library by default. If you want to access it, choose Go > Go to Folder and enter ~/Library.

The locations you get back from FileManager depend on whether your app is sandboxed. The above example was for a non-sandboxed app. If it were sandboxed the last line would look something like this:

Code Block
// -> /Users/quinn/Library/Containers/com.example.apple-samplecode.MySandboxedApp/Data/Library/Application Support


Sandboxed apps are not allowed to access standard locations (like ~/Library/Application Support) without express user permission, so File Manager redirects all such references to within the app’s container.

Share and Enjoy

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

Hi eskimo, sorry for replying so late. You are awesome! It's working! I didn't touch the FileManager programming stuff because I don't know where and how to program this on Mac. For those who have the same issue, what I did is to change the path in Unity from System.IO.Directory.GetCurrentDirectory() to Application.persistentDataPath, and the path will be exactly the same as

// -> /Users/quinn/Library/Containers/com.example.apple-samplecode.MySandboxedApp/Data/Library/Application Support 

(my app is sand-boxed) Previously I wanted to record the data at the same place of the executable app, but it seems to be troublesome. So for individual open source testing, .applicationSupportDirectory (Application.persistentDataPath in Unity) should be the safest setting because it never changes no matter the app is quarantined or not.

Thanks again eskimo! Appreciate it a lot!

How to give file&folder access to an unknown sourced app?
 
 
Q