Public /api/v1/relay websocket: no auth, no frame/subscription/rate limits #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
views_api.py:116-140(ws_relay) carries no auth dependency; withpublic_wsenabled (models.py:49, default false) any client reaches the multiplexer. Once connected,router.py::86receive_text()with no frame size bound, thenjson.loads(:194);:226-239everyREQstores an entry inoriginal_subscription_idsand pushes filters to all relays, with no cap on subscriptions per connection and no cap on connections (all_routers,views_api.py:140);:209-224everyEVENTis fanned out raw to every configured relay viapublish_message(json_str)with no validation beyond "has an id";Each
REQalso creates process-wide state inNostrRouter.received_subscription_events(tasks.py:41-42) thatstop()does not reclaim (router.py:62-78), so a client can open many subscriptions, disconnect, and leave the memory behind.Impact: an anonymous client can publish arbitrary events to all of the operator's relays through the operator's IP (spam/abuse attributed to us, relay bans), read through our connections, and exhaust memory. The missing limits also apply to the private endpoint.
Fix direction: keep
public_wsoff by default and document the exposure; add per-connection caps (max frame bytes, max open subscriptions, max concurrent connections, a message-rate limit) and replyNOTICE/CLOSEDinstead of growing state; validateEVENTpayloads (well-formed, signed, bounded size) before fan-out; clean up per-connection buffers onstop()(see the ClassVar item below).Found during reforge run #1 (sandbox nostrclient#3).