Skip to content

fix: Use modern email validator to support new gTLDs (.rocks, .radio, .app)#27136

Open
cyphercodes wants to merge 1 commit intoTryGhost:mainfrom
cyphercodes:fix-email-validation-gtld
Open

fix: Use modern email validator to support new gTLDs (.rocks, .radio, .app)#27136
cyphercodes wants to merge 1 commit intoTryGhost:mainfrom
cyphercodes:fix-email-validation-gtld

Conversation

@cyphercodes
Copy link
Copy Markdown

@cyphercodes cyphercodes commented Apr 5, 2026

Summary

Fixed email validation to support modern generic Top-Level Domains (gTLDs) like .rocks, .radio, .app, etc.

Problem

Ghost's email validation was rejecting legitimate email addresses using newer gTLDs approved by ICANN after 2012. The legacy validator (validator@7.2.0 from 2017) predates many of these TLDs.

Solution

Changed all validator.isEmail() calls to use {legacy: false} option, which enables the modern custom validator that properly handles all TLDs.

Files Changed

  • email-address-service-wrapper.js
  • automated_emails.js
  • invitations.js
  • password_reset.js
  • settings.js

Testing

  • Verified syntax of all changed files
  • The modern validator is already used in member-repository.js and sending-service.js

Fixes #26197


Note

Medium Risk
Touches email validation in user-management and auth-adjacent flows (invitations, password reset, settings), which could change what addresses are accepted/rejected. Scope is small and limited to validator options, but impacts multiple entry points.

Overview
Switches multiple API input validators to call validator.isEmail(..., {legacy: false}) instead of the legacy mode, aligning validation behavior across endpoints.

This updates email checks for automated email test sends, staff invitations, password reset token generation, settings email fields (e.g. members_support_address), and the email-address service wrapper so newer valid domains (newer gTLDs) are accepted consistently.

Reviewed by Cursor Bugbot for commit 0076c66. Bugbot is set up for automated code reviews on this repo. Configure here.

… .app)

Changed all validator.isEmail() calls to use {legacy: false} option,
which enables support for modern generic Top-Level Domains like
.rocks, .radio, .app that were approved by ICANN after 2012.

The legacy validator (validator@7.2.0 from 2017) predates many gTLDs
and rejects valid email addresses using these domains.

Files changed:
- email-address-service-wrapper.js
- automated_emails.js
- invitations.js
- password_reset.js
- settings.js

Fixes TryGhost#26197
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

Walkthrough

This pull request updates email validation logic across five files in the Ghost codebase. Each file's validator.isEmail(...) calls have been modified to explicitly pass {legacy: false} as an option, changing email validation behavior from the previous default approach. The affected files are the automated emails validator, invitations validator, password reset validator, settings validator, and the email address service wrapper. No changes were made to exported entity signatures, control flow logic, or error handling mechanisms.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and specifically describes the main change: switching to modern email validator to support new gTLDs, directly matching the changeset's purpose.
Description check ✅ Passed Description is directly related to the changeset, explaining the problem, solution, files changed, and testing approach for the email validator modernization.
Linked Issues check ✅ Passed All code changes directly implement the linked issue #26197 requirement to switch from legacy to modern email validator by passing {legacy: false} to validator.isEmail() calls across all identified files.
Out of Scope Changes check ✅ Passed All changes are in-scope, modifying only the validator option in five files to enable modern email validation, with no extraneous modifications or unrelated alterations present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js`:
- Line 76: Update the two member-facing validators to use the modern email rules
by adding the options object {legacy: false} to the validator.isEmail calls:
locate the email validation in magic-link.js (the function that validates the
magic-link/email input) and the email check in router-controller.js (the
controller handling member signup/signin) and change their
validator.isEmail(email) calls to validator.isEmail(email, {legacy: false}),
keeping any surrounding typeof/email presence checks intact so behavior matches
the other validators (e.g., automated_emails.js).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4b81fdd4-def0-4bc1-b71a-eba7fd44667b

📥 Commits

Reviewing files that changed from the base of the PR and between edabb63 and 0076c66.

📒 Files selected for processing (5)
  • ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js
  • ghost/core/core/server/api/endpoints/utils/validators/input/invitations.js
  • ghost/core/core/server/api/endpoints/utils/validators/input/password_reset.js
  • ghost/core/core/server/api/endpoints/utils/validators/input/settings.js
  • ghost/core/core/server/services/email-address/email-address-service-wrapper.js

const lexical = frame.data.lexical;

if (typeof email !== 'string' || !validator.isEmail(email)) {
if (typeof email !== 'string' || !validator.isEmail(email, {legacy: false})) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify member-facing email validation callsites still using default isEmail(...)
rg -nP --type=js -C2 '\bisEmail\s*\(\s*[^,()]+\s*\)' \
  ghost/core/core/server/services/members/members-api/controllers/router-controller.js \
  ghost/core/core/server/services/lib/magic-link/magic-link.js

# Compare with explicit modern validation usage added in this PR area
rg -nP --type=js -C2 '\bisEmail\s*\(\s*[^,()]+\s*,\s*\{\s*legacy\s*:\s*false\s*\}\s*\)' \
  ghost/core/core/server/api/endpoints/utils/validators/input \
  ghost/core/core/server/services/email-address/email-address-service-wrapper.js

Repository: TryGhost/Ghost

Length of output: 4390


Member authentication flows still use legacy email validation.

Two member-facing entry points (magic-link.js line 75 and router-controller.js line 706) still call isEmail(...) without { legacy: false }, while the current file and other API validators use the modern validation. This allows modern gTLDs to slip through validation in signup and signin flows, creating an inconsistent email validation scope.

These files need to adopt the same { legacy: false } parameter:

  • ghost/core/core/server/services/lib/magic-link/magic-link.js
  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js`
at line 76, Update the two member-facing validators to use the modern email
rules by adding the options object {legacy: false} to the validator.isEmail
calls: locate the email validation in magic-link.js (the function that validates
the magic-link/email input) and the email check in router-controller.js (the
controller handling member signup/signin) and change their
validator.isEmail(email) calls to validator.isEmail(email, {legacy: false}),
keeping any surrounding typeof/email presence checks intact so behavior matches
the other validators (e.g., automated_emails.js).

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0076c66. Configure here.

const email = frame.data.email;

if (typeof email !== 'string' || !validator.isEmail(email)) {
if (typeof email !== 'string' || !validator.isEmail(email, {legacy: false})) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete fix misses member sign-in email validation paths

Medium Severity

The PR updates five files to use {legacy: false} for new gTLD support but misses router-controller.js and magic-link.js, which also call isEmail() without this option in the member sign-in flow. Since member-repository.js already allows creating members with new gTLD emails, those members can be created but then blocked from signing in via magic link, as router-controller.js:706 and magic-link.js:75 still reject their emails using the legacy validator.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0076c66. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Email validation rejects valid modern gTLDs (.rocks, .radio, .app) due to legacy validator default

1 participant