AI agents: see /llms.txt for a full index of this site, or /llms-full.txt for concatenated documentation.

Workshop Docs

Enterprise control plane for Santa. Manage rules, approvals, telemetry, and policies across your macOS fleet.

Workshop Docs
View chapters on this page

Sandbox Rules

A Sandbox rule is an execution rule whose policy is Seatbelt. Instead of allowing or blocking the binary outright, it requires the binary to be launched through santactl sandbox (abbreviated santactl sb), which applies a macOS Seatbelt sandbox profile carried by the rule before executing it.

:::info Requirements

Santa gained sandbox support in 2026.5, but policies saved by Workshop use the BINARY_PATH parameter, which Santa only understands from 2026.6. Every Seatbelt rule is therefore stamped with a minimum version of 2026.6, and hosts on anything older will not receive it.

The feature is currently in beta.

:::

Overview

Seatbelt is the macOS sandbox. Profiles are written in SBPL (Sandbox Policy Language), a Scheme-like language that names the operations a process may perform and the arguments those operations are allowed to take.

A Sandbox rule pairs a normal execution rule identifier (CDHash, Signing ID, Team ID, Certificate or Binary hash) with an SBPL profile:

  • Running the binary directly is blocked, with the reason ”… (requires running under santactl sandbox)”.
  • Running it as santactl sandbox <binary> [args…] is allowed, with the profile applied to the process for its lifetime.
  • The sandbox is inherited by every child process, so a sandboxed shell cannot escape by spawning something else.

Santa verifies that the binary it is about to allow is the same one the sandbox was set up for, so the sandboxed launch cannot be handed off to a different binary. Seatbelt decisions are never cached — every execution is re-checked.

Creating a Sandbox rule

Create the rule as usual and set Policy to Seatbelt. A seatbelt policy body is required — the rule cannot be saved without one.

CEL rules can also produce a sandbox decision: if the expression can return SEATBELT, the rule must carry a seatbelt policy too. See the CEL Guide.

Workshop appends the following line to every saved policy, so the sandboxed binary is always permitted to execute itself:

(allow process-exec* (literal (param "BINARY_PATH")))

It is appended only once; re-saving a rule does not duplicate it.

Simple mode

The Simple tab generates a complete, deny-by-default profile from a handful of toggles. It is the recommended starting point: the generated profile already contains the boilerplate a typical CLI tool needs (system library reads, TTY access, logging, DNS, CoreFoundation shared memory) that is tedious and easy to get wrong by hand.

Switching a toggle regenerates the whole profile. If the policy in the Advanced tab is not something Simple mode would have produced (because it was hand-edited), Workshop shows a warning: touching any control will overwrite it.

Process

ControlEffect
Allow child processes (fork/exec)Adds (allow process-fork) and (allow process-exec). Required for anything that spawns subprocesses — shells, build tools, compilers. Leave it off to confine the binary to itself.

File reads

ControlEffect
Allow reading $HOMEAdds (subpath (param "HOME")) to the read list. The always-denied paths below still apply.
Additional read pathsOne (subpath …) entry per path, granting recursive read access to that directory tree. Accepts absolute paths, or $HOME/$CWD/~ prefixes ($HOME/Projects, $CWD/vendor).

File writes

ControlEffect
Allow writing to the working directory ($CWD)Adds (subpath (param "CWD")) to the write list — the directory santactl sandbox was invoked from.
Allow writing to temp directoriesAdds /private/tmp, /private/var/tmp and /private/var/folders.
Additional write pathsOne recursive (subpath …) write entry per path. Same $HOME/$CWD prefix handling as reads.

Blocked paths

ControlEffect
Paths that cannot be read or writtenEmits (deny file-read* file-write* …) after the allow sections. Because SBPL is last-match-wins, these win over any allow above — use them to punch holes in a broad allow.

Network

ControlEffect
Allow HTTPSOutbound TCP to *:443 plus UDP *:443 for QUIC/HTTP3.
Allow localhost (inbound, outbound, bind)Outbound TCP to localhost:*, plus network-inbound and network-bind on localhost:*. Needed for local dev servers and stdio bridges.
Additional outbound TCP destinationsA bare port (8080) becomes *:8080; a host:port value (example.com:993) is used verbatim.

Outbound UDP to port 53 (DNS) and Unix domain sockets are always allowed when any network access is granted.

Always in the generated profile

Regardless of the toggles, Simple mode emits:

  • (version 1), (deny default) and (debug deny) — deny everything not explicitly allowed, and log each denial.
  • Process introspection of self and children, sysctl-read, and the Mach services and POSIX shared-memory names CoreFoundation, logging, DNS and the security daemons need.
  • Read access to /System, /Library, /usr, /bin, /sbin, /opt, /private/etc, timezone and mds databases, plus /dev/null, /dev/zero, /dev/random, /dev/urandom and the TTY devices.
  • Write access to /dev/null, /dev/tty and the process’s TTY.
  • A (deny … (with no-log)) block for Apple telemetry services, to keep the denial log readable.

And these paths are always denied, even if a toggle or custom path would otherwise cover them:

Denied forPaths
Writes~/.zshrc, ~/.zshenv, ~/.zprofile, ~/.bashrc, ~/.bash_profile, ~/.profile, ~/.ssh, ~/.aws, ~/.gnupg, ~/Library/LaunchAgents, ~/Library/Application Support/com.apple.sharedfilelist
Reads~/.ssh, ~/.aws/credentials, ~/.gnupg, ~/Library/Keychains

Advanced mode

The Advanced tab is a raw editor for the SBPL profile. Use it when Simple mode cannot express the policy you need. Anything you write here is stored verbatim (plus the appended process-exec* directive).

Structure

(version 1)
(deny default)          ; default stance
(debug deny)            ; log every denial

(allow file-read*
       (subpath "/usr")
       (literal "/dev/urandom"))

(deny file-write*
       (subpath (string-append (param "HOME") "/.ssh")))

Key semantics:

  • (version 1) must be the first directive.
  • Set the default stance with (deny default) or (allow default). Always prefer (deny default).
  • Last match wins. A later deny overrides an earlier allow for the same operation and path, which is how the “Blocked paths” section in Simple mode works.
  • ; starts a comment; ;; is the convention for section headers.
  • Profiles must be self-contained. (import …) of system profiles is not supported here.

Operations

An operation names the class of action being authorized. * suffixes match a family of related operations (file-write* covers create, unlink, mode changes and so on).

OperationCovers
defaultEverything not otherwise matched.
file-read*, file-read-data, file-read-metadataReading file contents, and stat-style metadata access.
file-write*, file-write-data, file-write-create, file-write-unlink, file-write-mode, file-write-owner, file-write-flagsModifying files: contents, creation, deletion, permissions.
file-ioctlioctl(2) on a file or device — needed for TTYs.
file-mount, file-umountMounting and unmounting filesystems.
process-exec, process-exec*Executing a binary.
process-forkCreating child processes.
process-info*, process-info-pidinfo, process-info-pidfdinfo, process-info-setcontrolInspecting or controlling processes.
signalSending signals.
network-outbound, network-inbound, network-bindConnecting out, accepting in, binding a local address.
system-socketCreating sockets (including routing sockets).
mach-lookup, mach-registerLooking up or registering Mach services (XPC).
ipc-posix-shm*, ipc-posix-shm-read*, ipc-posix-shm-write-data, ipc-posix-shm-write-createPOSIX shared memory — CoreFoundation requires several of these.
ipc-posix-sem*POSIX semaphores.
sysctl-read, sysctl-writeReading and writing sysctl values.
iokit-open, iokit-get-propertiesOpening IOKit user clients and reading their properties.
user-preference-read, user-preference-writecfprefsd preference domains.
nvram*Reading and writing NVRAM variables.
device-camera, device-microphoneCapture devices.
authorization-right-obtainAcquiring an Authorization Services right.

The authoritative operation set is defined by the OS and varies between macOS releases; an unrecognized operation causes sandbox_init to fail. Test profiles that use uncommon operations against the macOS versions you deploy to.

Filters

Filters narrow which arguments an operation applies to. Multiple filters in one rule are OR’d — the rule matches if any of them do.

FilterMatches
(literal "/path")Exactly that path.
(subpath "/path")That directory and everything beneath it.
(regex #"^/dev/ttys[0-9]+$")Paths matching the regular expression. Note the #"…" literal syntax.
(global-name "com.apple.logd")A Mach service in the global namespace (used with mach-lookup).
(local-name "…")A Mach service in the local namespace.
(ipc-posix-name "…"), (ipc-posix-name-regex #"…")POSIX shared memory / semaphore names.
(iokit-user-client-class "IOHIDParamUserClient")An IOKit user client class (used with iokit-open).
(remote tcp "host:port"), (remote udp "*:53")Outbound destination. * is allowed for either half.
(local tcp "localhost:*")Local address, for network-inbound and network-bind.
(remote unix-socket), (local unix-socket)Unix domain sockets.
(target self), (target children), (target others)The process a signal or process-info operation applies to.
(vnode-type REGULAR-FILE)Restrict a file operation to a vnode type (DIRECTORY, SYMLINK, CHARACTER-DEVICE, …).

Filters can be combined with (require-all …), (require-any …) and (require-not …):

(allow file-write*
       (require-all (subpath (param "CWD"))
                    (require-not (regex #"\.git/"))))

Modifiers

ModifierEffect
(with no-log)Suppress the log entry for this rule. Useful for noisy, expected denials.
(with report)Log the violation even when the operation is allowed.
(with send-signal SIGKILL)Kill the process on a matching denial instead of returning an error.
(deny mach-lookup (with no-log)
       (global-name "com.apple.diagnosticd"))

Parameters

santactl sandbox supplies these parameters when it applies the profile. Reference them with (param "NAME"), and build paths beneath them with (string-append …).

ParameterValue
BINARY_PATHFull resolved path of the binary being sandboxed.
CWDThe working directory santactl sandbox was invoked from.
HOMEThe invoking uid’s home directory from the password database (so /var/root under sudo, not the inherited $HOME).
TMPDIRThe per-user Darwin temp directory.
UIDThe invoking uid, as a decimal string.
(allow file-read*
       (subpath (string-append (param "HOME") "/Projects")))

Referencing a parameter that was not supplied makes sandbox_init fail. CWD, HOME and TMPDIR are omitted if the OS cannot resolve them.

Using a Sandbox rule on the client

The canonical command is santactl sandbox:

$ santactl sandbox <command> [arguments...]

santactl sb is an alias for it, provided as a shorthand. The two are identical in every respect; this documentation uses the canonical santactl sandbox throughout.

$ santactl sb <command> [arguments...]   # identical to the above

-- ends option parsing, if the command name would otherwise look like a flag.

  • The command may be a path (absolute or containing a /), or a bare name resolved against PATH. Only absolute PATH entries are searched.
  • Root is not required. The Santa daemon must be running.
  • Arguments after the command are passed through unchanged.
  • The sandbox is applied before exec, so it is in effect from the very first instruction of the target — and is inherited by all of its children.

Common failures:

MessageCause
command not foundThe command did not resolve to an executable regular file.
No matching ruleNo execution rule matches the binary.
Rule is not a seatbelt ruleA rule matched, but it carries no seatbelt policy.
sandbox_init failed: …The profile is not valid SBPL, or references an undefined parameter.
Concurrent sandbox request rejectedAnother santactl sandbox request from the same process is already pending.

Troubleshooting a profile

Profiles generated by Simple mode include (debug deny), so every denial is logged. Watch them live on the host:

$ log stream --style compact --predicate '((processID == 0) AND (senderImagePath CONTAINS "/Sandbox")) OR (subsystem == "com.apple.sandbox.reporting")'

Each entry names the operation and the argument that was denied, which maps directly onto the operation/filter pair to add to the profile. Iterate in the Advanced tab until the denial log is clean, then roll the rule out.

Best practices

  • Start narrow. Keep (deny default) and grant only what the tool actually needs; a profile that starts from (allow default) protects nothing.
  • Prefer Simple mode. The generated baseline handles the OS plumbing that is easy to get wrong. Drop to Advanced only for the parts Simple mode cannot express.
  • Scope by code signing identity. As with any execution rule, a CDHash, Signing ID or Team ID rule is far harder to sidestep than a binary path.
  • Test on one host first. A broken profile does not fall back to unsandboxed execution — the binary simply will not run.
  • Watch the rollout. Hosts on Santa older than 2026.6 will silently not receive the rule, so the binary continues to be evaluated by the rest of your policy.