From b84634a89e3e17d4cab73143f341601009d0099f Mon Sep 17 00:00:00 2001 From: Pitchfork-and-Torch <297513015+Pitchfork-and-Torch@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:25:31 +0000 Subject: [PATCH] Reject bool for DNS timeout bool subclasses int, so timeout=True previously set resolver.lifetime to True and silently became a 1-second DNS timeout. Reject bool explicitly for timeout. --- email_validator/deliverability.py | 8 ++++++++ email_validator/validate_email.py | 3 +++ tests/test_deliverability.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/email_validator/deliverability.py b/email_validator/deliverability.py index 3cddc77..0b122d7 100644 --- a/email_validator/deliverability.py +++ b/email_validator/deliverability.py @@ -8,7 +8,14 @@ import dns.exception +def _reject_bool_timeout(timeout: Optional[int]) -> None: + # bool subclasses int; timeout=True would silently become lifetime 1s + if isinstance(timeout, bool): + raise TypeError("timeout must be an int or float, not bool") + + def caching_resolver(*, timeout: Optional[int] = None, cache: Any = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> dns.resolver.Resolver: + _reject_bool_timeout(timeout) if timeout is None: from . import DEFAULT_TIMEOUT timeout = DEFAULT_TIMEOUT @@ -34,6 +41,7 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option # If no dns.resolver.Resolver was given, get dnspython's default resolver. # Override the default resolver's timeout. This may affect other uses of # dnspython in this process. + _reject_bool_timeout(timeout) if dns_resolver is None: from . import DEFAULT_TIMEOUT if timeout is None: diff --git a/email_validator/validate_email.py b/email_validator/validate_email.py index 07b398a..9cc46c7 100644 --- a/email_validator/validate_email.py +++ b/email_validator/validate_email.py @@ -58,6 +58,9 @@ def validate_email( globally_deliverable = GLOBALLY_DELIVERABLE if timeout is None and dns_resolver is None: timeout = DEFAULT_TIMEOUT + # bool subclasses int; timeout=True would silently become DNS lifetime 1s + if isinstance(timeout, bool): + raise TypeError("timeout must be an int or float, not bool") if isinstance(email, str): pass diff --git a/tests/test_deliverability.py b/tests/test_deliverability.py index e1307c2..9505cb4 100644 --- a/tests/test_deliverability.py +++ b/tests/test_deliverability.py @@ -90,3 +90,18 @@ def put(self, key: Any, value: Any) -> Any: validate_email("test@gmail.com", dns_resolver=resolver) assert len(cache.cache) == 1 + + +def test_timeout_rejects_bool() -> None: + """bool subclasses int; timeout=True must not silently become lifetime 1s.""" + import pytest + from email_validator.deliverability import caching_resolver, validate_email_deliverability + + for value in (True, False): + with pytest.raises(TypeError, match="timeout must be an int or float, not bool"): + caching_resolver(timeout=value) + with pytest.raises(TypeError, match="timeout must be an int or float, not bool"): + validate_email_deliverability("example.com", "example.com", timeout=value) + # valid int still accepted on caching_resolver + resolver = caching_resolver(timeout=5) + assert resolver.lifetime == 5