diff --git a/Game/scripts/match_net.gd b/Game/scripts/match_net.gd index 97d40643..d2c81521 100644 --- a/Game/scripts/match_net.gd +++ b/Game/scripts/match_net.gd @@ -115,7 +115,7 @@ func configure_join_authorisations(tokens: Array, context: Dictionary, signing_k if not token is String or String(token).is_empty(): return false allowed[String(token)] = true - if allowed.is_empty() or String(context.get("match_id", "")).is_empty() or String(context.get("server_id", "")).is_empty() or int(context.get("protocol_version", 0)) < 1: + if allowed.is_empty() or not context.has("match_id") or not context["match_id"] is String or String(context["match_id"]).is_empty() or not context.has("server_id") or not context["server_id"] is String or String(context["server_id"]).is_empty() or not context.has("protocol_version") or not _valid_integer_claim(context["protocol_version"]) or int(context["protocol_version"]) < 1: return false _allowed_join_authorisations = allowed _join_authorisation_context = context.duplicate(true) diff --git a/Game/tests/cases/test_match_net.gd b/Game/tests/cases/test_match_net.gd index 901ecf1f..ceb6bdb9 100644 --- a/Game/tests/cases/test_match_net.gd +++ b/Game/tests/cases/test_match_net.gd @@ -96,6 +96,10 @@ func test_allocated_join_authorisation_is_allowlisted_and_bound_to_server() -> v match_net._remove_player(43) match_net._join_history[token]["lost_at"] = Time.get_unix_time_from_system() - MatchNet.RECONNECT_GRACE_SECONDS - 1.0 assert_eq(match_net._reserve_join_authorisation(token, 44), -1, "reclaim after the grace window is fenced") + var malformed_context := {"match_id": 123, "server_id": "server-1", "protocol": "1", "protocol_version": 1} + assert_true(not match_net.configure_join_authorisations([token], malformed_context), "numeric context identity is rejected") + malformed_context = {"match_id": "match-1", "server_id": "server-1", "protocol": "1", "protocol_version": 1.5} + assert_true(not match_net.configure_join_authorisations([token], malformed_context), "fractional context protocol is rejected") func test_allocated_join_authorisation_rejects_inconsistent_team_and_slot() -> void: diff --git a/multiplayer-next.md b/multiplayer-next.md index 0aae1daf..56d99381 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -1605,6 +1605,8 @@ Control-plane REST responses now fail closed on malformed ticket, proposal, or s Session establishment now also requires a present, syntactically valid, future `expires_at`, preventing malformed authentication responses from creating an unbounded client session. +MatchNet admission configuration now requires exact string opaque match/server IDs and a finite integral protocol version, preventing malformed server context from being coerced into a valid roster binding. + Signed MatchNet claims now also require exact JSON string/integer types for every identity, protocol, expiry, slot, team, and generation field; string-number coercion is rejected before canonical signature verification. Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.