java-topology/defects/spring-rts-0002/test/test_password_logging.py
russell@unturf.com d3746b98c9 spring-rts: 2 defects (CWE-407 + CWE-312), MOAD 0002-0005 CLEAN
spring-rts-0001: CWeapon::HasIncomingProjectile std::find on vector O(I)
  called from InterceptHandler::Update() O(W*P) nested loop = O(W*P*I).
  Fix: std::unordered_set<int> for O(1) lookup. 3x measured at W=10 P=200 I=100.

spring-rts-0002: GameServer logs passwords verbatim (CWE-312).
  Two LOG() calls in adduser command handler emit pwd.c_str() to log output.
  Fix: remove password values from log format strings.

MOAD-0002 (intertangle): pervasive global state (gs, gu, handlers) but
  architectural, not patchable per-defect.
MOAD-0003 (leaked context): thread_local in Threading.cpp is infrastructure,
  not request-scoped identity. CLEAN.
MOAD-0004: spring-rts-0002 covers this.
MOAD-0005 (thundering herd): simulation is single-threaded for determinism.
  No unsynchronized cache patterns. CLEAN.
2026-03-31 12:43:28 -04:00

74 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Unit test for spring-rts-0002 (CWE-312): GameServer logs passwords verbatim.
Verifies that our patch removes password values from LOG() format strings
while preserving other diagnostic information (client name, team, role).
"""
import re
import sys
import os
PATCH_PATH = os.path.join(os.path.dirname(__file__), "..", "patch", "spring-rts-0002.patch")
def test_patch_removes_password_from_log():
"""Verify patch removes password from LOG() calls."""
with open(PATCH_PATH) as f:
patch = f.read()
# Lines removed (old code) should contain password in LOG
removed_lines = [l for l in patch.splitlines() if l.startswith("-") and not l.startswith("---")]
added_lines = [l for l in patch.splitlines() if l.startswith("+") and not l.startswith("+++")]
# Old code logs pwd.c_str()
old_has_pwd = any("pwd.c_str()" in l for l in removed_lines)
assert old_has_pwd, "FAIL: expected old code to log pwd.c_str()"
# New code must NOT log pwd.c_str()
new_has_pwd = any("pwd.c_str()" in l for l in added_lines)
assert not new_has_pwd, "FAIL: patched code still logs pwd.c_str()"
# New code still logs name
new_has_name = any("name.c_str()" in l for l in added_lines)
assert new_has_name, "FAIL: patched code lost client name in log"
print("PASS password_removed_from_log")
def test_patch_preserves_team_info():
"""Verify patch still logs team assignment info."""
with open(PATCH_PATH) as f:
patch = f.read()
added_lines = [l for l in patch.splitlines() if l.startswith("+") and not l.startswith("+++")]
# Second LOG should still contain team %d
has_team = any("team" in l.lower() for l in added_lines)
assert has_team, "FAIL: patched code lost team info in log"
print("PASS team_info_preserved")
def test_patch_format_string_consistent():
"""Verify format string argument counts match."""
with open(PATCH_PATH) as f:
patch = f.read()
added_lines = " ".join(l[1:] for l in patch.splitlines() if l.startswith("+") and not l.startswith("+++"))
# Count %s and %d in added format strings
# First LOG: changed password for client "%s" = 2 args (__func__, name)
# Second LOG: added %s "%s" to team %d = 4 args (__func__, spectator/player, name, team)
# Just verify no pwd.c_str() appears
assert "pwd" not in added_lines, "FAIL: pwd still referenced in patched code"
print("PASS format_string_consistent")
if __name__ == "__main__":
test_patch_removes_password_from_log()
test_patch_preserves_team_info()
test_patch_format_string_consistent()
print("ALL TESTS PASSED")