From 4599c4834d6e0fa7e7197007164ad8476c98632b Mon Sep 17 00:00:00 2001 From: Jean Date: Sun, 19 Jul 2026 14:15:06 +0000 Subject: [PATCH] Bind game identity to UID (hijack + cap bypass) and warn on plaintext gossip --- modules/gameserv/src/lib.rs | 8 +++++--- src/gossip.rs | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/gameserv/src/lib.rs b/modules/gameserv/src/lib.rs index 479e576..ef10405 100644 --- a/modules/gameserv/src/lib.rs +++ b/modules/gameserv/src/lib.rs @@ -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. diff --git a/src/gossip.rs b/src/gossip.rs index 93ab75a..099953e 100644 --- a/src/gossip.rs +++ b/src/gossip.rs @@ -82,6 +82,11 @@ pub async fn run(engine: Shared, cfg: Gossip, peers: Vec, 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)); }