From 61daf139c5f2ae55e5e52c47e2ec78e2c3e068a3 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Thu, 3 Sep 2026 21:31:09 +0100 Subject: [PATCH] fix(multiplayer): fence workload control URLs --- Game/scripts/connection_lease_client.gd | 2 +- Game/scripts/server_result_client.gd | 2 +- Game/tests/cases/test_connection_lease_client.gd | 1 + Game/tests/cases/test_server_result_client.gd | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Game/scripts/connection_lease_client.gd b/Game/scripts/connection_lease_client.gd index 28fe617d..4484f759 100644 --- a/Game/scripts/connection_lease_client.gd +++ b/Game/scripts/connection_lease_client.gd @@ -146,7 +146,7 @@ static func event_key(match_id: String, player_id: String, operation: String, ge static func valid_configuration(base_url: String, workload_token: String, match_id: String, server_id: String) -> bool: - if not (base_url.begins_with("http://") or base_url.begins_with("https://")) or base_url.contains("\n") or base_url.contains("\r") or base_url.contains("?") or base_url.contains("#"): + if not (base_url.begins_with("http://") or base_url.begins_with("https://")) or base_url.contains("\n") or base_url.contains("\r") or base_url.contains("?") or base_url.contains("#") or base_url.contains("@"): return false if workload_token.is_empty() or workload_token.contains("\n") or workload_token.contains("\r"): return false diff --git a/Game/scripts/server_result_client.gd b/Game/scripts/server_result_client.gd index d266a769..8bbbd99d 100644 --- a/Game/scripts/server_result_client.gd +++ b/Game/scripts/server_result_client.gd @@ -84,7 +84,7 @@ static func response_is_accepted(http_code: int) -> bool: static func valid_configuration(base_url: String, workload_token: String, match_id: String, server_id: String) -> bool: - if not (base_url.begins_with("http://") or base_url.begins_with("https://")) or base_url.contains("\n") or base_url.contains("\r") or base_url.contains("?") or base_url.contains("#"): + if not (base_url.begins_with("http://") or base_url.begins_with("https://")) or base_url.contains("\n") or base_url.contains("\r") or base_url.contains("?") or base_url.contains("#") or base_url.contains("@"): return false if workload_token.is_empty() or workload_token.contains("\n") or workload_token.contains("\r"): return false diff --git a/Game/tests/cases/test_connection_lease_client.gd b/Game/tests/cases/test_connection_lease_client.gd index d114a781..48da6d5e 100644 --- a/Game/tests/cases/test_connection_lease_client.gd +++ b/Game/tests/cases/test_connection_lease_client.gd @@ -19,6 +19,7 @@ func test_connection_lease_response_classification_is_fail_closed() -> void: func test_connection_lease_configuration_and_keys_are_bound() -> void: assert_true(LeaseClient.valid_configuration("https://control.invalid", "workload-token", "match-1234567890", "server-123456789"), "valid workload configuration is accepted") assert_true(not LeaseClient.valid_configuration("https://control.invalid?token=leak", "workload-token", "match-1234567890", "server-123456789"), "query-bearing endpoint is rejected") + assert_true(not LeaseClient.valid_configuration("https://control@evil.invalid", "workload-token", "match-1234567890", "server-123456789"), "userinfo-bearing endpoint is rejected") assert_true(not LeaseClient.valid_configuration("https://control.invalid", "bad\ntoken", "match-1234567890", "server-123456789"), "header injection is rejected") var initial := LeaseClient.event_key("match-123456789", "player-12345678", "connect", 0) assert_true(initial != LeaseClient.event_key("match-123456789", "player-12345678", "disconnect", 1), "operation and generation bind the key") diff --git a/Game/tests/cases/test_server_result_client.gd b/Game/tests/cases/test_server_result_client.gd index b7c58cfa..c789e2ea 100644 --- a/Game/tests/cases/test_server_result_client.gd +++ b/Game/tests/cases/test_server_result_client.gd @@ -13,6 +13,7 @@ func test_result_nonce_is_deterministic_and_score_bound() -> void: func test_result_configuration_fails_closed() -> void: assert_true(Client.valid_configuration("https://control.invalid", "token", "match-123456789", "server-123456789"), "valid result reporter configuration is accepted") assert_true(not Client.valid_configuration("https://control.invalid?token=leak", "token", "match-123456789", "server-123456789"), "query-bearing endpoint is rejected") + assert_true(not Client.valid_configuration("https://control@evil.invalid", "token", "match-123456789", "server-123456789"), "userinfo-bearing endpoint is rejected") assert_true(not Client.valid_configuration("https://control.invalid", "", "match-123456789", "server-123456789"), "empty bearer is rejected")