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", () => {