Bind game identity to UID (hijack + cap bypass) and warn on plaintext gossip
All checks were successful
CI / check (push) Successful in 4m17s

This commit is contained in:
Jean Chevronnet 2026-07-19 14:15:06 +00:00
parent e23240bf52
commit 4599c4834d
No known key found for this signature in database
2 changed files with 10 additions and 3 deletions

View file

@ -107,10 +107,12 @@ impl GameServ {
Self { uid, games: HashMap::new(), nextid: 1 }
}
// A player's ranked identity: their account if registered, else their nick
// (guests play casually under their nick; ranked points need an account).
// A player's game identity: their account if registered, else their connection
// UID (not their nick, which is mutable — keying on it lets a nick-reuser hijack
// an abandoned guest game, and lets a nick-cycler evade the per-user game cap).
// `nick_a`/`nick_b` hold the display name separately.
fn ident<'a>(from: &'a Sender) -> &'a str {
from.account.unwrap_or(from.nick)
from.account.unwrap_or(from.uid)
}
// The side an account plays in a game (A = challenger), or None if not a party.

View file

@ -82,6 +82,11 @@ pub async fn run(engine: Shared, cfg: Gossip, peers: Vec<Peer>, origin: String,
};
let acceptor = tls.as_ref().map(|(a, _)| a.clone());
let connector = tls.as_ref().map(|(_, c)| c.clone());
if cfg.tls.is_none() {
// The handshake exchanges the shared secret before the peer is authenticated,
// so without TLS a passive eavesdropper on the link can learn it. Encrypt it.
tracing::warn!("gossip has no [gossip.tls] — the shared secret crosses the wire in plaintext; enable TLS for any non-loopback peer");
}
if let Some(bind) = cfg.bind.clone() {
tokio::spawn(listen(bind, engine.clone(), cfg.secret.clone(), origin.clone(), outbound.clone(), acceptor));
}