Files
ai-sandbox/.agents/skills/doc-sandbox/references/source-map.md
T
ducoterra 209f167e43 add doc-sandbox: answer from the installed program, not memory
The explainer for questions about a flag, a config key, a refusal, a
mechanism or a security boundary. ai-sandbox generates its own
Containerfile, entrypoint, squid config and netfilter rules from one
script, so the text of the program is the documentation of the version
the user will run: the skill sets a strict ground-truth order -- the
installed script and --help, then the repo docs, then the tests as the
executable spec -- and its own fact sheet last, overridden by all of
them.

scripts/locate.sh resolves that order on an arbitrary machine: the
installed script and its version, the source tree behind it (via
$AISB_REPO, the script's own parent, $PWD, the git root), the docs with
their line counts, and the config/state/cache locations.

references/source-map.md maps a question topic to the function, doc and
test that hold the answer, plus the vocabulary (split_target,
classify_allows, gate vs --no-gate vs --proxy, POL[]/P_*/OPT_*) and the
FAQ. The skill names which safe surface answers what: the --dry-run,
--print-entrypoint, --print-containerfile and --print-policy previews
build nothing and reach no registry, while inspect, doctor --deep,
build, update and gate up do change the machine and are not for an
explanation. It records the two honest exceptions rather than
overclaiming -- --print-policy ensures the home volume, and --no-gate
hits DNS.

Read-only by construction: no build, no container, no network call, and
it reads pi_config_bridge() rather than a models.json holding a live
key. Its worst failure mode is an invented flag, so the answer format
requires the real spelling, the source it came from, what the thing does
not do, and a plain "no" plus the closest real thing when the answer is
no.

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

12 KiB

Source map

Question topic → the function, doc and test that hold the answer. Line numbers move; names do not — grep for these in the installed script (readlink -f "$(command -v ai-sandbox)").

Flags, keys, defaults

question read
"what does --x do / does it exist" usage() (grouped by NETWORK / FILESYSTEM / ENVIRONMENT / IMAGE / PI CONFIG / LIMITS / RUN / ESCAPE HATCHES / SUBCOMMANDS), parse_args(), and KNOWN_OPTS + suggest() for the accepted set
"which config keys are legal" cfg_read_kv() — the case lists them, the accumulate list is cfg_accum_keys (allow env mount install label). Anything else warns unknown key and is ignored
"what wins, my flag or the file" resolve_opts() — every line is ${OPT_x:-${AISB_X:-$(cfgv x default)}}: CLI, then env, then config, then default. load_config() shows the file order
"AISB_* variables" the comment block above usage() and --help's ENVIRONMENT section
"why did arg parsing eat my harness flag" parse_args() stops at the first positional/unknown; -- sets COMMAND; --strict-args; default_command()/harness_argv() decide what actually runs
subcommand behaviour main() dispatch, then cmd_build / cmd_update / cmd_doctor / cmd_gate / cmd_ps / cmd_clean / cmd_inspect

Networking and enforcement

question read prove with
how --net allow holds at all entrypoint_script() → gen_rules(), plus build_argv()'s --cap-add=NET_ADMIN + the setpriv line ai-sandbox --print-entrypoint; docs/networking.md
can the sandboxed process lift the policy the capabilities the entrypoint drops before exec (--inh-caps=-all --bounding-set=-all --no-new-privs) tests/e2e/11_net.sh (flush attempt, unshare -n, raw socket, DNS to 8.8.8.8)
why an entry did nothing split_target() then classify_allows(): domains → the gate (CONNECT-time, subdomain match); IP/CIDR/ipv6 → direct netfilter rules, ports default 80,443 ai-sandbox --print-policy → gate_domains=, direct=, direct6=
which ports the gate relays write_squid_conf(): Safe_ports 80 443 8443, SSL_ports 443 8443, http_access deny all after the allowlist ai-sandbox gate logs
gate lifecycle / ports / reuse pick_port(), gate_up(), teardown(), cmd_gate(); AISB_GATE_KEEP=1; gate_port/gate_addr ai-sandbox gate status
--no-gate vs --proxy vs the gate resolve_proxy_endpoints() — --proxy uses your proxy (default port 3128); --no-gate resolves domains once on the host, opens DNS and warns; default is the built-in gate --dry-run (the HTTP(S)_PROXY args)
host-loopback / 100.64.0.1 build_argv()'s --network=pasta:--map-host-loopback,${gate_addr} and the NO_PROXY defaults docs/networking.md's measurement table
"is my machine able to do this" cmd_doctor(), tests/spike_network.sh ai-sandbox doctor --deep

Filesystem, mounts, the working directory

question read
why the project is at /project resolve_workdir() and mirror_block_of() — the deny list is what the image or the sandbox owns, and /tmp is a measured case (the tmpfs covers a bind of the same path). workdir_note= in --print-policy gives the reason
what is mounted, rw or ro build_argv(): the cwd, /run/aisb:ro (session policy), the home volume or tmpfs, the .agents share, then user --mounts
--mount grammar and defaults mount_dest() — SRC[:DEST][:ro|rw], default DEST=/mnt/<basename>; the option must be ro or rw; the source must exist
skills sharing agents_src_of(), agents_dest_taken(), the .agents block in build_argv(). Read-only, no override; a hand-written --mount on the same destination wins and the share is skipped with a log line
file ownership written by the agent --userns keep-id:uid=…,gid=… in build_argv() (files land owned by you), checked by doctor
SELinux labels set_selinux(), POL[selinux], AISB_SELINUX

Environment and secrets

question read
what is inherited AISB_IMPLICIT_ENV (TERM LANG LC_ALL LC_CTYPE TZ) and the explicit HOME/AISB/AISB_SESSION in build_argv()
how a value reaches the container build_env_file() — a 0600 file, NAME, NAME=VALUE and @file forms; unset --env NAME dies on a real run and reports env_missing= in preview
can a secret leak through output print_argv() (redacts the env file), print_policy() (names only), log_session() (net, counts, rc, command). tests/unit/04_env.sh, tests/e2e/12_sandbox.sh
what lands on disk per session session_init() / teardown(): $AISB_STATE_DIR/sessions/<id> (chmod 700), removed unless --keep

The pi bridge and thinking policy

question read
what is copied vs rewritten pi_config_bridge() — apiKey → "$VAR", !command values dropped and reported, host paths dropped and reported, auth.json and models-store.json never touched
why settings.json behaves oddly the per-key merge in the entrypoint's seeding block, and .aisb-settings.json.wrote — a key you edit in the sandbox stays yours; keys you never touched follow the host
when the run is refused for a credential pi_config_bridge() + cmd_doctor(): unset or empty token variable → refused. --pi-token-env NAME, --no-pi-token-env, --no-pi-config
"the model endpoint is not in the allowlist" warning pi_egress_hint(), pi_provider_hosts_of()
thinking level resolve_opts() (validation of off…max|host) and the seeding block: AISB_PI_THINKING is written over the merged settings every run; pi-only; --no-pi-config declines the policy too

Image, pinning, supply chain

question read
what is in the image containerfile() (git curl jq build tools python3 Node pi), gate_containerfile() (squid from the same base)
why the tag is what it is image_hash() (the policy, including the base digest, Node and pi versions) and image_ref(); --install changes it, so variants coexist
digest pinning and freshness base_pin_digest(), base_digest_online(), base_age_days(), base_ref_of(), cmd_update(); base_ttl_days, base_auto_update, AISB_BASE_PULL_FALLBACK, AISB_SKOPEO. Preview modes resolve cache-only — that is why --dry-run never touches the network
"what version of squid filtered that" the aisb.* image labels and /etc/aisb/manifest.txt inside the image (image_hash()/containerfile() write them)
rollback cmd_update() keeps the previous image and prints the podman rmi for it
stale home ensure_home_volume(), cmd_clean()'s --volumes[=GLOB]

The pipeline driver (bin/ai-sandbox-autophase)

question read
where does my launcher come from ap_resolve_launcher(), ap_probe_rc() (sources an rc file in a throwaway bash -ic and returns the definition; it never calls it), ap_rc_list(), AISB_AP_RC_FILES
how the child command is built ap_build_runner() / ap_build_run() — printf %q per argument; --sandbox-args defaults --no-tty --no-interactive; the prompt is one argument that may contain quotes, newlines and $
what it shadows from the phase library ap_install_overrides(): it first requires the library to provide find_root next_unit phase_entries phase_next_unit unit_phase is_phase_end count_units sweep_finished_phases execute_unit unit_report notify_task commit_phase push_phase (plus first_prompt/fix_prompt, kept as ap_lib_*), then shadows pi, first_prompt, fix_prompt, run_validation, push_phase — and nothing else. Order matters: the launcher resolves before pi() exists, or a launcher named pi would resolve to the shim
why a push was refused ap_git_fingerprint(), ap_git_guard(), ap_git_env(), ap_push_pending(); --trust-git-changes is the (loud) override
redaction ap_redact() — literal --env NAME=value and credentials inside remote URLs
what runs where ap_preflight(), ap_gate_preview() (--validate-in sandbox|host), ap_run_pipeline(), ap_status()
the contract without the skill installed tests/unit/10_autophase.sh and tests/fixtures/phase-lib/

Vocabulary

  • gate — the per-session squid container, --network=host, bound to the host loopback on a free port ≥ gate_port, run as the proxy user with --cap-drop=ALL, destroyed with the session. gate up is the explicit long-lived exception.
  • ruleset — the inet aisb nft table installed inside the container's namespace: output policy drop with room for loopback, established/related, ICMP, the direct IP entries, the gate's address:port, and DNS only in --no-gate mode. Input policy drop too, so nothing is published in.
  • POL[...] / P_* / OPT_* / CFG_* — resolved policy / resolved lists / raw CLI flags / config-file values. Reading raw env vars elsewhere is the bug pattern; a helper called as X="$(helper)" runs in a subshell, so any POL[..] it sets is discarded.
  • fail closed — no allowlist in allow mode, missing policy file (78), uninstallable ruleset (77), unset --env NAME, unset-or-empty bridged token: all refusals, never degradations.
  • escape hatch — --allow-unenforced, --insecure-keep-root, --net full, --mount, extra_args, AISB_EXTRA_ARGS. Named to be uncomfortable; nothing uses them implicitly.

FAQ (verify the version before quoting)

question answer
--print-policy says "could not create the home cache volume"? There is no usable podman (missing, or not rootless, or no /etc/subuid range) — --print-policy ensures the aisb-home volume exists, unlike --dry-run, which is the fully side-effect-free preview. Run ai-sandbox --dry-run to see the policy and ai-sandbox doctor for the host.
Why exit 77? The ruleset could not be installed, so the sandbox refused to run unfiltered. ai-sandbox doctor; check podman info | grep -i rootless and /etc/subuid. Debug with --allow-unenforced only to see the error, not to ship it.
Why "no egress allowlist configured"? allow mode with nothing in it is a hard error. --allow, .ai-sandbox/allow, or --net none/--net full if you mean it.
Can it read ~/.ssh? No — that path is not in the mount namespace unless you mounted it. Threat model #1 is what you do mount and allow.
Can it rewrite my skills? No. ~/.agents is read-only and there is no flag; but its contents are readable, so treat the shared tree as published to the agent.
Why is my project at /project? Mirroring refused because the host path would bury a path the image or sandbox owns; workdir_note= says which, --workdir chooses.
npm ci works but a package fails That ecosystem needs another host (python: files.pythonhosted.org; rust: static.crates.io/index.crates.io; go: sum.golang.org). Or the package fetched from an allowlisted host — allowing a domain is not a security review.
How do I let it push? You don't, from inside. --allow github.com + --env GH_TOKEN or --mount ~/.ssh:ro does it, and the threat model explains the price. For pipelines, run ai-sandbox-autophase, which pushes on the host.
How do I see what the gate allowed? ai-sandbox gate logs (and gate status, gate down). Don't put secrets in URLs: the gate sees what flows through it.
A stale container home after a rebuild? It is the aisb-home volume. ai-sandbox clean --volumes[=GLOB], or --ephemeral-home per run.
How do I refresh the base? ai-sandbox update --check then ai-sandbox update (or build --refresh-base). TTL re-resolves a digest, it never downloads on a preview.
Do --dry-run/--print-* touch the network? No — that determinism is deliberate. inspect is the one that asks the machine now.
Is the container root? It starts root with CAP_NET_ADMIN just long enough to install the ruleset, then setpriv to uid 1000 with no capabilities and no-new-privileges. --insecure-keep-root is the opposite.
What is NOT protected? docs/threat-model.md, and answer with it: allowlisted destinations, kernel/runtime escapes, anything you mounted, the cached home volume, DNS in --no-gate mode, the fact that a host-side push pushes what the agent wrote.