> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailofbits/skills/llms.txt
> Use this file to discover all available pages before exploring further.

# Seatbelt Sandboxer

> Generate minimal macOS Seatbelt sandbox configurations

<Info>
  **Author:** Spencer Michaels (Trail of Bits)
</Info>

## Overview

Generate a macOS Seatbelt configuration that sandboxes the target with the minimum set of permissions necessary for it to operate normally. Uses an iterative profiling approach to create allowlist-based sandbox profiles for applications.

## When to Use

Use this plugin when you need a targeted way to isolate a process on macOS without using containers. This can be helpful for:

<CardGroup cols={2}>
  <Card title="Supply Chain Risk" icon="shield">
    Applications at high risk of supply chain attacks (package managers, bundlers)
  </Card>

  <Card title="Untrusted Code Execution" icon="code">
    Trusted applications that execute potentially-untrusted third-party code (Javascript bundlers, build tools)
  </Card>

  <Card title="Blast Radius Reduction" icon="explosion">
    Reducing the impact if an application is exploited
  </Card>

  <Card title="Defense in Depth" icon="layer-group">
    Adding isolation layers to sensitive processes
  </Card>
</CardGroup>

<Warning>
  This plugin should NOT be used to run an untrusted process, since it requires running the target process to profile it in order to determine what permissions are actually needed.
</Warning>

## How It Works

<Steps>
  <Step title="Profile the Target Application">
    Identify the actual set of permissions required for the application to run normally.
  </Step>

  <Step title="Generate a Minimal Seatbelt Profile">
    Start from a default-deny profile.
  </Step>

  <Step title="Iteratively Expand Permissions">
    Test the application empirically to identify what calls fail with the minimal profile, and add the needed permissions until the application runs normally.
  </Step>

  <Step title="Create Helper Scripts if Needed">
    If the application has multiple subcommands that perform highly different functions (such as "serve" and "build" tasks), create separate Seatbelt configurations for each, and create a helper script to switch configurations based on how the target application is invoked.
  </Step>
</Steps>

## Profiling Methodology

### Step 1: Identify Application Requirements

Determine what the application needs across these resource categories:

<Tabs>
  <Tab title="File Operations">
    | Operation | Seatbelt Rule                          | Use Cases                                |
    | --------- | -------------------------------------- | ---------------------------------------- |
    | Read      | `file-read-data`, `file-read-metadata` | Reading source files, configs, libraries |
    | Write     | `file-write-data`, `file-write-create` | Output files, caches, temp files         |
    | Delete    | `file-write-unlink`                    | Cleanup operations                       |
    | Execute   | `file-map-executable`                  | Loading dylibs                           |
  </Tab>

  <Tab title="Network">
    | Operation | Seatbelt Rule      | Use Cases                    |
    | --------- | ------------------ | ---------------------------- |
    | Bind      | `network-bind`     | Server applications          |
    | Inbound   | `network-inbound`  | Accept connections           |
    | Outbound  | `network-outbound` | API calls, package downloads |
  </Tab>

  <Tab title="Process & IPC">
    | Operation | Seatbelt Rule    | Use Cases                |
    | --------- | ---------------- | ------------------------ |
    | Fork      | `process-fork`   | Spawning child processes |
    | Exec      | `process-exec*`  | Running binaries         |
    | Mach IPC  | `mach-lookup`    | System services, XPC     |
    | POSIX IPC | `ipc-posix-shm*` | Shared memory            |
  </Tab>

  <Tab title="System">
    | Operation    | Seatbelt Rule             | Use Cases                         |
    | ------------ | ------------------------- | --------------------------------- |
    | Sysctl       | `sysctl-read`             | Reading system info (CPU, memory) |
    | IOKit        | `iokit-open`              | Hardware access, device drivers   |
    | Dynamic Code | `dynamic-code-generation` | JIT compilation                   |
    | Signals      | `signal`                  | Signal handling                   |
  </Tab>
</Tabs>

### Step 2: Start with Minimal Profile

Begin with deny-all and essential process operations:

```scheme theme={null}
(version 1)
(deny default)

;; Essential for any process
(allow process-exec*)
(allow process-fork)
(allow sysctl-read)

;; Metadata access (stat, readdir) - doesn't expose file contents
(allow file-read-metadata)
```

### Step 3: Add File Read Access (Allowlist)

<Info>
  **Why `file-read-data` instead of `file-read*`?**

  * `file-read*` allows ALL file read operations from any path
  * `file-read-data` only allows reading file contents from listed paths
  * Combined with `file-read-metadata` (allowed broadly), this gives:
    * ✅ Can stat/readdir anywhere (needed for path resolution)
    * ❌ Cannot read contents of files outside allowlist
</Info>

```scheme theme={null}
(allow file-read-data
    ;; System paths (required for most runtimes)
    (subpath "/usr")
    (subpath "/bin")
    (subpath "/sbin")
    (subpath "/System")
    (subpath "/Library")
    (subpath "/opt")                    ;; Homebrew
    (subpath "/private/var")
    (subpath "/private/etc")
    (subpath "/private/tmp")
    (subpath "/dev")

    ;; Root symlinks for path resolution
    (literal "/")
    (literal "/var")
    (literal "/etc")
    (literal "/tmp")
    (literal "/private")

    ;; Application-specific config (customize as needed)
    (regex (string-append "^" (regex-quote (param "HOME")) "/\\.myapp(/.*)?$"))

    ;; Working directory
    (subpath (param "WORKING_DIR")))
```

### Step 4: Configure Network

<Tabs>
  <Tab title="No Network (Most Restrictive)">
    Use for build tools that don't need network access:

    ```scheme theme={null}
    (deny network*)
    ```
  </Tab>

  <Tab title="Localhost Only">
    Use for dev servers and local services:

    ```scheme theme={null}
    ;; Bind to local ports
    (allow network-bind (local tcp "*:*"))
    ;; Accept inbound connections
    (allow network-inbound (local tcp "*:*"))
    ;; Outbound to localhost + DNS only
    (allow network-outbound
        (literal "/private/var/run/mDNSResponder")  ;; DNS resolution
        (remote ip "localhost:*"))                   ;; localhost only
    ```
  </Tab>

  <Tab title="Full Network (Least Restrictive)">
    Avoid if possible:

    ```scheme theme={null}
    (allow network*)
    ```
  </Tab>
</Tabs>

### Step 5: Test Iteratively

<Steps>
  <Step title="Test Basic Execution">
    ```bash theme={null}
    sandbox-exec -f profile.sb -D WORKING_DIR=/path -D HOME=$HOME /bin/echo "test"
    ```
  </Step>

  <Step title="Test the Actual Application">
    ```bash theme={null}
    sandbox-exec -f profile.sb -D WORKING_DIR=/path -D HOME=$HOME \
      /path/to/application --args
    ```
  </Step>

  <Step title="Test Security Restrictions">
    ```bash theme={null}
    sandbox-exec -f profile.sb -D WORKING_DIR=/tmp -D HOME=$HOME \
      cat ~/.ssh/id_rsa
    # Expected: Operation not permitted
    ```
  </Step>

  <Step title="Debug Failures">
    Common failure modes:

    | Symptom                     | Cause                        | Fix                                 |
    | --------------------------- | ---------------------------- | ----------------------------------- |
    | Exit code 134 (SIGABRT)     | Sandbox violation            | Check which operation is blocked    |
    | Exit code 65 + syntax error | Invalid profile syntax       | Check Seatbelt syntax               |
    | `ENOENT` for existing files | Missing `file-read-metadata` | Add `(allow file-read-metadata)`    |
    | Process hangs               | Missing IPC permissions      | Add `(allow mach-lookup)` if needed |
  </Step>

  <Step title="Iterate Until Working">
    Repeat this process iteratively until you have generated a minimally-permissioned Seatbelt file and have confirmed empirically that the application works normally.
  </Step>
</Steps>

<Tip>
  If the program requires external input to function fully (such as a Javascript bundler that needs an application to bundle), find sample inputs from well-known, ideally official sources. For instance, [Rspack example projects](https://github.com/rstackjs/rstack-examples/tree/main/rspack/).
</Tip>

## Seatbelt Syntax Reference

<AccordionGroup>
  <Accordion title="Path Filters">
    ```scheme theme={null}
    (subpath "/path")           ;; /path and all descendants
    (literal "/path/file")      ;; Exact path only
    (regex "^/path/.*\\.js$")   ;; Regex match
    ```
  </Accordion>

  <Accordion title="Parameter Substitution">
    ```scheme theme={null}
    (param "WORKING_DIR")                                    ;; Direct use
    (subpath (param "WORKING_DIR"))                          ;; In subpath
    (string-append (param "HOME") "/.config")                ;; Concatenation
    (regex-quote (param "HOME"))                             ;; Escape for regex
    ```
  </Accordion>

  <Accordion title="File Operations">
    ```scheme theme={null}
    (allow file-read-data ...)          ;; Read file contents
    (allow file-read-metadata)          ;; stat, lstat, readdir (no contents)
    (allow file-read-xattr ...)         ;; Read extended attributes
    (allow file-test-existence ...)     ;; Check if file exists
    (allow file-map-executable ...)     ;; mmap executable (dylibs)
    (allow file-write-data ...)         ;; Write to existing files
    (allow file-write-create ...)       ;; Create new files
    (allow file-write-unlink ...)       ;; Delete files
    (allow file-write* ...)             ;; All write operations
    (allow file-read* ...)              ;; All read operations (use sparingly)
    ```
  </Accordion>

  <Accordion title="Network Operations">
    ```scheme theme={null}
    (allow network-bind (local tcp "*:*"))              ;; Bind to any local TCP port
    (allow network-bind (local tcp "*:8080"))           ;; Bind to specific port
    (allow network-inbound (local tcp "*:*"))           ;; Accept TCP connections
    (allow network-outbound (remote ip "localhost:*"))  ;; Outbound to localhost only
    (allow network-outbound (remote tcp))               ;; Outbound TCP to any host
    (allow network-outbound
        (literal "/private/var/run/mDNSResponder"))     ;; DNS via Unix socket
    (allow network*)                                    ;; All network (use sparingly)
    (deny network*)                                     ;; Block all network
    ```
  </Accordion>

  <Accordion title="Process Operations">
    ```scheme theme={null}
    (allow process-exec* ...)           ;; Execute binaries
    (allow process-fork)                ;; Fork child processes
    (allow process-info-pidinfo)        ;; Query process info
    (allow signal)                      ;; Send/receive signals
    ```
  </Accordion>
</AccordionGroup>

## Example: Generic CLI Application

```scheme theme={null}
(version 1)
(deny default)

;; Process
(allow process-exec*)
(allow process-fork)
(allow sysctl-read)

;; File metadata (path resolution)
(allow file-read-metadata)

;; File reads (allowlist)
(allow file-read-data
    (literal "/") (literal "/var") (literal "/etc") (literal "/tmp") (literal "/private")
    (subpath "/usr") (subpath "/bin") (subpath "/sbin") (subpath "/opt")
    (subpath "/System") (subpath "/Library") (subpath "/dev")
    (subpath "/private/var") (subpath "/private/etc") (subpath "/private/tmp")
    (subpath (param "WORKING_DIR")))

;; File writes (restricted)
(allow file-write*
    (subpath (param "WORKING_DIR"))
    (subpath "/private/tmp") (subpath "/tmp") (subpath "/private/var/folders")
    (literal "/dev/null") (literal "/dev/tty"))

;; Network disabled
(deny network*)
```

**Usage:**

```bash theme={null}
sandbox-exec -f profile.sb \
  -D WORKING_DIR=/path/to/project \
  -D HOME=$HOME \
  /path/to/application
```

## Known Limitations

<Warning>
  1. **Deprecated but functional**: Apple deprecated `sandbox-exec` but it works through macOS 14+
  2. **Temp directory access often required**: Many applications need `/tmp` and `/var/folders`
</Warning>

## Installation

```bash theme={null}
/plugin install trailofbits/skills/plugins/seatbelt-sandboxer
```

## When NOT to Use

* Linux containers (use seccomp-bpf, AppArmor, or namespaces instead)
* Windows applications
* Applications that legitimately need broad system access
* Quick one-off scripts where sandboxing overhead isn't justified

## References

* [Apple Sandbox Guide (reverse-engineered)](https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf)
* [sandbox-exec man page](https://keith.github.io/xcode-man-pages/sandbox-exec.1.html)
