[GH-ISSUE #5967] haveIBeenPwned plugin error: PasswordCompromisedMessage thrown instead of INVALID_EMAIL_OR_PASSWORD for unknown emails #10388

Closed
opened 2026-04-13 06:29:54 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @marcel-kammerloch on GitHub (Nov 13, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5967

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

import { betterAuth } from "better-auth";
import { haveIBeenPwned } from "better-auth/plugins";

export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
  plugins: [haveIBeenPwned()]
});
  1. Attempt to sign in with an unregistered email address.
  2. Use a password known to be compromised (e.g., "123456").
  3. You should get the "Please choose a more secure password." error, instead of "Invalid email or password"

Current vs. Expected behavior

Expected Behavior:

The login route should always return INVALID_EMAIL_OR_PASSWORD when:

  • The email does not match any user in the database, or
  • The password is invalid or insecure.

This ensures consistent behavior and prevents potential information leaks about which emails are registered.

Actual Behavior

Currently:

  • The plugin checks whether the password is compromised before hashing it.
  • During sign-in, if there’s no matching user for the email, the system still hashes the password to prevent timing attacks that could reveal valid email addresses.
  • However, if the password is found to be insecure, the plugin throws a PasswordCompromisedMessage instead of returning the generic INVALID_EMAIL_OR_PASSWORD error.

This leads to inconsistent error handling and can leak valid email adresses of registered users.

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Home",
    "release": "10.0.26200",
    "cpuCount": 8,
    "cpuModel": "11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz",
    "totalMemory": "7.79 GB",
    "freeMemory": "0.86 GB"
  },
  "node": {
    "version": "v22.17.1",
    "env": "development"
  },
  "packageManager": {
    "name": "pnpm",
    "version": "10.19.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.1"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.18.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.34",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Package, Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { haveIBeenPwned } from "better-auth/plugins";

export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
  plugins: [haveIBeenPwned()]
});

Additional context

I think the problem is in this file:

https://github.com/better-auth/better-auth/blob/canary/packages/better-auth/src/api/routes/sign-in.ts
(line 477-485)

Originally created by @marcel-kammerloch on GitHub (Nov 13, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5967 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ```js import { betterAuth } from "better-auth"; import { haveIBeenPwned } from "better-auth/plugins"; export const auth = betterAuth({ emailAndPassword: { enabled: true }, plugins: [haveIBeenPwned()] }); ``` 1. Attempt to sign in with an unregistered email address. 2. Use a password known to be compromised (e.g., "123456"). 3. You should get the "Please choose a more secure password." error, instead of "Invalid email or password" ### Current vs. Expected behavior **Expected Behavior:** The login route should always return INVALID_EMAIL_OR_PASSWORD when: - The email does not match any user in the database, or - The password is invalid or insecure. This ensures consistent behavior and prevents potential information leaks about which emails are registered. **Actual Behavior** Currently: - The plugin checks whether the password is compromised before hashing it. - During sign-in, if there’s no matching user for the email, the system still hashes the password to prevent timing attacks that could reveal valid email addresses. - However, if the password is found to be insecure, the plugin throws a PasswordCompromisedMessage instead of returning the generic INVALID_EMAIL_OR_PASSWORD error. This leads to inconsistent error handling and can leak valid email adresses of registered users. ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Home", "release": "10.0.26200", "cpuCount": 8, "cpuModel": "11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz", "totalMemory": "7.79 GB", "freeMemory": "0.86 GB" }, "node": { "version": "v22.17.1", "env": "development" }, "packageManager": { "name": "pnpm", "version": "10.19.0" }, "frameworks": [ { "name": "next", "version": "16.0.1" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "@prisma/client", "version": "^6.18.0" } ], "betterAuth": { "version": "^1.3.34", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Package, Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { haveIBeenPwned } from "better-auth/plugins"; export const auth = betterAuth({ emailAndPassword: { enabled: true }, plugins: [haveIBeenPwned()] }); ``` ### Additional context I think the problem is in this file: https://github.com/better-auth/better-auth/blob/canary/packages/better-auth/src/api/routes/sign-in.ts (line 477-485)
GiteaMirror added the locked label 2026-04-13 06:29:54 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 13, 2025):

Thanks for opening this issue. It appears that the haveIBeenPwned plugin currently performs its password compromise check before verifying the existence of the email address, so when an unregistered email is used with a compromised password, it returns a PasswordCompromisedMessage rather than the generic INVALID_EMAIL_OR_PASSWORD error. This behavior, intended to counter timing attacks, ends up causing inconsistent error handling and may inadvertently hint at which emails are registered.

To troubleshoot this, you might consider configuring a custom error message via the plugin options to override the default behavior. Alternatively, a fix could involve adjusting the order of operations in the sign-in route so that it verifies the email first, and only then performs the password compromise check. This adjustment would help ensure that incorrect credentials always lead to the same generic error response.

Tag @better-auth-agent if you would like an answer from the Agent. What other aspects of your authentication flow can we help you optimize?

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3529242127 --> @better-auth-agent[bot] commented on GitHub (Nov 13, 2025): Thanks for opening this issue. It appears that the haveIBeenPwned plugin currently performs its password compromise check before verifying the existence of the email address, so when an unregistered email is used with a compromised password, it returns a PasswordCompromisedMessage rather than the generic INVALID_EMAIL_OR_PASSWORD error. This behavior, intended to counter timing attacks, ends up causing inconsistent error handling and may inadvertently hint at which emails are registered. To troubleshoot this, you might consider configuring a custom error message via the plugin options to override the default behavior. Alternatively, a fix could involve adjusting the order of operations in the sign-in route so that it verifies the email first, and only then performs the password compromise check. This adjustment would help ensure that incorrect credentials always lead to the same generic error response. Tag @better-auth-agent if you would like an answer from the Agent. What other aspects of your authentication flow can we help you optimize? <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@rovertrack commented on GitHub (Nov 13, 2025):

const handleSignIn = async () => {
		const res =	await authClient.signIn.email({
				email:"sergios@gmail.com",
				password:"dfsddf@327568746587645",
			});
			console.log(res)
		};
Image its working as expected
<!-- gh-comment-id:3529311997 --> @rovertrack commented on GitHub (Nov 13, 2025): ``` const handleSignIn = async () => { const res = await authClient.signIn.email({ email:"sergios@gmail.com", password:"dfsddf@327568746587645", }); console.log(res) }; ``` <img width="1569" height="189" alt="Image" src="https://github.com/user-attachments/assets/7a1aca31-0e44-4659-b6dc-2004185fb288" /> its working as expected
Author
Owner

@marcel-kammerloch commented on GitHub (Nov 13, 2025):

It works because this is a secure password, if you use a password like 123456, the plugin will return the CompromisedPassword Error

const handleSignIn = async () => {
		const res =	await authClient.signIn.email({
				email:"sergios@gmail.com",
				password:"123456",
			});
			console.log(res)
		};
<!-- gh-comment-id:3529534910 --> @marcel-kammerloch commented on GitHub (Nov 13, 2025): It works because this is a secure password, if you use a password like 123456, the plugin will return the CompromisedPassword Error > ``` > const handleSignIn = async () => { > const res = await authClient.signIn.email({ > email:"sergios@gmail.com", > password:"123456", > }); > console.log(res) > }; > ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10388