← Back to blog

·

Three Poker Security Challenges: What the Source Actually Protects

How default Hold’em guards future cards, hidden hands and settlement, with post-hand privacy limits and offline tampering results.

Knowing the board early, reading an opponent’s hole cards and changing your hand to win test three different boundaries: randomness, information access and server settlement. BluffKing’s current security bounty offers RMB 10,000 for each reproduced and confirmed challenge.

This article examines the default Hold’em sealed-dealing path at source snapshot 78f381f46c8970f8478c903ed1f6e54ea8a71f0c, reviewed on October 26, 2026, and runs the repository’s offline verifier. We did not reproduce an attack against production or establish that its running binaries match this snapshot. These conclusions do not extend to every game, tournaments or advanced encrypted mode.

01 Predict community cards before reveal

Can a player know the exact community cards for a hand, including suits and ranks, before the server officially reveals them?

What does the source code do?

The separate dealer process generates a secret 32-byte server_seed using operating-system randomness, OsRng. Before dealing, it publishes a domain-separated SHA-256 commitment, keeping the seed itself secret.

Protocol-capable clients can contribute randomness by submitting commitments before revealing their seeds. The final deck seed combines the server seed, accepted client seeds in seat order and this hand’s hand_id. A client that times out may contribute nothing, so client entropy cannot be assumed for every hand.

The complete 256-bit seed initializes ChaCha20Rng, which drives a Fisher–Yates shuffle. Swap positions use rejection sampling to avoid modulo bias. Once the order is fixed, the game loop requests board cards from the dealer as play reaches each stage; client messages contain the board cards already revealed. See sources [1]–[4].

Is it possible?

With uncompromised secret seeds, a sound operating-system entropy source and secure cryptographic primitives, this review found no practical path through this implementation for an ordinary player to calculate the exact board in advance. Knowing the algorithm, hand ID, your client seed or previous outcomes still leaves a secret input missing. Equity estimates do not identify the actual suits and ranks.

A lucky guess is possible, but one correct guess does not establish access to hidden information. Evidence needs to bind a prediction to a specific hand and show that it was submitted before the official reveal.

Leaked seeds, a compromised host or a new authorization flaw could change that conclusion. The dealer holds the plaintext deck, so process isolation cannot establish that the operator can never know the cards. A seed commitment also does not independently rule out selective hand aborts.

02 Obtain other players’ hidden hole cards

Can a player or spectator obtain another player’s hole cards while they are still hidden?

What does the source code do?

The first boundary is filtering by recipient. For a hole-card event, translate_for preserves card values only for the owner; other seats receive an empty value, and spectators receive no such event. Reconnect snapshots also retain only the viewer’s own private cards. The boundary exists in the data sent by the server, rather than only in a card-back image. [5]

The second boundary is a seat-scoped dealing credential. The dealer generates a random 32-byte ticket for each seat. The game service sends the corresponding ticket only to the player actually dealt into this hand who still holds that seat. Someone joining later does not inherit the previous player’s cards merely by occupying the same seat.

The browser redeems the ticket over the dealer connection. The dealer resolves the seat from the ticket; a client cannot simply name a different seat to collect its cards. Internal card, board and settlement endpoints additionally require a token and a game-process signature. [4], [6]

Is it possible?

During a hand, this review found no path through these routes to read another player’s hidden cards merely by changing your page, inspecting your own received messages or reconnecting. A ticket is a bearer credential, however: stealing another player’s valid ticket, controlling their device or having them share their cards can expose information. Those conditions must be distinguished from an ordinary client having access to every card.

There is another boundary that must be disclosed: after the hand ends, default mode reveals seeds to the players actually dealt into that hand. With those seeds and the dealt-seat order, participants can reconstruct the entire deck, including opponents’ folded or unrevealed hole cards and the unused remainder. The fairness export also requires participation in that specific hand. Spectating or joining the table later does not grant access to its seeds. [7], [8]

If the question means reading an opponent’s hidden cards while play is ongoing, this review found no working entry point. If it includes reconstructing cards that remain face down in the interface after play ends, the default commit–reveal mechanism does allow hand participants to do that. This is a privacy boundary in the source; whether it meets bounty criteria requires an explicit time scope, rather than assuming a reward has been earned.

Default sealed dealing and advanced encrypted dealing have different trust boundaries. See poker fairness and card privacy.

03 Change your hand in server settlement

Can you make the server accept a modified hand and use it to determine the winner and settle the hand? Changing only the browser display does not qualify.

What does the source code do?

An ordinary action message submits fold, check, call, raise or all-in, together with an amount where applicable and the current decision number. It has no field allowing a player to choose their hole cards. Adding supposed replacement cards to a hostile message does not make the normal action handler replace the engine’s hand. [9]

The server also determines identity. The WebSocket handler injects sender_user_id from the authenticated connection; a same-named client field is not deserialized into that identity. Session logic checks that the sender owns the acting seat and that decision_epoch matches the current decision, preventing out-of-turn actions and stale action replay. [10], [11]

When default sealed dealing requires a showdown, the game service obtains contenders’ cards from the dealer and passes them to the engine for ranking. finish_blind() requires a completed hand and returns an error when a contested pot lacks required reveals; it does not invent cards. The service then validates the final deck commitment and checks that the board and showdown cards used in settlement match their positions in that deck. [4], [12]

Is it possible?

This review found no path through display changes or ordinary action requests that makes the server settle using forged hole cards. Achieving that would require an additional flaw that changes authoritative card state, bypasses the trusted card source or defeats settlement checks. Control over the server or host also changes the threat assumptions. This review does not prove that every endpoint and deployment configuration is free of such flaws.

What did offline record tampering show?

We ran the repository’s pf_demo_hand and pf_verify, making four modifications to the same deterministic example record: [13]

InputOffline result
Original example, checking 3 seats and 5 board cardsAccepted, exit code 0
Replace the first board card, J♦, with A♠Rejected: board mismatch
Replace seat 0’s K♣ with A♠Rejected: hole-card mismatch
Replace the server seed but retain its original commitmentRejected: commitment mismatch
Remove every hole card and board cardRejected: no comparable cards

These results show that the offline verifier detects these record changes. They did not exercise production actions or account settlement and cannot establish a live server-settlement test for challenge three. The example uses a fixed seed and does not establish production randomness quality. For checking a real hand, see how to verify a hand independently.

Source references

Paths refer to the reviewed repository. Links pin the matching published files to an immutable public-source snapshot; unlinked entries identify the server code reviewed.

  1. server/src/pf_dealing.rs — os_random_32 / seed_commit / derive_deck_seed
  2. engine/src/rng.rs — PokerRng::from_seed_bytes
  3. engine/src/deck.rs — Deck::new / rng_range
  4. server/src/sealed_dealer.rs — SealedHand / sync_tickets / deliver_holes / ActiveHand::finish
  5. server/src/protocol/translation.rs — translate_for / snapshot_for_user_with_equities_from
  6. server/src/sealed_dealer/authorization.rs — guard / sign
  7. server/src/session/fair_dealing.rs — pf_emit_reveal
  8. server/src/handlers/hands_fairness.rs — get_hand_fairness
  9. server/src/protocol/client_commands.rs — ClientCmd::Action
  10. server/src/ws/socket.rs — sender_user_id
  11. server/src/session/action_wait.rs — sender_valid / decision_epoch
  12. engine/src/game/pots.rs — finish_blind
  13. mental-poker/src/bin — pf_demo_hand / pf_verify