Files
ai-sandbox/.agents/skills/right-sandbox/references/settings-map.md
T
ducoterra 34f94e38be add right-sandbox: write a project's own ai-sandbox policy
Turns a project directory into that project's policy, so a plain
`ai-sandbox` in the directory is the correct, reviewable invocation. It
writes the two files load_config() actually reads -- .ai-sandbox/allow
and .ai-sandbox/config -- and nothing else: no sandbox.yaml, no local
override, no invented schema, because a file nobody reads is a policy
nobody has.

scripts/survey.sh gathers the evidence read-only and prints no values:
variable NAMES, remote and registry HOSTS, service PORTS, with URL
credentials stripped before anything is printed -- the same rule
--dry-run, --print-policy and the session log hold themselves to. The
agent decides from that evidence, then verifies with --dry-run and
--print-policy rather than assuming an allowlist entry means a working
toolchain.

references/settings-map.md maps every accepted key and flag to its
default, plus per-ecosystem allowlists and the survey-to-policy cheat
sheet. references/port-and-loopback.md is the honest answer on ports:
outbound to a service on the machine via the mapped loopback address
100.64.0.1 (needs an allow entry and a client that does not hand the
request to the egress proxy), and why a port the sandbox listens on is
not reachable from the host in --net allow -- with probes, because that
half is a reading of gen_rules(), not a measurement.

Policy files only. Never builds, never starts a container, and never
writes a real credential: it writes .ai-sandbox/env.example and tells
the user what to put in the gitignored 0600 file.

LLM-Generated: true
2026-10-29 08:37:51 -04:00

8.3 KiB

Settings map

Check this against the installed program before writing a config: ai-sandbox --help, and cfg_read_kv() / resolve_opts() in the script. Where they disagree, the script wins and this file is the bug.

Precedence

built-in default  <  ~/.config/ai-sandbox/{config,allow}  <  ./.ai-sandbox/{config,allow}
                  <  AISB_* environment  <  command-line flag

allow, env, mount, install, label accumulate from every source (deduped); every other key replaces. So a repo file cannot revoke a user-wide allow line — say so if the user expects a repo config to tighten their own machine.

The container, for orientation

Anything named "container" below is inside the sandbox: uid/gid 1000, the image's own sandbox account, its home the value of HOME in there (--userns=keep-id makes files the agent writes owned by you on the host). So ~/.agents exists on both sides of the wall and they are different directories — the host one is bind-mounted read-only onto the container one at agents_dest. /project is the image's own directory and the fallback working directory, not the normal one.

Config keys (key = value, # comments)

key default notes
net allow none | allow | full. allow requires a non-empty allowlist.
harness pi pi | codex | claude | none | a command line, e.g. pi --model turbo
workdir auto auto mirrors the host path; or an absolute path. project_dest is parsed and never read — use workdir.
cpus 4
memory 4g 512m/4g shape
pids 512
tmpfs_size 1g sizes /tmp and, with home_cache = 0, the container home
base_image docker.io/library/ubuntu:24.04 may itself be name@sha256:…
node_version 22.23.4 checksum-verified against nodejs.org
pi_version 1.4.0 npm package version baked into the image
base_ttl_days 7 0 re-resolve every build, -1 never
base_auto_update 1 0 keeps the recorded digest offline
selinux :z when getenforce exists :z, :Z or empty
home_cache 1 0 = throwaway tmpfs home (also --ephemeral-home)
gate_addr 100.64.0.1 the address that means the host's loopback inside the container
gate_port 18081 preferred first port; next free is used
extra_args empty appended verbatim to podman run, word-split. Escape hatch: do not commit it
pi_config 1 bridge ~/.pi/agent/{models,settings}.json with the credential as $VAR
pi_token_env AI_API_TOKEN the variable the bridged config reads
pi_token_auto 1 pass that variable automatically when set
pi_agent_dir ~/.pi/agent where to read the host config from
thinking xhigh off minimal low medium high xhigh max host; pi only, re-applied every run
agents 1 share ~/.agents read-only when the directory exists
agents_dir ~/.agents which directory; a project's own .agents/ needs nothing
agents_dest the container's ~/.agents keep it under the container $HOME — that is the only place a harness looks for user-level skills
allow — repeatable; same grammar as --allow
env — repeatable; NAME, NAME=VALUE (no secret in a committed file), or @path = an env file
mount — repeatable; SRC[:DEST][:ro|rw], default DEST=/mnt/<basename>
install — repeatable; apt packages, each distinct set is its own image tag
label — repeatable; K=V on the container

Anything else warns (unknown key) and is ignored.

Flags

egress    --net none|allow|full  --allow HOST[:PORT]  --allow-file F  --proxy HOST:PORT  --no-gate
files     --mount SRC[:DEST][:ro]  --no-cwd  --workdir DIR|auto  --read-only
          --home-cache  --ephemeral-home  --agents | --no-agents  --agents-dir PATH
env       --env NAME  --env NAME=VALUE  --env-file F          (F should be 0600)
image     --base-image REF  --install PKG  --no-build
harness   --harness NAME  --shell  --thinking LEVEL  --pi-token-env NAME  --no-pi-token-env
          --no-pi-config
limits    --cpus N  --memory SIZE  --pids N  --tmpfs-size SIZE
run       --name NAME  --label K=V  --keep  --interactive | --no-interactive  --no-tty
preview   --dry-run|-n  --print-policy  --print-entrypoint  --print-containerfile
hatches   --allow-unenforced  --insecure-keep-root  --strict-args

Parsing stops at the first positional or unknown argument, which becomes an argument to the harness — that is what makes alias pi=ai-sandbox behave like the harness. -- replaces the command entirely. --strict-args makes an unknown flag an error instead (use it in scripts).

Subcommands: build [--rebuild] [--gate] [--refresh-base], update [--check], doctor [--deep], gate up|down|status|logs, ps, clean [--all] [--volumes[=GLOB]], inspect, version.

Exit status: the command's own code; 77 the egress ruleset could not be installed; 78 the policy file was missing in the container. Both refusals are the sandbox working.

Allow-entry grammar

entry effect
example.com gate matches that name and anything below it, at CONNECT time
example.com:8443 same; the port does not create a netfilter rule, and the gate only relays 80/443/8443
10.0.0.5:5432 direct netfilter rule for that host and port — the way to allow non-HTTP
10.20.0.0/16 direct rule for the subnet, default ports 80,443 unless :port
[fd00::5]:8443 direct ipv6 rule
localhost resolves on the host → a direct rule for 127.0.0.1, i.e. the container's loopback. For your machine's services use 100.64.0.1

Per-ecosystem starting points

Add only what the project's own lockfiles and build files prove it contacts. Everything left out fails loudly, which is the feature.

stack entries
npm registry.npmjs.org (+ nodejs.org and, for prebuilds, release-assets.githubusercontent.com — read the git-host warning before adding anything from github.com)
python pypi.org, files.pythonhosted.org (the file CDN is separate; both are needed). A private index: whatever host --index-url, PIP_INDEX_URL, [[tool.uv.index]] or tool.poetry.source names
rust crates.io, static.crates.io, index.crates.io (+ static.rust-lang.org if it installs toolchains)
go proxy.golang.org, sum.golang.org (+ the host of a private module path, allow = <host> and GOPRIVATE in the env)
ruby rubygems.org, index.rubygems.org
php repo.packagist.org, api.github.com (composer metadata; check before allowing)
jvm repo1.maven.org, repo.maven.apache.org, plugins.gradle.org, services.gradle.org, dl.google.com
dotnet api.nuget.org
terraform registry.terraform.io, releases.hashicorp.com
playwright / puppeteer their download hosts (cdn.playwright.dev, storage.googleapis.com) — or commit the browsers and go --net none
container builds inside the sandbox registry-1.docker.io, auth.docker.io, production.cloudflare.docker.com

Gotchas worth a comment in the file

  • --env NAME=VALUE shows up in ps; the name-only and --env-file forms do not.
  • An unset variable named with --env fails the run (in preview it is reported as env_missing= instead) — that fail-closed behaviour is why preview output is worth reading.
  • The image's home is a named volume (aisb-home) that outlives the image: after a rebuild the home may be stale. ai-sandbox clean --volumes reclaims unused aisb-* volumes.
  • A project under /tmp, /usr, /etc, /var, /opt, /run, /home itself or the container's own home cannot mirror its host path and lands in /project, with a line saying so. Do not "fix" that with --workdir /tmp/...; the tmpfs covers it.
  • The host path may not contain : at all — podman cannot parse such a mount spec, and ai-sandbox refuses rather than passing the error along.
  • Nothing is inherited but TERM LANG LC_ALL LC_CTYPE TZ (plus HOME, set to the container home, AISB=1, AISB_SESSION). A tool that needs PATH, a dotfile or anything else from your real home needs an explicit grant.
  • .env files inside the project are readable by the agent — that is the point of the mount, and it is why the sandbox token should be a narrow one.