Skip to main content

sbrun

sbrun launches commands in a sandbox that only allows writes beneath the current directory tree plus paths you explicitly opt into.

  • macOS: uses the Seatbelt sandbox via libsandbox
  • Linux: uses unprivileged user namespaces + mount namespaces (inspired by bubblewrap) by default; when the native sbrun binary is installed setuid root, it automatically switches to a privileged mount-namespace backend

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
  • platform-specific sandboxing is selected at compile time

Install

Install the latest release (macOS Apple Silicon only):

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

On Linux, install the Python package (which includes the sbrun command) or build from source:

pip install sbrun

On Ubuntu 24.04 and later, the default Linux backend requires this one-time setup to allow unprivileged user namespaces:

sudo sbrun --kernel-install

This applies the required system-wide kernel settings immediately and persists them across reboots in /etc/sysctl.d/90-sbrun.conf. See the Linux platform notes for details and the alternative setuid setup.

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 name itself starts with -, use -- to stop option parsing:

cd /path/to/project
sbrun -- ./-strange-binary

Help and version:

sbrun --help
sbrun --version

Install the persistent Linux sysctl fix and apply it:

sudo sbrun --kernel-install

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
  • --kernel-install: install /etc/sysctl.d/90-sbrun.conf and run sysctl --system (Linux only; must be root, e.g. via sudo)
  • --prompt-init[=bash|zsh]: print shell code for the lock icon prompt hook
  • --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
  • with --kernel-install, sbrun installs the persistent Linux sysctl config and runs sysctl --system
  • 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
  • file descriptors above stderr are closed before the command runs
  • stdout/stderr redirected to regular files outside allowed writable paths are rejected unless SBRUN_ALLOW_STDIO_REDIRECTS=1
  • exit codes: 127 if the command is not found, 126 if it is not executable, 111 for other sbrun errors

To add a lock icon to sandboxed bash or zsh prompts, put this in your ~/.bashrc or ~/.zshrc:

eval "$(sbrun --prompt-init)"

If shell autodetection gets the wrong shell, use one of:

eval "$(sbrun --prompt-init=bash)"
eval "$(sbrun --prompt-init=zsh)"

The generated hook uses SBRUN_ACTIVE and preserves existing bash PROMPT_COMMAND and zsh precmd_functions hooks.

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

On first run, if no config file exists, sbrun auto-creates ~/.config/sbrun/config.toml with sensible platform defaults (writable /tmp, ~/.cache, ~/.config, etc). The defaults are also shipped in the repo as sbrun.default.macos.toml and sbrun.default.linux.toml.

Platform notes

macOS

The sandbox is applied via the Seatbelt profile language and libsandbox. All reads are allowed; writes are confined to the working directory and configured paths.

Linux

The sandbox uses unprivileged user namespaces (CLONE_NEWUSER) and mount namespaces (CLONE_NEWNS), the same approach used by bubblewrap. The root filesystem is bind-mounted read-only, then writable paths are bind-mounted back on top. Default installs require neither root nor setuid.

When neither the working directory nor any allowed write path is under /tmp, a private 256M tmpfs is mounted on /tmp so that TMPDIR stays usable; its contents are discarded when the sandbox exits. When /tmp is writable (the default config allows it), the real shared /tmp is used, as on macOS.

If the native sbrun binary is installed root-owned and setuid, sbrun automatically switches to a privileged Linux backend. In that mode it skips CLONE_NEWUSER, sets up the mount namespace as root, then drops back to the calling user before exec(). That avoids AppArmor's unprivileged user namespace restriction without changing kernel settings.

Example optional install:

sudo install -o root -g root -m 4755 ./target/release/sbrun /usr/local/bin/sbrun

The setuid mode only applies to the native binary, not a Python console-script wrapper.

Requires kernel.unprivileged_userns_clone=1 (the default on most distros).

On Ubuntu 24.04 and later, the default AppArmor policy restricts the unprivileged user namespaces that sbrun needs. Use one of the setup options below before running sandboxed commands. Without this setup, sbrun typically fails before starting your command with an error like:

  • write /proc/self/setgroups: Permission denied
  • write /proc/self/uid_map: Operation not permitted

You can confirm the host setup with:

unshare --user --map-root-user --mount sh -c 'id -u; mount | head -1'

If that fails, sbrun will fail too.

Two ways to make sbrun work on affected Ubuntu systems:

  1. keep the default unprivileged install and let sbrun install the persistent host setting:
sudo sbrun --kernel-install

That writes:

cat <<'EOF'
kernel.unprivileged_userns_clone=1
kernel.apparmor_restrict_unprivileged_userns=0
EOF

and then runs sysctl --system.

  1. install the native sbrun binary setuid root instead, which needs no kernel setting change:
sudo install -o root -g root -m 4755 sbrun /usr/local/bin/sbrun

GitHub Actions applies the same user-namespace settings on its Linux runners before running the full sandbox integration suite.

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.

Metadata

Release files for sbrun 0.0.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for sbrun 0.0.13
File Interpreter ABI Platform Actions
sbrun-0.0.13-py3-none-manylinux_2_39_x86_64.whl Python 3 none Linux glibc 2.39+ x86-64 Details for sbrun-0.0.13-py3-none-manylinux_2_39_x86_64.whl
sbrun-0.0.13-py3-none-manylinux_2_39_aarch64.whl Python 3 none Linux glibc 2.39+ ARM64 Details for sbrun-0.0.13-py3-none-manylinux_2_39_aarch64.whl
sbrun-0.0.13-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for sbrun-0.0.13-py3-none-macosx_11_0_arm64.whl

Total release size: 982.6 kB

Release files / sbrun-0.0.13-py3-none-manylinux_2_39_x86_64.whl

Download URL sbrun-0.0.13-py3-none-manylinux_2_39_x86_64.whl
Size 344.1 kB
Tags Linux glibc 2.39+ x86-64 Python 3
SHA-256 checksum
How to use checksums
5cdda08026e775bd5c37567aab6b7323df6a49cae185af7111e598a74f0c202a
BLAKE2b-256 checksum
How to use checksums
5068892556cc68bb41f164a3399b0ec1ead3ba8f3df2f125abccf422a3b11c2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / sbrun-0.0.13-py3-none-manylinux_2_39_aarch64.whl

Download URL sbrun-0.0.13-py3-none-manylinux_2_39_aarch64.whl
Size 335.5 kB
Tags Linux glibc 2.39+ ARM64 Python 3
SHA-256 checksum
How to use checksums
74b51d53434e5111da87278965238eef25c4025c39cc0b6c9c596aa17d4eacbd
BLAKE2b-256 checksum
How to use checksums
6a599c18d3f51bb50ff9853d77e1313e38a4be1cfc0f941d305fd5f069895acc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / sbrun-0.0.13-py3-none-macosx_11_0_arm64.whl

Download URL sbrun-0.0.13-py3-none-macosx_11_0_arm64.whl
Size 302.9 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
00c1d4227e3c9da709f13e584c525cd532860f7f7c3c2e12c915d6e97d1ea198
BLAKE2b-256 checksum
How to use checksums
24cc61a852cc921e9ad0de3f9ff907bd96ca8aba193cf573acee4d0a8ff7e5f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Security for sbrun 0.0.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 3 files in this release have been uploaded with provenance metadata.

Select from 3 files

Attestation for sbrun-0.0.13-py3-none-manylinux_2_39_x86_64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page