From 55aac233c10f7e9756df14a83faeb12c30a521ca Mon Sep 17 00:00:00 2001 From: Yoshifumi Nakamura Date: Thu, 28 May 2026 15:24:36 +0900 Subject: [PATCH] Harden admin user email handling Signed-off-by: Yoshifumi Nakamura --- .gitignore | 4 ++ result_server/routes/admin.py | 13 +++- result_server/templates/admin_users.html | 18 +++-- result_server/tests/test_admin_hardening.py | 75 +++++++++++++++++++++ result_server/utils/admin_policy.py | 17 +++++ 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2002883..d0d3515 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ __pycache__/ .pytest_cache/ .hypothesis/ +# Local Python virtual environments +.venv/ +.venv-*/ + # BenchPark repository (external reference) benchpark/ diff --git a/result_server/routes/admin.py b/result_server/routes/admin.py index c660f18..edc6905 100644 --- a/result_server/routes/admin.py +++ b/result_server/routes/admin.py @@ -17,7 +17,7 @@ url_for, ) -from utils.admin_policy import parse_affiliations +from utils.admin_policy import is_valid_email, parse_affiliations from utils.audit_logging import audit_event from utils.rate_limit import rate_limited from utils.user_store import get_user_store @@ -121,6 +121,17 @@ def add_user(): if not email: flash("Email is required.") return redirect(url_for("admin.users")) + if not is_valid_email(email): + audit_event( + "admin_user_invite_rejected", + actor=session.get("user_email"), + target=email[:64], + result="failure", + level=logging.WARNING, + details={"reason": "invalid_email_format"}, + ) + flash("Invalid email address.") + return redirect(url_for("admin.users")) if store.user_exists(email): flash(f"User {email} is already registered. Use 'Reinvite' to reset their TOTP.") diff --git a/result_server/templates/admin_users.html b/result_server/templates/admin_users.html index 3e22a09..6a30b5a 100644 --- a/result_server/templates/admin_users.html +++ b/result_server/templates/admin_users.html @@ -128,13 +128,13 @@

Registered Users

{% if u.email != session.get('user_email') %} -
+ {% if csrf_token is defined %}{% endif %}
-
+ {% if csrf_token is defined %}{% endif %}
@@ -149,4 +149,14 @@

Registered Users

+ {% endblock %} diff --git a/result_server/tests/test_admin_hardening.py b/result_server/tests/test_admin_hardening.py index 4d2049c..669722e 100644 --- a/result_server/tests/test_admin_hardening.py +++ b/result_server/tests/test_admin_hardening.py @@ -13,6 +13,8 @@ install_portal_test_stubs() +from utils.admin_policy import is_valid_email + class _Store: def __init__(self): @@ -89,6 +91,39 @@ def _cleanup(paths): shutil.rmtree(path) +@pytest.mark.parametrize( + "email", + [ + "user@example.com", + "first.last@example.co.jp", + "u+tag@example.com", + "user_name-123@a-b.example", + ], +) +def test_is_valid_email_accepts_well_formed_addresses(email): + assert is_valid_email(email) + + +@pytest.mark.parametrize( + "email", + [ + "evil';alert(1);//@x.com", + 'user"@x.com', + "