diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-13 14:30:51 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-13 14:30:51 +0200 |
| commit | 8c5227365118383540a5e77b1885aef7e62bf6ec (patch) | |
| tree | c288158f6f7cf36de8e4fa185c060402bca65ec4 /packages/meshbay-node/src/meshbay_node/hub_client.py | |
| parent | 146a6759fa73386e9b59570956aeedd7e1cfd978 (diff) | |
| download | meshbay-8c5227365118383540a5e77b1885aef7e62bf6ec.tar.gz | |
fix(hub): require proof of possession on node announce — closes M8
Phase 11.5.10.
POST /v1/nodes/announce accepted any pk_node with no proof the announcer held
the matching private key, so a user could register a node record carrying
someone else's node key, and records accumulated without limit.
The announcer now signs a domain-separated message binding the key to their
account — meshbay:node_announce:{user_id}:{pk_node}:{timestamp} — reusing the
shape already proven by /v1/nodes/auth, so a signature for one can never
satisfy the other. Same 60-second window.
Re-announcing the same key now updates the existing record in place instead of
creating a new row.
Three test helpers had to be taught to sign, which is the useful part: nothing
in the suite had ever exercised announce with an attacker's key. The new tests
cover the missing proof, a foreign key, a stale timestamp, and idempotence.
Note for the record: the node key is independent of the user's identity key.
Two hub tests asserted the announced pk_node equalled the user's pk_ed, which
happened to be true only because the daemon announces its keystore key. They
now assert against the announced key itself.
Tests: 157 hub+common, node suite green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/hub_client.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/hub_client.py | 10 |
1 files changed, 10 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py index 432af0a..a379b68 100644 --- a/packages/meshbay-node/src/meshbay_node/hub_client.py +++ b/packages/meshbay-node/src/meshbay_node/hub_client.py @@ -163,9 +163,19 @@ class HubClient: raise RuntimeError("Not logged in") await self.ensure_fresh_token() + # Proof of possession of the node key (M8) — same domain-separated shape + # as node_auth, so a signature for one can never satisfy the other. + timestamp = int(time.time()) + message = (f"meshbay:node_announce:{self._session.user_id}:" + f"{self._keys.pk_ed25519_b64}:{timestamp}").encode() + signature = base64.b64encode( + self._keys.sk_ed25519.sign(message)).decode() + r = await self._http.post("/v1/nodes/announce", json={ "pk_node": self._keys.pk_ed25519_b64, "endpoint_hint": endpoint_hint, + "timestamp": timestamp, + "signature": signature, }, headers=self._session.auth_headers) r.raise_for_status() node_id = r.json()["node_id"] |