[GH-ISSUE #8908] [Bug] sentinel emailValidation.enabled config doesn't disable normalization in DB hooks #28556

Closed
opened 2026-04-17 20:00:30 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @aakashkulkarni on GitHub (Apr 2, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8908

Originally assigned to: @jonathansamines on GitHub.

Bug Description

When configuring sentinel with security: { emailValidation: { enabled: false } }, email normalization is only disabled in the request hook middleware path (via createEmailHooks), but NOT in the database hook user.create.before or any direct normalizeEmail() calls.

Minimal Reproduction

sentinel({
    apiKey: '...',
    security: { emailValidation: { enabled: false } },
})

Expected: emails are not normalized (e.g. user+tag@gmail.com stays as user+tag@gmail.com in the user table)

Actual: emails are still normalized in DB hooks (e.g. user+tag@gmail.com becomes user@gmail.com in the user table)

Root Cause

normalizeEmail() (around line ~1599 in @better-auth/infra@0.1.12 dist/index.mjs) checks:

context.getPlugin("sentinel")?.options?.emailValidation?.enabled === false

However, getPlugin() returns the raw plugin definition object, which has no .options property. Here's why:

  1. The sentinel plugin's init() method returns options that get merged into the global options object via defu in runPluginInit.
  2. But getPlugin(id) calls options.plugins.find(p => p.id === id), which returns the original plugin definition — not the merged global options.
  3. This means getPlugin("sentinel")?.options is always undefined, and the normalization guard never triggers.

Where it works vs. doesn't

Path Disabled by config?
Request hook middleware (createEmailHooks) ✅ Yes — checks emailConfig.enabled directly
user.create.before DB hook ❌ No — calls normalizeEmail() with broken guard
Direct normalizeEmail() calls (~12 occurrences) ❌ No — same broken guard

Suggested Fix

In normalizeEmail(), check context.options?.emailValidation?.enabled instead of context.getPlugin("sentinel")?.options?.emailValidation?.enabled, since runPluginInit merges the sentinel init result into context.options.

Version

@better-auth/infra@0.1.12
@better-auth/infra@0.1.13

Originally created by @aakashkulkarni on GitHub (Apr 2, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8908 Originally assigned to: @jonathansamines on GitHub. ## Bug Description When configuring sentinel with `security: { emailValidation: { enabled: false } }`, email normalization is only disabled in the request hook middleware path (via `createEmailHooks`), but **NOT** in the database hook `user.create.before` or any direct `normalizeEmail()` calls. ## Minimal Reproduction ```typescript sentinel({ apiKey: '...', security: { emailValidation: { enabled: false } }, }) ``` **Expected:** emails are not normalized (e.g. `user+tag@gmail.com` stays as `user+tag@gmail.com` in the user table) **Actual:** emails are still normalized in DB hooks (e.g. `user+tag@gmail.com` becomes `user@gmail.com` in the user table) ## Root Cause `normalizeEmail()` (around line ~1599 in `@better-auth/infra@0.1.12` dist/index.mjs) checks: ```js context.getPlugin("sentinel")?.options?.emailValidation?.enabled === false ``` However, `getPlugin()` returns the raw plugin definition object, which has no `.options` property. Here's why: 1. The sentinel plugin's `init()` method returns options that get merged into the **global** `options` object via `defu` in `runPluginInit`. 2. But `getPlugin(id)` calls `options.plugins.find(p => p.id === id)`, which returns the **original plugin definition** — not the merged global options. 3. This means `getPlugin("sentinel")?.options` is always `undefined`, and the normalization guard never triggers. ## Where it works vs. doesn't | Path | Disabled by config? | |------|-------------------| | Request hook middleware (`createEmailHooks`) | ✅ Yes — checks `emailConfig.enabled` directly | | `user.create.before` DB hook | ❌ No — calls `normalizeEmail()` with broken guard | | Direct `normalizeEmail()` calls (~12 occurrences) | ❌ No — same broken guard | ## Suggested Fix In `normalizeEmail()`, check `context.options?.emailValidation?.enabled` instead of `context.getPlugin("sentinel")?.options?.emailValidation?.enabled`, since `runPluginInit` merges the sentinel init result into `context.options`. ## Version `@better-auth/infra@0.1.12` `@better-auth/infra@0.1.13`
GiteaMirror added the devopsbug labels 2026-04-17 20:00:30 -05:00
Author
Owner

@tmcw commented on GitHub (Apr 13, 2026):

It's pretty surprising behavior that even sentinel will normalize emails: this isn't mentioned in the documentation except in a section for error messages which doesn't mention that sentinel is the plugin doing normalization. Coming from an different authentication provider that did not normalize email addresses, this was a pretty big gotcha of better auth.

<!-- gh-comment-id:4238638170 --> @tmcw commented on GitHub (Apr 13, 2026): It's pretty surprising behavior that even sentinel will normalize emails: this isn't mentioned in [the documentation](https://better-auth.com/docs/infrastructure/plugins/sentinel#email-validation) except in a section for error messages which doesn't mention that sentinel is the plugin doing normalization. Coming from an different authentication provider that did not normalize email addresses, this was a pretty big gotcha of better auth.
Author
Owner

@jonathansamines commented on GitHub (Apr 14, 2026):

Hey @tmcw @aakashkulkarni We just released @better-auth/infra v0.2.3 with a couple of changes:

  • The security: { emailValidation: { enabled: false } } should now be respected on all operations
  • We have introduced a separate option security: { emailNormalization: { enabled: false } } if you want more granular control and want to disable this feature. See email normalization

Please let us know if you are still seeing issues after the upgrade!

<!-- gh-comment-id:4246894255 --> @jonathansamines commented on GitHub (Apr 14, 2026): Hey @tmcw @aakashkulkarni We just released `@better-auth/infra` v0.2.3 with a couple of changes: - The `security: { emailValidation: { enabled: false } }` should now be respected on all operations - We have introduced a separate option `security: { emailNormalization: { enabled: false } }` if you want more granular control and want to disable this feature. See [email normalization](https://better-auth.com/docs/infrastructure/plugins/sentinel#email-normalization) Please let us know if you are still seeing issues after the upgrade!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28556