Skip to content

Instantly share code, notes, and snippets.

@CJHwong
Created February 17, 2026 07:16
Show Gist options
  • Select an option

  • Save CJHwong/9bdc059fbc26a37cbe1a7eb159c206d7 to your computer and use it in GitHub Desktop.

Select an option

Save CJHwong/9bdc059fbc26a37cbe1a7eb159c206d7 to your computer and use it in GitHub Desktop.
Sandbox.sh
;; my.sb — Personalized Seatbelt profile for macOS
;;
;; Policy: deny-default → allow reads globally → deny reads on secrets → allowlist writes only
;;
;; Parameters (injected by wrapper):
;; _HOME — user home directory
;; _PROJECT_DIR — resolved project working directory
;; _TMPDIR — macOS per-user temp directory
(version 1)
(deny default)
;; ---------------------------------------------------------------------------
;; Process & system essentials
;; ---------------------------------------------------------------------------
(allow process-exec)
(allow process-fork)
(allow process-info*)
(allow signal)
(allow mach*)
(allow sysctl*)
(allow ipc-posix-shm*)
(allow ipc-posix-sem*)
(allow iokit-open)
(allow system-socket)
(allow system-fsctl)
(allow pseudo-tty)
(allow user-preference-read)
(allow file-ioctl)
;; ---------------------------------------------------------------------------
;; Network — fully allowed
;; ---------------------------------------------------------------------------
(allow network*)
;; ---------------------------------------------------------------------------
;; Read access — global allow, then deny secrets
;; ---------------------------------------------------------------------------
(allow file-read*)
;; -- Cryptographic keys --
;; Allows ssh config, known_hosts, and public keys; blocks private keys.
(deny file-read*
(require-all
(subpath (string-append (param "_HOME") "/.ssh"))
(require-not (literal (string-append (param "_HOME") "/.ssh/config")))
(require-not (literal (string-append (param "_HOME") "/.ssh/known_hosts")))
(require-not (literal (string-append (param "_HOME") "/.ssh/known_hosts.old")))
(require-not (regex (string-append "^" (regex-quote (param "_HOME")) "/\\.ssh/.*\\.pub$")))
))
(deny file-read* (subpath (string-append (param "_HOME") "/.gnupg")))
;; -- Cloud providers (Reasonable Access) --
;; Allow reading configs (profiles/regions) but deny credentials.
(deny file-read*
(require-all
(subpath (string-append (param "_HOME") "/.aws"))
(require-not (literal (string-append (param "_HOME") "/.aws/config")))
))
(deny file-read* (literal (string-append (param "_HOME") "/.aws/credentials")))
;; -- Container & orchestration --
(deny file-read* (subpath (string-append (param "_HOME") "/.docker")))
(deny file-read* (subpath (string-append (param "_HOME") "/.kube")))
;; -- Version control tokens (Reasonable Access) --
;; Allow reading gh settings but deny authentication hosts.
(deny file-read*
(require-all
(subpath (string-append (param "_HOME") "/.config/gh"))
(require-not (literal (string-append (param "_HOME") "/.config/gh/config.yml")))
))
(deny file-read* (literal (string-append (param "_HOME") "/.config/gh/hosts.yml")))
;; -- Package manager tokens --
(deny file-read* (literal (string-append (param "_HOME") "/.pypirc")))
;; -- Privacy & Secrets --
;; Simplified history block using regex
(deny file-read* (regex (string-append "^" (regex-quote (param "_HOME")) "/\\..*_history$")))
(deny file-read* (literal (string-append (param "_HOME") "/.env")))
(deny file-read* (literal (string-append (param "_HOME") "/.zhistory")))
(deny file-read* (subpath (string-append (param "_HOME") "/.zsh")))
;; -- macOS Sensitive Data --
;; Allowed to enable macOS Security framework (Keychain) for tool authentication (e.g., Claude login)
(allow file-read* (subpath (string-append (param "_HOME") "/Library/Keychains")))
(deny file-read* (subpath (string-append (param "_HOME") "/Library/Safari")))
;; -- Browser profiles --
(deny file-read* (subpath (string-append (param "_HOME") "/Library/Application Support/Google/Chrome")))
(deny file-read* (subpath (string-append (param "_HOME") "/Library/Application Support/Zen")))
;; ---------------------------------------------------------------------------
;; Write access — deny-default, explicit allowlist only
;; ---------------------------------------------------------------------------
(allow file-write* (subpath (string-append (param "_HOME") "/.claude")))
(allow file-write* (literal (string-append (param "_HOME") "/.claude.json")))
;; Ollama: Needed to sync aliases, save configurations, and manage models/temporary files
(allow file-write* (subpath (string-append (param "_HOME") "/.ollama")))
(allow file-write* (subpath (param "_PROJECT_DIR")))
(allow file-write* (subpath (param "_TMPDIR")))
(allow file-write* (subpath "/private/var/folders/"))
(allow file-write* (subpath "/private/tmp/"))
(allow file-write* (subpath "/dev/"))
;; ---------------------------------------------------------------------------
;; Mandatory write denies — defense-in-depth
;; ---------------------------------------------------------------------------
(deny file-write* (subpath (string-append (param "_PROJECT_DIR") "/.git/hooks")))
(deny file-write* (literal (string-append (param "_PROJECT_DIR") "/.git/config")))
(deny file-write* (literal (string-append (param "_PROJECT_DIR") "/.bashrc")))
(deny file-write* (literal (string-append (param "_PROJECT_DIR") "/.zshrc")))
(deny file-write* (literal (string-append (param "_PROJECT_DIR") "/.gitconfig")))
(deny file-write* (literal (string-append (param "_PROJECT_DIR") "/.mcp.json")))
(deny file-write* (subpath (string-append (param "_PROJECT_DIR") "/.vscode")))
(deny file-write* (subpath (string-append (param "_PROJECT_DIR") "/.idea")))
#!/usr/bin/env bash
# sb — Run any command inside a macOS sandbox-exec jail
#
# Usage: sb <command> [args...]
# Example: sb npm install
# Example: sb bash
#
# Policy (defined in my.sb):
# - Writes blocked outside: project dir, ~/.claude/, tmpdir, /dev/
# - Reads blocked on: private keys, auth tokens, etc.
# - Network, exec, IPC: all allowed
#
# Safety: refuses to run from $HOME (would allowlist writes to entire home dir)
set -euo pipefail
# Find the profile in the home directory
PROFILE="${HOME}/my.sb"
if [[ ! -f "$PROFILE" ]]; then
echo "error: sandbox profile not found: ${PROFILE}" >&2
exit 1
fi
if [[ $# -eq 0 ]]; then
echo "Usage: sb <command> [args...]" >&2
echo "Example: sb bash" >&2
exit 1
fi
# Resolve real paths
RESOLVED_HOME="$(cd "$HOME" && pwd -P)"
PROJECT_DIR="$(pwd -P)"
# Safety guard: refuse to run from $HOME directly
if [[ "$PROJECT_DIR" == "$RESOLVED_HOME" ]]; then
echo "error: refusing to run from \$HOME ($RESOLVED_HOME)" >&2
echo "" >&2
echo "Running from \$HOME would grant write access to your entire" >&2
echo "home directory, defeating the purpose of the sandbox." >&2
echo "" >&2
echo "Instead, cd into a project directory first:" >&2
echo " cd ~/my-project && sb <command>" >&2
exit 1
fi
# Resolve TMPDIR (macOS sets this to a per-user path like /var/folders/xx/...)
RESOLVED_TMPDIR="$(cd "${TMPDIR:-/tmp}" && pwd -P)"
# Execute the provided command inside the sandbox
exec sandbox-exec -f "$PROFILE" \
-D "_HOME=${RESOLVED_HOME}" \
-D "_PROJECT_DIR=${PROJECT_DIR}" \
-D "_TMPDIR=${RESOLVED_TMPDIR}" \
"$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment