Sandboxed terminal spawning using gVisor + pasta. Available as a CLI tool and a Go library.
- Creates a user + network namespace via
unshare - Sets up NAT networking with
pasta(internet access, local IPs blocked) - Generates an OCI bundle and launches
runsc(gVisor) for syscall-level isolation - Host filesystem is mounted read-only; home directory is writable by default
- Private IP ranges (RFC 1918, CGNAT, link-local, loopback, multicast, and reserved ranges) are blocked via iptables
- Sandboxed processes run with
noNewPrivilegesand a minimal capability set (file-ownership ops only — no CAP_SYS_ADMIN, no CAP_NET_RAW), so ping, FUSE, and nested mounts do not work inside the sandbox
runsc(gVisor) — syscall interception runtimepasta(passt) — user-space network namespace setupiptables/ip6tables— firewall rules inside the namespacensenter,unshare�� namespace management (from util-linux)ip— route discovery (from iproute2)
Package is wrapped with runsc, pasta, iptables, ip, nsenter, and unshare on PATH.
# one-off / ad-hoc
nix run path:/home/fipso/code/claudebox
# install into the user profile
nix profile install path:/home/fipso/code/claudebox
# dev shell with runtime deps
nix develop path:/home/fipso/code/claudebox
# or: nix-shellThis laptop's system flake (~/nix/nixos) pulls the local path input and adds
claudebox to home.packages. After a rebuild:
sudo nixos-rebuild switch --flake ~/nix/nixos#p1-gen4claudebox [flags] [shell]Spawns a sandboxed shell attached to your terminal. If no shell is specified, your default shell is used.
| Flag | Description |
|---|---|
--mount /path:ro |
Extra bind mount (repeatable). No suffix defaults to rw. |
--lan-range 192.168.1.0/24 |
CIDR range to allow LAN access to (repeatable). Wildcards (0.0.0.0/0, ::/0) are rejected. Note: CGNAT 100.64.0.0/10 (Tailscale) is blocked by default — allow it explicitly if needed |
--no-mount-home |
Don't mount home directory read-write |
--proxy-port 8080 |
Port to whitelist through the LAN firewall (e.g. for a local proxy) |
# Default sandboxed shell
claudebox
# Sandboxed bash with a project directory mounted read-write
claudebox --mount /home/user/code/myproject bash
# Hide home, only mount specific paths
claudebox --no-mount-home --mount /home/user/.config:ro --mount /tmp/work fish
# Allow access to a local network range
claudebox --lan-range 192.168.1.0/24import "claudebox"
// Spawn a sandboxed PTY
spty, err := claudebox.SpawnSandboxedPTY(
"", // shell (empty = default)
80, 24, // cols, rows
8, 16, // cellW, cellH
nil, // extra env vars
nil, // allowed LAN ranges
true, // mount home directory
nil, // extra mounts
)
if err != nil {
log.Fatal(err)
}
defer spty.Close()
// Read output
go func() {
for data := range spty.Output() {
os.Stdout.Write(data)
}
}()
// Write input
spty.Write([]byte("echo hello\n"))
// Resize
spty.Resize(120, 40, 8, 16)
// Wait for exit
<-spty.Done()You can also use the building blocks directly:
// Set up network namespace with pasta
netns, err := claudebox.StartPasta(0, nil)
defer netns.Stop()
// Generate an OCI bundle for runsc
mounts := []claudebox.MountSpec{
{Path: "/home/user/code", Mode: "rw"},
{Path: "/data", Mode: "ro"},
}
err = claudebox.GenerateBundle(bundleDir, "/bin/bash", nil, true, mounts)| Type / Function | Description |
|---|---|
MountSpec |
Bind mount spec with Path and Mode ("ro" / "rw") |
PastaNetns |
Holds state for a pasta-managed network namespace |
SandboxedPTY |
PTY running inside a gVisor sandbox |
SpawnSandboxedPTY() |
Create a fully sandboxed terminal (high-level) |
GenerateBundle() |
Generate an OCI bundle directory with config.json for runsc |
StartPasta() |
Create a network namespace with internet access and LAN isolation |
DefaultShell() |
Returns the user's default shell |