From 7f9b63a24c422a401ac9e2610c1e73344509d6f9 Mon Sep 17 00:00:00 2001 From: Evan Date: Tue, 5 May 2026 14:59:09 -0600 Subject: [PATCH] Increase max intent size to 2kb (#3852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Raised MAX_INTENT_SIZE from 500 to 2000 bytes — the move_warship intent could exceed the old limit and get rejected. Removed the separate MAX_CONFIG_INTENT_SIZE (also 2000) and the intentType branching, since both paths now share the same cap. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan --- src/server/ClientMsgRateLimiter.ts | 18 +++--------------- src/server/GameServer.ts | 3 --- tests/server/ClientMsgRateLimiter.test.ts | 10 ++++++++++ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/server/ClientMsgRateLimiter.ts b/src/server/ClientMsgRateLimiter.ts index d243b6460..99e88beb4 100644 --- a/src/server/ClientMsgRateLimiter.ts +++ b/src/server/ClientMsgRateLimiter.ts @@ -3,8 +3,7 @@ import { ClientID } from "../core/Schemas"; const INTENTS_PER_SECOND = 10; const INTENTS_PER_MINUTE = 150; -const MAX_INTENT_SIZE = 500; -const MAX_CONFIG_INTENT_SIZE = 2000; +const MAX_INTENT_SIZE = 2000; const TOTAL_BYTES = 2 * 1024 * 1024; // 2MB per client export type RateLimitResult = "ok" | "limit" | "kick"; @@ -17,30 +16,19 @@ interface ClientBucket { export class ClientMsgRateLimiter { private buckets = new Map(); - check( - clientID: ClientID, - type: string, - bytes: number, - intentType?: string, - ): RateLimitResult { + check(clientID: ClientID, type: string, bytes: number): RateLimitResult { const bucket = this.getOrCreate(clientID); bucket.totalBytes += bytes; if (bucket.totalBytes >= TOTAL_BYTES) return "kick"; if (type === "intent") { - // Config updates are lobby-only and not stored in turn history, - // so they can be larger than regular intents. - const maxSize = - intentType === "update_game_config" - ? MAX_CONFIG_INTENT_SIZE - : MAX_INTENT_SIZE; // Intents are stored in turn history for the duration of the game, so // oversized intents would accumulate and fill up server RAM. // Intents are also sent to all players, so it increase outgoing // data. // Intents should never be larger than MAX_INTENT_SIZE, so we assume the client is malicious. - if (bytes > maxSize) { + if (bytes > MAX_INTENT_SIZE) { return "kick"; } if ( diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 2f6175776..21f3a4418 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -349,13 +349,10 @@ export class GameServer { } const clientMsg = parsed.data; const bytes = Buffer.byteLength(message, "utf8"); - const intentType = - clientMsg.type === "intent" ? clientMsg.intent.type : undefined; const rateResult = this.intentRateLimiter.check( client.clientID, clientMsg.type, bytes, - intentType, ); if (rateResult === "kick") { this.log.warn(`Client rate limit exceeded, kicking`, { diff --git a/tests/server/ClientMsgRateLimiter.test.ts b/tests/server/ClientMsgRateLimiter.test.ts index 9732d3c40..31c5a6db8 100644 --- a/tests/server/ClientMsgRateLimiter.test.ts +++ b/tests/server/ClientMsgRateLimiter.test.ts @@ -28,6 +28,16 @@ describe("ClientMsgRateLimiter", () => { } expect(limiter.check(CLIENT_B, "intent", SMALL)).toBe("ok"); }); + + it("allows intents up to MAX_INTENT_SIZE", () => { + const limiter = new ClientMsgRateLimiter(); + expect(limiter.check(CLIENT_A, "intent", 2000)).toBe("ok"); + }); + + it("kicks intents exceeding MAX_INTENT_SIZE", () => { + const limiter = new ClientMsgRateLimiter(); + expect(limiter.check(CLIENT_A, "intent", 2001)).toBe("kick"); + }); }); describe("non-intent messages", () => {