From 9e6bb3e98461d68f4d5be1200271928ef9fb3607 Mon Sep 17 00:00:00 2001 From: Alfredooe <68240493+Alfredooe@users.noreply.github.com> Date: Sat, 3 Jan 2026 22:19:53 +0000 Subject: [PATCH 1/4] FIX: Prevent negative hops amplification attack. --- src/p2p/messaging.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/p2p/messaging.js b/src/p2p/messaging.js index 67967c6..4ed555a 100644 --- a/src/p2p/messaging.js +++ b/src/p2p/messaging.js @@ -65,7 +65,8 @@ class MessageHandler { } // Only relay if we haven't already relayed this message (bloom filter check) - if (hops < MAX_RELAY_HOPS && !this.bloomFilter.hasRelayed(id, seq)) { + // Security: hops must be >= 0 to prevent negative hops bypass attack + if (hops >= 0 && hops < MAX_RELAY_HOPS && !this.bloomFilter.hasRelayed(id, seq)) { this.bloomFilter.markRelayed(id, seq); this.diagnostics.increment("heartbeatsRelayed"); this.relayCallback({ ...msg, hops: hops + 1 }, sourceSocket); @@ -97,7 +98,7 @@ class MessageHandler { this.broadcastCallback(); // Use id:leave as key for LEAVE messages - if (hops < MAX_RELAY_HOPS && !this.bloomFilter.hasRelayed(id, "leave")) { + if (hops >= 0 && hops < MAX_RELAY_HOPS && !this.bloomFilter.hasRelayed(id, "leave")) { this.bloomFilter.markRelayed(id, "leave"); this.relayCallback({ ...msg, hops: hops + 1 }, sourceSocket); } @@ -116,15 +117,20 @@ const validateMessage = (msg) => { const allowedFields = ['type', 'id', 'seq', 'hops', 'nonce', 'sig']; const fields = Object.keys(msg); return fields.every(f => allowedFields.includes(f)) && - msg.id && typeof msg.seq === 'number' && - typeof msg.hops === 'number' && msg.nonce && msg.sig; + typeof msg.id === 'string' && msg.id.length > 0 && + typeof msg.seq === 'number' && Number.isInteger(msg.seq) && msg.seq >= 0 && + typeof msg.hops === 'number' && Number.isInteger(msg.hops) && msg.hops >= 0 && + typeof msg.nonce === 'number' && Number.isInteger(msg.nonce) && msg.nonce >= 0 && + typeof msg.sig === 'string' && msg.sig.length > 0; } if (msg.type === "LEAVE") { const allowedFields = ['type', 'id', 'hops', 'sig']; const fields = Object.keys(msg); return fields.every(f => allowedFields.includes(f)) && - msg.id && typeof msg.hops === 'number' && msg.sig; + typeof msg.id === 'string' && msg.id.length > 0 && + typeof msg.hops === 'number' && Number.isInteger(msg.hops) && msg.hops >= 0 && + typeof msg.sig === 'string' && msg.sig.length > 0; } return false; From 29a2243f997819ef6e8d41952814ce3c8194b58c Mon Sep 17 00:00:00 2001 From: Alfredooe <68240493+Alfredooe@users.noreply.github.com> Date: Sat, 3 Jan 2026 22:24:10 +0000 Subject: [PATCH 2/4] Fix: Add RL --- src/config/constants.js | 6 ++++++ src/p2p/messaging.js | 14 ++++++++++++-- src/state/diagnostics.js | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index aba2c63..704d506 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -25,6 +25,10 @@ const BROADCAST_THROTTLE = 1000; const DIAGNOSTICS_INTERVAL = 10000; const PORT = process.env.PORT || 3000; +// Rate Limiting: Semi effective mitigation to limit abuse from malicious peers / Sybil attack leveraging weak PoW. 100 new peers per 5 seconds per connection. +const RATE_LIMIT_WINDOW = 5000; +const RATE_LIMIT_MAX_NEW_PEERS = 100; + module.exports = { TOPIC_NAME, TOPIC, @@ -39,4 +43,6 @@ module.exports = { BROADCAST_THROTTLE, DIAGNOSTICS_INTERVAL, PORT, + RATE_LIMIT_WINDOW, + RATE_LIMIT_MAX_NEW_PEERS, }; diff --git a/src/p2p/messaging.js b/src/p2p/messaging.js index 4ed555a..43f3766 100644 --- a/src/p2p/messaging.js +++ b/src/p2p/messaging.js @@ -1,5 +1,5 @@ const { verifyPoW, verifySignature, createPublicKey } = require("../core/security"); -const { MAX_RELAY_HOPS } = require("../config/constants"); +const { MAX_RELAY_HOPS, RATE_LIMIT_WINDOW, RATE_LIMIT_MAX_NEW_PEERS } = require("../config/constants"); const { BloomFilterManager } = require("../state/bloom"); class MessageHandler { @@ -62,10 +62,20 @@ class MessageHandler { if (wasNew) { this.diagnostics.increment("newPeersAdded"); this.broadcastCallback(); + if (hops === 0) { + const now = Date.now(); + if (!sourceSocket.rateLimiter || now > sourceSocket.rateLimiter.resetTime) { + sourceSocket.rateLimiter = { count: 0, resetTime: now + RATE_LIMIT_WINDOW }; + } + if (++sourceSocket.rateLimiter.count > RATE_LIMIT_MAX_NEW_PEERS) { + this.diagnostics.increment("rateLimitedConnections"); + sourceSocket.destroy(); + return; + } + } } // Only relay if we haven't already relayed this message (bloom filter check) - // Security: hops must be >= 0 to prevent negative hops bypass attack if (hops >= 0 && hops < MAX_RELAY_HOPS && !this.bloomFilter.hasRelayed(id, seq)) { this.bloomFilter.markRelayed(id, seq); this.diagnostics.increment("heartbeatsRelayed"); diff --git a/src/state/diagnostics.js b/src/state/diagnostics.js index 659e406..6fcfabb 100644 --- a/src/state/diagnostics.js +++ b/src/state/diagnostics.js @@ -12,6 +12,7 @@ class DiagnosticsManager { bytesReceived: 0, bytesRelayed: 0, leaveMessages: 0, + rateLimitedConnections: 0, }; this.interval = null; From 688bf1316110667a5f71cb48b2a72e4be65c4a62 Mon Sep 17 00:00:00 2001 From: Alfredooe <68240493+Alfredooe@users.noreply.github.com> Date: Sat, 3 Jan 2026 22:33:02 +0000 Subject: [PATCH 3/4] Fix: Increase RATE_LIMIT_MAX_NEW_PEERS --- src/config/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/constants.js b/src/config/constants.js index 704d506..825dcd4 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -27,7 +27,7 @@ const PORT = process.env.PORT || 3000; // Rate Limiting: Semi effective mitigation to limit abuse from malicious peers / Sybil attack leveraging weak PoW. 100 new peers per 5 seconds per connection. const RATE_LIMIT_WINDOW = 5000; -const RATE_LIMIT_MAX_NEW_PEERS = 100; +const RATE_LIMIT_MAX_NEW_PEERS = 1000; module.exports = { TOPIC_NAME, From c4ff0190551a21a09aa8599643787393d18617cb Mon Sep 17 00:00:00 2001 From: Alfredooe <68240493+Alfredooe@users.noreply.github.com> Date: Sat, 3 Jan 2026 22:44:21 +0000 Subject: [PATCH 4/4] Copilot Review Fix --- src/p2p/messaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p2p/messaging.js b/src/p2p/messaging.js index 43f3766..8d7d552 100644 --- a/src/p2p/messaging.js +++ b/src/p2p/messaging.js @@ -67,7 +67,7 @@ class MessageHandler { if (!sourceSocket.rateLimiter || now > sourceSocket.rateLimiter.resetTime) { sourceSocket.rateLimiter = { count: 0, resetTime: now + RATE_LIMIT_WINDOW }; } - if (++sourceSocket.rateLimiter.count > RATE_LIMIT_MAX_NEW_PEERS) { + if (++sourceSocket.rateLimiter.count >= RATE_LIMIT_MAX_NEW_PEERS) { this.diagnostics.increment("rateLimitedConnections"); sourceSocket.destroy(); return;