diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 26 |
1 files changed, 26 insertions, 0 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index d1630f0..4147fe7 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -1425,6 +1425,28 @@ chat opens at the newest page. A forwards pager is not what a chat opens with. epoch with no messages is harmless; an epoch key deleted while messages still need it is an unreadable archive. +**A message is bounded in size and in rate, like every other member-supplied +write.** Sending one costs the operator a row that nothing expires, every other +connected member a relayed copy, and every member of the group a notification — +so the two bounds answer the two halves: **64 KB of ciphertext** for what one +message may cost, and **60 a minute per account per group** for how often one +member may impose it. Both are checked before anything is stored or relayed, and +a refusal names itself (`chat_too_large`, `chat_rate_limited`) and is audited. + +The numbers are meant to be invisible. The sealed payload is the text, a thread +id, a display name and a timestamp — an attachment is a file on a root and +travels as a reference (§4.5) — so 64 KB is some sixty thousand characters, and +sixty a minute is far above a person typing. The rate is keyed by **account**, +not by connection: a second tab does not make anyone type faster, and keying on +the session would hand a script one budget per socket it opens. + +**There is deliberately no node-wide chat ceiling** beside the per-account one, +and the contrast with link previews is the reason. A preview spends the *node's* +egress and its third-party quota, which is one shared thing and deserves a +shared bound; a chat message spends the sender's own group. A node-wide ceiling +would let a busy group silence a quiet one — the same defect this bound closes, +one level up. + ### 6.7 Operator surface Two personas need different tools, and the headless one is the normal deployment: @@ -2574,6 +2596,10 @@ had already been asked. | **AV18** | **The hub runs on exactly one worker, and says so at startup.** `_connected_nodes`, `_node_groups`, `_webrtc_answers` and the relay registry are per-process: a second worker makes a node intermittently unreachable for half its members, which is a symptom that describes something else entirely | | **AV14** | **MHP binds its audience, and the hub reads its own identity at call time.** A token is minted for one peer and accepted by that peer only. `federation.py` bound `_hub_id` and `_hub_sk_pem` at import, which is before `load_hub_keypair` runs, so it signed with `None` and called itself `meshbay.org` whatever the instance was named — and the verifier named no audience for the `aud` the issuer sets, which PyJWT refuses outright. MHP could not complete one authenticated request between two hubs | | **AV15** | **A hash is checked for shape before it is a key lookup**, on the unauthenticated blocklist endpoints a node consults | +| **AV20** | **Chat is bounded in size and in rate, like every other member-supplied write** (§6.6). A message is a row on the operator's disk that nothing expires, a relayed copy for every connected member and a notification for every member of the group; the only ceiling was the frame size. Uploads had carried four protections and a cap since C5a because somebody asked what one member costs the others on that path, and nobody had asked it on this one | +| **AV21** | **A lease is what the node granted, not what the client called it** (§5.5). `tr` was read as a boolean, so any non-empty string skipped the leaseless ceiling and every cap behind it, and a queued transfer was held back only by the honesty of the client waiting in the queue | +| **AV22** | **The node's own controls take no authority from a hub token** (§6.7). `node_status`, `node_settings_set`, `roster_read`, `denylist_read`, `denylist_clear` and `node_reload` were gated on the account id in the JWT, which is the hub's to choose — NS4 and M3 with the check written the other way round. The gate is a proved operator device, which a hub holding no user keys cannot produce | +| **AV23** | **An upload's owner is recorded when the upload ends and applied when the entry is created**, which are different moments (§5.4). Written against the index at the end of the upload it matched nothing, every time, and left every uploaded file owned by nobody — so no member could delete what they had sent | ### 13.6 Chat design findings |