❝

TL;DR: If the script is small enough to read, read it; otherwise run it under a sandbox-exec profile for auditing. After the run, check the filesystem.

Running an installation command such as curl -fsSL https://example.com/install.sh | sh might be dangerous for your system.

But you can understand what the installer is going to touch, using a sandboxing tool, so you can decide if you want to install the software or not. On macOS, you can use the built-in sandbox-exec tool. It is the same kernel mechanism the App Store uses to confine downloaded apps.

1. Audit an installer script

If the script is small enough to read, read it. For everything else, run it sandboxed and watch the filesystem. For example, consider the mise installer at https://mise.run.

Create the sandbox-exec profile definition file somewhere, say /Users/you/sandboxing/audit.sb, with the following content:

(version 1)
(allow default)
(deny file-write* (subpath "/Users/you"))
(deny file-write* (subpath "/usr"))
(deny file-write* (subpath "/etc"))
(deny file-write* (subpath "/private/var"))

Then create a temporary folder, and run the installation script under sandbox-exec with that profile:

mkdir -p /tmp/installer-test; cd /tmp/installer-test
sandbox-exec -f /Users/you/sandboxing/audit.sb sh -c 'curl -fsSL https://mise.run | sh'

What happens? The mise installer script, using mktemp, tries to create a working directory under /var/folders/.../T/, which resolves to /private/var/folders/.../T/. The kernel denies the write and the installer's own output prints a failure:

mktemp: mkdtemp failed on /var/folders/dd/.../T/tmp.XXX: Operation not permitted

That failure message is what you are looking for with the audit. The denied path is what the install intended to write. If the install was blocked at a temporary staging directory (the mise case), allow that path and re-run; the second run will show the next write. If the blocked path is under your home or /usr/local, check whether that path matches what the tool is for; if it does, lift the deny for that path and re-run.

For finer-grained visibility while a run is in progress, log stream --style compact --predicate 'sender=="Sandbox"' prints every sandbox denial on the system, including the operation type and path. The log stream is system-wide, so filter it for your own executions.

2. Audit a shell snippet from a stranger

Before you run a snippet shared, for example on a post you are reading, audit it the same way. Use a profile that denies writes and network access:

(version 1)
(allow default)
(deny file-write*)
(deny network*)

Under this profile, the snippet can read files and run read-only commands (find, grep, ls, cat), but anything beyond that should fail with Operation not permitted. When you see that error, you know the snippet tried to do something, and the kernel denied it.

3. Beyond audit: credential isolation

Switching the deny list from writes to reads gives you a profile that protects your credential paths:

(version 1)
(allow default)
(deny file-read* (subpath "/Users/you/.ssh"))
(deny file-read* (subpath "/Users/you/.aws"))
(deny file-read* (subpath "/Users/you/.gnupg"))

Using that profile, when running a command that tries to read any of those paths, the read will fail.

Two traps to know

Real-world curl | sh scripts may wrap their calls or functions so that the kernel's "Operation not permitted" is silenced and the non-zero exit is masked. In that case, the script would print a green checkmark and Done! while every protected write failed.

The man page of sandbox-exec mentions that it is deprecated. That is for app developers who want to ship a sandboxed app through the Mac App Store, not for shell-level confinement of one-off commands.

❝

sandbox-exec is not in the book, but it would be a natural addition to The Modern CLI Stack for anyone who runs curl | sh on macOS against code they have not read. If you want the full toolkit, The Modern CLI Stack is a free ~50-page PDF + EPUB covering mise, starship, zoxide, fzf, broot, ripgrep, fd, bat, eza, delta, tldr, atuin, lazygit.

Reply

Avatar

or to participate