From 8082dd0bf71eb0bb91c5f87f984710a7ba7ee1ff Mon Sep 17 00:00:00 2001 From: vsecoder Date: Sat, 18 Apr 2026 17:54:08 +0300 Subject: [PATCH] fix(node): rate-limit relay HTTP endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relay routes were not wrapped in any guards — /relay/broadcast accepted unlimited writes from any IP, and /relay/inbox could be scraped at line rate. Combined with the per-recipient FIFO eviction (MailboxPerRecipientCap=500), an unauthenticated attacker could wipe a victim's real messages by spamming 500 garbage envelopes. This commit wraps writes in withSubmitTxGuards (10/s per IP + 256 KiB body cap) and reads in withReadLimit (20/s per IP) — the same limits already used for /api/tx and /api/address. Co-Authored-By: Claude Opus 4.7 (1M context) --- node/api_relay.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/node/api_relay.go b/node/api_relay.go index e7ac306..eb3ce53 100644 --- a/node/api_relay.go +++ b/node/api_relay.go @@ -37,12 +37,19 @@ type RelayConfig struct { // DELETE /relay/inbox/{envID} ?pub= // GET /relay/contacts ?pub= func registerRelayRoutes(mux *http.ServeMux, rc RelayConfig) { - mux.HandleFunc("/relay/send", relaySend(rc)) - mux.HandleFunc("/relay/broadcast", relayBroadcast(rc)) - mux.HandleFunc("/relay/inbox/count", relayInboxCount(rc)) - mux.HandleFunc("/relay/inbox/", relayInboxDelete(rc)) - mux.HandleFunc("/relay/inbox", relayInboxList(rc)) - mux.HandleFunc("/relay/contacts", relayContacts(rc)) + // Writes go through withSubmitTxGuards: per-IP rate limit (10/s, burst 20) + // + 256 KiB body cap. Without these, a single attacker could spam + // 500 envelopes per victim in a few seconds and evict every real message + // via the mailbox FIFO cap. + mux.HandleFunc("/relay/send", withSubmitTxGuards(relaySend(rc))) + mux.HandleFunc("/relay/broadcast", withSubmitTxGuards(relayBroadcast(rc))) + + // Reads go through withReadLimit: per-IP rate limit (20/s, burst 40). + // Protects against inbox-scraping floods from a single origin. + mux.HandleFunc("/relay/inbox/count", withReadLimit(relayInboxCount(rc))) + mux.HandleFunc("/relay/inbox/", withReadLimit(relayInboxDelete(rc))) + mux.HandleFunc("/relay/inbox", withReadLimit(relayInboxList(rc))) + mux.HandleFunc("/relay/contacts", withReadLimit(relayContacts(rc))) } // relayInboxList handles GET /relay/inbox?pub=[&since=][&limit=N]