fix(game): patch Winner Spoofing vulnerability with strict majority consensus#3955
fix(game): patch Winner Spoofing vulnerability with strict majority consensus#3955berkelmali wants to merge 3 commits into
Conversation
game.end() is async but was called without await inside a try/catch. The try/catch only catches synchronous errors. Added .catch() to the returned promise to prevent unhandled promise rejections from crashing the server.
WalkthroughGame termination now handles errors asynchronously via promise rejection, and winner consensus logic tightens the voting threshold to strict majority while re-evaluating winner status when clients disconnect to catch delayed consensus formations. ChangesGame Completion and Winner Consensus
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/server/GameServer.ts`:
- Around line 609-612: The winner-consensus logic incorrectly uses
potentialWinner.ips (all historical voter IPs) instead of only currently
connected voters; update checkWinnerConsensus (and the consensus computation
where potentialWinner.ips is used) to compute votes by intersecting
potentialWinner.ips with the set of currently connected client IPs (e.g.,
this.clients / this.connectedClients / this.players map) and use that active-IP
count when comparing against votesNeeded so disconnected voters no longer count
toward consensus.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7961b4f4-54d2-4e54-8cce-bfed4363b175
📒 Files selected for processing (2)
src/server/GameManager.tssrc/server/GameServer.ts
| } else { | ||
| // Check if remaining clients have reached a winner consensus | ||
| this.checkWinnerConsensus(); | ||
| } |
There was a problem hiding this comment.
Disconnect consensus must count only active voter IPs.
At Line 611, consensus is re-checked after disconnect. But at Lines 1246-1248, it uses all historical voter IPs (potentialWinner.ips.size), including disconnected voters. In 1v1, a client can vote and disconnect, and still force winner consensus.
🔧 Proposed fix
- } else {
+ } else if (!this._hasEnded) {
// Check if remaining clients have reached a winner consensus
this.checkWinnerConsensus();
}
@@
private checkWinnerConsensus() {
- if (this.winner !== null) {
+ if (this.winner !== null || this._hasEnded) {
return;
}
const activeUniqueIPs = new Set(this.activeClients.map((c) => c.ip));
+ if (activeUniqueIPs.size === 0) {
+ return;
+ }
for (const [winnerKey, potentialWinner] of this.winnerVotes.entries()) {
- if (potentialWinner.ips.size * 2 > activeUniqueIPs.size) {
+ const activeVotes = [...potentialWinner.ips].filter((ip) =>
+ activeUniqueIPs.has(ip),
+ ).length;
+ if (activeVotes * 2 > activeUniqueIPs.size) {
this.winner = potentialWinner.winner;
this.log.info(
- `Winner determined by ${potentialWinner.ips.size}/${activeUniqueIPs.size} active IPs after client disconnect`,
+ `Winner determined by ${activeVotes}/${activeUniqueIPs.size} active IPs after client disconnect`,
{
winnerKey: winnerKey,
},
);Also applies to: 1244-1256
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/server/GameServer.ts` around lines 609 - 612, The winner-consensus logic
incorrectly uses potentialWinner.ips (all historical voter IPs) instead of only
currently connected voters; update checkWinnerConsensus (and the consensus
computation where potentialWinner.ips is used) to compute votes by intersecting
potentialWinner.ips with the set of currently connected client IPs (e.g.,
this.clients / this.connectedClients / this.players map) and use that active-IP
count when comparing against votesNeeded so disconnected voters no longer count
toward consensus.
Resolves #3958
Description:
This PR fixes a critical "Winner Spoofing" vulnerability in 1v1 matches. Previously, a malicious client could send a
winnermessage to force an immediate win because the voting logic (potentialWinner.ips.size * 2 < activeUniqueIPs.size) failed to return in a 1v1 scenario (1 * 2 < 2 evaluates to false), bypassing intended consensus.The logic has been updated to require a strict majority consensus (
potentialWinner.ips.size * 2 <= activeUniqueIPs.size). In a 1v1 game, a single vote will no longer trigger an immediate win. Furthermore, to prevent legitimate wins from being stuck if a losing player rage-quits before voting, a newcheckWinnerConsensus()routine runs on client disconnects to automatically declare a win if the remaining votes constitute a strict majority.Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
barfires