Skip to main content

Run commands under the macOS sandbox with writes confined to selected paths

Project description

sbrun

sbrun launches commands under the macOS sandbox and only allows writes beneath the current directory tree plus paths you explicitly opt into.

The implementation is a single Rust crate:

  • the sbrun binary is the CLI
  • the same crate also exposes a Python sbrun.exec(...) API via PyO3
  • the binary applies the sandbox directly through libsandbox

Install

Install the latest macOS arm64 release:

curl -fsSL https://raw.githubusercontent.com/AnswerDotAI/sbrun/main/install.sh | bash

Build locally:

cargo build --release

Install the Python extension into an active virtualenv:

maturin develop --release

Use

Start an interactive login shell:

cd /path/to/project
sbrun

Run a command directly:

cd /path/to/project
sbrun python3 app.py

Run a shell snippet with your current $SHELL:

cd /path/to/project
sbrun -c 'touch ok.txt && echo hello'

Allow writes to an extra directory:

cd /path/to/project
sbrun --write /tmp -- python3 -c 'open("/tmp/sbrun-demo", "w").write("ok")'

Set environment variables to project-local directories:

cd /path/to/project
sbrun --env-dir IPYTHONDIR --env-dir MPLCONFIGDIR -- ipython

Remove selected variables from the child environment:

cd /path/to/project
sbrun --unset-env GITHUB_API_KEY --unset-env OPENAI_API_KEY -- python3 app.py

If the command itself starts with -, use -- to stop option parsing:

cd /path/to/project
sbrun -- -lc 'printf hello\n'

Help and version:

sbrun --help
sbrun --version

CLI

  • -w, --write PATH: allow writes to a regular file or directory; repeatable
  • -d, --env-dir VAR: set VAR to .sbrun/VAR; repeatable
  • -u, --unset-env VAR: remove VAR from the child environment; repeatable
  • -c, --command STRING: run $SHELL -lc STRING
  • --config PATH: load that TOML file and ignore the standard config locations
  • --no-config: ignore config files entirely
  • --: stop parsing sbrun options

Behavior:

  • with no command, sbrun launches your $SHELL as an interactive login shell
  • with -c/--command, sbrun runs $SHELL -lc STRING
  • otherwise sbrun execs the given command directly
  • SBRUN_ACTIVE=1 is exported in the child environment
  • HOME stays your real home directory when one is available
  • TMPDIR is set to /tmp
  • the shell history file is writable by default
  • stdout/stderr redirected to regular files outside allowed writable paths are rejected unless SBRUN_ALLOW_STDIO_REDIRECTS=1

For bash prompt logic, you can use SBRUN_ACTIVE without replacing an existing PROMPT_COMMAND or PS1. Put this in ~/.bashrc:

sbrun_prompt_prefix() {
  [[ ${SBRUN_ACTIVE:-} == 1 ]] || return
  case $PS1 in
    '🔒 '*) ;;
    *) PS1="🔒 $PS1" ;;
  esac
}

case "$(declare -p PROMPT_COMMAND 2>/dev/null)" in
  "declare -a "*)
    case " ${PROMPT_COMMAND[*]} " in
      *" sbrun_prompt_prefix "*) ;;
      *) PROMPT_COMMAND+=(sbrun_prompt_prefix) ;;
    esac
    ;;
  *)
    case ";${PROMPT_COMMAND:-};" in
      *";sbrun_prompt_prefix;"*) ;;
      *) PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }sbrun_prompt_prefix" ;;
    esac
    ;;
esac

If your login shell does not source ~/.bashrc, put the same snippet in ~/.bash_profile.

Config

sbrun reads TOML config from:

  • $XDG_CONFIG_DIRS/.../sbrun/config.toml
  • $XDG_CONFIG_HOME/sbrun/config.toml
  • ~/.config/sbrun/config.toml when XDG_CONFIG_HOME is unset

--config PATH replaces those defaults with one explicit file. --no-config skips config loading entirely.

Example:

version = 1

write = ["/tmp", "/Volumes/scratch"]
optional_write = [
  "~/.cache",
  "~/Library/Caches",
]

Rules:

  • version must be 1 when present
  • write entries are required and error if they do not resolve
  • optional_write entries are ignored when they do not resolve
  • config paths must be absolute or start with ~/
  • env_dir and unset_env are CLI-only

The repo ships a practical default config in sbrun.default.toml.

Python

The Python API is intentionally minimal and follows the same exec model as the CLI:

import sbrun

sbrun.exec(
    ["python3", "app.py"],
    write=["/tmp"],
    env_dir=["IPYTHONDIR"],
    unset_env=["GITHUB_API_KEY"],
)

On success, sbrun.exec(...) does not return because it replaces the current process image. On failure, it raises a Python exception.

Development

Build, test, and release notes live in DEV.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sbrun-0.0.5-cp39-abi3-macosx_11_0_arm64.whl (324.0 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

File details

Details for the file sbrun-0.0.5-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: sbrun-0.0.5-cp39-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 324.0 kB
  • Tags: CPython 3.9+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sbrun-0.0.5-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8dd457b10dc0a3bf82bf3c9999ee8c979c207a97284045e8961eba43982dba8d
MD5 7bc4b6e9be4685acdec9a3deb033e5b6
BLAKE2b-256 03daf41690d1f29d20e18389fcdac1b3cad8488deb8619b0b021883da68f5ff9

See more details on using hashes here.

Provenance

The following attestation bundles were made for sbrun-0.0.5-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on AnswerDotAI/sbrun

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page