aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/src/meshbay_common
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-14 17:51:48 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-14 17:51:48 +0200
commitf0984e86d9cb596a282ce6feb7cfc2f075b2794b (patch)
tree192198a5d596a06d15095bb1d3540dd4a8d397c8 /packages/meshbay-common/src/meshbay_common
parent9fa2117de1caf4d713cc0b7a310b9549467738c3 (diff)
downloadmeshbay-f0984e86d9cb596a282ce6feb7cfc2f075b2794b.tar.gz
feat!: identity keys per node — C4's blast radius drops to one operator
One keypair was copied to every node its owner joined, so cracking the bundle on any single node yielded the identity used on all of them: their content on other operators' machines, and the ability to sign as them anywhere. That lateral reach was the part of C4 worth attacking. Each node now gets its own keypair, generated the first time its owner joins it and left with that node alone. An operator who cracks what sits on their own disk holds a key that is a stranger to every other node — and on their own node, one that unlocks nothing they did not already hold: they serve the content, the index and every byte of it by design. Nothing changes for the user. A first contact with a node already needed that operator's code, and the key is created in the same step; a second browser still recovers it from the node with the passphrase alone. Two operators can also no longer tell they host the same person by comparing keys. BREAKING, and deliberately without a compatibility path — the deployment is wiped for the next demo: - users.pk_ed25519 / pk_x25519 dropped (migration a7c31f9e40b2) - registration no longer sends or stores a key - PUT /v1/users/me/keys and regenerateKeys() gone; rotation is now `member unpin` plus a fresh code, decided on the machine that pinned it - /pubkeys returns an account id and a node's linking key. It was the directory H3 read, and nothing wraps for it any more - the pk_user JWT claim is gone That last one closed a live defect the inventory turned up: the node recorded pk_user as the uploader's identity and authorized deletion against it, so a hub issuing a token naming its own key could delete anyone's uploads on any node. Attribution now uses the key the node itself pinned. A simplification falls out. Registration generates nothing, so a scripted signup is a real account: `demo.py bootstrap` takes a wiped hub and node to a working demo with no browser, which was impossible while keys were born in one. Also fixes, found by running it on a wiped deployment: the key handed back on a join now belongs to the group the connection is for, not the group named in the invitation — an operator pairs node-wide but redeems the code while opening a group, and expects to read it. Tests: 343, including the two that state the property — a key pinned by one node is refused at another, and someone else's code does not admit it. Verified end to end against a wiped hub and node: bootstrap, pair, invite, join, download, stream, second browser, revoke. Design: docs/per-node-identity-v1.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common')
-rw-r--r--packages/meshbay-common/src/meshbay_common/handshake.py7
1 files changed, 5 insertions, 2 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/handshake.py b/packages/meshbay-common/src/meshbay_common/handshake.py
index 223f064..fca218f 100644
--- a/packages/meshbay-common/src/meshbay_common/handshake.py
+++ b/packages/meshbay-common/src/meshbay_common/handshake.py
@@ -75,9 +75,13 @@ class AuthorizedPeer:
user_id: str
group_id: str
username: str
- pk_user: str
jti: str
+ # No `pk_user`. The hub used to put a user key in the token and the node
+ # recorded it as the uploader's identity, which let whoever issued tokens
+ # decide who could delete a file. Identity keys are pinned by the node
+ # (see roster.py); the hub certifies accounts, not keys.
+
def handshake_transcript(
role: str,
@@ -193,7 +197,6 @@ def authorize_token(
user_id=user_id,
group_id=group_id,
username=decoded.get("username", ""),
- pk_user=decoded.get("pk_user", ""),
jti=jti,
)