A sandboxed harness found none of your skills, prompt templates or standing AGENTS.md, because those live in ~/.agents and only the project was mounted. It is now bind-mounted at /home/sandbox/.agents by default -- bound, never copied -- with --no-agents to opt out and --agents-dir for a curated set, reported by --print-policy and by doctor. Read-only is the load-bearing part and has no escape hatch: a skill is an instruction the model follows later, on the host, so a sandbox that could write ~/.agents had a persistence path out of itself, planted as a helpful-looking SKILL.md. It holds even under --insecure-keep-root, the mount lands after /home/sandbox itself so it cannot be shadowed by the home volume or tmpfs, and an explicit --mount on the same destination wins because podman refuses duplicates. A missing or unreadable directory is a silent skip like the pi bridge's; an explicit --agents-dir that resolves to nothing is a hard error. The exposure is stated in docs/threat-model.md: the tree, its .git history and anything pasted into a skill are readable by whatever runs in there. tests/unit/09_agents.sh covers resolution, precedence and the bind-never-copy canary; tests/e2e/12_sandbox.sh mounts it in a real container, checks the host tree survives a run byte for byte and that even root inside the sandbox cannot write it. LLM-Generated: true
214 lines
6.8 KiB
Markdown
214 lines
6.8 KiB
Markdown
# 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 aipi.reeseapps.com --env AI_API_TOKEN
|
|
```
|
|
|
|
With your pi config bridged, the token is found on its own, so the allowlist is the
|
|
only thing you have to say:
|
|
|
|
```bash
|
|
pi --allow aipi.reeseapps.com
|
|
```
|
|
|
|
Same thing, but remembered by the repo (so teammates review the policy, not just
|
|
your shell history):
|
|
|
|
```bash
|
|
mkdir -p .ai-sandbox
|
|
printf 'aipi.reeseapps.com\n' > .ai-sandbox/allow
|
|
pi
|
|
```
|
|
|
|
## Pi's own configuration
|
|
|
|
The bridge rewrites your host `~/.pi/agent/{models,settings}.json` instead of copying
|
|
it: providers, models, thinking budgets and preferences come along, the credential
|
|
becomes `"$AI_API_TOKEN"`, and anything that only works on the host (a `!command`
|
|
header, a path into `~`) is dropped with a warning. `auth.json` is never bridged.
|
|
|
|
```bash
|
|
# the everyday case: same provider as the host, token from the environment
|
|
pi --allow aipi.reeseapps.com
|
|
|
|
# a model your gateway serves, chosen per run
|
|
pi --allow aipi.reeseapps.com --model juggernaut
|
|
|
|
# keep the model's endpoint pinned by IP as well, on a LAN inference host
|
|
pi --allow aipi.reeseapps.com --allow 10.20.0.7:11434
|
|
|
|
# a different variable name for the credential
|
|
pi --allow aipi.reeseapps.com --pi-token-env GW_TOKEN
|
|
|
|
# do not hand the token over automatically: the run then fails closed unless you
|
|
# pass it yourself, which is what you want in shared scripts
|
|
pi --allow aipi.reeseapps.com --env AI_API_TOKEN --no-pi-token-env
|
|
|
|
# no bridge: pi starts with its own defaults inside the volume
|
|
pi --no-pi-config --allow aipi.reeseapps.com
|
|
```
|
|
|
|
Edits made inside the sandbox are kept; changes to the host config are picked up on
|
|
the next run; deleting `~/.pi/agent/models.json` inside the sandbox re-seeds it.
|
|
|
|
## Your skills and standing instructions
|
|
|
|
`~/.agents` is shared read-only at `/home/sandbox/.agents` by default, so the sandboxed
|
|
harness finds the same skills, prompt templates and `AGENTS.md` you use on the host.
|
|
A project's own `.agents/` needs nothing: it arrives with the project mount, and harnesses
|
|
that read both see the project's alongside yours.
|
|
|
|
```bash
|
|
# the everyday case: your skills are simply there
|
|
pi --allow aipi.reeseapps.com
|
|
|
|
# a run where the harness should not have them: reviewing untrusted code, or a
|
|
# skills set that mentions internal hosts you would rather it not repeat back
|
|
pi --net none --no-agents -- bash -lc 'make lint'
|
|
|
|
# a curated set for sandboxes -- e.g. host skills stay yours, this dir is what you share
|
|
pi --agents-dir ~/.config/ai-sandbox/agents --allow aipi.reeseapps.com
|
|
|
|
# two sets, per trust level, chosen by config file in a repo
|
|
# .ai-sandbox/config: agents_dir = ./.agents-shared
|
|
```
|
|
|
|
It is read-only and there is no flag to change that: a skill is an instruction you
|
|
follow on the host later, so letting a sandbox write one would let it persist past the
|
|
run. If a workflow really needs to *author* skills, do it in the project directory and
|
|
move the result into `~/.agents` yourself, where you can read it first.
|
|
|
|
## Per harness
|
|
|
|
```bash
|
|
# pi (baked into the image, config bridged)
|
|
pi --allow aipi.reeseapps.com
|
|
|
|
# pi with a model chosen on the command line
|
|
pi --harness 'pi --model turbo' --allow aipi.reeseapps.com
|
|
|
|
# a gateway that speaks a different protocol still needs only its host
|
|
pi --allow gateway.internal:8443 --env AI_API_TOKEN
|
|
|
|
# OpenAI Codex CLI / Claude Code: they read their own config files, so mount or
|
|
# generate what they need and allow their endpoints; the pi bridge does not apply
|
|
pi --allow chatgpt.com --env CODEX_API_KEY
|
|
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY
|
|
|
|
# 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 aipi.reeseapps.com --allow 10.20.0.5:5432
|
|
```
|
|
|
|
## 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 aipi.reeseapps.com --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: no project, no home, no skills
|
|
pi --net none --no-cwd --ephemeral-home --read-only --no-agents -- 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 aipi.reeseapps.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 aipi.reeseapps.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 = AI_API_TOKEN
|
|
```
|
|
|
|
`.ai-sandbox/allow`:
|
|
|
|
```
|
|
aipi.reeseapps.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.
|