Skip to main content

Overview

The dist/ directory contains packaged distribution artifacts for consumers. These are generated files, not the source of truth.
Never hand-edit dist/ files. All changes must be made in bin/ and profiles/, then regenerated via ./scripts/generate-dist.sh.

Distribution Artifacts

Running ./scripts/generate-dist.sh produces five committed artifacts:

dist/safehouse.sh

Single-file executable containing embedded profiles + runtime shell logic. Portable and self-contained.

dist/Claude.app.sandboxed.command

Single-file launcher for Claude Desktop. Fetches latest apps policy from GitHub at runtime.

dist/Claude.app.sandboxed-offline.command

Offline launcher for Claude Desktop with embedded apps policy (no network required).

dist/profiles/safehouse.generated.sb

Static default policy template with HOME_DIR and workdir placeholders.

dist/profiles/safehouse-for-apps.generated.sb

Static apps policy with --enable=macos-gui,electron,all-agents,all-apps.

dist/safehouse.sh: Single-File Executable

Purpose

The dist/safehouse.sh executable is a self-contained, portable distribution of Agent Safehouse. It embeds:
  • All profile files (profiles/**/*.sb) as heredoc blocks
  • Full runtime shell logic from bin/safehouse.sh and bin/lib/*.sh
  • Metadata (embedded profiles last modified timestamp)
Consumers can download one file and run it without cloning the repo.

Structure

1

Banner and Metadata

#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Agent Safehouse Dist Binary (generated file)
# Project: https://agent-safehouse.dev
# Embedded Profiles Last Modified (UTC): 2026-03-09T12:34:56Z
# Generated by: scripts/generate-dist.sh
# ---------------------------------------------------------------------------
set -euo pipefail
2

Profile Keys Array

PROFILE_KEYS=(
  "profiles/00-base.sb"
  "profiles/10-system-runtime.sb"
  "profiles/20-network.sb"
  "profiles/30-toolchains/bun.sb"
  "profiles/30-toolchains/deno.sb"
  # ... all profile files in lexicographic order
)
3

Embedded Profile Bodies

Each profile is embedded as a heredoc:
embedded_profile_body() {
  case "$1" in
    "profiles/00-base.sb")
      cat <<'__SAFEHOUSE_EMBEDDED_profiles_00_base_sb__'
(version 1)
;; Base profile content...
__SAFEHOUSE_EMBEDDED_profiles_00_base_sb__
      ;;
    "profiles/10-system-runtime.sb")
      cat <<'__SAFEHOUSE_EMBEDDED_profiles_10_system_runtime_sb__'
;; System runtime content...
__SAFEHOUSE_EMBEDDED_profiles_10_system_runtime_sb__
      ;;
    # ... all profiles
  esac
}
4

Inlined Runtime Logic

All shell logic from bin/safehouse.sh, bin/lib/common.sh, bin/lib/policy/*.sh, and bin/lib/cli.sh is concatenated inline.
5

Overridden Functions

Key functions are overridden to read from embedded profiles instead of filesystem:
append_profile() {
  local target="$1"
  local source="$2"
  local key content

  key="$(profile_key_from_source "$source")"
  if content="$(embedded_profile_body "$key" 2>/dev/null)"; then
    append_policy_chunk "$content"
    append_policy_chunk ""
    return
  fi

  # Fallback: read from disk (for --append-profile external files)
  if [[ -f "$source" ]]; then
    content="$(<"$source")"
    append_policy_chunk "$content"
    append_policy_chunk ""
    return
  fi

  echo "Missing profile module: ${source}" >&2
  exit 1
}
6

Main Entrypoint

main "$@"

Usage

Identical to bin/safehouse.sh:
# Download and run
curl -fsSL https://raw.githubusercontent.com/eugene1g/agent-safehouse/main/dist/safehouse.sh -o safehouse.sh
chmod +x safehouse.sh
./safehouse.sh --enable=docker,ssh -- myagent

# Or install globally
mv safehouse.sh /usr/local/bin/safehouse
safehouse --enable=docker,ssh -- myagent

Static Policy Files

dist/profiles/safehouse.generated.sb

Default policy template with:
  • HOME_DIR placeholder: "/__SAFEHOUSE_TEMPLATE_HOME__"
  • Workdir grant placeholder: "/__SAFEHOUSE_TEMPLATE_WORKDIR__"
  • All agents enabled (--enable=all-agents)
  • No optional integrations (use --enable=... when generating)
Use case: Consumers who want to manually replace placeholders and use the policy directly with sandbox-exec.
# Generate policy with custom workdir
sed 's|/__SAFEHOUSE_TEMPLATE_HOME__|/Users/alice|g; s|/__SAFEHOUSE_TEMPLATE_WORKDIR__|/Users/alice/projects/myapp|g' \
  dist/profiles/safehouse.generated.sb > /tmp/policy.sb

sandbox-exec -f /tmp/policy.sb -- myagent

dist/profiles/safehouse-for-apps.generated.sb

Apps policy template with:
  • HOME_DIR placeholder: "/__SAFEHOUSE_TEMPLATE_HOME__"
  • Workdir grant placeholder: "/__SAFEHOUSE_TEMPLATE_WORKDIR__"
  • All agents + all apps enabled (--enable=macos-gui,electron,all-agents,all-apps)
  • Electron and macOS GUI integrations (for Claude Desktop, VS Code, etc.)
Use case: Embedded in Claude Desktop launchers (Claude.app.sandboxed.command, Claude.app.sandboxed-offline.command).

Generation Process

Invoking the Generator

./scripts/generate-dist.sh
Requires macOS with sandbox-exec. The generator invokes bin/safehouse.sh to produce static policies.

Generation Steps

1

Collect and Validate Profiles

collect_profiles() {
  while IFS= read -r rel_path; do
    profile_files+=("$rel_path")
  done < <(
    cd "$ROOT_DIR"
    find profiles -type f -name '*.sb' | LC_ALL=C sort
  )
}
Validates required profiles (00-base.sb, 10-system-runtime.sb, etc.) and counts by stage prefix.
2

Resolve Embedded Profiles Timestamp

resolve_embedded_profiles_last_modified_utc() {
  # Prefer git commit metadata for deterministic output
  epoch="$(git -C "$ROOT_DIR" log -1 --format=%ct -- "${profile_files[@]}" 2>/dev/null || true)"

  if [[ ! "$epoch" =~ ^[0-9]+$ ]] || (( epoch <= 0 )); then
    # Fallback: latest filesystem mtime
    epoch="$(latest_embedded_profile_epoch_from_fs)"
  fi

  format_epoch_utc "$epoch"
}
Uses git log for deterministic timestamps across CI/machines. Falls back to filesystem mtime if git is unavailable.
3

Generate dist/safehouse.sh

write_dist_script() {
  emit_banner "$embedded_profiles_last_modified_utc"
  emit_array_declaration "PROFILE_KEYS" "${profile_files[@]}"
  emit_embedded_profiles_function
  emit_safehouse_globals
  emit_inlined_runtime_sources
  emit_embedded_overrides
  echo 'main "$@"'
}
Concatenates banner, profile keys, embedded heredocs, runtime logic, and overrides into a single executable.
4

Generate Static Policies

generate_static_policy_files() {
  # Create template directories
  mkdir -p "$template_home" "$template_workdir"

  # Generate default policy
  HOME="$template_home" "$GENERATOR" --enable=all-agents --workdir="" --output "$default_policy_path" >/dev/null

  # Generate apps policy
  HOME="$template_home" "$GENERATOR" --enable=macos-gui,electron,all-agents,all-apps --workdir="" --output "$apps_policy_path" >/dev/null

  # Rewrite HOME_DIR to template placeholder
  rewrite_static_policy_home_dir_literal "$default_policy_path"
  rewrite_static_policy_home_dir_literal "$apps_policy_path"

  # Append workdir grant template
  append_static_policy_workdir_grant "$default_policy_path"
  append_static_policy_workdir_grant "$apps_policy_path"
}
Invokes bin/safehouse.sh with template paths, then rewrites HOME_DIR and workdir placeholders.
5

Generate Claude Launchers

write_claude_launcher "$launcher_path"
write_claude_offline_launcher "$launcher_offline_path" "$apps_policy_path"
Creates single-file launchers for Claude Desktop (online and offline variants).
6

Cleanup and Output

cleanup_template_root
printf '%s\n' "$output_path"
printf '%s\n' "$launcher_path"
printf '%s\n' "$launcher_offline_path"
printf '%s\n' "$default_policy_path"
printf '%s\n' "$apps_policy_path"

Deterministic Output

Why Determinism Matters

Dist artifacts are committed to the repository. Non-deterministic generation causes unnecessary diffs and merge conflicts.

How Safehouse Achieves Determinism

find profiles -type f -name '*.sb' | LC_ALL=C sort
Forces consistent ordering across different machines and locales.
epoch="$(git log -1 --format=%ct -- "${profile_files[@]}" 2>/dev/null)"
Uses git log commit timestamp instead of filesystem mtime. Same commit = same timestamp.
template_home="/__SAFEHOUSE_TEMPLATE_HOME__"
template_workdir="/__SAFEHOUSE_TEMPLATE_WORKDIR__"
Static placeholders instead of actual home directory paths. Ensures generated policies are portable.
HOME="$template_home" "$GENERATOR" --enable=all-agents --workdir="" --output "$default_policy_path"
Overrides HOME to template path during generation. Output is independent of the generator’s actual home directory.

CI/CD Integration

Auto-Regeneration Workflow

Safehouse CI automatically regenerates dist/ when profiles or runtime logic change:
# .github/workflows/dist-regen.yml
name: Regenerate Dist Artifacts

on:
  push:
    paths:
      - 'profiles/**/*.sb'
      - 'bin/**/*.sh'

jobs:
  regenerate:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Regenerate dist
        run: ./scripts/generate-dist.sh
      - name: Commit changes
        run: |
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add dist/
          git commit -m "Regenerate dist artifacts" || exit 0
          git push

Manual Regeneration

When contributing:
# After editing profiles/ or bin/
./scripts/generate-dist.sh

# Commit both source and dist changes
git add profiles/ bin/ dist/
git commit -m "Add SSH integration profile"
PRs that modify profiles/ or bin/ must include regenerated dist/ files. CI will fail if dist/ is out of sync.

Inspecting Dist Artifacts

Verify Embedded Profiles

# List all embedded profile keys
grep -A 100 'PROFILE_KEYS=(' dist/safehouse.sh | grep '"profiles/'

Extract a Single Embedded Profile

# Extract docker.sb
dist/safehouse.sh # (source the file functions)
embedded_profile_body "profiles/55-integrations-optional/docker.sb"

Compare Source vs Embedded

# Source profile
cat profiles/60-agents/aider.sb

# Embedded profile
awk '/"profiles\/60-agents\/aider.sb"/,/^__SAFEHOUSE_EMBEDDED/ {print}' dist/safehouse.sh | head -n -1 | tail -n +3

Key Takeaways

Generated, Not Source

dist/ artifacts are generated files. All changes must be made in bin/ and profiles/, then regenerated.

Single-File Distribution

dist/safehouse.sh embeds all profiles and runtime logic for portable, self-contained execution.

Deterministic Output

Lexicographic sorting, git timestamps, and template paths ensure identical output across machines.

CI Auto-Regeneration

Safehouse CI auto-commits regenerated dist/ when profiles or runtime logic change.

Next Steps

Contributing

Learn how to contribute profiles, runtime logic, and tests to Agent Safehouse.

Policy Architecture

Understand how profiles are assembled and concatenated at runtime.

Build docs developers (and LLMs) love