diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-14 01:27:21 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-14 01:27:21 +0200 |
| commit | f15efd23f66c521ca9206789482bb38e7326eeb4 (patch) | |
| tree | f069b741d3fe0114c3b5889c02dc0392c5201f68 /packages/meshbay-node/src/meshbay_node/replication.py | |
| parent | aab4bc98a3361d9f23e048a52705baa2f4a4a078 (diff) | |
| download | meshbay-f15efd23f66c521ca9206789482bb38e7326eeb4.tar.gz | |
feat(node)!: the node wraps the group key — closes H3 and M3
The invite flow fetched the invitee's pk_x25519 from the hub and wrapped the
GEK for whatever came back (app.js:1466, and gek-init did the same server-side).
The hub is the key directory, so a hub answering with its own key was handed the
group key by an honest member following the protocol exactly. No forgery, no
injection, nothing for the client to notice. That was H3.
The fix is not safety numbers. Nobody reads the directory any more:
- the node holds the GEK and wraps it itself, on every connection, for the
X25519 key the joiner signed with their Ed25519 identity in one transcript
(meshbay:join:v1), so the identity key vouches for the encryption key;
- identities are bound to accounts by a one-time code the hub never sees —
40 bits, single use, one account, bounded per connection AND node-wide;
- the node's own roster decides who may receive the key. Hub membership lets
someone reach a node; it no longer gets them anything. A hub that invents an
account and mints it a token is answered not_authorized_for_group.
Safety numbers would have made substitution detectable by a human who checks, at
the moment there is nothing to check against — first contact. Removing the lookup
makes it impossible, and costs the user one code to pass along.
M3 falls out of the same work. The daemon auto-pinned its own keystore key as
admin_pk_ed25519 while the browser signs with the user identity key, so every
privileged operation failed closed with a signature error that looked like a bug
somewhere else; the demo only worked because a deploy script overwrote the value.
Authority now comes from the roster, established locally by `operator pair`.
Asking the hub for the operator's key — the obvious-looking fix — would have let
the hub install itself as node administrator.
BREAKING: gek_bundle_store is deleted, not gated. No member hands the node key
material at all, so C5b becomes structural rather than an authorization to check.
Existing stored bundles are still served, so current deployments keep working.
Also:
- join_policy (invite|open) is read from node.toml, never from the hub — a hub
able to declare a group open would be handed its key. Unknown group ⇒ invite.
- admin signatures are verified against the roster on every check, so unpinning
takes effect without a restart. admin_pk_ed25519 stays readable as legacy.
- two C5b tests were rewritten, deliberately: they asserted that
gek_bundle_store demanded an operator signature, and the message is gone. They
now assert the stronger property. The file says not to fix these tests, so
this is the record of why they changed.
- a slice-1 bug found while writing slice 2: connect() never passed skEdB64, so
pairing would have failed at runtime with no test able to catch it.
Tests: 152 node+common here, including an end-to-end DataChannel run where a
member who has never held the group key redeems a code in the pre-proof window
and receives the key wrapped for a key only they can open.
Design: docs/invite-pairing-v1.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/replication.py')
0 files changed, 0 insertions, 0 deletions