aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_NODE_PROTOCOL.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-12 09:47:07 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-12 16:36:54 +0200
commit4cce50f09a73739387d5058a6f8183ebac65ae2c (patch)
tree30e9d72280295d913983910a956b0d3306696ec8 /docs/MESHBAY_NODE_PROTOCOL.md
parent9cc2909cb4a360c81b471ceab1d9578a7655a88e (diff)
downloadmeshbay-4cce50f09a73739387d5058a6f8183ebac65ae2c.tar.gz
fix: an empty group claim is a claim on nothing
A node that hosts no groups sends no `group_ids` on its hub socket, and the hub resolved the claim with `set(claimed_groups or authorized)` — so "I host nothing" arrived as "I host every group this account belongs to", other members' included. Such a node can serve none of them: it holds no GEK, and its own handshake refuses them with "Group not hosted on this node". `/v1/groups/{id}/nodes` answers in registration order and `_node_groups` is in-memory, so which node a client was sent to depended on who reconnected first after a hub restart. GroupPage took `nodes[0]` with no fallback. On 2026-09-11 a hub deploy at 20:14 reshuffled the registry, a second member's unconfigured node won the race, and a group stopped opening for everyone in it with its only real host online throughout. Any member could take one of their groups down, by accident, by leaving an empty node running. Four changes, because no one of them is sufficient: - the hub never widens an absent claim, and `update_groups` goes through the same ceiling as registration — it assigned its list verbatim, so the bound that makes C2 hold at authentication was one message wide - the node states the empty set rather than omitting the field - the refusal carries `not_hosted`, so a client can tell "try the next node" from "you, here, must do something first" - GroupPage walks the list instead of indexing into it The three lines involved date from 13, 20 and 23 August and each is defensible alone. The defect is in the seam, which is where the last two also were: a falsy empty collection must never mean "unspecified". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4YmK41VsEURWFdop4EEeT
Diffstat (limited to 'docs/MESHBAY_NODE_PROTOCOL.md')
-rw-r--r--docs/MESHBAY_NODE_PROTOCOL.md10
1 files changed, 9 insertions, 1 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md
index f7e8c48..81605c6 100644
--- a/docs/MESHBAY_NODE_PROTOCOL.md
+++ b/docs/MESHBAY_NODE_PROTOCOL.md
@@ -466,7 +466,7 @@ absent. `verify_proof` compares with `hmac.compare_digest`.
| `group_id` non-empty | `group_id is required` | an absent group means no membership check to make; there is no default group, and a node's first group is not one |
| not on the denylist for `user_id`, `jti` **or** `group_id` | `Token revoked` | all three targets, and persisted to disk: a revocation that a restart forgets is not one |
| `group_id ∈ token.groups` | `Not a member of this group`, code `not_a_member` | the membership check itself — a token is proof of an account, never of a group |
-| `group_id ∈ node.hosted_groups` | `Group not hosted on this node` | |
+| `group_id ∈ node.hosted_groups` | `Group not hosted on this node`, code `not_hosted` | the hub may hand a client several nodes for one group, and only some of them host it |
`AuthorizedPeer` carries `user_id`, `group_id`, `username`, `jti` — and deliberately
**no user public key**. A key arriving in a token would be a key the hub chose, and the
@@ -478,6 +478,14 @@ node's roster. The hub certifies accounts, not keys.
group (`groups` is baked in at login and the hub pushes no updates), so the client
refreshes once and retries on that code rather than telling a member they are not one.
+`not_hosted` is the client's signal to try the **next** node the hub offered for the
+group rather than to report a failure. `/v1/groups/{id}/nodes` returns every node
+registered for the group, in hub registration order, and that order is not a ranking:
+a node listed first is not necessarily one that holds the group's files. Refusing
+without a code made this indistinguishable from a refusal the reader has to act on,
+and a client that stopped at the first node let one wrongly registered peer make a
+group unopenable for all of its members (2026-09-11).
+
### 6.4 Channel binding
| Transport | Anchor | Construction |