# Permanent household gate with family accounts — EXIT TEST (written before the spike ran) Written 2026-10-01 ~19:30 UTC, before anything below was built or measured. Model: `audits/login-gate-2026-09-29/B/B-VERDICT.md`. Operator ruling `09` §3 decision 63: option A (extend the box's own gate), measured before any build; the build waits for the operator's go. ## What is tested A gate that stands IN FRONT OF an app permanently (not only until its first setup), letting in only members of the household's family list, each with their OWN login. Apps: **Grimmory** (e-book library; e-readers sync over OPDS / Kobo / KOReader) and **MeTube** (yt-dlp web UI with NO login of its own — its fit verdict R-767 was "stop: no login at all"). ## Venue and method (fences: the spike ships nothing) - Scratch guest **9202** only, through its traefik (`https://192.168.0.114` + Host). Grimmory and MeTube are started BY HAND with `docker compose` from throwaway files in `/root/spike/` on 9202 (Grimmory from the held template in `audits/new-apps-2026-10-01/wip/grimmory/`, MeTube from upstream's image) — never the live catalog, never a release. - The gate is a **throwaway auth service** on 9202 (a small Go program in a container on `traefik-public`) answering traefik `forwardAuth`, with a family list of two members, a sign-in page, a session cookie, logout, and a per-visitor lock that reads the visitor by the SAME rule as controller v0.286 (`clientaddr.go`). Its source is kept in this folder as evidence. - The tunnel hop is simulated as in Part A (a container at `172.16.253.2`); one outside address on the real tunnel is not needed for the gate's logic, which is the same code. - Everything is removed afterwards (containers, files, traefik labels, images). ## Exit items — each PASS or FAIL, measured 1. **A stranger reaches nothing** of Grimmory or MeTube: every path tried (the front page, the API, static files, an unknown path, MeTube's socket.io websocket upgrade and its download/add API) answers the gate (302 to the sign-in for a browser GET, 401 otherwise) and never the app. Pass = 0 app answers. 2. **A family member with their OWN login** (not the dashboard password) gets in; the session lasts **days** (cookie lifetime ≥ 7 days, survives a gate restart); **logout** works (the old cookie is refused afterwards). 3. **A stranger's wrong guesses lock only the stranger**: after N wrong sign-ins from one visitor (through the simulated tunnel, rotating a forged leftmost address), that visitor is refused; a family member from another address signs in at once. Pass = both. 4. **Grimmory's e-reader paths** (OPDS, Kobo sync, KOReader sync) work for a faithful `curl` of the client through a per-app PATH EXCEPTION — and on those paths **the app's own authentication stays in force**: a stranger there meets the app's own 401 (not the app's data). Pass = the client answers 200 with its own credentials, the stranger 401. 5. **The family login cannot reach the box dashboard**: the gate cookie is host-only to the app host; the dashboard does not accept it; the family password does not work at the dashboard login. 6. **Cost**, measured: the added time per gated request (ms, gated vs ungated, same request, median of ≥ 50); what happens while the gate's answerer is DOWN (stop it: the gated app must be refused — closed, not open); the build's cost in sessions (estimated from the spike's code size and what the controller already has). ## Also answered (in the verdict) Where family accounts live and who manages them; one sign-in for all gated apps on the domain or per app; how Radicale/Dawarich-style API clients are excepted; whether MeTube becomes publishable behind it. ## Verdict rule The spike PASSES only if items 1–5 all pass. Item 6 is a cost, reported, not a pass/fail. A failed item is reported as measured, with what a build would have to do about it.