Since 2012, all apps on the Mac App Store must run in an app sandbox, which restricts access to system resources unless explicitly required. The secure sandbox isolates the app and defines access controls, protecting users from malicious code with undesired behaviour.

Here's how to setup a sandbox for an app downloaded from outside the Mac App Store.

In my case, I wanted to test out Kodi v17.0 "Krypton" Release Candidate 4 (previously XBMC), an open-source, cross-platform media centre software. I also installed a Kodi Add-on from an "untrusted source," which sounds dangerous, doesn't it?

Enter, sandbox! My goal was to prevent Kodi from reading my files, and writing files in locations I did not expect. This goes a long way to securing the system but does not guarantee that you are "protected"!

Information on sandboxing is rather sparse, but I found two great sources:

Also, your mac also comes with pre-configured sandbox rules found in /usr/share/sandbox/ which are good starting points.

Creating a Sandbox and Running It

To run an app sandboxed, first create a file with the set of rules to permit or deny access to system resources, e.g. file system, network, audio, etc.

In kodi.sb:

(version 1)
(deny default)
(allow network*)
(allow iokit-open)
(allow file-read-metadata)
(allow mach* sysctl-read)
(allow ipc-posix-shm 
  (ipc-posix-name-regex "^AudioIO"))
(allow process-exec 
  (regex "/Applications/Kodi.app"))
(allow file-read-data 
  (literal "/dev/urandom"))
(allow file-read-data 
  (regex 
    "^/Applications/Kodi.app" 
    "^/System/Library/"
    "^/usr/share/zoneinfo/"))
(allow file-write* file-read-data 
  (regex 
    "^/Users/[[username]]/Library/Logs"
    "^/Users/[[username]]/Library/Application Support/Kodi"))

Replace [[username]] with your MacOS login.

Now, instead of running the application directly, run it via Terminal:

sandbox-exec -f kodi.sb /Applications/Kodi.app/Contents/MacOS/Kodi

Finally, to create a "shortcut" to sandbox-exec that can be quickly run from Finder / Spotlight, create a file called kodi.command as below. The individual commands can be concatenated into a single line, or you can maintain the line breaks for readability:

sandbox-exec -p '(version 1)(deny default)
(allow network*)(allow iokit-open)
(allow file-read-metadata)(allow mach* sysctl-read)
(allow ipc-posix-shm (ipc-posix-name-regex "^AudioIO"))
(allow process-exec (regex "/Applications/Kodi.app"))
(allow file-read-data (literal "/dev/urandom"))
(allow file-read-data (regex "^/Applications/Kodi.app" "^/System/Library/" "^/usr/share/zoneinfo/"))
(allow file-write* file-read-data (regex "^/Users/[[username]]/Library/Logs" "^/Users/[[username]]/Library/Application Support/Kodi"))' /Applications/Kodi.app/Contents/MacOS/Kodi &

Manual Sandbox Testing

To configure the rules, my process was:

  • Initially, deny all access,
  • Run Kodi (which would inevitably fail), and:
    • Inspect the console output,
    • Inspect the Kodi log files and via Console,
    • And also view the open files and ports in Activity Monitor (screen shot below).
  • Add individual allow permissions one at a time, until I get the functionality I expect.

Via Activity Monitor, double click on an app and select Open Files and Ports:

Activity Monitor Open Files and Ports

I didn't test everything, and I intentionally did not want Kodi to access my filesystem. You might want to change this behaviour, e.g. add your movies and music folders. I also see Kodi is trying to access /Users/[[username]]/Library/Saved Application State/org.xbmc.kodi.savedState/ but I was simply too lazy to add it.

Sandbox Rules

To briefly explain the rules:

  • deny default - deny everything by default.
  • allow network - allows network access.
  • allow iokit-open - access to device drivers, required for Core Image and OpenGL.
  • allow file-read-metadata - without which, no ability to list directories (ls).
  • allow mach* sysctl-read - to get to system info in read mode.
  • (allow ipc-posix-shm (ipc-posix-name-regex "^AudioIO")) - it took me the longest time to enable audio, turns out AudioIO is implemented using shared memory.
  • (allow process-exec (regex "/Applications/Kodi.app")) - allow the Kodi process, and any child processes, to run.
  • (allow file-read-data (literal "/dev/urandom")) - to avoid the error Error in GnuTLS initialization: Failed to acquire random data, configured to be an exact match (literal, compare with regex below).
  • (allow file-read-data (regex ... - read access to system library files and the Kodi.app contents itself:
    • The regex pattern ^ means "starting with" i.e. allow read only access to files and folders starting with /System/Library/.
    • You can add other folders here, e.g. "^/usr/lib/.*\.dylib$" to access user libraries. The $ means "ending with" and is an example of being explicit!
    • Or the movies, music and org.xbmc.kodi.savedState folders mentioned above.
  • (allow file-write* file-read-data (regex ... - allow write access to:
    • Logs folder.
    • Application Support where add-ons, preferences and databases are stored.

Conclusion

MacOS has an extremely granular sandboxing capability, courtesy of BSD, and is enabled by default for apps from the Mac App Store.

However, to sandbox any other application, it's rather involved and poorly documented. I hope the simplified explanation and sample rules above help you.

Updated 9 Feb: allow read access to /usr/share/zoneinfo for the time to be displayed correctly based on the configured time zone.

Update 4 Mar: use sandbox-exec -p profile-string instead, to avoid the dependency on an external .sb file.

Update 26 Mar: fixed a small "bug" where I refer to sandbox_exec instead of sandbox-exec.

You might want to check out the follow up post, Run code in a macOS Sandbox dated 11 June 2023... 6.5 years after this post.