Skip to content

Policy Architecture

safehouse composes a final sandbox policy from modular profiles, then runs your command under sandbox-exec.

Policy assembly order:

LayerCoverage
00-base.sbDefault deny, helper functions, HOME replacement token
10-system-runtime.sbmacOS runtime binaries, temp dirs, IPC
20-network.sbNetwork policy
30-toolchains/*.sbApple Toolchain Core, Node, Python, Go, Rust, Bun, Java, PHP, Perl, Ruby
40-shared/*.sbShared cross-agent modules
50-integrations-core/*.sbCore integrations (container-runtime-default-deny, git, launch-services, scm-clis, ssh-agent-default-deny, worktree-common-dir, worktrees)
55-integrations-optional/*.sbOpt-in integrations (--enable=...)
60-agents/*.sbPer-agent profile selection by command basename
65-apps/*.sbPer-app bundle selection (Claude.app, Visual Studio Code.app)
Config/env/CLI grantsTrusted .safehouse config, env grants, CLI grants, auto-detected app bundle read grant, selected workdir, launch-time active-worktree common-dir grant, and launch-time sibling worktree read grants
Appended profilesUser profile overlays via --append-profile (loaded last)

Ordering Rules Matter

Later rules win. If behavior is unexpected, check ordering first.

Important implications:

  • Broad late grants (for example --add-dirs or --enable=wide-read) can reopen earlier read denies.
  • Appended profiles (--append-profile) are the correct final override layer for must-not-read path denials.
  • Active linked-worktree common-dir detection happens at launch.
  • Sibling worktree read grants are a launch-time snapshot. New worktrees created after the process starts are not added to the active policy.

Path Matchers

Safehouse uses standard sandbox matchers:

  • literal: exact path
  • subpath: recursive path
  • prefix: starts-with path
  • regex: regex matcher

Ancestor literal read grants are intentionally emitted for traversal compatibility.

Built-In Absolute Path Resolution

Built-in profiles/* modules are authored with explicit absolute paths such as /etc, /private/etc/localtime, or /private/var/select/sh.

During policy rendering, Safehouse checks built-in absolute literal and subpath entries in allow file-read* stanzas and resolves symlink targets on the current host. If a path resolves somewhere else, Safehouse emits:

  • ancestor literal grants for the resolved target path
  • a matching literal or subpath read grant for that resolved target

This keeps built-in macOS compatibility paths working when the authored path is a symlink but the sandbox needs the real target path to match.

Current scope is intentionally narrow:

  • built-in profiles/* content only
  • absolute literal and subpath rules only
  • allow file-read* stanzas only

Dynamic CLI/config grants already normalize user-provided paths separately. Read/write, metadata-only, home-*, prefix, and regex rules are not auto-expanded by this mechanism today.

Home Placeholder Replacement

profiles/00-base.sb uses HOME_DIR placeholder token:

  • __SAFEHOUSE_REPLACE_ME_WITH_ABSOLUTE_HOME_DIR__

Assembly logic in /Users/eugene/server/agent-safehouse/bin/lib/policy/render.sh replaces this with the actual absolute home path.

HOME_DIR exists so profiles can express narrow home-relative rules through the shared helpers:

  • home-literal
  • home-subpath
  • home-prefix

It is not a blanket grant for $HOME.

Safehouse also generates a file-read-metadata block for /, the path to $HOME, and $HOME itself. That metadata-only traversal lets runtimes stat or walk toward explicitly allowed home-scoped paths without granting recursive reads of the whole home directory.

In practice, that means stat "$HOME" can succeed while ls "$HOME" and cat ~/secret.txt still fail unless a more specific rule grants them.

See also: Bin Architecture

Open source under the Apache 2.0 License.