summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/hub_client.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-13 14:30:51 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-13 14:30:51 +0200
commit8c5227365118383540a5e77b1885aef7e62bf6ec (patch)
treec288158f6f7cf36de8e4fa185c060402bca65ec4 /packages/meshbay-node/src/meshbay_node/hub_client.py
parent146a6759fa73386e9b59570956aeedd7e1cfd978 (diff)
downloadmeshbay-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.py10
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"]