NostrRouter buffers are ClassVars: unbounded growth and isolation-by-luck between connections #8
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?
router.py:39-44declaresreceived_subscription_events,received_subscription_notices,received_subscription_eosenoticesandreceived_command_resultsasClassVars shared across every websocket connection. The singlesubscribe_eventsthread writes into them for anysub_idthe relays echo (tasks.py:41-42,50,58-61); each connection drains only the keys in its ownoriginal_subscription_ids(router.py:109-114).Problems:
urlsafe_short_hash()uniqueness of the rewritten sub-id; nothing structural prevents one connection draining another's events.stop()(router.py:62-78) closes subscriptions and clearsreceived_command_resultsfor pending publishes, but never deletes this connection's keys fromreceived_subscription_events/received_subscription_eosenotices. Events arriving after disconnect, or for ids no live connection owns, are stored forever.received_subscription_notices(router.py:185-191) is popped by whichever connection runs first and then discarded; with zero connections it only grows.PR #3 already introduced the right pattern for OKs:
tasks.py:63-72only keeps results some router is waiting on, androuter.py:65-66clears them on stop. The other three buffers need the same treatment.Impact: memory exhaustion reachable from the unauthenticated public endpoint; latent cross-connection event leak on the endpoint labelled private.
Fix direction: make the buffers per-instance (like
original_subscription_ids), have the relay-manager callbacks route to the owning router via a registry keyed by rewritten sub-id (drop unknown ids on the floor), delete this connection's keys instop(), and add a hard cap/TTL per subscription buffer. Check against real #4 (nostr-sdk rebase) since the callbacks intasks.pyare rewritten there.Found during reforge run #1 (sandbox nostrclient#5).