add ai-sandbox: rootless podman sandbox for agentic development
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
This commit is contained in:
+146
@@ -0,0 +1,146 @@
|
||||
# Recipes
|
||||
|
||||
Concrete invocations. `pi` below is whatever you aliased to `ai-sandbox`.
|
||||
|
||||
## A first session
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
mkdir -p .ai-sandbox
|
||||
printf 'api.anthropic.com\n' > .ai-sandbox/allow
|
||||
pi --env ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
## Per harness
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```ini
|
||||
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.
|
||||
Reference in New Issue
Block a user