Skip to content

fix(auth): reject non-ASCII email local parts at validation#20267

Open
vbudhram wants to merge 1 commit intomainfrom
fxa-12710
Open

fix(auth): reject non-ASCII email local parts at validation#20267
vbudhram wants to merge 1 commit intomainfrom
fxa-12710

Conversation

@vbudhram
Copy link
Copy Markdown
Contributor

@vbudhram vbudhram commented Mar 26, 2026

Because

  • Sentry error FXA-AUTH-2GE: accounts with non-ASCII local parts (e.g. `andrée@restmail.net) can never receive email because AWS SES does not support SMTPUTF8
  • punycode.toASCII() was silently encoding non-ASCII local parts (e.g. andréexn--andre-9ua), masking the problem at validation time

This pull request

  • Removes punycode.toASCII() from the local part check in validators.js so EMAIL_USER rejects non-ASCII characters directly
  • Updates existing test expectations in validators.spec.ts for unicode local parts (Δ٢, 🦀🧙) from true to false
  • Unicode domains are unaffected (punycode encoding is still used for the domain part)

Issue that this pull request solves

Closes: https://mozilla-hub.atlassian.net/browse/FXA-12710

Checklist

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

Notes

I guess this error has been around for a long time, perhaps masked by Sentry noise or issue linking. With the updated Sentry it turns out that emails local part was using unicode with AWS does not support. The frontend would create these users but they could never get verified.

@vbudhram vbudhram requested a review from a team as a code owner March 26, 2026 18:07
Copilot AI review requested due to automatic review settings March 26, 2026 18:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates email validation to reject non-ASCII characters in the email local part to avoid creating accounts that can’t receive mail via AWS SES (no SMTPUTF8 support), and updates test expectations accordingly.

Changes:

  • Stop using punycode.toASCII() for validating the local part; validate local part directly against EMAIL_USER.
  • Update/add Jest tests to expect unicode local parts to be invalid while keeping unicode domains valid.
  • Add a mail-sending-time guard that skips sending when an address has a non-ASCII local part, with logging/metrics to reduce Sentry noise from existing accounts.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/fxa-auth-server/lib/senders/email.js Adds a runtime skip for non-ASCII local parts (logging + statsd metric) before attempting SES send/bounce checks.
packages/fxa-auth-server/lib/routes/validators.spec.ts Updates existing unicode-local-part expectations and adds explicit coverage for additional non-ASCII local-part examples.
packages/fxa-auth-server/lib/routes/validators.js Removes punycode conversion from local-part validation; keeps punycode conversion for the domain part.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +507 to +515
// Skip non-ASCII local parts. SES doesn't support SMTPUTF8, so these
// always fail at RCPT TO. Silently return (like bounce check below)
// to avoid Sentry noise from pre-existing accounts.
const [localPart] = to.split('@');
if (/[^\x20-\x7E]/.test(localPart)) {
statsd.increment('email.nonAsciiLocalPart', { template });
log.warn('mailer.send.nonAsciiLocalPart', { to, template });
return;
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The check for “non-ASCII local parts” uses /[^\x20-\x7E]/, which flags any non-printable ASCII control characters as well (e.g. \n, \t) and would count/log them under the nonAsciiLocalPart metric even though they’re still ASCII. Consider changing the regex to test strictly for non-ASCII bytes (e.g. [^\x00-\x7F]) or update the comment/metric name to match what’s actually being detected.

Copilot uses AI. Check for mistakes.
Comment on lines +507 to +514
// Skip non-ASCII local parts. SES doesn't support SMTPUTF8, so these
// always fail at RCPT TO. Silently return (like bounce check below)
// to avoid Sentry noise from pre-existing accounts.
const [localPart] = to.split('@');
if (/[^\x20-\x7E]/.test(localPart)) {
statsd.increment('email.nonAsciiLocalPart', { template });
log.warn('mailer.send.nonAsciiLocalPart', { to, template });
return;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The PR description lists only validation/test updates, but this change also alters runtime behavior by silently skipping sends for some existing accounts (and adds new logging/metrics). Please update the PR description (or add a note) to reflect this additional behavior change so reviewers/operators don’t miss it.

Copilot uses AI. Check for mistakes.
it('isValidEmailAddress rejects non-ASCII local parts', () => {
// Accented letters
expect(validators.isValidEmailAddress('andrée.torta@free.fr')).toBe(false);
expect(validators.isValidEmailAddress('bendegúz@gmail.com')).toBe(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm sure these aren't real but let's use restmail or example here please


it('isValidEmailAddress rejects non-ASCII local parts', () => {
// Accented letters
expect(validators.isValidEmailAddress('andrée.torta@free.fr')).toBe(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(same here)

@vbudhram vbudhram force-pushed the fxa-12710 branch 2 times, most recently from 4c609c8 to 38a6398 Compare March 26, 2026 21:04
// Note: punycode.toASCII() is intentionally NOT used here because
// it would encode non-ASCII chars (e.g. 'andrée' → 'xn--andre-9ua'),
// masking the problem.
if (!EMAIL_USER.test(parts[0])) {
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.

TIL

Add validation to reject email addresses with non-ASCII characters in
the local part (before the @). Updates validators, email sender, and
related test files to handle non-ASCII rejection consistently.
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.

4 participants