summaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_DESIGN.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
-rw-r--r--docs/MESHBAY_DESIGN.md22
1 files changed, 21 insertions, 1 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index bda33f1..16e728f 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -2473,7 +2473,8 @@ Two structural recommendations from that review stand as rules:
### 13.5b Availability between members (`AV`)
-**Added 2026-09-12, after the finding that produced it.** The first three
+**Added 2026-09-12, after the finding that produced it, and extended the same
+day when `users.py` and `admin.py` were read under it.** The first three
reviews asked who can read what, who can impersonate whom, and what a hostile
node can forge. None asked **what a legitimate but misconfigured or careless
member costs everyone else** — and that is the question a group platform lives
@@ -2489,6 +2490,21 @@ else bears the cost?** Where the answer is "someone other than the sender",
there must be a ceiling, and it must apply on every path that writes the state
— not only the one where the ceiling was first thought of.
+Two shapes recur, and are worth naming because each accounts for several
+entries. **A limit written on one of several equivalent paths** — three
+endpoints send mail and one of them had neither a rate limit nor a captcha;
+every list in `admin.py` is bounded and the two outside it were not; the group
+claim was bounded at registration and not on reload. And **a bound that counts
+the wrong thing** — a per-IP rate limit bounds a caller, never the mailbox that
+receives what they cause, which is why `AV10` is a cooldown per *account* under
+a limit per IP rather than a tighter limit.
+
+`admin.py` was read under this lens and needed nothing. Its moderator/admin
+line is drawn explicitly — a moderator may not change a role, may not revoke,
+and may not touch an admin's account at all — self-modification is refused, and
+every list it serves is bounded. It is the part of the hub where the question
+had already been asked.
+
| Label | The rule it names |
|---|---|
| **AV1** | **An empty claim is a claim on nothing.** A node's group set is `authorized ∩ claimed`, and an absent or empty `group_ids` registers it for no group rather than all of its owner's — on registration and on `update_groups` alike (§7.2) |
@@ -2499,6 +2515,10 @@ there must be a ceiling, and it must apply on every path that writes the state
| **AV6** | **A relay proves possession of its approved key.** A public key is not a password, and the register call is unauthenticated by design — it is not a user — so the proof is the only thing standing between a stranger and where nodes send relayed traffic |
| **AV7** | **A node bounds how many peers it holds and how long an unproven one lasts.** The hub's cap is per calling account, which is a limit on each member and not on the machine, so without this an operator's exposure grew with the size of their groups |
| **AV8** | **One account cannot make the hub mail another at will.** The invitation email's subject comes from the group row, never from the request, and the endpoint is metered |
+| **AV9** | **No mail is sent from the event loop.** `smtplib` is synchronous and waits up to ten seconds; called from an async handler that wait is the whole instance's, not one request's. Every send goes through `mail.send_off_loop` |
+| **AV10** | **Every path that makes the hub send mail is metered, per account.** A rate limit that counts by IP bounds a caller, not an inbox. Changing one's address mails an arbitrary stranger, so it carries a cooldown as well as a limit; so does a reset request |
+| **AV11** | **A namespace a client writes into is closed, and its rows are capped.** The preference key space is an allow-list plus `default_tab:<group_id>` checked as a group id, the value is length-bounded, and the row count per account is bounded |
+| **AV12** | **Every list has an upper bound on `limit` and a floor under `offset`.** Including the ones that take no authentication at all |
### 13.6 Chat design findings