Files
ai-sandbox/.agents/skills/right-sandbox/references/port-and-loopback.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

6.2 KiB

Ports and loopback

Two separate problems wear the same name. Get the direction right before touching a config.

what it is possible in --net allow?
outbound the sandboxed process reaches a service running on your machine (dev postgres, redis, a mock API, a local model server) yes — one allow entry, sometimes plus a proxy bypass
inbound your browser on the host reaches a server the sandbox started no — the generated ruleset drops it; three workarounds below

Container-to-container is a third case, already measured on the reference host: two containers on a shared user-defined network cannot reach each other, so do not design a sidecar proxy, a service-mesh sandbox or a "sandbox plus its own database container" topology. The database runs on the host (compose) and is reached as in §1.

1. Outbound: a service on your machine

With --net allow the container is started as --network pasta:--map-host-loopback,<gate_addr> (gate_addr, default 100.64.0.1). Inside the container, 100.64.0.1:PORT lands on the host's 127.0.0.1:PORT. 127.0.0.1 inside the container is the container itself and almost never what you meant.

Two layers have to agree, and this is where every "it still does not work" comes from:

  1. The ruleset. Only the gate's port is routable through that address by default. Add:
    allow = 100.64.0.1:5432        # postgres published by docker compose on the host
    
    (IP entries become direct netfilter rules — the right shape for a non-HTTP protocol.)
  2. The proxy variables. When a gate is in use, the container gets HTTP(S)_PROXY/ALL_PROXY and NO_PROXY=localhost,127.0.0.1,::1. A client that honours them sends the request to the gate, which allows the destination if it is in the allowlist and the port is one of 80, 443, 8443.

So:

the service what the config needs
postgres, redis, amqp, ssh, a raw socket — anything that ignores proxy variables allow = 100.64.0.1:<port>
HTTP on 80, 443 or 8443 allow = 100.64.0.1:<port> (the gate relays it)
HTTP on any other port (3000, 8000, 5173, 9200…) allow = 100.64.0.1:<port> and a proxy bypass for that address, and measure which of the two NO_PROXY values wins

The bypass, and the honest way to confirm it:

# .ai-sandbox/config -- the dev API on the host is not reachable through the egress gate
env = NO_PROXY=100.64.0.1,localhost,127.0.0.1,::1
env = no_proxy=100.64.0.1,localhost,127.0.0.1,::1

ai-sandbox sets NO_PROXY with --env before the env file, so the interaction between that and an env-file value is podman's flag handling, not ai-sandbox's. Do not reason about it: run ai-sandbox --dry-run, look at the order of the flags, and probe for real:

ai-sandbox --allow 100.64.0.1:3000 --shell
  curl -sS -m 3 -o /dev/null -w '%{http_code}\n' http://100.64.0.1:3000/health
  env | grep -i -E 'proxy|no_proxy'

Then point the app at the mapped address without putting a secret in a committed file — a URL with no password in it is not a secret:

env = DATABASE_URL=postgresql://dev@100.64.0.1:5432/app_dev

If 100.64.0.1 collides with a real network the project must reach, change gate_addr (and remember the gate itself is reached at that address too).

2. Inbound: "I want to see the app the agent is building"

There is no publish flag (--publish/-p are not in parse_args()). You can push one through the escape hatch — extra_args = -p 8000:8000 or AISB_EXTRA_ARGS="-p 8000:8000" — and then read the ruleset the entrypoint installs: the input chain is policy drop with room only for lo, established,related and a few icmpv6 types, so a fresh inbound connection is dropped inside the container. Confirm on the machine instead of trusting that reading:

AISB_EXTRA_ARGS="-p 8000:8000" ai-sandbox --shell
  # inside:  python3 -m http.server 8000 &
  # host:    curl -sS -m 3 -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/

Expected on a stock host in --net allow: no answer. If yours answers, that is a fact worth adding to docs/networking.md — measure and report it, do not build the workflow on it.

What to tell the user instead, in order of preference:

  1. Run the server on the host. The project mount is their disk: the agent edits, the host's npm run dev / runserver picks it up, the browser hits a normal host port. Nothing is published, nothing is opened, egress stays enforced. This is the answer for 90% of "preview the app" requests.
  2. One named preview run, when the server itself must be the sandboxed process (the agent needs to drive it, seed it, watch its logs):
    AISB_EXTRA_ARGS="-p 8000:8000" ai-sandbox --net full -- npm run dev
    
    Say out loud what this is: --net full means that run has no egress policy. It is a preview of a trusted project, not a project setting — which is why it never belongs in .ai-sandbox/config.
  3. Split the loop: the agent builds and unit-tests in the sandbox, writes a screenshotable static export or a make preview target, and the human runs that target. This keeps the review step where it belongs.

If the user genuinely needs publish-with-enforcement, the honest answer is that it is not implemented: a --publish PORT would need an input accept rule per port in gen_rules(), the port list carried in the session policy, and a threat-model line about who may connect. Offer to write it as a change to bin/ai-sandbox — as its own phase, with a tests/e2e probe — rather than hacking extra_args into a committed config and calling it secure.

3. Notes that keep coming up

  • The gate binds the host's loopback only, on a free port at or above gate_port (18081); ai-sandbox gate status and gate logs show a long-lived one.
  • NO_PROXY covering localhost is correct and must stay — tooling that refuses to proxy the container's own loopback (dev databases, auth servers, vite HMR) breaks otherwise.
  • --no-gate opens DNS inside the container; the mapped loopback address still needs its allow line.
  • --net none has no interface at all: neither direction works, which makes it the right mode for make test on a toolchain that is already installed.