Assembly Order
Agent Safehouse assembles sandbox policies from modular.sb files in a fixed, deterministic order. Later rules win — understanding this order is critical when debugging unexpected behavior.
Base Layer (00-base.sb)
Defines The
HOME_DIR placeholder, helper macros (home-subpath, home-literal, home-prefix), and the foundational (deny default) rule.HOME_DIR placeholder is replaced at assembly time with the actual resolved home directory path.System Runtime (10-system-runtime.sb)
Grants access to core macOS system libraries, frameworks, fonts, locales, and dyld caches required for process startup.
Network (20-network.sb)
Enables TCP/UDP network operations, DNS resolution, and related mach services (
com.apple.networkd, com.apple.SystemConfiguration).Toolchains (30-toolchains/*.sb)
Language runtimes and package managers: Node.js, Python, Ruby, Go, Rust, Java, Deno, Bun, PHP, Perl, and version managers (nvm, pyenv, rbenv, asdf).All
.sb files in profiles/30-toolchains/ are concatenated in lexicographic order.Shared Resources (40-shared/*.sb)
Cross-agent resources like
agent-common.sb (temp directories, shell config, terminal access).Core Integrations (50-integrations-core/*.sb)
Always-on integrations:
git.sb, scm-clis.sb (gh, gh-copilot, gh-dash, gh-i, gh-markdown-preview, gh-poe), launch-services.sb, and container-runtime-default-deny.sb.Optional Integrations (55-integrations-optional/*.sb)
Opt-in integrations enabled via
--enable=<feature> or injected automatically when required by agent/app profiles:docker,kubectl,ssh,clipboard,spotlightmacos-gui,electron,chromium-headless,chromium-fullkeychain(auto-injected via$$require=metadata)cloud-credentials,1password,cleanshot,agent-browser,browser-native-messaging,shell-init,process-control,lldb
Agent Profiles (60-agents/*.sb)
Agent-specific grants selected by command basename match or explicit
--enable=all-agents:aider.sb,claude-code.sb,cursor-agent.sb,goose.sb,opencode.sb,pi.sb, and others.
--enable=all-agents).App Profiles (65-apps/*.sb)
App bundle-specific grants for GUI apps like
claude-app.sb and vscode-app.sb.Selected by app bundle detection or --enable=all-apps.Dynamic CLI Path Grants
Paths from
--add-dirs-ro (read-only) are emitted first, followed by --add-dirs (read/write).Each path grant includes ancestor directory literals for traversal (see Policy Architecture).Workdir Grant
The selected working directory (default:
$PWD) receives a recursive read/write subpath grant.Omitted if --workdir= explicitly empty or SAFEHOUSE_WORKDIR= unset.Profile Layers
Stage Prefix System
Profiles are organized by numeric stage prefix:| Stage | Directory | Purpose | Selection |
|---|---|---|---|
00 | profiles/ | Base definitions, HOME_DIR, (deny default) | Always |
10 | profiles/ | System runtime (dyld, frameworks) | Always |
20 | profiles/ | Network access | Always |
30 | profiles/30-toolchains/ | Language runtimes, package managers | Always (all files) |
40 | profiles/40-shared/ | Shared agent resources (temp, shell) | Always (all files) |
50 | profiles/50-integrations-core/ | Git, SCM CLIs, launch services | Always (all files) |
55 | profiles/55-integrations-optional/ | Docker, SSH, keychain, GUI, etc. | Opt-in (--enable) or auto-injected |
60 | profiles/60-agents/ | Agent-specific grants | Command-matched or --enable=all-agents |
65 | profiles/65-apps/ | App bundle-specific grants | Bundle-matched or --enable=all-apps |
Lexicographic Concatenation
Within each stage directory, files are concatenated usingfind ... | LC_ALL=C sort:
Dependency System
$$require= Metadata
Profiles can declare dependencies using inline metadata:
electron.sb is selected (via --enable=electron or agent profile requirement), macos-gui.sb is automatically injected even if not explicitly enabled.
$$require= is machine-read metadata. Human-facing comments like ;; Requires: macos-gui are documentation-only.Keychain Auto-Injection
Thekeychain.sb integration is special: it’s never explicitly enabled via --enable=keychain. Instead, it’s auto-injected when any selected agent/app profile declares:
keychain.sb is automatically included in the policy assembly.
Dependency Resolution Logic
Frombin/lib/policy/30-assembly.sh:
- Explicit
--enable=<feature>✅ - Agent/app profile declares
$$require=55-integrations-optional/<feature>.sb$$✅ - Already-enabled integration declares
$$require=55-integrations-optional/<feature>.sb$$✅
Ancestor Literals
When granting access to a directory path, Safehouse emits ancestor directory literals for traversal:Why file-read* with literal (not file-read-metadata with subpath)?
Why file-read* with literal (not file-read-metadata with subpath)?
Agents like Claude Code call
readdir() on every ancestor directory during startup. If only file-read-metadata (stat) is granted, the agent cannot list directory contents, which causes it to blank PATH and break.Using literal (not subpath) keeps this safe: it grants read access to the directory entry itself (listing its immediate children), but does NOT grant recursive read access to files or subdirectories under it.emit_path_ancestor_literals() in bin/lib/policy/30-assembly.sh (lines 289-323).
Explain Mode
Use--explain to inspect the effective policy configuration:
Testing Policy Behavior
Run Tests
tests/sections/*.sh, using helpers from tests/lib/common.sh:
assert_allowed: Verify operation succeeds inside sandboxassert_denied: Verify operation fails inside sandboxassert_policy_contains: Check for literal string in generated policyassert_policy_order_literal: Verify rule ordering
Add a Test Section
If your session is already sandboxed, tests cannot run. State this explicitly in your PR and provide static validation instead.
Key Takeaways
Order Matters
Policy rules are concatenated in fixed stage order. Later rules override earlier ones. Check assembly order first when debugging.
Deterministic Assembly
Lexicographic file sorting ensures identical policy output across machines and CI. Stage prefixes control module loading.
Dependency Injection
Use
$$require=path/to/profile.sb$$ to auto-inject optional integrations when your agent/app profile is selected.Ancestor Literals
Directory grants include ancestor literals for traversal. Using
literal (not subpath) keeps ancestor access safe.