Created
February 17, 2026 07:16
-
-
Save CJHwong/9bdc059fbc26a37cbe1a7eb159c206d7 to your computer and use it in GitHub Desktop.
Sandbox.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;; 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"))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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