# 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`, 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:` | | HTTP on 80, 443 or 8443 | `allow = 100.64.0.1:` (the gate relays it) | | HTTP on any other port (3000, 8000, 5173, 9200…) | `allow = 100.64.0.1:` **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: ```ini # .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: ```bash 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: ```ini 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: ```bash 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): ```bash 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.