Harden the hotseat tally endpoint
Security review of the accumulator commit found three holes, all in the unauthenticated hotseat ping. Worst: Number(undefined) is NaN, and NaN survives Math.min/max — one malformed report would have poisoned commandsPlayed and friends permanently (NaN serializes to null). All numeric fields now pass through a NaN-proof clamp with a fallback. The dedupe ledger caps at 50k hotseat entries so spammed random ids cannot grow stats.json without bound, and each connection may deliver at most 20 reports — a real device finishes a handful of games; a firehose is abuse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
580af58424
commit
f62fcf2510
@@ -125,6 +125,7 @@ interface Session {
|
||||
overLimitStrikes: number;
|
||||
roomsCreated: number;
|
||||
lastCatchUpAt: number;
|
||||
hotseatReports: number;
|
||||
}
|
||||
|
||||
function underRateLimit(s: Session): boolean {
|
||||
@@ -177,7 +178,7 @@ wss.on("connection", (socket) => {
|
||||
const session: Session = {
|
||||
socket, playerId: null, roomId: null, token: null, claimFails: 0,
|
||||
bucket: 30, lastRefill: Date.now(), overLimitStrikes: 0,
|
||||
roomsCreated: 0, lastCatchUpAt: 0,
|
||||
roomsCreated: 0, lastCatchUpAt: 0, hotseatReports: 0,
|
||||
};
|
||||
sessions.add(session);
|
||||
send(socket, { type: "welcome", game: "wizwar" });
|
||||
@@ -298,6 +299,8 @@ wss.on("connection", (socket) => {
|
||||
break;
|
||||
}
|
||||
case "hotseatReport": {
|
||||
// A device finishes a handful of games at most; a firehose is abuse.
|
||||
if (++session.hotseatReports > 20) return;
|
||||
const id = String(msg.id ?? "").slice(0, 64);
|
||||
const stage = msg.stage === "finished" ? "finished" : msg.stage === "started" ? "started" : null;
|
||||
if (!id || !stage) return send(socket, { type: "error", message: "bad report" });
|
||||
|
||||
Reference in New Issue
Block a user