aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_DESIGN.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
-rw-r--r--docs/MESHBAY_DESIGN.md14
1 files changed, 9 insertions, 5 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 38977af..f1473f0 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -1755,10 +1755,14 @@ card text lives in a bounded in-memory TTL cache; the image rides the same
blake3-keyed store as any other thumbnail. **The new surface is SSRF**, because the
URL is a member's choice and it triggers an outbound request from the operator's
machine: http(s) only, no credentials, a port allowlist, every resolved address
-must be globally routable, redirects followed by hand so each hop is re-checked,
-and the address the connection landed on re-checked before the body is read (the
-request itself has been sent by then, so this refuses the answer, not the
-request). The body is read as a stream and stops at its cap — 512 KiB of page,
+must be globally routable, and redirects followed by hand so each hop is
+re-checked. **The socket is opened to the address that was checked**: the name is
+resolved once, off the event loop, every answer is checked, and the connection
+goes to that IP literal while TLS verifies the certificate for the name. Checking
+one resolution and letting the HTTP client make another would let a name answer
+clean and then with a LAN address, and the request would leave before anything
+looked; resolving on the event loop would stall every group on a slow name. No
+proxy from the environment is used, since a proxy resolves the name itself. The body is read as a stream and stops at its cap — 512 KiB of page,
2 MiB of image — counted after decompression, so a small compressed response is
bounded like any other; an image declared larger than its cap is not read, and
the whole fetch has a 15-second deadline. Previews are rate-limited per
@@ -3456,7 +3460,7 @@ be understood, not so the incident can be retold.
| **M2a** | `sender_id` comes from the authenticated session (**NS6**). Now guaranteed by there being **one** chat implementation: QUIC does not carry chat at all (§5.1) |
| **M2b** | Chat broadcast is per group (**H1**), on the one transport that carries chat |
| **M2c** | No transport runs a synchronous media process on the event loop, and every one is capped |
-| **M3** *(third review)* | Link-preview SSRF is gated: rate limit, port allowlist, globally-routable check, per-hop re-check, connect-address re-check, size guard (§6.5) |
+| **M3** *(third review)* | Link-preview SSRF is gated: rate limit, port allowlist, globally-routable check, per-hop re-check, connection pinned to the checked address, size guard (§6.5) |
| **M4** *(third review)* | Federation binds a pushed row's source to the signer, checks the token audience, caps the push, rejects replays, and scopes revocation to the peer's own entries (§7.6) |
| **M5** *(third review)* | A CSP and security headers apply to the hub-served application, verified against the running app — a mis-tuned CSP shows as a blank page |
| **M6** *(third review)* | **Withdrawn.** It misread the node registering a hub membership during the CLI invite flow — which is deliberate — as authorization drift |