From 1835e8ef3181c3d1c51bb49edfd8431bbf5e60d1 Mon Sep 17 00:00:00 2001 From: shakti Date: Thu, 23 Jul 2026 09:49:44 +0530 Subject: [PATCH] fix: use re.fullmatch in email validator to reject trailing newlines (#39234) (#39320) Co-authored-by: Asuka Minato --- api/libs/helper.py | 7 ++++-- api/tests/unit_tests/libs/test_helper.py | 29 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/api/libs/helper.py b/api/libs/helper.py index 7066f9eab45..752342bfad6 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -221,8 +221,11 @@ def current_timestamp() -> int: def email(email): # Define a regex pattern for email addresses pattern = r"^[\w\.!#$%&'*+\-/=?^_`{|}~]+@([\w-]+\.)+[\w-]{2,}$" - # Check if the email matches the pattern - if re.match(pattern, email) is not None: + # Use re.fullmatch instead of re.match to reject trailing newlines. + # In Python, '$' matches at end-of-string OR just before a trailing newline, + # so re.match accepts "user@example.com\n". re.fullmatch requires the entire + # string to match, closing the mail header-injection vector. (#39234) + if re.fullmatch(pattern, email) is not None: return email error = f"{email} is not a valid email." diff --git a/api/tests/unit_tests/libs/test_helper.py b/api/tests/unit_tests/libs/test_helper.py index 1a93dbbca13..dbc2cd6cba8 100644 --- a/api/tests/unit_tests/libs/test_helper.py +++ b/api/tests/unit_tests/libs/test_helper.py @@ -2,7 +2,7 @@ from datetime import datetime import pytest -from libs.helper import OptionalTimestampField, escape_like_pattern, extract_tenant_id +from libs.helper import OptionalTimestampField, email, escape_like_pattern, extract_tenant_id from models.account import Account from models.model import EndUser @@ -126,3 +126,30 @@ class TestEscapeLikePattern: result = escape_like_pattern("test\\%_value") # Should be: test\\\%\_value assert result == "test\\\\\\%\\_value" + + +class TestEmailValidator: + """Tests for the email() validator — regression for #39234.""" + + def test_valid_email_accepted(self): + assert email("user@example.com") == "user@example.com" + + def test_trailing_newline_rejected(self): + with pytest.raises(ValueError, match="not a valid email"): + email("user@example.com\n") + + def test_trailing_carriage_return_newline_rejected(self): + with pytest.raises(ValueError, match="not a valid email"): + email("user@example.com\r\n") + + def test_multiple_newlines_rejected(self): + with pytest.raises(ValueError, match="not a valid email"): + email("user@example.com\n\n") + + def test_empty_string_rejected(self): + with pytest.raises(ValueError, match="not a valid email"): + email("") + + def test_invalid_email_rejected(self): + with pytest.raises(ValueError, match="not a valid email"): + email("not-an-email")