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:
2026-10-27 17:29:17 -04:00
parent 0242ab5b5e
commit 8ee535f6ae
14 changed files with 740 additions and 49 deletions
+3 -2
View File
@@ -10,11 +10,12 @@
# 10.20.0.0/24 pinned subnet (default ports 80,443 unless :port given)
# [fd00::5]:8443 pinned ipv6
api.anthropic.com
aipi.reeseapps.com # your model gateway
registry.npmjs.org
# typical additions, by kind:
# api.openai.com
# another gateway or a LAN inference host:
# gateway.internal:8443
# github.com # git push, if you also mount ~/.ssh
# pypi.org
# files.pythonhosted.org
+17 -2
View File
@@ -50,6 +50,21 @@ net = allow
# unless you say so. An explicit refresh is `ai-sandbox update [--check]`.
# base_auto_update = 1
# --- pi --------------------------------------------------------------------
# Bridge ~/.pi/agent/{models,settings}.json into the sandbox, with every provider
# apiKey replaced by an environment reference (nothing secret is written into the
# container). 0 = pi starts with its own defaults.
# pi_config = 1
#
# The variable the bridged config reads the credential from, and whether ai-sandbox
# passes that one variable automatically when it is set. If it reaches neither, the
# run is refused instead of launching a pi that cannot talk to its model.
# pi_token_env = AI_API_TOKEN
# pi_token_auto = 1
#
# Where to read the host config from (default ~/.pi/agent).
# pi_agent_dir = ~/.pi/agent
# --- behaviour -------------------------------------------------------------
# SELinux bind-mount option: ':z' (shared), ':Z' (private) or empty to disable.
# Defaults to ':z' when getenforce exists.
@@ -65,11 +80,11 @@ net = allow
# --- repeatable keys -------------------------------------------------------
# These may appear more than once, and are merged with --allow / --env / etc.
#
# allow = api.anthropic.com
# allow = aipi.reeseapps.com
# allow = registry.npmjs.org:443
# allow = 10.20.0.5:5432
#
# env = ANTHROPIC_API_KEY
# env = AI_API_TOKEN
# env = CI_JOB_TOKEN
#
# mount = ~/.ssh:ro
+3 -3
View File
@@ -6,6 +6,6 @@
# Nothing here is passed to the container unless you ask for it with --env or
# --env-file, so listing a variable is not the same as granting it.
#
# ANTHROPIC_API_KEY=sk-ant-...
# OPENAI_API_KEY=sk-...
# ANTHROPIC_MODEL=sonnet
# AI_API_TOKEN=... your gateway's token
# AI_API_TOKEN_FILE=... if a tool prefers a file over a variable
# AI_MODEL=turbo model id your gateway serves