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)
Terminal deny rulesUnconditional deny rules emitted after all path grants and appended profiles

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.

Terminal Deny Rules

To prevent accidentally granting access to some critical files Safehouse unconditionally emits a final deny block as the very last section of every generated policy. Since this block is always last, it wins over any earlier write grant regardless of how the workdir, additional profiles, etc are configured.

Currently this is used to protect the workdir config file (<workdir>/.safehouse) from being modified from inside the sandbox. To opt out pass --allow-workdir-config-writes.

See also: Bin Architecture

Open source under the Apache 2.0 License.