App running shell script question

I want to use NSTask to run a shell script that resides in Resouces directory. The script performs some operations on files in the app's Data directory.


But I am concerned that Apple could refuse my app for some reasons. Can anyone confirm if this approach is safe. Thanks.

Accepted Reply

Mac App Store apps are, in general, allowed to run other executables from within their bundle, and that includes shell scripts. There are two obvious gotchas here:

  • The shell script must be built in to your app. This is based on guideline 2.16 (that bob133 has helpfully pasted in above so I don’t need to provide a reference).

  • The script will only be able to access data in your app’s container. This is enforced by the App Sandbox. In compiled code you can use security scoped bookmarks to retain persistent access to areas outside of your app’s container, but that’s hard to do in a shell script.

It sounds like you have both of these issues in hand so I agree that it’s reasonable to submit your app and see what App Review makes of it.

Share and Enjoy

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

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

Replies

You can't run scripts in the Resources directory. You can, however, run them from another directory (I can't remember what it is specifically), but the user has to explicitly put them there in order for it to work. The implication is that only advanced users would be able to move the scripts themselves, and they would be able to read them to find out what the app is doing and decide for themselves whether or not they're okay with that.

Thanks for replying.


I can run the script using the following code in debugging when I run my app in Xcode:


NSBundle* myBundle = [NSBundle bundleForClass:[self class]];
NSString* exe = myBundle.executablePath;
NSString* exeDir = [exe substringToIndex:exe.length - exe.lastPathComponent.length];
NSString* prg = [myBundle pathForResource:@"fixdb" ofType:@"sh"];
NSString* sqlite3 = [exeDir stringByAppendingPathComponent:@"sqlite3"];
   
NSPipe* pipeRead = [NSPipe pipe];
NSTask* task = [[NSTask alloc] init];
task.launchPath = prg;
task.standardOutput = pipeRead;
task.arguments = @[sqlite3];
[task launch];
[task waitUntilExit];
NSLog(@"terminationStatus: %d, reason: %ld", task.terminationStatus, task.terminationReason);
   
NSString* output = [[NSString alloc] initWithData:[pipeRead.fileHandleForReading readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSLog(@"%@", output);


The only problem is that if Apple Review rules allow this kind of code.

Have a look at these App Review regulations:


2.15 Apps must be self-contained, single application installation bundles, and cannot install code or resources in shared locations

2.16 Apps that download or install additional code or resources to add functionality or change their primary purpose will be rejected

2.27 Apps that request escalation to root privileges or use setuid attributes will be rejected

2.31 Apps that are not sandboxed appropriately may be rejected

You almost certainly won't be able to get away with this. Are you sure there are no other ways to do what you're thinking of? I would recommend that you look into XPC services, which are a supported way to run extra code under higher priveleges:

https://developer.apple.com/videos/play/wwdc2012-241/

https://developer.apple.com/library/mac/samplecode/SandboxingAndNSXPCConnection/Introduction/Intro.html#//apple_ref/doc/uid/DTS40012665

The shell script does NOT require higher privileges; it operates on files in the app's sandbox Data directory.


I think I will have a try. If Apple rejects this approach I have to find another way.


Thanks for you replies.

Mac App Store apps are, in general, allowed to run other executables from within their bundle, and that includes shell scripts. There are two obvious gotchas here:

  • The shell script must be built in to your app. This is based on guideline 2.16 (that bob133 has helpfully pasted in above so I don’t need to provide a reference).

  • The script will only be able to access data in your app’s container. This is enforced by the App Sandbox. In compiled code you can use security scoped bookmarks to retain persistent access to areas outside of your app’s container, but that’s hard to do in a shell script.

It sounds like you have both of these issues in hand so I agree that it’s reasonable to submit your app and see what App Review makes of it.

Share and Enjoy

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

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

If I want to copy a shell-script or binary (helper) into /usr/local/bin will I be able to do so and distribute on App store?

If that's not possible, how do I access the path of the application folder for use within a shell script in order to locate a bundled binary inside the app bundle.

What do I mean:


My.app
  ...something...
    my-mach-o-binary
   ...something...
     my-bash-script.sh

I want my-bash-script to call my-mach-o-binary. How can I get the path of my-mach-o-binary to use in my-bash-script?

If I am able to copy the binary to /usr/local/bin it will be easy to locate from the bash script...but if not...I cannot assume the My.app is running within /Applications it could be anywhere.

If I want to copy a shell-script or binary (helper) into /usr/local/bin will I be able to do so and distribute on App store?

No. But even if it were that still leaves significant issues.

If that's not possible, how do I access the path of the application folder for use within a shell script in order to locate a bundled binary inside the app bundle.

That’s may be tricky, but the problem isn’t the one you’re worried about (I can come back to that later if necessary). Let’s start with a simple question: Do you plan to run this tool from your app and from the command line? Or solely from the command line?

Share and Enjoy

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