From 15d0ed306b94cd98137104b69ff91393c141f267 Mon Sep 17 00:00:00 2001 From: vsecoder Date: Sat, 18 Apr 2026 17:55:11 +0300 Subject: [PATCH] fix(ws): hard-deny inbox:* / typing:* when authX is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WS topic-auth check had a soft-fail fallback: if the authenticated identity had no registered X25519 public key (authX == ""), the topic-ownership check was skipped and the client could subscribe to any inbox:* or typing:* topic. Exploit: register an Ed25519 identity without an X25519 key, subscribe to the victim's inbox topic, receive their envelope notifications. Now both topics hard-require a registered X25519. Clients must call REGISTER_KEY (publishing X25519) before subscribing. The scope is narrow — only identities that haven't completed REGISTER_KEY yet could have exploited this — but a hard fail is still correct. Co-Authored-By: Claude Opus 4.7 (1M context) --- node/ws.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/node/ws.go b/node/ws.go index d8a680f..22dc548 100644 --- a/node/ws.go +++ b/node/ws.go @@ -521,13 +521,17 @@ func (h *WSHub) authorizeSubscribe(c *wsClient, topic string) error { if authed == "" { return fmt.Errorf("inbox:* requires auth") } - // If we have an x25519 mapping, enforce it; otherwise accept - // (best-effort — identity may not be registered yet). - if authX != "" { - want := strings.TrimPrefix(topic, "inbox:") - if want != authX { - return fmt.Errorf("inbox:* only for your own x25519") - } + // Hard-require a registered X25519 identity — otherwise an + // Ed25519-only identity could subscribe to ANY inbox topic by + // design (authX == "" skipped the equality check). Fixed: we + // now refuse the subscription until the client publishes an + // X25519 key via REGISTER_KEY. + if authX == "" { + return fmt.Errorf("inbox:* requires a registered X25519 identity (send REGISTER_KEY first)") + } + want := strings.TrimPrefix(topic, "inbox:") + if want != authX { + return fmt.Errorf("inbox:* only for your own x25519") } return nil } @@ -536,11 +540,12 @@ func (h *WSHub) authorizeSubscribe(c *wsClient, topic string) error { if authed == "" { return fmt.Errorf("typing:* requires auth") } - if authX != "" { - want := strings.TrimPrefix(topic, "typing:") - if want != authX { - return fmt.Errorf("typing:* only for your own x25519") - } + if authX == "" { + return fmt.Errorf("typing:* requires a registered X25519 identity") + } + want := strings.TrimPrefix(topic, "typing:") + if want != authX { + return fmt.Errorf("typing:* only for your own x25519") } return nil }