Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
3.9 KiB
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 withdocker composefrom throwaway files in/root/spike/on 9202 (Grimmory from the held template inaudits/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 traefikforwardAuth, 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
- 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.
- 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).
- 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.
- Grimmory's e-reader paths (OPDS, Kobo sync, KOReader sync) work for a faithful
curlof 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. - 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.
- 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.