docs: what actually gates access, now that there is a deployment
The internal deployment exists (deployments repo, `mirror/`: quadlet, nginx edge, certbot, route53, VM SOP), so the docs that said "the real fix is HTTPS" were describing a future that has arrived from a different direction — TLS terminates in nginx on the service VM, not in this process, and there is no `--https` flag here. SECURITY.md's trust model said "trusted: anyone who can open a TCP connection". With an allow list in production that sentence was simply no longer true, so the table now names the address space, and a new section says what the list is and what it is not: one wall (network/nginx) and one belt (the app, for the path that bypasses nginx at 127.0.0.1:8080), plus the thing neither can do — once two browsers are in the room their media never asks the server again, and it can never tell you *who*, only *where from*. Saying "defence in depth" about that would have been the dishonest version. PLAN.md gains A19 (TLS + admission in production, LOCKED) and A18 becomes deprioritised rather than deleted — standalone `mirror serve --https` is still worth having for offline/ad-hoc use, it just stopped being the thing blocking cross-device sharing, and candidate phase 1 is now share-page resilience. AGENTS.md gains the two rules most likely to be broken by a well-meaning edit: trust XFF only from a trusted proxy, and change the nginx and app lists in the same commit. LLM-Generated: true
This commit is contained in:
+13
-6
@@ -62,7 +62,8 @@ on a phone, and a person on a work machine can all join by opening a URL.**
|
||||
| A15 | CLI | `argparse`, single `serve` subcommand, flags also accepted bare | One command; no dependency for a two-option CLI | **LOCKED** |
|
||||
| A16 | Quality gates | `ruff check` + `ruff format --check` + `pyright` (0 errors) + `pytest` green, always | Non-negotiable before a phase is called complete | **LOCKED** |
|
||||
| A17 | Scale-out beyond a mesh | *Proposed*: WebSocket/MSE relay or WebCodecs SFU when a sharer's uplink, not the CPU, is the limit | Revisit only with a measured problem. Status: **PROPOSED** | Proposed |
|
||||
| A18 | TLS | *Proposed*: `mirror serve --https` with an auto-generated self-signed cert | Solves the secure-context problem (§6) for LAN IPs without a CA. Status: **PROPOSED** | Proposed |
|
||||
| A18 | TLS (standalone) | *Proposed*: `mirror serve --https` with an auto-generated self-signed cert | Still open for a bare `mirror serve`. The problem it existed to solve is solved in production by A19's deployment, which terminates TLS in nginx. Status: **PROPOSED** (deprioritised) |
|
||||
| A19 | TLS + admission in production | TLS terminates in **nginx on the service VM** (no gateway, no `proxy_protocol`); access is decided by **source address** at both edges — nginx `allow`/`deny` and `MIRROR_ALLOW_CIDRS`, with `X-Forwarded-For` believed only from `MIRROR_TRUSTED_PROXIES` (loopback) | There are no accounts, so the address *is* the access control; the app repeats the decision because a container port can be reached without the proxy. Over HTTPS every device gets `getDisplayMedia()`, which is what the fleet needs. Deployment lives in `deployments/mirror/` | **LOCKED** |
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
@@ -198,7 +199,8 @@ MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay # Linux-native capture
|
||||
|
||||
| Constraint | Why | Handling |
|
||||
|---|---|---|
|
||||
| `getDisplayMedia` needs a secure context | Browser rule. `http://<lan-ip>` is not one | `/mirror` detects it and prints the three ways out (localhost, HTTPS, the per-origin Chrome flag). Real fix is A18 |
|
||||
| `getDisplayMedia` needs a secure context | Browser rule. `http://<lan-ip>` is not one | `/mirror` detects it and prints the three ways out (localhost, HTTPS, the per-origin Chrome flag). Production is HTTPS at the edge (A19), so this only bites a bare `mirror serve` on a LAN IP |
|
||||
| No accounts means the address list is the whole gate | Product promise (§1) | Enforced twice (nginx + app, A19). `/healthz` is the one exempt route, because the container health probe comes from loopback. See `SECURITY.md` §4 |
|
||||
| Mesh upload = N × bitrate per sharer | WebRTC mesh | Per-leg `maxBitrate` cap (default 12 Mbit/s). A17 when it bites |
|
||||
| Wayland capture depends on the portal | Compositor policy | Each share is confirmed by the user; X11 needs no such prompt. Documented in `SECURITY.md` |
|
||||
| No reconnection of a *sharer* identity | Peer ids are per-socket | Viewer clears the tile and the sharer re-publishes on reconnect; the wall recovers by itself |
|
||||
@@ -210,12 +212,17 @@ Ordered by what a user would notice first. Each would become
|
||||
`.agents/phases/todo/NN_name/{00_phase.md,NN_task.md}` via the `phase-authoring`
|
||||
skill, with its own independently-runnable test suite.
|
||||
|
||||
1. **HTTPS mode** — self-signed cert + a "how to trust it" screen (A18). Unblocks
|
||||
capture from `http://<lan-ip>` on other devices, the single biggest papercut.
|
||||
2. **Share-page resilience** — reconnect with roster resync, "source lost"
|
||||
> HTTPS is no longer first, because the deployment solved it: TLS terminates in
|
||||
> nginx (A19) and every device on the LAN can share. `mirror serve --https` (A18)
|
||||
> stays on the list for the standalone case, ranked where its real cost lands.
|
||||
|
||||
1. **Share-page resilience** — reconnect with roster resync, "source lost"
|
||||
states, `surfaceSwitching` UX, per-leg health on the sharer panel.
|
||||
3. **Wall UX** — keyboard navigation (j/k/Enter/Esc), per-tile mute, layout
|
||||
2. **Wall UX** — keyboard navigation (j/k/Enter/Esc), per-tile mute, layout
|
||||
memory, "auto-stage newest mirror", PiP.
|
||||
3. **HTTPS for `mirror serve`** — A18: self-signed cert, the "why is this
|
||||
untrusted" screen, and a hint that a real deployment should terminate TLS in
|
||||
nginx instead. For ad-hoc and offline use only now.
|
||||
4. **Capacity & fairness** — adaptive bitrate per leg by watcher count,
|
||||
`MIRROR_MAX_MIRRORS` surfaced in the UI, graceful degradation for a weak
|
||||
sharer uplink.
|
||||
|
||||
Reference in New Issue
Block a user