The Fundamental Defence
The most important security property of the system is also the simplest: the client sends intent, not outcomes. When a player taps three cards, the client sends their IDs to the server viaIMultiplayerService.SendClaim. It does not send “these cards form a valid Set.” It does not pre-validate and skip sending invalid claims. The server runs SetValidator on every claim independently of what the client thinks. The server’s result is the result.
A modified client that injects a “valid” claim message for three cards that do not form a Set will receive the same “invalid” response from the server as an honest client would. There is nothing the client can send to change the server’s validation outcome.
What the Client Can and Cannot Do
Rate Limiting
Sending hundreds of claim messages per second does not help an attacker — it actively hurts them. The Nakama match handler applies a per-player, per-tick rate limit on incoming claim messages. The processing model:- The server collects all client messages since the last tick.
- For each player, it processes at most one claim per tick (50 ms window).
- Excess claims are discarded — they do not queue up for future ticks.
match_claim messages will have all but one processed per tick, and that one will be validated on its merits. The flood provides zero advantage and wastes bandwidth.
The rate limit is configured in the Nakama match handler logic. When implementing the handler, ensure the rate-limit check runs before claim validation, so attackers cannot even reach the validator with excess messages.
Simultaneous Claim Fairness
Simultaneous claims are resolved entirely by server-received timestamp. The only way for a player to win a contested claim is to send the message earlier — which means tapping faster. No client-side manipulation can backfill a timestamp on the server. The only edge case — two messages with identical server-received timestamps — is resolved by lower session ID, a deterministic tiebreaker that is documented, tested, and consistent.The OnlineMatchController as a Security Boundary
TheOnlineMatchController enforces a clean separation between the network and the game session, which also serves as a security boundary. GameSession never directly holds a reference to IMultiplayerService, which means:
- Game logic code cannot inadvertently call
SendClaimwith a pre-validated result. - There is no code path where the session can inspect server messages before the controller translates them — raw
ServerMessagetypes never appear in gameplay code. - A developer cannot shortcut the flow by having
GameSessionwrite state outside ofApplyServerState, because the session’s multiplayer interface only exposesApplyServerStateandSendClaim.
Client-Side Save Integrity
Local save data (personal stats, settings, unlocked cosmetics for offline modes) is protected by an HMAC signature with an obfuscated key. On load,LocalSaveService verifies the signature. If tampering is detected:
- The corrupted data is discarded.
- Stats are reset to defaults.
- Competitive data (scores, MMR, leaderboard positions) is unaffected — it is owned by the server, not the local save file.
IAP Receipt Validation
Cosmetic items purchased via Google Play are not granted by the client. The flow is: The client never decides ownership. Even if a client modifies its local save to mark a cosmetic as owned, the server-side Nakama Storage record is authoritative. The cosmetic will not appear on other players’ devices or persist across reinstalls.Server-Side Data Ownership Table
What Is Not In Scope for v1.0
The following anti-cheat features are explicitly out of scope for the initial release. Do not implement them:- Replay system for dispute resolution. Disputes are handled by the server’s authoritative log. A full replay viewer is a post-launch roadmap item.
- Real-time anti-cheat scanning (e.g., memory scanning, root detection). Structural authority is the primary defence; scanning adds complexity without changing the attack surface meaningfully for this game type.
- Spectator mode for moderation. Spectator mode itself is out of scope for v1.0 (per Hard Boundaries §3.1).
Hard Boundary Reminder
Server-authoritative for all multiplayer is a Hard Boundary. Any change that introduces client-side Set validation in online modes — even temporarily, even “just for testing” — requires a formal change request reviewed by the Product Owner and Tech Lead before implementation. This is not a preference; it is a project constraint documented in the Hard Boundaries specification.
Implementation Checklist
-
IOnlineGameSessionhas noValidateSetmethod — onlySendClaim. Verify this before writing any multiplayer game flow code. -
OnlineMatchControlleris the only code that callsIMultiplayerService.SendClaimand subscribes toIMultiplayerService.Messages—GameSessionnever holds a reference toIMultiplayerService - Rate limiting is configured in the Nakama match handler before the claim validation path
-
LocalSaveServicecomputes and verifies the HMAC signature on every load and save - IAP receipt validation runs server-side via Google Play’s verification API before any Nakama Storage grant
- Deterministic tiebreaker (lower session ID) for exact-millisecond simultaneous claims is implemented and covered by a server-side unit test
- Client-side cosmetic unlock UI reads from server-confirmed Nakama Storage, not from the local save file
- Leaderboard submissions go through the Nakama leaderboard module API, not a direct client write
Common Mistakes
Related Pages
Authority Model
The IMultiplayerService contract, the OnlineMatchController pattern, and the IOnlineGameSession interface design.
Match Lifecycle
The full match flow including simultaneous claim resolution and post-match MMR updates.
Sync & Reconnect
Full-state broadcasts, the client mirror model, and the 30-second reconnect window.
Architecture Layers
How the Infrastructure assembly boundary prevents Nakama SDK types from leaking into gameplay code.