How to fix sandboxing problem in macOS Mojave?

Our app is not runnung on macOS Mojave any more.


Although a security scoped bookmark is created and started, the app has has no access to files in the defined directory for the security scoped bookmarked. Are there any changes in the API? Is this a bug in Mojave?


We need urgently a solution, because our app is in the store.

Accepted Reply

OK. I get it. You can just use NSWorkspace to open the preference pane bundle directly. That will open the pane in System Preferences. Two lines:


  NSString * path = @"/System/Library/PreferencePanes/Security.prefPane";
 
  [[NSWorkspace sharedWorkspace] openFile: path];

Replies

We appreciate the support of the community memgers and Apple staff in this thread.


We will use the suggested solution approach with opening the security pane as a workaround to update our app for Mojave users asap in the the app store. Furthermore we will consequently use the scripting bringe, that the user needn't add the app to the security settings for the core features. After this urgent compatibility release we will exchange QLPreviewView for content, that is stored in the user's library directory.


Anyway the disadvantage ot using the scripting bridge is a very poor performance and not deterministic. I the app has to identify a specific email, it has to iterate over all accounts, mailboxes and messages. There is no scripting option to retrieve a specific email by a given ID or path.


In my opinion, strict privacy protection is a good decision, but in this particular case Mojave offers not a perfect usability. Regular users won't distinguish beween technical stuff like sandboxing and security settings. A user would like to decide which data respoectively content an app can access. And if he or she would like to grant general access to his or her content, this decision should include documents, emails and so on. It's even annoying for a user to grant access with serveral promts like for addressbook and calendar.

Just so you know, you have a very high chance of App Review rejection. My advice is to bury the button to ask for Full Disk Access somewhere in your preferences. Make sure your app has some kind of functionality without it. Try to avoid mentioning it inside the app or in the help. Whatever you do, don't suggest that it is "required". You can communicate the details to your customers through your website or via any other channels. If you make a big deal in your screenshots or documenation about accessing data in this location, you'll need to change those. App Review will try to run your app in the manner you describe it. App Review will not click on a "full disk access" control so your app and all documentation needs assume that full disk access has not been granted.

My advice is to bury the button to ask for Full Disk Access somewhere in your preferences.

This is very bad advice. It’s effectively recommending that folks cheat App Review, and that often ends badly.

If you’re going to do something that’s questionable, it’s best to note that in the review notes as you submit to App Review. That way any problems that are going to crop up do so during the review phase, where it’s easy to accommodate change.

The opposite approach can leave you in the unfortunate position of having to take away features from your users.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

eskimo wrote:


This is very bad advice. It’s effectively recommending that folks cheat App Review, and that often ends badly.

Can you recommend some other place for the feature?


I am most definitely NOT recommending that anyone cheat App Review.


I am using my own recent experience with a similar feature. You can try to explain it in review notes until you are blue in the face. It won't help. It doesn't matter if the feature isn't required. It doesn't matter if your app works just fine without it. If App Review sees it, that's an automatic rejection. By all means, after your 3rd or 4th rejection, mention in the review notes how you moved that button to preferences. They probably aren't going to read those notes anyway.


As an Apple engineer, you really don't know what it is like for app developers trying to get software released on the App Store. I had yet another rejection last night due to the inverse of this strategy. I had users giving me 1 star reviews for no other reason than having an in-app purchase button visible. So I re-worked the display so the user wouldn't see that button unless they were really interested in that feature and scrolled down to learn more. My app was rejected because there was supposedly no way for the user to purchase the in-app content. So in my response, I used the attach button to include a screen recording showing how to use the "See solution" button (which is visible) to automatically scroll the view down to the in-app purchase information. I'm now back "in review" for good or bad.


From what the OP has described, this is a critical piece of their app's functionality. As I explained in my reply two weeks ago, that app is dead in the water as far as the App Store goes. They need to pull the app and release it under a Developer ID until they can get this figured out. I have said repeatedly that my suggestions are likely to trigger an App Store rejection. My concern was that they are going to have the app pop-up some big button demanding full disk access. That's a non-starter for App Review. They can do that with a Developer ID distribution, and implement it however they way, whether it is an "official API" or not.


The OP may be able to get a functional version of the app back in the store. They will have to do the following:

1) Make sure it works without access to restricted locations

2) Make sure none of the metadata or screenshots refers to any data from said restricted locations


At that point, I see no problem in having a preference setting that allows full disk access. As far as I can tell, App Review doesn't have a problem with that either. The App Review guidelines are a "living document" after all. If you don't want people doing that, tell them to stop and they will (at least the honest ones will). Problem solved. But if it is not forbidden, then there is no reason why developers shouldn't use it as long as they ensure that the app works without it.


It doesn't sound like the OPs app is really suitable for the App Store anyway. That is another excellent reason to go the Developer ID route. If they want to do a little extra work, they can get a "Lite" version in the App Store for marketing if nothing else. Then, they can be at their leisure to work through future rejections without getting all stressed out about it. That is what I'm doing. I'm not hiding anything or cheating anyone.

We'll try to use a dialogie as an info NSAlert as soon read access would increase the user experience. Granting access to.the app will be optional aslo optining the security settings will be optional. So the app won't crash and would work for the basic features.


There are just two mein disadvantages, if the user doesn't grant access to the library folder in the security settings:


  1. No quicklook preview (that we could replace with an own implementation)
  2. Low performance for retrieving a specific email (for example) from the scripting bridge.


The second point is critical:


I have 490 emails on my test systems and it takes 1,87 seconds to fetch a specific email with the first code example and 1,71 seconds with the second implementation. The pure iteration part takes 0.42 seconds. Most users have thousands of emails.


NSEnumerator *accountEnumerator = mailApp.accounts.objectEnumerator;
MailAccount *account;
BOOL matched;
while ((account = [accountEnumerator nextObject]) && !matched) {
     NSEnumerator *mailboxEnumerator = account.mailboxes.objectEnumerator;
     MailMailbox *mailbox;
     while ((mailbox = [mailboxEnumerator nextObject]) && !matched) {
          NSUInteger index = [mailbox.messages indexOfObjectPassingTest:^BOOL(MailMessage * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
               return ((MailMessage *)obj).id == messageId;
          }];
          if (index != NSNotFound) {
               MailMessage *message = [mailbox.messages objectAtIndex:index];
               NSData *mailContent = [message.source dataUsingEncoding:NSUTF8StringEncoding];
               MCOMessageParser *parser = [MCOMessageParser messageParserWithData:mailContent];
               fulltext = [parser plainTextBodyRenderingAndStripWhitespace:NO];
               matched = YES;;
          }
     }
}

Second faster impelementation::


SBElementArray *accounts = mailApp.accounts;
matched = NO;
for (MailAccount *account in accounts) {
     if (matched) break;
     SBElementArray *mailboxes = account.mailboxes;
     for (MailMailbox *mailbox in mailboxes) {
          if (matched) break;
          SBElementArray *messages = mailbox.messages;
               for (MailMessage *message in messages) {
                    NSInteger messageId2 = message.id;
                    if (messageId2 == messageId) {
                         fileContents = [message.source dataUsingEncoding:NSUTF8StringEncoding];
                         parser = [MCOMessageParser messageParserWithData:fileContents];
                         fulltext = [parser plainTextBodyRenderingAndStripWhitespace:NO];
                         matched = YES;
                         break;
                    }
               }
          }
     }


I don't see a way to select a specific email via scxripting bridge. Has someone an idea?