From 785832ae4b2d89966189db59eedeb8fcbd90439a Mon Sep 17 00:00:00 2001 From: tuanaiseo Date: Sat, 4 Apr 2026 22:50:41 +0700 Subject: [PATCH 1/2] fix(security): relay client connection lacks authentication for j `mp_client_connection` accepts only `server_id` and attaches clients to an active server instance without validating a token or session ownership. If `server_id` is guessable/leaked, unauthorized users can connect to and interact with private multiplayer sessions. Affected files: relayservice.js, relay.js Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com> --- server/relay/relayservice.js | 52 +++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/server/relay/relayservice.js b/server/relay/relayservice.js index 780d60e4..6331e84c 100644 --- a/server/relay/relayservice.js +++ b/server/relay/relayservice.js @@ -23,8 +23,11 @@ this.RelayService = class RelayService { if (msg.token == null) { return; } - return this.session.serverTokenCheck(msg.token, msg.server_id, () => { + return this.session.serverTokenCheck(msg.token, msg.server_id, (valid) => { var instance; + if (!valid) { + return this.session.socket.close(); + } instance = new ServerInstance(this, msg.server_id, this.session); return RelayService.servers[msg.server_id] = instance; }); @@ -38,12 +41,22 @@ this.RelayService = class RelayService { clientConnection(msg) { var server; - if (msg.server_id == null) { - return; + if (!this.canTryClientConnection()) { + return this.session.socket.close(); + } + if ((msg.server_id == null) || (msg.token == null)) { + this.clientConnectionFailed(); + return this.session.socket.close(); } server = RelayService.servers[msg.server_id]; if (server != null) { - return server.clientConnection(this.session); + return this.session.serverTokenCheck(msg.token, msg.server_id, (valid) => { + if (!valid) { + this.clientConnectionFailed(); + return this.session.socket.close(); + } + return server.clientConnection(this.session); + }); } else { return this.session.socket.close(); } @@ -62,8 +75,39 @@ this.RelayService = class RelayService { }); } + canTryClientConnection() { + var entry, now; + now = Date.now(); + entry = RelayService.client_failures[this.session.socket.remoteAddress]; + if (entry == null) { + return true; + } + if (now - entry.time > 60000) { + delete RelayService.client_failures[this.session.socket.remoteAddress]; + return true; + } + return entry.count < 5; + } + + clientConnectionFailed() { + var entry, now; + now = Date.now(); + entry = RelayService.client_failures[this.session.socket.remoteAddress]; + if ((entry == null) || (now - entry.time > 60000)) { + return RelayService.client_failures[this.session.socket.remoteAddress] = { + count: 1, + time: now + }; + } else { + entry.count += 1; + return entry.time = now; + } + } + }; this.RelayService.servers = {}; +this.RelayService.client_failures = {}; + module.exports = this.RelayService; From 42a1a9039274730532f23e8484faacea200ac529 Mon Sep 17 00:00:00 2001 From: tuanaiseo Date: Sat, 4 Apr 2026 22:50:42 +0700 Subject: [PATCH 2/2] fix(security): relay client connection lacks authentication for j `mp_client_connection` accepts only `server_id` and attaches clients to an active server instance without validating a token or session ownership. If `server_id` is guessable/leaked, unauthorized users can connect to and interact with private multiplayer sessions. Affected files: relayservice.js, relay.js Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com> --- server/relay/relay.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/server/relay/relay.js b/server/relay/relay.js index 759ab163..3cbdbef2 100644 --- a/server/relay/relay.js +++ b/server/relay/relay.js @@ -40,12 +40,13 @@ Relay = class Relay { return this.client.send(msg); }); this.client.on("message", (msg) => { - var data; + var callback, data; try { data = JSON.parse(msg.data); if (data.name === "check_server_token") { - if (data.valid && (this.token_requests[data.token] != null)) { - this.token_requests[data.token](); + callback = this.token_requests[data.token]; + if (callback != null) { + callback(data.valid === true); return delete this.token_requests[data.token]; } } @@ -94,12 +95,17 @@ Relay = class Relay { } serverTokenCheck(token, server_id, callback) { - this.token_requests[token] = callback(); - return this.client.send(JSON.stringify({ - name: "check_server_token", - server_id: server_id, - token: token - })); + this.token_requests[token] = callback; + try { + return this.client.send(JSON.stringify({ + name: "check_server_token", + server_id: server_id, + token: token + })); + } catch (error) { + delete this.token_requests[token]; + return callback(false); + } } };