bridge the host pi config, with the credential by env only
The image ships pi with no provider and no preferences, so a sandboxed run
meant configuring the model twice. ai-sandbox now reads the host
~/.pi/agent/{models,settings}.json and rewrites them instead of copying them:
every provider apiKey becomes "$AI_API_TOKEN" (pi resolves an env reference
itself, so no value is written to disk), '!command' header values are dropped
rather than executed, settings that point into $HOME are dropped because that
path is not mounted, and auth.json / models-store.json / sessions / extensions
are never read.
The rewritten pair is seeded into /home/sandbox/.pi/agent at container start,
owned by uid 1000 and mode 600, and is treated as managed: a host change is
re-seeded, an edit made inside the sandbox is kept and reported, a deletion
takes the host version again.
The token follows the existing environment rules -- one variable, passed by
name through the 0600 env file, announced when auto-passed. A bridged run
whose token variable is unset or empty is refused instead of starting a pi
that cannot reach its model, and the bridge never widens the allowlist: it
warns with the exact --allow to add.
Docs and examples now use a self-hosted gateway (https://aipi.reeseapps.com)
instead of vendor API endpoints, since that is the only thing this tool is
used against. 391 assertions pass (make lint, ./tests/run.sh --e2e).
LLM-Generated: true
This commit is contained in:
@@ -13,19 +13,24 @@ allowlist that the sandboxed process cannot lift.
|
||||
## Non-negotiables
|
||||
|
||||
1. **Single script.** The Containerfile, the container entrypoint, the squid
|
||||
config and the nftables rules are all generated from `bin/ai-sandbox`
|
||||
(`containerfile()`, `entrypoint_script()`, `write_squid_conf()`, `gen_rules()`
|
||||
inside the entrypoint). Keep it that way; do not split policy into files that
|
||||
can drift from the script.
|
||||
config, the nftables rules and the bridged pi config are all generated from
|
||||
`bin/ai-sandbox` (`containerfile()`, `entrypoint_script()`, `write_squid_conf()`,
|
||||
`gen_rules()` inside the entrypoint, `pi_config_bridge()`). Keep it that way; do
|
||||
not split policy into files that can drift from the script.
|
||||
2. **Fail closed.** Any path where the sandbox cannot guarantee its policy must
|
||||
refuse to start rather than degrade: no allowlist in `allow` mode, missing
|
||||
policy file in the entrypoint (exit 78), ruleset that cannot be installed
|
||||
(exit 77), `--env NAME` where NAME is unset. Escape hatches exist but must be
|
||||
explicitly named (`--allow-unenforced`, `--insecure-keep-root`, `--net full`).
|
||||
(exit 77), `--env NAME` where NAME is unset, a bridged pi config whose credential
|
||||
variable is unset *or empty*. Escape hatches exist but must be explicitly named
|
||||
(`--allow-unenforced`, `--insecure-keep-root`, `--net full`, `--no-pi-config`).
|
||||
3. **Never leak a secret.** Values go through a `0600` env file, never argv.
|
||||
Nothing that prints (`--dry-run`, `--print-policy`, the session log, error
|
||||
messages) may echo a value. If you add output, add a test that a canary value
|
||||
does not appear in it.
|
||||
does not appear in it. The pi bridge is the case that tests this hardest: it reads
|
||||
a file that may hold a live key, so it rewrites `apiKey` to `"$VAR"`, drops
|
||||
`!command` values instead of running them, never touches `auth.json`, and
|
||||
`tests/unit/08_pi_config.sh` asserts a canary secret from the host agent directory
|
||||
appears nowhere in ai-sandbox state.
|
||||
4. **No hardcoded machine paths, usernames or ports.** Everything is overridable
|
||||
via `AISB_*` or a config file; defaults must work for any user on any host.
|
||||
5. **Measure, don't assume, about podman.** Rootless networking varies by version
|
||||
|
||||
Reference in New Issue
Block a user