---
title: "Good Fences make Good Agents: sandvault + /sv skill (part 1 of n)"
description: A simple solution to working with agents that cannot be trusted to run 'ls'. Install with brew. Works in seconds. I added a few features.
doc_version: "1.0"
last_updated: 2026-08-25
slug: sandvault-simple-sandbox-for-agents
kind: code-story
status: shipped
date: 2026-05-24
summary: A simple solution to working with agents that cannot be trusted to run 'ls'. Install with brew. Works in seconds. I added a few features.
tags:
  - sandvault
  - security
  - agentsh
  - sandboxes
  - agents
  - claude-code
  - codex
  - sandbox-exec
---

# Good Fences make Good Agents: sandvault + /sv skill (part 1 of n)

_A simple solution to working with agents that cannot be trusted to run 'ls'._


### Ship

- commits: 7
- lines: +2267 −21
- PR: https://github.com/webcoyote/sandvault/commits?author=jesserobbins

## 01 · rm -rf /

I watched Claude test permissions on my machine by running `rm -rf /` and report back cheerily that the command had failed. I killed off everything and went quickly from **WTF** to ==absolutely-fucking-not==.

### Good fences make good Agents, or so the saying goes.

The first time you see an agent attempt something like this, or you realize it has just broken containment, it activates something deep, and dark, inside. The first instinct is of course the correct one: *nuke it from orbit, the only way to be sure*. The second is the realization that there is no going back to the way things were before, and no going forward by being a meat-based approval presser. Which leaves building some kind of containment while running `--dangerously-skip-permissions`, and then obviously placing a demolition charge on the machine and maybe an axe near the network cable, just in case. "Yes, I'm sure."

### The fence-building industrial complex

I count twelve of these from people I know, and that is the undercount. Building fences that turn into gated communities for agents we **cannot trust to run `ls`**.

I found [sandvault](https://github.com/webcoyote/sandvault) through [a post by Mike McQuaid](https://mikemcquaid.com/sandboxed-agent-worktrees-my-coding-and-ai-setup-in-2026/). Other fences below, from people I trust.

## 02 · TLDR: Enter Sandvault

<img src="/labs/sandvault.webp" alt="Sandvault logo" class="cstory__p-logo" /> Sandvault is from [Patrick Wyatt](https://www.codeofhonor.com/), whose claims to fame include games where you have to build a lot of containment. Each agent runs as a dedicated macOS user behind a `sandbox-exec` profile. The user can write to its own home and a shared workspace under `/Users/Shared/sv-$USER/`. It cannot read your home, your keychain, your SSH agent, or any other user.

```bash
brew install sandvault
sv build
sv claude                # also: sv codex, sv opencode, sv gemini, sv shell
# shortcuts: sv cl, sv co, sv o, sv g, sv s
cat PROMPT.md | sv gemini   # stdin works too

```

[`/sv`](https://github.com/webcoyote/sandvault/pull/143) is the protocol I added on top of that floor.

## 03 · Introducing /sv - seamless sandbox handoff

Type `/sv` in Claude Code or Codex CLI. A sandboxed worker spins up on the other side of the wall with your briefing, and runs there until you're done. You stay in your conversation. `/sv pull` brings back whatever it did.

After talking with [Jesse Vincent](https://github.com/obra) about [claude-session-driver](https://github.com/obra/claude-session-driver), his tmux skill for orchestrating subagents, I realized this flow could be collapsed to a single skill. I built `/sv` and sent Patrick a PR. It works like this.

> ¶ The diagram below is part of another research project I'm working on. Feedback appreciated.



```text
/sv         hand a task off to a sandboxed Claude or Codex worker
/sv status  read the report channel from the sandboxed worker
/sv pull    pull the result branch back through the deploy key

```

The worker is bounded twice. `sandbox-exec` keeps it out of your home directory and every other repo on disk. The per-repo deploy key keeps it out of every GitHub repo except the one you cloned. Two independent fences, both enforced by the kernel or by GitHub, neither relying on the worker behaving.

## 04 · How the sandbox boundary actually works



Run `sv claude` and the agent runs as `sandvault-jesse`, sees only what `sandvault-jesse` can see. If it does something stupid, the radius is bounded by one user account. The model is concrete:

```text
writable:  /Users/Shared/sv-$USER     -- shared with you and sandvault-$USER
writable:  /Users/sandvault-$USER     -- sandvault's own home directory
readable:  /usr, /bin, /etc, /opt     -- system directories
no access: /Users/*                   -- every other user, including yours
no access: /Volumes/*                 -- mounted, remote, and network drives

```

Cross-boundary work goes through the shared workspace at `/Users/Shared/sv-$USER/`. When you [`sv-clone`](https://github.com/webcoyote/sandvault/blob/main/sv-clone) a repo, sandvault adds a `sandvault` remote on your host-side repo pointing at the in-sandbox clone, so `git fetch sandvault` pulls commits the sandboxed agent made.

**First-run reality.** The sandbox user is a fresh macOS user with no credentials, so on first run you have to log into things inside the sandbox. Run `sv claude` and Claude Code asks you to authenticate. Run `sv codex` and Codex shows you an OAuth URL. Same for `gh`, npm, anything else the agent will need. It is the equivalent of setting up a new laptop on the first try. After that, the sandbox user keeps its own session state, so subsequent runs come up signed in.

**Running several at once.** Point [claude-session-driver](https://github.com/obra/claude-session-driver) at the `sv` commands. Each driven session can be an `sv claude` or `sv codex` invocation, so the sandboxing comes along for free. A Claude supervisor can dispatch to `sv codex` (or [Codex](https://developers.openai.com/codex/) to `sv claude`) to keep the workers on a different token budget.

## 05 · My sandvault pro-tips

### `--browser`: headless Chrome from inside the sandbox

`sv --browser claude` starts Chrome on the host with a dynamic CDP port and exposes the endpoint inside the sandbox as `$SV_BROWSER_ENDPOINT`. Playwright and Puppeteer connect with one line. The Chrome instance runs in an isolated user-data directory so your real Chrome profile is untouched. The sandboxed agent gets a full browser without ever seeing your cookies, sessions, or open tabs.

```bash
sv --browser claude
sv --lightpanda claude        # use Lightpanda instead of Chrome
# inside the sandbox:
node -e "import('playwright').then(({ chromium }) =>
  chromium.connectOverCDP(process.env.SV_BROWSER_ENDPOINT))"

```

### `--ios`: drive the iOS Simulator from inside the sandbox

Same shape as `--browser`. Simulator.app runs on the host (it's a GUI), an HTTP bridge runs on localhost, the bridge endpoint is exposed inside the sandbox as `$SV_IOS_SIMULATOR_ENDPOINT`. The bridge translates calls into `xcrun simctl` plus [iosef](https://github.com/riwsky/iosef). Add `--ios-gui` to watch the simulator window while the agent drives it.

```bash
sv --ios claude              # bridge only, no visible window
sv --ios --ios-gui claude    # bridge + watch the simulator

```

### Nested-sandbox carve-outs

macOS doesn't support recursive sandboxes, so `swift` and `xcodebuild` (which already sandbox themselves) break inside sandvault unless you tell them not to. Sandvault sets `SV_SESSION_ID` in the environment so your build scripts can detect they're already inside a sandbox and stand down their own.

```bash
if [ -n "$SV_SESSION_ID" ]; then
  export SWIFTPM_DISABLE_SANDBOX=1
  XCODE_FLAGS="--disable-sandbox"
fi
xcodebuild $XCODE_FLAGS build

```

### `--native-install` (`-N`): self-contained sandboxes

By default sandvault uses your host's Homebrew to install Claude / Codex / OpenCode / Gemini. With `-N`, the AI tools install *inside* the sandbox using each tool's own installer. Useful when you want the sandbox to be self-contained, or when you don't want sandvault touching host Homebrew at all.

```bash
sv --native-install build                        # one-off
sv -N claude                                     # short form
export SANDVAULT_ARGS="--native-install"         # make it the default

```

### Dotfiles sync (with a different prompt for the sandbox)

Drop your shell config into `/Users/Shared/sv-$USER/user/`, and sandvault copies it into the sandbox user's home on each build. I keep a different-coloured prompt in there so I always know when I'm looking at a sandbox shell. Mike McQuaid's [dotfiles](https://github.com/MikeMcQuaid/dotfiles) have a `script/sync` worth borrowing.

```bash
mkdir -p /Users/Shared/sv-$USER/user
cp ~/.zshrc        /Users/Shared/sv-$USER/user/.zshrc
cp ~/.gitconfig    /Users/Shared/sv-$USER/user/.gitconfig
cp -R ~/.config    /Users/Shared/sv-$USER/user/.config
sv build                # picks them up on next build

```

## 06 · A few other fences my friends with trust issues are building...

Sandvault isn't the only one I'd trust on my machine. Full writeup coming; until then, public links only:

[**agentsh**](https://github.com/canyonroad/agentsh) — Eran Sandler ([@erans](https://github.com/erans)). Execution-layer security: policy-enforced bash-compatible shell. Point your harness at agentsh instead of `/bin/bash` and it intercepts syscalls against a deterministic policy. Ships a local DLP proxy for secret detection. Stricter quarantine than sandvault. Eran also writes about [why he built it](https://eran.sandler.co.il/post/2026-02-21-it-was-the-shell-damn-it-why-i-built-agentsh/).

[**navaris**](https://github.com/erans/navaris) — Eran Sandler. A sandbox control plane for managing isolated execution environments across multiple backends (LXC / Firecracker), with copy-on-write fork support. Signature feature is that you can *peek into* a running sandbox without breaking it.

[**Stockyard**](https://github.com/prime-radiant-inc/stockyard) — [Jesse Vincent](https://github.com/obra). Firecracker VM farm + ZFS. 1.9-second container start on a NUC. ZFS copy-on-write snapshots from pre-tool-use hooks for state rollback. Tailscale auth, in-browser console. Ephemeral cattle VMs.

[**agent-safehouse**](https://github.com/eugene1g/agent-safehouse) — by [Eugene](https://github.com/eugene1g). Single self-contained shell script that wraps any agent invocation with `sandbox-exec`. Claude Code, Codex, Gemini CLI, Cursor Agent, Cline, Aider.

Full writeup is the next post in the series unless I get distracted by something else.

## 07 · What I shipped

Seven commits upstream. `/sv` is the headline; `agentsview` export is the second new feature; the rest is the plumbing `/sv` needed and the bugs I tripped on getting it there.

**`e565225`** Add /sv Claude Code skill for handing tasks off to sandvault — _jesserobbins, Apr 24, 2026_ (+92 −0)

**`2970e9a`** Add agentsview export from sandbox sessions — _jesserobbins, May 3, 2026_ (+1760 −0)

**`1d62630`** Add per-repo SSH deploy keys for cloned repositories — _jesserobbins, Apr 22, 2026_ (+113 −2)

**`49ae69f`** Add --fix-permissions flag, umask detection, and permission hardening — _jesserobbins, Mar 31, 2026_ (+276 −16)

**`66a1233`** Fix SSH mode when Remote Login is set to "All users" — _jesserobbins, Mar 31, 2026_ (+20 −2)

**`f1959b1`** Fix git object permissions with --no-hardlinks — _jesserobbins, Apr 21, 2026_ (+1 −1)

**`d983d62`** Fix sandvault user not added to sandvault group — _jesserobbins, Apr 20, 2026_ (+5 −0)

## Sitemap

See [sitemap.md](https://jesserobbins.com/sitemap.md) for the full list of pages on this site.
