Skip to content

fix(rate-limit): match ignoreEmails against the non-normalized email - #21061

Merged
nshirley merged 1 commit into
mainfrom
fxa-13194
Aug 20, 2026
Merged

fix(rate-limit): match ignoreEmails against the non-normalized email#21061
nshirley merged 1 commit into
mainfrom
fxa-13194

Conversation

@vbudhram

@vbudhram vbudhram commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Because

  • A stage bypass pattern like ^.*\+srl\d{0,4}@mozilla\.com$ never matched, so rate limits could not be skipped for those test accounts.
  • customs.js normalizes the email before it reaches the rate-limit library. Normalization strips the +suffix that the pattern needs.

This pull request

  • Adds an optional nonNormalizedEmail parameter to RateLimit.skip(). It matches config.ignoreEmails against the raw email and the normalized one.
  • Adds the lowercased raw email as a third match candidate. Normalization lowercases before it strips the +suffix, so a mixed-case input like User+srl1@Mozilla.com matched neither of the first two forms.
  • Passes the raw email through CustomsClient.checkV2() from check() and checkAuthenticated().
  • Keeps RateLimit.check() on the normalized email, so a +suffix address cannot evade rate limits.
  • Keeps the normalized email on the BigQuery row that a skipped check writes.
  • Adds specs for the +suffix match and for the normalized fallback.

Issue that this pull request solves

Closes: FXA-13194

Checklist

Put an x in the boxes that apply

  • 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.

How to review (Optional)

  • Key files/areas to focus on: skip() in libs/accounts/rate-limit/src/lib/rate-limit.ts, and the checkV2() call site in packages/fxa-auth-server/lib/customs.js.
  • Suggested review order: rate-limit.ts, then customs.js, then the two spec files.
  • Risky or complex parts: skip() matches on the raw email and the normalized one. A match on the raw email only would drop the trim and the lowercase that an existing pattern can depend on. Read the deployment note below.

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

Warning: audit the deployed ignoreEmails values before you merge this.

The bypass is config-gated, not code-gated. config.ignoreEmails is a per-environment list of regexes. This fix makes those patterns match the raw address as well as the normalized one. Any pattern already in a production config becomes more permissive, because it then also matches user+anything@.... The owner of the prod config must make that call.

BlockOn and BlockOnOpts do not change. A raw email is not a new blocking dimension, so it stays out of the mapped type.

@vbudhram
vbudhram requested a review from a team as a code owner August 19, 2026 00:28
@vbudhram vbudhram added the auto label Aug 19, 2026
Copilot AI balanced review requested due to automatic review settings August 19, 2026 00:28

Copilot AI left a comment

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.

Pull request overview

Updates rate-limit email exclusions to consider alias-preserving input while retaining normalized keys and telemetry.

Changes:

  • Passes the original email into RateLimit.skip().
  • Matches exclusions against original and normalized emails.
  • Adds coverage for aliases, fallback matching, and BigQuery output.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/fxa-auth-server/lib/customs.js Forwards original emails to rate-limit checks.
packages/fxa-auth-server/lib/customs.spec.ts Tests original-email forwarding.
libs/accounts/rate-limit/src/lib/rate-limit.ts Extends ignored-email matching.
libs/accounts/rate-limit/src/lib/rate-limit.spec.ts Tests matching and telemetry behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +123 to +127
const emails = [nonNormalizedEmail, opts.email].filter(
(x): x is string => x != null
);
const ignoredEmail = this.config.ignoreEmails?.some((x) =>
emails.some((email) => email.match(x))
## Because

- A stage bypass pattern like `^.*\+srl\d{0,4}@mozilla\.com$` never matched, so rate limits could not be skipped for those test accounts.
- `customs.js` normalizes the email before it reaches the rate-limit library. Normalization strips the `+suffix` that the pattern needs.

## This pull request

- Adds an optional `nonNormalizedEmail` parameter to `RateLimit.skip()`. It matches `config.ignoreEmails` against the raw email and the normalized one.
- Adds the lowercased raw email as a third match candidate. Normalization lowercases before it strips the `+suffix`, so a mixed-case input like `User+srl1@Mozilla.com` matched neither of the first two forms.
- Passes the raw email through `CustomsClient.checkV2()` from `check()` and `checkAuthenticated()`.
- Keeps `RateLimit.check()` on the normalized email, so a `+suffix` address cannot evade rate limits.
- Keeps the normalized email on the BigQuery row that a skipped check writes.
- Adds specs for the `+suffix` match and for the normalized fallback.

## Issue that this pull request solves

Closes: FXA-13194
@nshirley
nshirley merged commit e23da08 into main Aug 20, 2026
20 checks passed
@nshirley
nshirley deleted the fxa-13194 branch August 20, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants