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.md17
1 files changed, 15 insertions, 2 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 16e728f..8da4552 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -2499,6 +2499,16 @@ the wrong thing** — a per-IP rate limit bounds a caller, never the mailbox tha
receives what they cause, which is why `AV10` is a cooldown per *account* under
a limit per IP rather than a tighter limit.
+**Open for decision, not a defect:** `moderation.AUTO_BLOCK_THRESHOLD` is 3.
+Three distinct accounts blocking a hash adds it to the list every node
+enforces, network-wide, automatically, with manual admin removal the only
+undo. That is already far better than the anonymous version it replaced, and
+it is still a censorship primitive an attacker buys for the price of three
+email addresses. Raising it buys little; requiring the reporting accounts to
+be more than a day old would cost a patient attacker a day and cost an honest
+reporter nothing after their first. Left as it is because it is a moderation
+policy rather than a bug, and the person who sets that policy is the operator.
+
`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
@@ -2516,9 +2526,12 @@ had already been asked.
| **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 |
+| **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 *and* a daily ceiling; a reset request and a registration resend carry cooldowns |
+| **AV13** | **The mail server is not a relay, and `mail.py` is where that is decided.** Every message passes one function; `purpose` is keyword-required and checked against a closed list, so a helper that names anything else does not send and one that names nothing is a TypeError. Under it sit a bound per **recipient** — the thing a person being mail-bombed actually experiences, unmoved by which account, address or endpoint asks — and an instance-wide hourly ceiling, because registration is open and "per account" is a bound an attacker buys more of |
| **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 |
+| **AV12** | **Every list has an upper bound on `limit` and a floor under `offset`.** Including the ones that take no authentication at all — the public group directory and the content blocklist |
+| **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 |
### 13.6 Chat design findings