[PR #5973] [CLOSED] fix(haveibeenpwned): prevent email enumeration via compromised password check #6354

Closed
opened 2026-03-13 12:55:46 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/5973
Author: @sivaratrisrinivas
Created: 11/13/2025
Status: Closed

Base: canaryHead: fix/haveibeenpwned-leak


📝 Commits (6)

📊 Changes

8 files changed (+31 additions, -7 deletions)

View changed files

📝 packages/better-auth/src/api/index.ts (+2 -0)
📝 packages/better-auth/src/api/middlewares/index.ts (+0 -1)
📝 packages/better-auth/src/api/routes/sign-in.ts (+2 -0)
packages/better-auth/src/api/state/compromise-check.ts (+6 -0)
📝 packages/better-auth/src/api/state/oauth.ts (+0 -0)
📝 packages/better-auth/src/oauth2/state.ts (+1 -1)
📝 packages/better-auth/src/plugins/haveibeenpwned/haveibeenpwned.test.ts (+11 -0)
📝 packages/better-auth/src/plugins/haveibeenpwned/index.ts (+9 -5)

📄 Description

Fixes #5967

What is the issue?

Signing in with an unregistered email and a compromised password (e.g., "123456") returns PASSWORD_COMPROMISED instead of INVALID_EMAIL_OR_PASSWORD. This leaks which emails are registered.

Why is it happening?

The haveIBeenPwned plugin checks password compromise during hash. For unknown emails, the sign-in route still hashes the password (to prevent timing attacks), but the plugin throws PASSWORD_COMPROMISED before the route can return the generic error.

How did we fix it?

Added an optional skipCompromiseCheck parameter to password.hash(). The haveIBeenPwned plugin respects this flag and skips the check when set. The sign-in route passes skipCompromiseCheck: true when hashing dummy passwords for unknown emails, ensuring INVALID_EMAIL_OR_PASSWORD is returned consistently.

Changes:

  • Added skipCompromiseCheck option to password hash API
  • Updated haveIBeenPwned plugin to skip check when flag is set
  • Modified sign-in route to use flag for unknown email hashes
  • Added regression test to prevent future leaks

Testing: All tests pass, including new regression test. Backward compatible.



Summary by cubic

Prevents email enumeration on sign-in by skipping the haveIBeenPwned check when hashing dummy passwords for unknown emails. Adds a request-scoped flag to control the compromise check.

  • Bug Fixes

    • Added request state getCompromiseCheck/setCompromiseCheck; sign-in sets false for unknown emails.
    • Updated haveIBeenPwned plugin to honor the flag and call the original hash.
    • Added a regression test to ensure unknown users always get “Invalid email or password” (401).
  • Refactors

    • Moved OAuth state helpers from middlewares to api/state and updated imports/exports.

Written for commit 376cc97807. Summary will update automatically 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/5973 **Author:** [@sivaratrisrinivas](https://github.com/sivaratrisrinivas) **Created:** 11/13/2025 **Status:** ❌ Closed **Base:** `canary` ← **Head:** `fix/haveibeenpwned-leak` --- ### 📝 Commits (6) - [`04712dc`](https://github.com/better-auth/better-auth/commit/04712dc0c7d8b9384057783ea49969aa46766a9b) fix(haveibeenpwned): prevent email enumeration via compromised password check - [`d19b1b2`](https://github.com/better-auth/better-auth/commit/d19b1b2e55916bcd050a6e27cc31424939560019) chore: apply biome formatting - [`895510f`](https://github.com/better-auth/better-auth/commit/895510f17562ea18649772d210f3038c895bb91b) fix: code - [`2e41a76`](https://github.com/better-auth/better-auth/commit/2e41a766a386907ea90a5c507f9346775dbf40fa) fix: code - [`a0773b7`](https://github.com/better-auth/better-auth/commit/a0773b7ab544167cf6653274b8f741ff74ac2276) fix: type - [`376cc97`](https://github.com/better-auth/better-auth/commit/376cc97807a3443a5285d9d0ebb9c264170ab4ed) fix: type ### 📊 Changes **8 files changed** (+31 additions, -7 deletions) <details> <summary>View changed files</summary> 📝 `packages/better-auth/src/api/index.ts` (+2 -0) 📝 `packages/better-auth/src/api/middlewares/index.ts` (+0 -1) 📝 `packages/better-auth/src/api/routes/sign-in.ts` (+2 -0) ➕ `packages/better-auth/src/api/state/compromise-check.ts` (+6 -0) 📝 `packages/better-auth/src/api/state/oauth.ts` (+0 -0) 📝 `packages/better-auth/src/oauth2/state.ts` (+1 -1) 📝 `packages/better-auth/src/plugins/haveibeenpwned/haveibeenpwned.test.ts` (+11 -0) 📝 `packages/better-auth/src/plugins/haveibeenpwned/index.ts` (+9 -5) </details> ### 📄 Description **Fixes #5967** ### What is the issue? Signing in with an unregistered email and a compromised password (e.g., "123456") returns `PASSWORD_COMPROMISED` instead of `INVALID_EMAIL_OR_PASSWORD`. This leaks which emails are registered. ### Why is it happening? The haveIBeenPwned plugin checks password compromise during hash. For unknown emails, the sign-in route still hashes the password (to prevent timing attacks), but the plugin throws `PASSWORD_COMPROMISED` before the route can return the generic error. ### How did we fix it? Added an optional `skipCompromiseCheck` parameter to `password.hash()`. The haveIBeenPwned plugin respects this flag and skips the check when set. The sign-in route passes `skipCompromiseCheck: true` when hashing dummy passwords for unknown emails, ensuring `INVALID_EMAIL_OR_PASSWORD` is returned consistently. **Changes:** - Added `skipCompromiseCheck` option to password hash API - Updated haveIBeenPwned plugin to skip check when flag is set - Modified sign-in route to use flag for unknown email hashes - Added regression test to prevent future leaks **Testing:** All tests pass, including new regression test. Backward compatible. --- <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Prevents email enumeration on sign-in by skipping the haveIBeenPwned check when hashing dummy passwords for unknown emails. Adds a request-scoped flag to control the compromise check. - **Bug Fixes** - Added request state getCompromiseCheck/setCompromiseCheck; sign-in sets false for unknown emails. - Updated haveIBeenPwned plugin to honor the flag and call the original hash. - Added a regression test to ensure unknown users always get “Invalid email or password” (401). - **Refactors** - Moved OAuth state helpers from middlewares to api/state and updated imports/exports. <sup>Written for commit 376cc97807a3443a5285d9d0ebb9c264170ab4ed. Summary will update automatically 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-03-13 12:55:46 -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#6354