Help us shape the future of Go! Take the 2026 Go Developer Survey

ptrbox

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 1 Imported by: 0

README

        _        _
  _ __ | |_ _ __| |__   _____  __
 | '_ \| __| '__| '_ \ / _ \ \/ /    ptrbox
 | |_) | |_| |  | |_) | (_) >  <     disposable Claude Code sandboxes, one VM per repo
 | .__/ \__|_|  |_.__/ \___/_/\_\
 |_|

CI

ptrbox gives every repo its own Lima VM on your Mac, with Claude Code inside and a kernel-enforced firewall around it. The agent can install packages, run tests and edit code. It cannot reach the internet except through a domain allowlist, cannot see anything on your Mac except that one project directory, and holds no credentials except its own Claude token.

The problem

Running a coding agent directly on your Mac gives it your files, your ssh keys, your browser sessions, and your network. Permission prompts help, but only if you read every one.

ptrbox runs the agent in a VM instead. The isolation comes from the hypervisor and a firewall in the guest kernel, so it holds no matter what the agent does — including --dangerously-skip-permissions. The guarantees come from the machine, not from the agent asking first.

Why use this

  • Egress is default-deny, per sandbox. An nftables firewall in each guest allows exactly one route out: a CONNECT-only squid proxy with a domain allowlist. Each VM has its own list, so granting pypi.org to one project grants it to nothing else. ptrbox logs --denied shows what was blocked and which VM asked.
  • One directory, no keys. The project repo is the only mount. Git push happens on the host, where your ssh keys stay. The only credential in a VM is the Claude token — injected from the macOS Keychain, and only after every security assertion has passed.
  • No root, ever. Passwordless sudo is removed and setuid binaries are stripped on every boot. The agent cannot undo the firewall.
  • VMs are disposable. Everything a sandbox contains — distro, sizing, runtimes, packages, allowlist — is declared in host-side config and installed at create time. That config survives ptrbox rm, so deleting a VM loses nothing: ptrbox new rebuilds the same box in minutes. If a sandbox is in a bad state, delete it and make another.
  • Only what you ask for. Language runtimes (node, Python via uv, Go) and browser testing are each one flag, off by default. A VM without node has no npm, and no npm registry in its allowlist — every runtime you skip is attack surface the agent never has.
  • Verified before handover. ptrbox new reboots the VM to activate the firewall, then checks everything: direct egress blocked, proxy working, denied domains refused, sudo gone, requested runtimes present. If any check fails, your token never enters the VM.
  • The proxy is sandboxed too. Squid parses input every agent can influence, so it runs in its own VM with no mounts and no credentials — an exploit lands in an empty guest, not on your Mac. It starts with the first sandbox and stops with the last.
macOS host
├── 127.0.0.1:8888              port forward into the proxy VM - the host's
│                               only part of the egress path
├── git + ssh keys              stay on the host; you push from the host
├── ~/code/<repo>/              the ONLY directory a sandbox VM can see
├── Lima VM "ptrbox-proxy"      squid, CONNECT-only domain allowlist
│    └── no mounts, no credentials, nothing else installed
└── Lima VM per repo (vz + virtiofs)
     ├── mount:    ~/code/<repo> -> /workspace   (nothing else is mounted)
     ├── nftables: default-deny egress; only the proxy and DNS
     ├── no sudo:  passwordless root removed, setuid stripped, every boot
     └── claude    any permission mode - the VM is the boundary

Quick start

Requires macOS on Apple Silicon, Lima (brew install lima), and a Claude Pro/Max/Team subscription.

go install github.com/PtrMzk/ptrbox/cmd/ptrbox@latest
ptrbox install                # dependency checks, proxy VM, config files

claude setup-token            # prints a 1-year token; then store it:
security add-generic-password -a "$USER" -s claude-sandbox-token -w

ptrbox new my-api             # creates ~/code/my-api if needed, builds its VM
ssh lima-my-api
cd /workspace && claude

ptrbox install is idempotent — safe to re-run after every upgrade, and it never overwrites an edited file without asking. It never installs packages on the Mac for you; a missing dependency prints the brew install line and stops. ptrbox new takes a few minutes: it shows you the plan first (and offers to edit it), provisions with the network open, then reboots to activate the firewall and verifies everything before handing the VM over.

Your repo is a live two-way mount, so the agent's edits appear on your Mac immediately. Review and push from the host, where your keys are:

cd ~/code/my-api
git add -p && git commit && git push

Done with a project? ptrbox rm my-api destroys the VM and reclaims the disk. The repo, the VM's config and its allowlist are untouched.

Commands

Command What it does
ptrbox install [--yes] [--update] Host setup: dependencies, the proxy VM, config files. Idempotent; --update refreshes seeded files, carrying your settings.
ptrbox new <repo> Create the repo if needed, provision and verify its VM.
ptrbox rm <repo|vm> Destroy a VM and its artifacts. Leaves the repo alone.
ptrbox start <repo|vm> Start a stopped VM, bringing the proxy VM up first.
ptrbox stop <repo|vm> Stop a VM; the proxy VM stops with the last sandbox.
ptrbox logs [--denied] Read the proxy log. --denied shows what was blocked.
ptrbox allow <vm> [domain…] Add domains to that sandbox's allowlist, or open it in $EDITOR; --list prints it. Applies live.
ptrbox sync-proxy Push hand-edited allowlists to the proxy now.

A bare name lands under ~/code; anything with a slash is used as a path. Prefer ptrbox start/stop over limactl start/stop for sandboxes — a sandbox started without its proxy has no network at all.

When a build can't reach something

ptrbox logs --denied                  # find the domain (localport= says which VM asked)
ptrbox allow my-api files.example.com # grant it to that sandbox; validates and reloads

Each sandbox's list starts as a copy of the template (~/.config/ptrbox/allowed_domains.txt) minus the groups for runtimes it does not have. The per-VM file survives ptrbox rm, so a re-create keeps its egress; delete the file to reset. If an edit doesn't parse, the previous version is restored automatically. Every entry widens what the VM can reach — keep each list small.

Configuration

ptrbox install writes ~/.config/ptrbox/config, annotated, with every line commented out — configuring is uncommenting, not assembling. It covers the guest distro, the repo root, VM sizing, runtimes, extra apt packages, the Claude model and the in-VM git identity. Every key also works as a PTRBOX_* environment variable, which wins over the file.

Settings that describe one sandbox rather than your Mac can be set per VM:

# ~/.config/ptrbox/vms/thesis
PTRBOX_EXTRA_PACKAGES="texlive-latex-recommended latexmk"
PTRBOX_MEMORY=4GiB

Layers, lowest first, each stating only what it changes:

built-in defaults  <  ~/.config/ptrbox/config  <  ~/.config/ptrbox/vms/<vm>  <  environment

ptrbox new bakes the result into the VM, so editing a file does nothing until you re-create that sandbox — and because the file outlives the VM, a re-create brings the same sandbox back. Per-VM config is host-side, never read from the repo mount, so an agent cannot choose what its own sandbox contains.

What's inside a VM

Debian 13 (or Ubuntu 24.04), Claude Code, tmux, build tooling — plus the runtimes you ask for, and only those. Each is per-VM settable and off by default:

PTRBOX_NODE=true               # npm/npx, and therefore npx-based MCP servers
PTRBOX_NODE_VERSION=22.11.0    # or lts, the default
PTRBOX_UV=true                 # Python, via uv
PTRBOX_GO=true                 # the upstream Go toolchain, under $HOME
PTRBOX_PLAYWRIGHT=true         # Chromium/GTK libraries + the Playwright CDNs
PTRBOX_EXTRA_PACKAGES="ripgrep sqlite3"   # any apt packages, checked by name

Claude Code is always installed: it is a native binary and needs no runtime. If a runtime or package you asked for fails to install, ptrbox new fails — immediately, not a week later when something needs it. Anything that needs root is an edit to vm/provision/ followed by a re-create — the template stays the single source of truth for what a sandbox contains.

Development

make build    # dist/ptrbox
make lint     # go vet, plus shellcheck on the guest scripts
make test     # ~250 cases against a faked limactl and Keychain
make check    # both; needs no Mac, no VM and no network
make smoke    # the real thing: recreates a scratch VM (macOS only)

make check runs anywhere, including Linux and CI, because every external command is faked — it covers the whole lifecycle plus the security invariants asserted against the generated VM configs. Changing what a VM contains means changing vm/, then make golden: the diff to the golden files is the review surface. The host CLI depends on the Go standard library and nothing else.

The layout follows where code runs, because that boundary is the security model: cmd/ and internal/ run on your Mac, vm/ becomes the guests, host/ is the squid config and seed allowlist pushed into the proxy VM.

Limitations

  • macOS on Apple Silicon only.
  • At most 16 sandboxes at once: each holds one of the proxy's per-VM ports (ptrbox rm frees the slot).
  • The proxy VM idles at a few hundred MB of RAM while any sandbox runs; ptrbox errs toward leaving it up rather than cutting a live agent's network.
  • Guest DNS can still resolve external names (queries go out even though connections are blocked); closing that hole is planned.
  • uninstall and doctor are not implemented yet.

License

MIT, see LICENSE.

Documentation

Overview

Package ptrbox embeds the assets the binary installs: the Lima VM templates, the provisioning scripts that run inside them, the verification script, the squid config template plus its seed allowlist, and the annotated config file `ptrbox install` writes for you.

These stay as .yaml/.sh/.txt files rather than becoming Go string constants for two reasons. They execute inside Debian guests, so bash is their native language and rewriting them in Go is not on the table. And vm/ is the documented source of truth for what a sandbox VM contains - the review surface for every security-relevant change - which only works if the files are readable on their own terms.

Embedding them means the compiled binary carries everything it provisions: no PTRBOX_ROOT, no checkout to locate, nothing on disk that a later run could find modified. `go install` produces a complete ptrbox.

Index

Constants

This section is empty.

Variables

View Source
var Assets embed.FS

Assets holds the vm/, host/ and config/ trees, rooted exactly as they appear in the repository, so paths like "vm/provision/10-base.sh", "host/squid.conf.in" and "config/ptrbox.conf.example" mean the same thing in code, in tests and in docs.

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
cmd
ptrbox command
Command ptrbox creates and manages sandboxed Claude Code VMs, one per repo.
Command ptrbox creates and manages sandboxed Claude Code VMs, one per repo.
internal
cli
Package cli is ptrbox's command surface.
Package cli is ptrbox's command surface.
config
Package config resolves ptrbox's settings and the names and paths derived from them.
Package config resolves ptrbox's settings and the names and paths derived from them.
lima
Package lima is the whole of ptrbox's contact with limactl.
Package lima is the whole of ptrbox's contact with limactl.
lima/limafake
Package limafake simulates limactl, including each VM's filesystem.
Package limafake simulates limactl, including each VM's filesystem.
narrate
Package narrate turns limactl's log into ptrbox's voice.
Package narrate turns limactl's log into ptrbox's voice.
proxy
Package proxy manages the shared egress proxy VM.
Package proxy manages the shared egress proxy VM.
render
Package render turns the VM templates into the configs Lima is actually started with.
Package render turns the VM templates into the configs Lima is actually started with.
rendertest
Package rendertest holds the fixed template values used by the golden-file test and the security invariant assertions, plus the rendering helpers both of them need.
Package rendertest holds the fixed template values used by the golden-file test and the security invariant assertions, plus the rendering helpers both of them need.
ui
Package ui is ptrbox's output voice.
Package ui is ptrbox's output voice.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL