# Phase 0B — Offline and Network Behavior Method: a Docker network created with `--internal` (no NAT, no DNS to the outside). The Ollama container was attached to it so the app could reach a model while having no path to the Internet. Isolation was verified from inside the app container before testing: ``` blocked ('1.1.1.1', 443) OSError blocked ('openrouter.ai', 443) gaierror blocked ('fonts.googleapis.com', 443) gaierror ``` ## AI-DnD — fails offline as shipped; fine once one file is vendored First turn on the isolated network died. The SSE stream emitted the `player` event and stopped. Container log: ``` requests.exceptions.ConnectionError: HTTPSConnectionPool( host='openaipublic.blob.core.windows.net', port=443): Max retries exceeded with url: /encodings/cl100k_base.tiktoken (NameResolutionError ... Temporary failure in name resolution) ``` `tiktoken` downloads its BPE encoding on first use, and AI-DnD calls it for context budgeting on every turn. On the host this was invisible, because the file had already been cached to `/tmp/data-gym-cache/9b5ad71b…` during an earlier online run. After copying that 1.7 MB file into the container: ``` OFFLINE TURN GENERATED: "I am Vale, an explorer. I journey alone through the darkened depths of the lighthouse..." ``` So: a hard blocker on a clean air-gapped install, and a packaging fix — vendor the encoding (or pre-seed `TIKTOKEN_CACHE_DIR`, or replace the tokenizer). Worth stressing that **static analysis could not have found this**; it took an actually isolated run. Other AI-DnD network surface: - **Google Fonts at runtime.** The built SPA's `index.html` still contains `fonts.googleapis.com/css2?family=Cinzel...&family=Crimson+Pro...&family=Inter...`, and `main.py`'s CSP explicitly allows `fonts.googleapis.com` / `fonts.gstatic.com`. Violates specification §12. Fix: self-host three families. - Only other remote host in backend Python is `https://openrouter.ai/api/v1`, used as a default endpoint constant and a header-attribution host check. Both removable with the cloud-provider path. - `analytics.py` is a **self-hosted** counter writing to two local tables. No third-party script, no outbound request. It records no IP, no user agent, and hashes the user id with HMAC. Removable, and not a telemetry leak in the meantime. - Connection test and turns honour the configured endpoint only; no automatic URL retrieval or content fetching was observed. ## Open Dungeon — runtime clean, build and telemetry are not - **Next.js telemetry is on by default** and printed its notice on first start. Needs `NEXT_TELEMETRY_DISABLED=1` or `next telemetry disable` in the production configuration. - **Fonts are fine at runtime.** `layout.tsx` uses `next/font/google`, which downloads at *build* time and self-hosts. The served page contains no `fonts.googleapis.com` reference. The trade-off is that the build needs network access. - Remote hosts referenced in `src/`: `openrouter.ai` (preset provider URL and a model list), plus `ko-fi.com` and `github.com/sponsors` donation links in the UI. Everything else is `127.0.0.1` / `localhost` (13 occurrences). - Local story play needs no cloud service: Ollama for text, a local FLUX worker or the user's own ComfyUI for images. - 335 npm packages with 6 high-severity advisories is the largest supply-chain surface of the three. ## ai-adventure — verifiably local-only The strongest posture by a wide margin, and the easiest to audit: - **Exactly one outbound call site in the whole codebase**: `urlopen` in `llm/lm_studio.py`. Nothing else in `local_adventure/` opens a socket. - **No hardcoded remote host anywhere.** The only `http` string in the package is the scheme check in `content/models.py`. - **One direct dependency** (`pydantic`), six packages in the closure. - It is the only candidate that already implements specification §12's non-local-endpoint warning: ```python def _endpoint_warnings(config): hostname = urlparse(config.model.base_url).hostname if hostname not in {"127.0.0.1", "localhost", "::1"}: return ["model.base_url is not loopback; game prompts and content will be sent to that endpoint. Enable API authentication."] ``` - `audit.store_prompts` defaults to `false`, with prompt *hashes* stored instead. - Tests, world validation, session creation, replay, branching and export are all offline by construction. ## Summary | | Local play works offline | Cloud service required | Telemetry / remote assets | Removal difficulty | |---|---|---|---|---| | AI-DnD | **Only after vendoring the tiktoken encoding** | No | Google Fonts at runtime; local-only analytics tables | Low — one vendored file, three self-hosted fonts, delete analytics | | Open Dungeon | Yes at runtime; build needs network | No | Next.js telemetry on by default | Low — one env var; fonts already self-hosted | | ai-adventure | **Yes, unconditionally** | No | **None** | Nothing to remove |