lionkor/sbh

Sandbox-Here is a convenience script around bubblewrap to sandbox agents and similar semi-trusted processes

Python

8

10 commits

updated Aug 17, 2026

See the code

README

sandbox-here

Run a command so it can only see the current directory and its own config files. Everything else in your home directory is either read-only or hidden completely.

It's a Python script that builds a bubblewrap container and drops your command inside.

Why?

AI coding agents run LLM-generated shell commands on your machine. A command can read SSH keys, scrape shell history, exfiltrate ~/Documents, modify git config, or leak internal hostnames — even after you approved it.

sandbox-here blocks all of that. The command sees the project directory and its own config. Nothing else.

What it's not

It's not a jail for untrusted code. A determined attacker will get out. User namespaces are a hardening tool, not a security boundary. With --net, the command can make arbitrary outbound connections. There's no seccomp filter unless you add one, no resource limits, and writes to the app's own config directory persist on disk.

Think of it like locking your car. Stops casual snooping and mistakes. Won't stop someone with a brick. For running code you don't trust, use a VM or a separate machine.

Installation

mkdir -p ~/.config/sandbox-here
cp sandbox-here ~/.config/sandbox-here/
chmod +x ~/.config/sandbox-here/sandbox-here

Needs bubblewrap 0.11+ and Python 3.10+. That's it.

Then alias it in your shell:

alias sbh="$HOME/.config/sandbox-here/sandbox-here"

(sbh is what I use day-to-day. The full name works too.)

Usage

# Show usage and options
sbh --help

# Open a shell sandboxed to the current directory
sbh

# Run a command
sbh npm install
sbh make
sbh cargo build

# Allow network — for package managers or curl
sbh --net pip install requests

# Mount container runtime sockets
sbh --docker docker build .
sbh --podman podman run ...
sbh --docker --net docker pull alpine

# Mount extra files/dirs
# Shortcut: mount SRC read-write at its own path (relative paths OK)
sbh --add ../test-dir
# Full form: mode + SRC + DST (for remapping)
sbh --add ro /usr/share/dict/words /usr/share/dict/words
sbh --add rw /tmp/scratch /tmp/scratch
sbh --add ro /some/config.json /etc/myapp/config.json

When you run sbh npm, the script detects npm and mounts ~/.npm/ read-write. sbh cargo gets ~/.cargo/, sbh git gets ~/.gitconfig. sbh with no command opens your shell and makes the shell's own configs writable.

Symlinks are followed. If /usr/bin/sh points to bash, running sbh sh grants write access to ~/.bashrc and ~/.config/bash/.

What gets blocked

Sensitive paths are invisible — not even mounted read-only:

.ssh .gnupg .codex .pki .docker .mozilla .thunderbird .librewolf
.aws .azure .gcloud .gsutil .copilot .electrum .tor .gnome .kde4

Environment variables are wiped clean. Only a short safelist gets re-injected (PATH, HOME, TERM, LANG, a few toolchain vars). DEEPSEEK_API_KEY, SSH_AUTH_SOCK, SSLKEYLOGFILE, DISPLAY, DBUS_SESSION_BUS_ADDRESS — all stripped. If you need to pass something through, prefix it with SANDBOX_.

/etc/hosts is overlaid with a minimal file so your LAN machines, Tailnet nodes, and other internal hostnames don't leak.

Network is off by default. --net turns it on.

Container sockets (--docker, --podman, --containerd) are not mounted by default. Pass the flag to bind-mount the socket into the sandbox so docker, podman, or ctr work inside. --docker respects DOCKER_HOST when set to a unix:// path; TCP daemons need --net instead (no local socket to mount).

Use --add to mount arbitrary paths. --add SRC mounts the path read-write at its own location — relative paths resolve against the current directory, so sbh --add ../test-dir works from anywhere. The full form --add ro|rw SRC DST mounts SRC at DST instead, for remapping (e.g. a config file into /etc). ro gives read-only access, rw gives read-write. Repeat --add for multiple paths. Both SRC and DST are required in the full form — if you want the same path at the same mount point, specify it twice.

What doesn't get blocked

Any dotfile not on the blocklist is visible read-only — .bashrc, .gitconfig, .vimrc. If you stash secrets in those, they're visible.

With --net, the command has the same network access you do. It can hit localhost services, scan your LAN, make outbound connections.

The app's own config directory is writable and persists to disk. If you run sbh pi, the agent's config at ~/.pi/ survives sandbox teardown. A compromised agent can modify its own settings to persist across restarts.

/etc/passwd, /proc/cpuinfo, and other world-readable system files are visible. No attempt is made to hide them.

Seccomp

Optional. Drop a seccomp.bpf next to the script and it gets loaded. bubblewrap's repo has example policies. Without one, no syscall filtering happens.

Files

~/.config/sandbox-here/
├── sandbox-here      the script
├── seccomp.bpf       optional syscall filter
├── README.md         this file
└── AGENTS.md         reference for AI agents running inside the sandbox

License

Do whatever you want. It's a few hundred lines of Python that wraps bubblewrap. Don't care.

Significant stargazers

Nicholas Moen

65 followers · starred Jul 2026

lionkor/sbh

Sandbox-Here is a convenience script around bubblewrap to sandbox agents and similar semi-trusted processes

Python

8

10 commits

updated Aug 17, 2026

See the code

README

sandbox-here

Run a command so it can only see the current directory and its own config files. Everything else in your home directory is either read-only or hidden completely.

It's a Python script that builds a bubblewrap container and drops your command inside.

Why?

AI coding agents run LLM-generated shell commands on your machine. A command can read SSH keys, scrape shell history, exfiltrate ~/Documents, modify git config, or leak internal hostnames — even after you approved it.

sandbox-here blocks all of that. The command sees the project directory and its own config. Nothing else.

What it's not

It's not a jail for untrusted code. A determined attacker will get out. User namespaces are a hardening tool, not a security boundary. With --net, the command can make arbitrary outbound connections. There's no seccomp filter unless you add one, no resource limits, and writes to the app's own config directory persist on disk.

Think of it like locking your car. Stops casual snooping and mistakes. Won't stop someone with a brick. For running code you don't trust, use a VM or a separate machine.

Installation

mkdir -p ~/.config/sandbox-here
cp sandbox-here ~/.config/sandbox-here/
chmod +x ~/.config/sandbox-here/sandbox-here

Needs bubblewrap 0.11+ and Python 3.10+. That's it.

Then alias it in your shell:

alias sbh="$HOME/.config/sandbox-here/sandbox-here"

(sbh is what I use day-to-day. The full name works too.)

Usage

# Show usage and options
sbh --help

# Open a shell sandboxed to the current directory
sbh

# Run a command
sbh npm install
sbh make
sbh cargo build

# Allow network — for package managers or curl
sbh --net pip install requests

# Mount container runtime sockets
sbh --docker docker build .
sbh --podman podman run ...
sbh --docker --net docker pull alpine

# Mount extra files/dirs
# Shortcut: mount SRC read-write at its own path (relative paths OK)
sbh --add ../test-dir
# Full form: mode + SRC + DST (for remapping)
sbh --add ro /usr/share/dict/words /usr/share/dict/words
sbh --add rw /tmp/scratch /tmp/scratch
sbh --add ro /some/config.json /etc/myapp/config.json

When you run sbh npm, the script detects npm and mounts ~/.npm/ read-write. sbh cargo gets ~/.cargo/, sbh git gets ~/.gitconfig. sbh with no command opens your shell and makes the shell's own configs writable.

Symlinks are followed. If /usr/bin/sh points to bash, running sbh sh grants write access to ~/.bashrc and ~/.config/bash/.

What gets blocked

Sensitive paths are invisible — not even mounted read-only:

.ssh .gnupg .codex .pki .docker .mozilla .thunderbird .librewolf
.aws .azure .gcloud .gsutil .copilot .electrum .tor .gnome .kde4

Environment variables are wiped clean. Only a short safelist gets re-injected (PATH, HOME, TERM, LANG, a few toolchain vars). DEEPSEEK_API_KEY, SSH_AUTH_SOCK, SSLKEYLOGFILE, DISPLAY, DBUS_SESSION_BUS_ADDRESS — all stripped. If you need to pass something through, prefix it with SANDBOX_.

/etc/hosts is overlaid with a minimal file so your LAN machines, Tailnet nodes, and other internal hostnames don't leak.

Network is off by default. --net turns it on.

Container sockets (--docker, --podman, --containerd) are not mounted by default. Pass the flag to bind-mount the socket into the sandbox so docker, podman, or ctr work inside. --docker respects DOCKER_HOST when set to a unix:// path; TCP daemons need --net instead (no local socket to mount).

Use --add to mount arbitrary paths. --add SRC mounts the path read-write at its own location — relative paths resolve against the current directory, so sbh --add ../test-dir works from anywhere. The full form --add ro|rw SRC DST mounts SRC at DST instead, for remapping (e.g. a config file into /etc). ro gives read-only access, rw gives read-write. Repeat --add for multiple paths. Both SRC and DST are required in the full form — if you want the same path at the same mount point, specify it twice.

What doesn't get blocked

Any dotfile not on the blocklist is visible read-only — .bashrc, .gitconfig, .vimrc. If you stash secrets in those, they're visible.

With --net, the command has the same network access you do. It can hit localhost services, scan your LAN, make outbound connections.

The app's own config directory is writable and persists to disk. If you run sbh pi, the agent's config at ~/.pi/ survives sandbox teardown. A compromised agent can modify its own settings to persist across restarts.

/etc/passwd, /proc/cpuinfo, and other world-readable system files are visible. No attempt is made to hide them.

Seccomp

Optional. Drop a seccomp.bpf next to the script and it gets loaded. bubblewrap's repo has example policies. Without one, no syscall filtering happens.

Files

~/.config/sandbox-here/
├── sandbox-here      the script
├── seccomp.bpf       optional syscall filter
├── README.md         this file
└── AGENTS.md         reference for AI agents running inside the sandbox

License

Do whatever you want. It's a few hundred lines of Python that wraps bubblewrap. Don't care.

Significant stargazers

Nicholas Moen

65 followers · starred Jul 2026

Languages

Python

100.0%