---
title: "Good Fences make Good Agents: Sandvault (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-05-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 (part 1 of N)

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

## 01 · rm -rf /

I asked Claude what execution protections were in place. Claude tested by running `rm -rf /` and reported 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 something like this, or an agent break containment, it activates something. (At least if you are an engineer with *ahem* a certain amount of experience.)

### The fence-building industrial complex

As a result of whatever that impulse is, I am now aware of something like *12* containment projects from people I know, and the wider count is bigger. Apparently it's a thing, 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/). [agentsh](https://github.com/canyonroad/agentsh), [Stockyard](https://github.com/prime-radiant-inc/stockyard), [navaris](https://github.com/erans/navaris), and a few more from people I trust are at the bottom of this post.

## 02 · TLDR: Enter Sandvault

Sandvault is from [Patrick Wyatt](https://www.codeofhonor.com/), whose claims to fame include games where you have to build a lot of containment.

![Sandvault logo](/labs/sandvault.webp)

```bash
brew install webcoyote/tap/sandvault
sv build
sv claude      # or sv codex, sv shell

```

### Handing a task off with /sv

[`/sv`](https://github.com/webcoyote/sandvault/pull/143) is a Claude Code skill that packages the current task as a briefing file in the shared workspace, opens a new terminal, runs `sv-clone` to spin up a sandboxed Claude (or Codex) with a per-repo SSH deploy key whose write scope is exactly the one repo, and hands the briefing to the sandboxed agent as its first instruction. The worker runs, commits, and pushes its branch back through that single deploy key. The host session never has to leave the conversation.

*This was my commit BTW, and it's been very useful.*

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

```

The two properties that make this worth the setup: the host's context is spent on judgement, not on watching the worker type. And the worker is bounded twice. Once by `sandbox-exec` (it can't reach your home directory or any other repo on disk). Once by the deploy key (it can't push to any GitHub repo besides the one you cloned). Two independent fences. You can leave the worker alone.

### How the sandbox boundary actually works



`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. No new protocols, no daemons, just git remotes.

### First-run reality

The strict-isolation model has one immediate consequence worth knowing: 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.

A few features that turned out to matter once I was past first-run:

**Dotfiles sync.** Drop your shell config into `/Users/Shared/sv-$USER/user/` (e.g. `.zshrc`, `.gitconfig`, `.config/`), 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.

**`--native-install` (`-N`).** 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 (`curl ... | bash`, `npm install -g`). Useful if you want the sandbox to be self-contained, or if you don't want sandvault touching host Homebrew at all. Set `export SANDVAULT_ARGS="--native-install"` to make it the default.

**`--browser` for headless Chrome.** `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: `chromium.connectOverCDP(process.env.SV_BROWSER_ENDPOINT)`. The Chrome instance runs in an isolated user-data directory so your real Chrome profile is untouched.

**`--ios` for iOS Simulator.** Same shape: 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 an agent works it.

**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; you check for it in your build scripts and pass `--disable-sandbox` / `SWIFTPM_DISABLE_SANDBOX=1` / etc. The README has the exact incantations.

**Maintenance.** `sv b -r` rebuilds the sandbox and re-applies ACLs (useful if files got copied in with wrong ownership). `sv uninstall` removes the sandvault user, the sandbox profile, and the shared-workspace ACL config, but does not delete `/Users/Shared/sv-$USER`. Your work stays put.

### Running several at once

For running multiple sandboxed workers at once, point a tmux-session manager at the `sv` commands. [Jesse Vincent](https://github.com/obra) has been working on exactly this with [claude-session-driver](https://github.com/obra/claude-session-driver) (*"launch, control, and monitor other Claude Code sessions as workers via tmux"*). 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.

## 03 · 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.

## 04 · My own contributions to this project

Seven contributions landed upstream: four new features and three bug fixes.

### New features

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

Mirrors sandbox-side session JSONLs into the host's [agentsview](https://github.com/erans/agentsview) via read-only symlinks. Adds an opt-in prompt during `sv setup`, a contamination pre-flight that refuses to run if the mirror paths point at non-sandbox directories, and a vendored TOML config writer so it works on system Python 3.9.

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

The `/sv` Claude Code skill. Hand a task off from a host Claude session to a sandboxed Claude or Codex with three commands. Detailed up above.

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

Fresh ED25519 key per cloned repo. `origin` rewritten to SSH, `core.sshCommand` pinned to that key with `IdentitiesOnly=yes`, public key uploaded via `gh` scoped to that one repo. The sandbox can push without seeing any other repo's credentials.

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

A restrictive umask makes `sv build` exit 0 with a broken environment. Adds an opt-in `--fix-permissions` flag, warns on every invocation when umask is restrictive, hardens `/var/sandvault/`, and adds six tests for the umask/permissions behavior.

### Bug fixes

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

Sandvault was checking for the `com.apple.access_ssh` group as the SSH-enabled signal, which does not exist when Remote Login is set to "All users". Falls back to checking whether sshd is actually running, and adds an SSH smoke test at build time.

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

One-character fix. `git clone --local` hardlinks loose objects, which inherit mode 0400 from the source, which makes commit history unreadable to the sandvault user. Switching to `--no-hardlinks` forces a copy with the calling user's umask.

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

Five lines. `PrimaryGroupID` alone does not register the user in `dseditgroup` membership checks, so ACL inheritance worked but membership-based ACL checks did not. Found by a sandboxed Claude during its own permission tests.

## Sitemap

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