From 4d8638e62faf59062b91987e0e5ce92944bc7224 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:30:36 +0100 Subject: [PATCH] fix(multiplayer): harden join expiry validation --- Game/scripts/match_net.gd | 3 +++ Game/tests/cases/test_match_net.gd | 4 ++++ multiplayer-next.md | 2 ++ 3 files changed, 9 insertions(+) diff --git a/Game/scripts/match_net.gd b/Game/scripts/match_net.gd index f55d3d33..a2adbde6 100644 --- a/Game/scripts/match_net.gd +++ b/Game/scripts/match_net.gd @@ -11,6 +11,7 @@ extends Node const NetCodec = preload("res://scripts/net_codec.gd") const SimConstants = preload("res://scripts/sim_constants.gd") +const AssignmentState = preload("res://scripts/assignment_state.gd") signal player_joined(peer_id: int, player_name: String) signal player_left(peer_id: int) @@ -342,6 +343,8 @@ func _valid_join_authorisation(token: String) -> bool: return false var protocol := str(claims.get("Protocol", "")) var expires_at := str(claims.get("ExpiresAt", "")) + if not AssignmentState.is_valid_expiry_timestamp(expires_at): + return false var expiry := Time.get_unix_time_from_datetime_string(expires_at) if not _join_signing_key.is_empty(): var signature_token := str(envelope["Signature"]) diff --git a/Game/tests/cases/test_match_net.gd b/Game/tests/cases/test_match_net.gd index c646f46a..6f042bc3 100644 --- a/Game/tests/cases/test_match_net.gd +++ b/Game/tests/cases/test_match_net.gd @@ -74,6 +74,10 @@ func test_allocated_join_authorisation_is_allowlisted_and_bound_to_server() -> v assert_eq(assigned[0]["team"], 1, "assigned roster preserves team") assert_eq(assigned[0]["slot"], 5, "assigned roster preserves slot") assert_true(match_net._valid_join_authorisation(token), "allowlisted matching token is accepted") + var malformed_claims := claims.duplicate() + malformed_claims["ExpiresAt"] = "tomorrow" + var malformed_token := Marshalls.raw_to_base64(JSON.stringify({"Authorisation": malformed_claims, "Signature": "trusted-signature"}).to_utf8_buffer()) + assert_true(not match_net._valid_join_authorisation(malformed_token), "malformed expiry claim is rejected before admission") assert_true(not match_net._valid_join_authorisation(token + "tampered"), "token mutation is rejected") var wrong_claims := claims.duplicate() wrong_claims["ServerID"] = "other-server" diff --git a/multiplayer-next.md b/multiplayer-next.md index ab32d037..3ca27982 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -1575,4 +1575,6 @@ Reconfiguring the client with new credentials now clears the prior session expir Assignment expiry validation now fails closed on malformed non-empty timestamps before invoking the date parser, and fresh-assignment checks share the same format boundary. This prevents malformed assignment manifests from reaching transport startup. +MatchNet join-authorisation admission now applies the same expiry format guard before parsing signed roster claims, closing the malformed-expiry gap at the transport handshake boundary. + 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.