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:
+45
-5
@@ -8,8 +8,8 @@ run?". This file answers the second question honestly.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Trusted** | Anyone who can open a TCP connection to the server's port, and every host on the same L2/L3 network as them |
|
||||
| **Untrusted** | Anyone who can reach that port and is not in the above set — including anything on the LAN that should not be (guest Wi-Fi, IoT VLAN, a cafe) |
|
||||
| **Trusted** | Anyone inside the address space the server answers — with no allow list configured that is anyone who can open a TCP connection, and in the fleet deployment it is the client LAN (`10.2.0.0/22`) and nothing else |
|
||||
| **Untrusted** | Anyone outside that space — including anything on the LAN that was not named in it (guest Wi-Fi, IoT VLAN, a cafe), and everything on the internet |
|
||||
| **Not a threat model** | Malicious JavaScript served from this origin (there is only one origin and you run the server), supply-chain of a machine you already control |
|
||||
|
||||
The product promise is *"open a URL, share your screen"*. Every gate we add
|
||||
@@ -52,7 +52,10 @@ limiting, no accounts, and no audit trail.
|
||||
`/mirror` page detects this and prints the three ways forward:
|
||||
|
||||
1. Share from the machine running the server, via `http://localhost:8055/mirror`.
|
||||
2. Serve over HTTPS (planned: `mirror serve --https` with a self-signed cert).
|
||||
2. Put TLS in front of it. This is no longer a plan: the fleet deployment
|
||||
terminates HTTPS in nginx on the service VM (`https://mirror.reeseapps.com`),
|
||||
so every device there can share. `mirror serve --https` for standalone use is
|
||||
still open (A18).
|
||||
3. Per-device and explicit: `chrome://flags/#unsafely-treat-insecure-origin-as-secure`
|
||||
with the exact origin, then reload.
|
||||
|
||||
@@ -60,6 +63,36 @@ Option 3 is a *deliberate, per-device, user-granted* exception. Do not automate
|
||||
on someone else's machine without telling them what it does: it marks that origin
|
||||
as trusted for powerful APIs, permanently, on that profile.
|
||||
|
||||
### Access control is an address list, not an account
|
||||
|
||||
Because there is nothing to log in with, the source address decides everything.
|
||||
That makes the list strong and also makes it the only thing standing between a
|
||||
stranger and every screen in the building, so treat it as the security boundary
|
||||
it is:
|
||||
|
||||
| Layer | Mechanism | Bypass it and you have |
|
||||
|---|---|---|
|
||||
| Network | routing / firewall, `allow`/`deny` in nginx | nothing — this is the wall |
|
||||
| App | `MIRROR_ALLOW_CIDRS` in the container | a process already running on the server host |
|
||||
|
||||
The app layer exists because a container port can be reached without going
|
||||
through the proxy (`127.0.0.1:8080` on the service VM). It judges the
|
||||
`X-Forwarded-For` value **only** from an address in `MIRROR_TRUSTED_PROXIES`, and
|
||||
the proxy must *overwrite* that header (`proxy_set_header X-Forwarded-For
|
||||
$remote_addr`) rather than append, so a browser cannot write its own pass. What
|
||||
the app layer cannot do is resist someone who already has code on the server: at
|
||||
that height they can forge the header, so it is a second lock on the outside door
|
||||
and not a host firewall. Say that plainly rather than implying defence in depth
|
||||
where there is one wall and one belt.
|
||||
|
||||
Two things the address list does **not** do, both inherent to WebRTC:
|
||||
|
||||
* Once two browsers are in the room, their media flows directly between them and
|
||||
never consults the server again. Admission to the roster is admission to the
|
||||
stream.
|
||||
* It cannot tell you *who* is watching — only *where from*. Device labels are
|
||||
self-reported and cosmetic; never treat one as an identity.
|
||||
|
||||
### Wayland asks every time; X11 does not
|
||||
|
||||
On **X11/Xorg**, Chromium uses its native capturer: the picker appears and that is
|
||||
@@ -81,8 +114,15 @@ user-initiated action.
|
||||
|
||||
## 5. Operational guidance
|
||||
|
||||
* **Bind narrowly.** `MIRROR_HOST=0.0.0.0` is the default because the point is
|
||||
other devices. If only this machine should reach it, `MIRROR_HOST=127.0.0.1`.
|
||||
* **Name the networks, do not just bind.** `--allow-cidr 10.2.0.0/22` (or
|
||||
`MIRROR_ALLOW_CIDRS`) is the setting that actually restricts *who*, whereas
|
||||
`MIRROR_HOST` only restricts *which interface*. `MIRROR_HOST=0.0.0.0` with an
|
||||
allow list is the deployment shape; `MIRROR_HOST=127.0.0.1` is how you keep a
|
||||
dev instance to yourself.
|
||||
* **A refusal should explain itself.** A refused client gets a 403 naming the
|
||||
networks that are allowed and the address that was judged — because "not
|
||||
allowed" in front of a proxy is undiagnosable otherwise. Keep that behaviour if
|
||||
you touch the gate.
|
||||
* **Firewall the port.** Restrict 8055 to the LAN range you actually want:
|
||||
`firewall-cmd --add-rich-rule='rule family=ipv4 source address=192.168.1.0/24 port port=8055 protocol=tcp accept'`
|
||||
* **Cap the blast radius.** `MIRROR_MAX_MIRRORS` limits concurrent mirrors;
|
||||
|
||||
Reference in New Issue
Block a user