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)

Operations

File Operations

file-read* includes listing directory contents (readdir()). Use file-read-metadata if you only need stat access.

Network Operations

IPC Operations

IOKit Operations


Matchers

File Path Matchers

Matches a single file or directory exactly. Safest and most precise.
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.
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.
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.
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:
Usage:
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.
Use case: Grant access to specific macOS system services.
Matches mach service names using a regex pattern.
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:
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:
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:
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

2

Add standard header

3

Declare dependencies (if needed)

Use $$require= for implicit optional integration injection. For documentation-only dependencies, use comments:
4

Write sandbox rules

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

Add test markers (optional)

Used by assert_policy_order_literal and assert_policy_contains in tests.
6

Add tests

Create a test section in tests/sections/:
7

Regenerate dist artifacts

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

Recent Denial History

Kernel-Level Stream

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.