diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-10 17:30:22 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-10 17:30:22 +0200 |
| commit | 803e654086ac63e2d01fa4eb9cca50a4ebebcb73 (patch) | |
| tree | 8b79995745bdb19da7fb3ec33b55a68f8cc73676 | |
| parent | 6cf21a019963468cb853e5c763ef0097115efa46 (diff) | |
| download | meshbay-803e654086ac63e2d01fa4eb9cca50a4ebebcb73.tar.gz | |
fix(node): device messages are authenticated-only, and the code now says so
`device_add_request` and `device_hello` were dispatched behind
`and self._nonce_node`, which reads as "pre-proof, once the challenge has gone
out" — and is not what happens: both branches sit after the
`self._user_id is None` guard, so the nonce is always set by the time either is
reached, and a peer that has not finished its handshake gets "Handshake
required" instead.
The guard is removed rather than the branches moved. Filing a device is not
something a peer needs *in order to* prove possession of the group key, which is
the only reason anything is served pre-proof: the request is countersigned later
by a device already pinned, so requiring the caller to finish its own handshake
first costs nothing and keeps the pre-proof surface at three messages.
A test drives all six device messages through the real dispatcher on an
unauthenticated session, because this is a property of the order of its branches
and of nothing else.
Node suite 1216 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 4 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_device_linking.py | 26 |
2 files changed, 28 insertions, 2 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index 2a90bb4..46fbba3 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -555,7 +555,7 @@ class WebRTCPeerSession: self._do_invite_create(msg) elif mtype == MNP.MEMBER_REVOKE: self._do_member_revoke(msg) - elif mtype == MNP.DEVICE_REQUEST and self._nonce_node: + elif mtype == MNP.DEVICE_REQUEST: self._spawn(self._do_device_request(msg)) elif mtype == MNP.DEVICE_LOOKUP: self._spawn(self._do_device_lookup(msg)) @@ -565,7 +565,7 @@ class WebRTCPeerSession: self._spawn(self._do_device_list(msg)) elif mtype == MNP.DEVICE_REVOKE: self._spawn(self._do_device_revoke(msg)) - elif mtype == MNP.DEVICE_HELLO and self._nonce_node: + elif mtype == MNP.DEVICE_HELLO: self._spawn(self._do_device_hello(msg)) elif mtype == MNP.APPS_ENABLED: self._do_apps_enabled(msg) diff --git a/packages/meshbay-node/tests/test_device_linking.py b/packages/meshbay-node/tests/test_device_linking.py index 3580ff8..6d50580 100644 --- a/packages/meshbay-node/tests/test_device_linking.py +++ b/packages/meshbay-node/tests/test_device_linking.py @@ -82,6 +82,32 @@ async def _session(tmp_path: Path, roster, user_id: str = "alice"): return session +async def test_device_messages_need_an_authenticated_session(tmp_path, roster): + """ + Filing a device is not a pre-proof message, and must not become one. + + The pre-proof window exists for what a peer needs *in order to* prove + possession of the group key, and adding a device is not that: the request is + countersigned later by a device already pinned, so nothing is lost by + requiring the caller to finish its own handshake first. Left in the window it + would be a second thing a hub that forges a JWT could reach. + + Driven through the real dispatcher, because this is a property of the order + of its branches and of nothing else. + """ + session = await _session(tmp_path, roster) + session._user_id = None # challenged, has not proved anything yet + _sk, pk_ed, pk_x = _keys() + + for mtype in ("device_add_request", "device_hello", "device_lookup", + "device_add", "device_list", "device_revoke"): + session.sent.clear() + session._dispatch_message({"type": mtype, "pk_ed25519": pk_ed, + "pk_x25519": pk_x}) + assert _last(session) == {"type": "error", + "detail": "Handshake required"}, mtype + + def _last(session): return session.sent[-1] if session.sent else {} |