macOS includes a sandboxing mechanism to closely control what processes can do on the system. Sandboxing can restrict file system accesses on a path level, control which host/port pairs can be reached over the network, limit which binaries can be executed, and much more. All applications installed via the App Store are subject to sandboxing.

This sandboxing functionality is exposed via the sandbox-exec(1) command-line utility, which unfortunately has been listed as deprecated for at least the last two major versions of macOS. It is still there, however, and the supplemental manual pages like sandbox(7) or sandboxd(8) do not mention the deprecation… which makes me think that the new App Sandboxing feature is built on the same kernel subsystem as sandbox-exec(1).

Anyway, so if this tool is deprecated, why am I writing about it?

Because we still use sandbox-exec(1) in Bazel to implement action sandboxing on macOS. For example, each action (e.g. each compiler invocation) runs with a configuration that looks like this:

(version 1)
(debug deny)
(allow default)
(deny network*)
(allow network* (local ip "localhost:*"))
(allow network* (remote ip "localhost:*"))
(allow network* (remote unix-socket))
(deny file-write*)
(allow file-write*
    (subpath "/dev")
    (subpath "/private/var/tmp/_bazel_cooluser/2187421153de0d0eb425e00ad6f725cb/sandbox/darwin-sandbox/1/execroot/__main__")
    (subpath "/private/var/folders/p4/px0ds0qs4w58bk9fjp34q32h0000gn/C")
    (subpath "/private/var/tmp")
    (subpath "/Users/cooluser/Library/Logs")
    (subpath "/Users/cooluser/Library/Developer")
    (subpath "/private/var/folders/p4/px0ds0qs4w58bk9fjp34q32h0000gn/T")
    (subpath "/private/tmp")

This profile restricts actions so that they can only write to a specific set of directories and so that they cannot access remote network services. Or does it?

We got a recent bug report saying that network blocking had stopped working (#10068), which made us fear that the deprecation had finally taken place on Catalina and that our use of this tool was now completely broken.

Strange though, so that sent me digging. And as part of this research, I stumbled upon the /usr/share/sandbox/ directory, which contains sandboxing profiles for a lot of services. This alone is very cool because it shows that the system actually uses this feature to harden a great deal of functionality. And this is also promising because, while I can imagine Apple yanking the sandbox-exec(1) tool altogether, the sandboxing subsystem will have to remain for their own use in system services (and thus available in some maybe-undocumented way if the answer is not App Sandboxing).

All the files in this directory are also a good resource to learn the kinds of things that can be controlled via the sandboxing functionality. To get a sense of these, run:

$ grep -h 'allow[ -]' /usr/share/sandbox/* | sed -Ee 's,^.*allow.([^ )]*).*$,\1,' | sort | uniq

Where does this leave Bazel though? For now we’ll continue to rely on sandbox-exec(1) and fix the known bug regarding networking. We know the tool may go away. We’ll keep monitoring the situation. But for now, we have no pressure to migrate to something else… and we have many more things to focus our limited engineering time on.

Where does this leave you? We’ll fix the bug and you’ll continue to enjoy sandboxing on macOS. But if you have the time and knowedge to migrate Bazel to use App Sandboxing (if that’s the right answer), then we’ll gladly accept your contributions!

Happy reverse-enginneering.