[PR #8097] [MERGED] fix: prevent email enumeration on /change-email, add customSyntheticUser #24645

Closed
opened 2026-04-15 22:29:27 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8097
Author: @nphlp
Created: 2/22/2026
Status: Merged
Merged: 2/28/2026
Merged by: @Bekacru

Base: canaryHead: fix/email-enumeration


📝 Commits (2)

  • 6cc88c6 fix: prevent email enumeration on /change-email and improve sign-up synthetic user
  • 13a4a21 fix: prevent email enumeration on /change-email when verification is not configured

📊 Changes

10 files changed (+426 additions, -72 deletions)

View changed files

📝 docs/content/docs/authentication/email-password.mdx (+52 -0)
📝 docs/content/docs/plugins/admin.mdx (+24 -0)
📝 docs/content/docs/reference/options.mdx (+1 -0)
📝 docs/content/docs/reference/security.mdx (+6 -0)
📝 packages/better-auth/src/api/routes/sign-up.test.ts (+124 -0)
📝 packages/better-auth/src/api/routes/sign-up.ts (+43 -10)
📝 packages/better-auth/src/api/routes/update-user.test.ts (+83 -0)
📝 packages/better-auth/src/api/routes/update-user.ts (+54 -47)
📝 packages/better-auth/src/plugins/open-api/__snapshots__/open-api.test.ts.snap (+0 -15)
📝 packages/core/src/types/init-options.ts (+39 -0)

📄 Description

Summary

Complements #8091 — extends enumeration protection to the /change-email
endpoint and adds customSyntheticUser for plugin compatibility.

/change-email

  • Return { status: true } instead of 422 USER_ALREADY_EXISTS when target
    email is taken (unconditional — endpoint always returns same shape)
  • Simulate HMAC token generation to prevent timing attacks
  • Remove OpenAPI 422 response spec

/sign-up/email — customSyntheticUser

  • New option to build a custom synthetic user when plugins add fields
    to the user table (admin: role, banned, etc.)
  • Fixes property order to match DB adapter output (id last via transformOutput)
  • Separates coreFields, additionalFields and id as building blocks
  • Without customSyntheticUser, behavior is unchanged (#8091 logic)

Tests

  • 2 tests /change-email (returns 200, email unchanged)
  • 1 test sign-up key order with additionalFields
  • 1 test customSyntheticUser with admin plugin

Docs

  • email-password.mdx: Email Enumeration Protection section, customSyntheticUser example, TypeTable entry
  • admin.mdx: Email Enumeration Protection section
  • options.mdx: customSyntheticUser option
  • security.mdx: Email Enumeration Protection section

Closes #8096
Refs #7972


Summary by cubic

Prevents email enumeration on sign-up and change-email, and adds a customizable synthetic user so responses stay indistinguishable when plugins add fields. Aligns with OWASP guidance, complements #8091, closes #8096, and enforces a uniform 400 when verification isn’t configured.

  • Bug Fixes

    • /change-email returns 200 with { status: true } for existing emails; adds an early config check so both existing and non-existing emails hit the same 400 when no email-sending flow is available; simulates token generation to normalize timing; removes 422 from OpenAPI.
    • Tests cover same-shape success, unchanged email, and the uniform 400 path.
  • New Features

    • Added emailAndPassword.customSyntheticUser to build the synthetic sign-up user when protection is active; callback assembles core fields, plugin fields, additional fields, then id (id last), and only auto-includes user.additionalFields.
    • Tests verify indistinguishable responses and admin plugin fields; docs add an Email Enumeration Protection section and examples.

Written for commit 13a4a212be. Summary will update on new commits.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8097 **Author:** [@nphlp](https://github.com/nphlp) **Created:** 2/22/2026 **Status:** ✅ Merged **Merged:** 2/28/2026 **Merged by:** [@Bekacru](https://github.com/Bekacru) **Base:** `canary` ← **Head:** `fix/email-enumeration` --- ### 📝 Commits (2) - [`6cc88c6`](https://github.com/better-auth/better-auth/commit/6cc88c6f7eb881f74e3efbd36b7aa089ec05013f) fix: prevent email enumeration on /change-email and improve sign-up synthetic user - [`13a4a21`](https://github.com/better-auth/better-auth/commit/13a4a212be32a9911a1abb8843aae3beed63497a) fix: prevent email enumeration on /change-email when verification is not configured ### 📊 Changes **10 files changed** (+426 additions, -72 deletions) <details> <summary>View changed files</summary> 📝 `docs/content/docs/authentication/email-password.mdx` (+52 -0) 📝 `docs/content/docs/plugins/admin.mdx` (+24 -0) 📝 `docs/content/docs/reference/options.mdx` (+1 -0) 📝 `docs/content/docs/reference/security.mdx` (+6 -0) 📝 `packages/better-auth/src/api/routes/sign-up.test.ts` (+124 -0) 📝 `packages/better-auth/src/api/routes/sign-up.ts` (+43 -10) 📝 `packages/better-auth/src/api/routes/update-user.test.ts` (+83 -0) 📝 `packages/better-auth/src/api/routes/update-user.ts` (+54 -47) 📝 `packages/better-auth/src/plugins/open-api/__snapshots__/open-api.test.ts.snap` (+0 -15) 📝 `packages/core/src/types/init-options.ts` (+39 -0) </details> ### 📄 Description ## Summary Complements #8091 — extends enumeration protection to the /change-email endpoint and adds customSyntheticUser for plugin compatibility. ### /change-email - Return `{ status: true }` instead of `422 USER_ALREADY_EXISTS` when target email is taken (unconditional — endpoint always returns same shape) - Simulate HMAC token generation to prevent timing attacks - Remove OpenAPI 422 response spec ### /sign-up/email — customSyntheticUser - New option to build a custom synthetic user when plugins add fields to the user table (admin: `role`, `banned`, etc.) - Fixes property order to match DB adapter output (`id` last via `transformOutput`) - Separates `coreFields`, `additionalFields` and `id` as building blocks - Without `customSyntheticUser`, behavior is unchanged (#8091 logic) ### Tests - 2 tests /change-email (returns 200, email unchanged) - 1 test sign-up key order with `additionalFields` - 1 test `customSyntheticUser` with admin plugin ### Docs - `email-password.mdx`: Email Enumeration Protection section, `customSyntheticUser` example, TypeTable entry - `admin.mdx`: Email Enumeration Protection section - `options.mdx`: `customSyntheticUser` option - `security.mdx`: Email Enumeration Protection section Closes #8096 Refs #7972 <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Prevents email enumeration on sign-up and change-email, and adds a customizable synthetic user so responses stay indistinguishable when plugins add fields. Aligns with OWASP guidance, complements #8091, closes #8096, and enforces a uniform 400 when verification isn’t configured. - **Bug Fixes** - /change-email returns 200 with { status: true } for existing emails; adds an early config check so both existing and non-existing emails hit the same 400 when no email-sending flow is available; simulates token generation to normalize timing; removes 422 from OpenAPI. - Tests cover same-shape success, unchanged email, and the uniform 400 path. - **New Features** - Added emailAndPassword.customSyntheticUser to build the synthetic sign-up user when protection is active; callback assembles core fields, plugin fields, additional fields, then id (id last), and only auto-includes user.additionalFields. - Tests verify indistinguishable responses and admin plugin fields; docs add an Email Enumeration Protection section and examples. <sup>Written for commit 13a4a212be32a9911a1abb8843aae3beed63497a. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:29:27 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#24645