From ab70f2bc67bacd151b5c369fc2192aa1a3fa4306 Mon Sep 17 00:00:00 2001 From: Boyuan Gao Date: Fri, 11 Sep 2026 15:46:40 -0700 Subject: [PATCH] fix(auth): reject email domains with error website responses --- service/email_domain_dns.go | 13 +++++++++---- service/email_domain_dns_test.go | 9 +++++++++ service/registration_security.go | 2 +- service/registration_security_test.go | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/service/email_domain_dns.go b/service/email_domain_dns.go index 92c03371d..8f788c54b 100644 --- a/service/email_domain_dns.go +++ b/service/email_domain_dns.go @@ -19,7 +19,7 @@ type emailDomainDNSCheck struct { MXHost string // first MX host, lowercase, trailing dot trimmed ARecord bool // at least one A/AAAA record resolves PrivateARecord bool // resolved A points to loopback/private/link-local (parked placeholder) - WebsiteReachable bool // an HTTP(S) server answered on port 80/443 + WebsiteReachable bool // an HTTP(S) server returned a non-error status on port 80/443 MajorProviderMX bool // MX host belongs to a mainstream mail provider (gmail/outlook/qq/...) DisposableMX bool // MX host matches known disposable/temp-mail infrastructure } @@ -265,8 +265,13 @@ func safeEmailDomainDialContext(ctx context.Context, network, addr string) (net. } // emailDomainWebsiteReachable returns true if an HTTP(S) server answers on the -// domain (any status code counts — a real site exists). TLS failure falls back -// to plain HTTP. Connections are restricted to public IPs (SSRF hardening). +// domain with a non-error (2xx/3xx) status. 4xx/5xx responses mean the root +// domain is unavailable for registration. TLS failure falls back to plain HTTP. +// Connections are restricted to public IPs (SSRF hardening). +func emailDomainWebsiteStatusOK(statusCode int) bool { + return statusCode >= http.StatusOK && statusCode < http.StatusBadRequest +} + func emailDomainWebsiteReachable(domain string) bool { for _, scheme := range []string{"https", "http"} { ctx, cancel := context.WithTimeout(context.Background(), 2500*time.Millisecond) @@ -280,7 +285,7 @@ func emailDomainWebsiteReachable(domain string) bool { cancel() if err == nil { resp.Body.Close() - return true + return emailDomainWebsiteStatusOK(resp.StatusCode) } } return false diff --git a/service/email_domain_dns_test.go b/service/email_domain_dns_test.go index 7fef100e1..858eff600 100644 --- a/service/email_domain_dns_test.go +++ b/service/email_domain_dns_test.go @@ -56,3 +56,12 @@ func TestSafeEmailDomainDialContextRefusesPrivateTargets(t *testing.T) { _, err = safeEmailDomainDialContext(context.Background(), "tcp", "169.254.169.254:80") require.Error(t, err) } + +func TestEmailDomainWebsiteStatusRequiresSuccessfulResponse(t *testing.T) { + for _, status := range []int{200, 204, 301, 302, 399} { + require.True(t, emailDomainWebsiteStatusOK(status), status) + } + for _, status := range []int{400, 401, 403, 404, 500, 502, 503} { + require.False(t, emailDomainWebsiteStatusOK(status), status) + } +} diff --git a/service/registration_security.go b/service/registration_security.go index 87c8689dc..e1ca8c530 100644 --- a/service/registration_security.go +++ b/service/registration_security.go @@ -51,7 +51,7 @@ func EvaluateRegistrationEmail(email string, cfg system_setting.RegistrationSecu if cfg.RejectEmailDomainWithoutMX && !dnsCheck.MXRecord { return RegistrationEmailDecision{}, ErrRegistrationDomainUnavailable } - if cfg.RejectEmailDomainWithoutWebsite && !dnsCheck.WebsiteReachable && !dnsCheck.MajorProviderMX { + if cfg.RejectEmailDomainWithoutWebsite && !dnsCheck.WebsiteReachable { return RegistrationEmailDecision{}, ErrRegistrationDomainUnavailable } } diff --git a/service/registration_security_test.go b/service/registration_security_test.go index c9cb75695..7f54b009a 100644 --- a/service/registration_security_test.go +++ b/service/registration_security_test.go @@ -155,11 +155,11 @@ func TestRegistrationSecurityDNSMissingWebsitePolicy(t *testing.T) { _, err = EvaluateRegistrationEmail("user@withweb.example", cfg, nil) require.NoError(t, err) - // major-provider MX exempts from the website requirement (email-only domain) + // A major-provider MX does not exempt a domain whose root website is unavailable. restore = stubDNSChecker(emailDomainDNSCheck{MXRecord: true, MXHost: "alt1.gmail-smtp-in.l.google.com", MajorProviderMX: true}) defer restore() _, err = EvaluateRegistrationEmail("user@corp.example", cfg, nil) - require.NoError(t, err) + require.ErrorIs(t, err, ErrRegistrationDomainUnavailable) } func TestRegistrationSecurityDNSDisabledByDefaultInPolicy(t *testing.T) {