fix(ws): hard-deny inbox:* / typing:* when authX is empty
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) <noreply@anthropic.com>
This commit is contained in:
17
node/ws.go
17
node/ws.go
@@ -521,14 +521,18 @@ 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 != "" {
|
||||
// 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
|
||||
}
|
||||
if strings.HasPrefix(topic, "typing:") {
|
||||
@@ -536,12 +540,13 @@ func (h *WSHub) authorizeSubscribe(c *wsClient, topic string) error {
|
||||
if authed == "" {
|
||||
return fmt.Errorf("typing:* requires auth")
|
||||
}
|
||||
if authX != "" {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user