A single bash script that runs a coding harness (pi by default) inside a rootless Ubuntu 24.04 container with only the current directory mounted at /project, an explicit egress allowlist and an explicit environment: nothing is passed in that was not named on the command line or in a config file. Network enforcement here is empirical rather than assumed (tests/spike_network.sh, docs/networking.md). The original design -- a sidecar proxy on an internal network -- does not work rootless on this host, so allow mode runs pasta with --map-host-loopback, installs an inet aisb ruleset inside the container that drops every egress path except the gate's, and relays through a squid gate bound to the host's loopback that resolves names itself and refuses anything unlisted. The entrypoint checks that the default-drop rule really exists before setpriv drops to uid 1000 with an empty capability set, and exits 77/78 rather than run unenforced. The Containerfile, the entrypoint, the squid config generator and the ruleset renderer all live in bin/ai-sandbox, and the image is tagged by content hash, so a policy change can never silently reuse a stale image. Tests are pure bash: 161 assertions start no container and touch no network, 256 run real ones, including proof that a raw socket, a stripped proxy env and DNS all stay blocked while an allowlisted host works over both http and CONNECT. LLM-Generated: true
4.0 KiB
4.0 KiB
Recipes
Concrete invocations. pi below is whatever you aliased to ai-sandbox.
A first session
cd ~/Projects/myapp
ai-sandbox doctor # is this machine ready?
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY
Same thing, but remembered by the repo (so teammates review the policy, not just your shell history):
mkdir -p .ai-sandbox
printf 'api.anthropic.com\n' > .ai-sandbox/allow
pi --env ANTHROPIC_API_KEY
Per harness
# pi (baked into the image)
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY
# pi with a proxy/harness you pass explicitly
pi --harness 'pi --model sonnet' --allow api.anthropic.com --env ANTHROPIC_API_KEY
# OpenAI Codex CLI (install it into the image with --install or use a custom base)
pi --allow chatgpt.com --allow api.openai.com --env OPENAI_API_KEY
# Claude Code
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY --env CLAUDE_CODE_OAUTH_TOKEN
# just a shell in the sandbox
pi --shell
# any other build/test command, no network needed
pi --net none -- make test
Per ecosystem
Only the endpoints the tool actually needs. Everything else stays blocked, so a typosquatted package from an unexpected host fails loudly instead of quietly working.
# npm / node
pi --allow registry.npmjs.org --allow nodejs.org --allow github.com
# python: pypi needs both of these (the CDN that serves the files is separate)
pi --allow pypi.org --allow files.pythonhosted.org
# rust
pi --allow crates.io --allow static.crates.io --allow index.crates.io
# go
pi --allow proxy.golang.org --allow sum.golang.org
# docker/podman images from within a build
pi --allow registry-1.docker.io --allow auth.docker.io --allow production.cloudflare.docker.com
# a project that also needs its dev database (non-HTTP: pinned IP + port)
pi --allow api.anthropic.com --allow 10.20.0.5:5432 --env ANTHROPIC_API_KEY
Git
# fetch/push over https with a token in the environment
pi --allow github.com --env GH_TOKEN -- git push origin main
# ssh: the key has to be mounted deliberately (an explicit, reviewable grant)
pi --allow github.com --mount ~/.ssh:ro -- git push origin main
# read-only view of a sibling repo for context
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY --mount ../docs:/docs:ro
Tightening a run
# nothing persistent, nothing readable, nothing writable outside the repo
pi --net none --ephemeral-home --read-only -- make build
# an entirely blank sandbox for untrusted code
pi --net none --no-cwd --ephemeral-home --read-only -- bash
# CI-ish: refuse to start if the policy or environment is not exactly right
pi --strict-args --net allow --allow-file .ai-sandbox/allow --env-file .ci/env -- npm ci
Long-lived gate shared by several sandboxes
ai-sandbox gate up --allow api.anthropic.com --allow registry.npmjs.org
# name=aisb-gate
# port=18081
# proxy_env=export HTTPS_PROXY=http://100.64.0.1:18081
ai-sandbox --proxy 100.64.0.1:18081 --allow api.anthropic.com --shell
ai-sandbox gate logs # what did it allow/deny?
ai-sandbox gate down
Debugging
ai-sandbox --dry-run --allow example.com --env KEY # the exact podman invocation
ai-sandbox --print-policy # effective policy, no secrets
ai-sandbox --print-entrypoint | less # the enforcement script
ai-sandbox --keep --net none -- false # leave the container for forensics
ai-sandbox ps
AISB_GATE_KEEP=1 pi --allow example.com --shell # keep the gate after exit
ai-sandbox clean --all
# "the ruleset would not install" (exit 77): check the host
podman info | grep -i rootless
grep "$(id -un)" /etc/subuid
Making it the default for a project
.ai-sandbox/config:
net = allow
harness = pi
cpus = 8
memory = 8g
mount = ../specs:/specs:ro
env = ANTHROPIC_API_KEY
.ai-sandbox/allow:
api.anthropic.com
registry.npmjs.org
Then pi alone in that directory is the whole invocation — and because both files
are in the repository, the sandbox policy gets reviewed with the code.