Skip to main content

Writing .sb Files

Sandbox profiles use Sandbox Profile Language (SBPL), a Scheme-like DSL for macOS Seatbelt. Safehouse profiles are modular .sb files that follow a consistent structure.

Basic Structure

Every Safehouse profile includes:
  1. Header comment (category, description, source path)
  2. Dependency metadata (optional $$require= declarations)
  3. Sandbox rules (allow/deny operations with matchers)
;; ---------------------------------------------------------------------------
;; Category: Feature Name
;; Description of what this profile grants access to.
;; Source: path/to/profile.sb
;; ---------------------------------------------------------------------------

(allow <operation>
    (<matcher> "<path>")
)

Operations

File Operations

OperationDescriptionExample
file-read*Read file contents, metadata, and list directories(allow file-read* (literal "/tmp/foo"))
file-read-metadataRead file metadata (stat) only, no contents(allow file-read-metadata (literal "/etc/hosts"))
file-write*Write, create, delete files(allow file-write* (subpath "/tmp"))
file-read* file-write*Combined read/write access(allow file-read* file-write* (home-subpath "/.myapp"))
file-read* includes listing directory contents (readdir()). Use file-read-metadata if you only need stat access.

Network Operations

OperationDescriptionExample
network-outboundEstablish outbound TCP/UDP connections(allow network-outbound (remote ip))
network-inboundAccept inbound connections (rare)(allow network-inbound (local tcp "*:8080"))

IPC Operations

OperationDescriptionExample
mach-lookupConnect to mach services(allow mach-lookup (global-name "com.apple.SecurityServer"))
mach-registerRegister mach services (rare)(allow mach-register (global-name-regex #"^com\\.myapp\\."))
ipc-posix-shm-*POSIX shared memory access(allow ipc-posix-shm-read-data (ipc-posix-name "com.apple.AppleDatabaseChanged"))

IOKit Operations

OperationDescriptionExample
iokit-openOpen IOKit user clients (GPU, hardware access)(allow iokit-open (iokit-user-client-class "IOSurfaceRootUserClient"))

Matchers

File Path Matchers

Matches a single file or directory exactly. Safest and most precise.
(allow file-read*
    (literal "/Users/alice/.gitconfig")
    (literal "/etc/hosts")
)
When used on a directory, literal grants access to list the directory’s immediate children (via readdir()), but not recursive access to subdirectories or their files.
Use case: Grant access to a specific config file or single directory.
Matches a directory and all files/subdirectories recursively under it.
(allow file-read* file-write*
    (subpath "/Users/alice/projects/myapp")
    (home-subpath "/.docker")
)
Broader grant. Only use subpath when you need full recursive access. Prefer literal for single files or narrower grants.
Use case: Grant full access to a project directory or toolchain installation.
Matches paths that start with the given string.
(allow file-read*
    (prefix "/usr/local/bin/node")
)
This matches /usr/local/bin/node, /usr/local/bin/node-v18, /usr/local/bin/nodejs, etc.
Less common in Safehouse profiles. Use literal or subpath when possible for clarity.
Use case: Match versioned binaries or symlinks with a common prefix.
Matches paths using a POSIX Extended Regular Expression.
(allow file-read*
    (regex #"^/Users/[^/]+/\\.nvm/versions/node/v[0-9]+\\.[0-9]+\\.[0-9]+/")
)
High complexity. Use sparingly. Regex patterns are harder to audit and can unintentionally match sensitive paths.
Use case: Match dynamic paths with variable components (e.g., usernames, version numbers).

Helper Macros (Safehouse-Specific)

Safehouse provides helper macros defined in 00-base.sb:
(define HOME_DIR "__SAFEHOUSE_REPLACE_ME_WITH_ABSOLUTE_HOME_DIR__")
(define (home-subpath rel) (subpath (string-append HOME_DIR rel)))
(define (home-literal rel) (literal (string-append HOME_DIR rel)))
(define (home-prefix rel) (prefix (string-append HOME_DIR rel)))
Usage:
(allow file-read* file-write*
    (home-subpath "/.myapp")               ;; /Users/alice/.myapp (recursive)
    (home-literal "/.myapp.conf")          ;; /Users/alice/.myapp.conf (exact)
    (home-prefix "/.config/myapp")         ;; /Users/alice/.config/myapp* (prefix)
)
Always use home-* macros instead of hardcoding /Users/alice. The HOME_DIR placeholder is replaced at runtime with the actual home directory path.

Mach Service Matchers

Matches a specific mach service name.
(allow mach-lookup
    (global-name "com.apple.SecurityServer")
    (global-name "com.apple.cfprefsd.daemon")
)
Use case: Grant access to specific macOS system services.
Matches mach service names using a regex pattern.
(allow mach-lookup
    (global-name-regex #"^org\\.chromium\\.crashpad\\.child_port_handshake\\.")
)
Use case: Match dynamic service names (e.g., Chromium crashpad child processes).

Real Examples

Example 1: Aider Agent Profile

From profiles/60-agents/aider.sb:
;; ---------------------------------------------------------------------------
;; Agent: Aider
;; Aider CLI binary, state, and config paths.
;; Source: 60-agents/aider.sb
;; ---------------------------------------------------------------------------

(allow file-read* file-write*
    (home-prefix "/.local/bin/aider")
    (home-prefix "/.local/bin/aider-install")
    (home-prefix "/.aider")
    (home-literal "/.aider.conf.yml")
    (home-literal "/.aider.model.settings.yml")
    (home-literal "/.aider.model.metadata.json")
    (home-subpath "/.config/aider")
    (home-subpath "/.cache/aider")
    (home-subpath "/.local/share/aider")
)
Breakdown:
  • home-prefix "/.local/bin/aider" matches /Users/alice/.local/bin/aider, /Users/alice/.local/bin/aider-install, etc.
  • home-literal grants access to specific config files.
  • home-subpath grants recursive access to cache and data directories.

Example 2: Keychain Integration

From profiles/55-integrations-optional/keychain.sb:
;; ---------------------------------------------------------------------------
;; Integration: Keychain
;; macOS Keychain and Security framework for agent authentication/credential storage.
;; Source: 55-integrations-optional/keychain.sb
;; ---------------------------------------------------------------------------

(allow file-read* file-write*
    (home-subpath "/Library/Keychains")
    (home-literal "/Library/Preferences/com.apple.security.plist")
)

(allow file-read*
    (literal "/Library/Preferences/com.apple.security.plist")
    (literal "/Library/Keychains/System.keychain")
    (subpath "/private/var/db/mds")
)

(allow file-read-metadata
    (home-literal "/Library")
    (home-literal "/Library/Keychains")
    (literal "/Library")
    (literal "/Library/Keychains")
)

(allow mach-lookup
    (global-name "com.apple.SecurityServer")
    (global-name "com.apple.security.agent")
    (global-name "com.apple.securityd.xpc")
    (global-name "com.apple.security.authhost")
    (global-name "com.apple.secd")
    (global-name "com.apple.trustd")
)

(allow ipc-posix-shm-read-data ipc-posix-shm-write-create ipc-posix-shm-write-data
    (ipc-posix-name "com.apple.AppleDatabaseChanged")
)
Breakdown:
  • file-read-metadata grants stat-only access to parent directories (no content listing).
  • mach-lookup grants access to security-related mach services.
  • ipc-posix-shm-* grants shared memory access for keychain change notifications.

Example 3: Electron Integration (with Dependency)

From profiles/55-integrations-optional/electron.sb:
;; ---------------------------------------------------------------------------
;; Integration: Electron
;; Chromium/Electron runtime: GPU, Metal shaders, crashpad, and WebView.
;; Source: 55-integrations-optional/electron.sb
;; $$require=55-integrations-optional/macos-gui.sb$$
;; #safehouse-test-id:electron-integration#
;; ---------------------------------------------------------------------------

;; -- GPU / Metal shader compilation --------------------------------------------
;; #safehouse-test-id:electron-gpu-metal#

(allow mach-lookup
    (global-name "com.apple.MTLCompilerService")
    (global-name "com.apple.SafariPlatformSupport.Helper")
)

(allow iokit-open
    (iokit-user-client-class "IOSurfaceRootUserClient")
    (iokit-user-client-class "AGXDeviceUserClient")
)

(allow file-read-metadata
    (literal "/private/var/db/.AppleSetupDone")
)

;; -- Chromium crashpad crash reporting -----------------------------------------
;; #safehouse-test-id:electron-crashpad#
;; #safehouse-test-id:electron-crashpad-lookup#

(allow mach-lookup
    (global-name-regex #"^org\\.chromium\\.crashpad\\.child_port_handshake\\.")
)

;; #safehouse-test-id:electron-crashpad-register#
(allow mach-register
    (global-name-regex #"^org\\.chromium\\.crashpad\\.child_port_handshake\\.")
)
Key features:
  • $$require=55-integrations-optional/macos-gui.sb$$ auto-injects macos-gui.sb when electron.sb is enabled.
  • #safehouse-test-id:*# markers are used by test assertions to verify rule structure/order.
  • global-name-regex matches dynamic crashpad service names.

Authoring Checklist

When creating a new profile:
1

Choose the right stage prefix

StagePurpose
30-toolchains/Language runtimes, package managers
40-shared/Cross-agent resources
50-integrations-core/Always-on integrations (git, gh)
55-integrations-optional/Opt-in integrations (docker, ssh, keychain)
60-agents/Agent-specific grants
65-apps/App bundle-specific grants
2

Add standard header

;; ---------------------------------------------------------------------------
;; Category: Feature Name
;; Description of what this profile grants access to.
;; Source: path/to/profile.sb
;; ---------------------------------------------------------------------------
3

Declare dependencies (if needed)

;; $$require=55-integrations-optional/macos-gui.sb$$
Use $$require= for implicit optional integration injection. For documentation-only dependencies, use comments:
;; Requires: macos-gui integration (documentation only)
4

Write sandbox rules

Prefer literal > subpath > prefix > regex (narrowest first).Use home-* macros instead of hardcoded /Users/alice.
5

Add test markers (optional)

;; #safehouse-test-id:my-feature#
(allow file-read*
    (home-subpath "/.myapp")
)
Used by assert_policy_order_literal and assert_policy_contains in tests.
6

Add tests

Create a test section in tests/sections/:
# tests/sections/my-feature.sh
section_begin "my feature"

assert_allowed "read from granted path" \
  cat ~/.myapp/config.json

assert_denied "write to system path" \
  touch /System/test

register_section
7

Regenerate dist artifacts

./scripts/generate-dist.sh
Commit both the new profile and regenerated dist/ files in the same PR.

Best Practices

Narrow Grants

Use literal for single files/directories. Only use subpath when you need full recursive access.

Use Home Macros

Always use home-subpath, home-literal, home-prefix instead of hardcoded /Users/alice.

Document Why

Explain why a grant is needed, not just what it does. Future maintainers will thank you.

Test Behavior

Add assert_allowed and assert_denied tests for every new profile. Verify both positive and negative cases.

Debugging Sandbox Denials

Watch Live Denials

/usr/bin/log stream --style compact --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "deny("'
2026-03-09 10:23:45.123 kernel: Sandbox: myagent(1234) deny(1) file-read-data /Users/alice/.ssh/id_rsa

Recent Denial History

/usr/bin/log show --last 2m --style compact --predicate 'process == "sandboxd"'

Kernel-Level Stream

/usr/bin/log stream --style compact --info --debug --predicate '(processID == 0) AND (senderImagePath CONTAINS "/Sandbox")'
Run these commands before invoking safehouse to capture denials as they happen.

Reference Material

Official Sources

  • macOS built-in profiles: /System/Library/Sandbox/Profiles/ and /usr/share/sandbox/
  • Safehouse profiles: profiles/ (primary style reference)
  • Assembled examples: dist/profiles/safehouse.generated.sb and dist/profiles/safehouse-for-apps.generated.sb

Matcher Preference Order

  1. literal — Exact path match (safest, most precise)
  2. subpath — Recursive directory match (broader, use when needed)
  3. prefix — Starts-with match (less common, use for versioned paths)
  4. regex — Regular expression match (highest complexity, use sparingly)

Next Steps

Policy Architecture

Understand assembly order, profile layers, and dependency system.

Customization

Learn about machine-local overrides and --append-profile for runtime customization.

Build docs developers (and LLMs) love