Elevated Permissions in macOS, Or So We Thought (edited)

We rolled our own installer, and naturally came up against this problem like everyone else. There are several ways to solve writing files and folders to the system Library:

  • AuthorizationExecuteWithPrivileges This is mildly complicated to use and has been deprecated since 10.7, so it will go away at some point, likely without warning, and this is not optimal.

  • SMJobBless This is extremely complicated to use, and has also been deprecated; see previous entry re: suboptimal use cases.

  • osascript This generally works and is relatively simple to implement. However, we quickly discovered that a fair portion of the userbase is infected with this problem, which requires moderate computer competence on the part of the customer to sort out.

There are a few other ways to elevate permissions for file ops, but they are either hacky or not smooth UX. The ideal way that works well from within a JUCE app and doesn’t use any deprecated methods is AppleScript. I couldn’t find a pure example of a file operation, and no examples whatsoever of using AppleScript in JUCE, so I’ve made one here for those of you trying to solve this issue. Harvesting warnings and implementing fail states I leave as an exercise to the reader, but best I can tell, this works every time, on every flavor of macOS, and uses nothing deprecated.

AppleScriptWrapper.h

#pragma once
#include "../JuceLibraryCode/JuceHeader.h"

class AppleScriptWrapper {
public:
  static String adminLevelOp(String);
};

AppleScriptWrapper.mm

#include "Foundation/NSString.h"
#include "Foundation/NSAppleScript.h"
#include "AppleScriptWrapper.h"

String AppleScriptWrapper::adminLevelOp(String cmd){
    NSDictionary *error = [NSDictionary new];
    NSString *appleScriptString = @"do shell script ";
    appleScriptString = [appleScriptString stringByAppendingString:@"\""];
    appleScriptString = [appleScriptString stringByAppendingString:[NSString stringWithUTF8String: (const char*) cmd.toUTF8()]];
    appleScriptString = [appleScriptString stringByAppendingString:@"\""];
    appleScriptString = [appleScriptString stringByAppendingString:@" with administrator privileges"];
    NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:appleScriptString];
    NSString *result;
    result = [[appleScript executeAndReturnError: &error] stringValue];
    return [result UTF8String];
}

Usage:

In this example, I’m using a literal statemen but its use should be relatively obvious. Just put all your file commands in to one String, and bang it to the function, and there you go.

String cmd = "mv -f '/Users/myName/Library/Caches/My Installer/myPlug.clap' '/Library/Audio/Plug-Ins/CLAP'";
String f = AppleScriptWrapper::adminLevelOp(cmd);

Those of you that are skilled with Obj C error harvesting feel free to offer advice with respect to that.

6 Likes

In the past, my application was using Apple Script scripts as a foundation for executing bash scripts using admin privileges (in the similar way you do).

Everything was working perfectly until Catalina came out :frowning:

My OSA scripts just stopped working without any obvious reason and warning, when trying to access any of the Catalina “protected” locations (permission issues regardless of admin privileges).

I was forced to create privileged helper tool service (launch daemon), a part of it also being SMJobBless function. I can see now this function is deprecated with Ventura, so sooner or later i’ll have to update my app+helper tool to new standards by also replacing SMJobBless with the new SMAppService framework function(s).

This whole privileged helper tool stuff is pretty complicated, but IMO this is the only future-safe way to go in any case, because Apple is introducing new protection constrains more or less with any new OS version.

I (with end-user perspective) hate it when some installer installs background services which are privileged. It undermines the actual security concept between user and root rights. I wouldn’t be surprised if Apple no longer supports this technique in the next few years.

You can also run a script or even a binary with admin-rights inside a tradition pkg installer, why not just use this?

My app is actually a custom installer for proprietary eco-system. It creates and performs complex installation scenarios (depending on user actions/decisions), so using traditional pkg installer is out of the question.

Creating privileged helper tool (launch daemon in my case) is technically pretty complex task. All components (app and service) must be officially notarized and signed as well - so they can be completely trusted. Upon first app run, user is informed about it and must personally approve installing and execution of the service, giving it rights to access files/folders on the system etc. Ventura is taking even more care about informing user of app-specific services.

On the contrary, using Apple Script with admin privileges in the past was rather easy and could led to misc security breaches! That’s why Apple introduced additional protection with Catalina, 100% forcing privileged service method - it’s integral part of the OS and to my knowledge its the safest method available at the moment.

IMHO idea of Apple not supporting this technique anymore sounds similar (not the same!) to Apple removing support for “sudo” command from the terminal app. But with Apple, anything is possible. :sweat_smile:

Comparing to Apple security approach, creating fully privileged Windows installer app is almost a piece of cake.

To people that may be slightly confused by the above, osascript is an engine in macOS that compiles and runs several different flavors of scripting language. AppleScript is one such. So you often see osascript and AppleScript conflated in blog and forum posts (as @zabukowski just did).

osascript generally works fine for this application, but SPECIFIC TO OUR SITUATION AS AUDIO DEVELOPERS some AudioUnits hold open the osascript port and prevent elevating permissions. This is the Reddit post I linked to above. While this osascript problem will affect all users, our users tend to have a lot of AudioUnits installed, where your average non-audio person will have none. Best I can tell, this problem is a bug with osascript specifically related to AudioUnints, or with AudioUnit V2 itself, and isn’t the result of any malicious behavior.

To the best of my knowledge, this osascript bug does not affect AppleScript directly, and my AppleScript wrapper above is simple, elevates to full sudo privileges using the correct Apple dialogue, and contains no deprecated methods. If you need continual access, the SMAppService method (which makes SMJobBless look simple by comparison) is the way to go, definitely.

1 Like

Despite talking what has become apparent is the tallest of tall shit in the previous entries, I’d like to note that the method I describe has a similar problem to the osascript problem I describe in the original post. The interesting thing is that it is a different group of people.

That is to say that one (small, like 1% of the user base) group is affected by that osascript bug, and another (also about 1%) group is affected by the same bug when using the Applescript method I describe, but it is different groups of people.

So fun. Thanks, Apple!

We have rolled our installer back to the AuthorizationExecuteWithPrivileges method until we can learn how to implement the SMAppService method. Teamwork makes the dream work.

3 Likes

The hits just keep on coming. With Xcode 14.3 and its posse of SDKs, AuthorizationExecuteWithPrivileges appears to have stopped working at all. This was deprecated with 10.7, so I guess it had a good run.

I use the applescript method from the top of this thread to make a license file available to all users and it works great. It also appears to be recommended by the Apple eskimo.

Speaking as an end user I prefer standard pkg installers since they can be inspected but as a developer I can see the appeal of a custom installer.

I have seen apps/plugins use the ‘osascript wants to make changes’ method and I find it a little unprofessional. To anyone who is hesitant to deal with some obj-c in their code when you can call osascript as a Child Process in JUCE, I would recommend going the extra mile and getting the code above to compile in a JUCE module or something. You’ll feel all powerful when you figure it out!