# Leaked Password Protection Is Currently Disabled: How to Fix It

URL: https://tuvio.dev/blog/supabase-leaked-password-protection
Published: 2026-10-29
By the Tuvio team

"Leaked password protection is currently disabled" means Supabase Auth lets your users sign up with passwords that are already in public breach lists, such as `password123`. Nothing in your database is exposed by it. The fix is one toggle: **Authentication**, then **Sign In / Providers**, then **Email**, then **Prevent use of leaked passwords**. It needs the Pro plan or above. On the Free plan you can run the same check yourself in a few lines of code, shown below.

This post covers what the setting does, where the warning comes from in Supabase and in Lovable, how to turn it on, and what it doesn't protect.

## What "leaked password protection is currently disabled" means

People reuse passwords. When one site leaks, attackers take the email and password pairs and try them on other sites. That's called credential stuffing, and Supabase's [password security guide](https://supabase.com/docs/guides/auth/password-security) names it directly: attackers "can use these lists of leaked passwords to automate sign-in attempts (known as credential stuffing) and steal or access sensitive user data."

Leaked password protection stops a user from picking a password that is already on those lists. With it off, Supabase Auth accepts any password that meets your length and character rules, including ones attackers try first.

## What leaked password protection does

When it's on, Supabase Auth checks each new password against Have I Been Pwned's Pwned Passwords database and rejects the ones it finds. In Supabase's words, it "uses the open-source HaveIBeenPwned.org Pwned Passwords API to reject passwords that have been leaked and are known by malicious actors."

The dashboard describes the toggle this way: "Rejects the use of known or easy to guess passwords on sign up or password change." A rejected user gets a weak-password error with the message "Password is known to be weak and easy to guess, please choose a different one."

### Does Supabase send my users' passwords to Have I Been Pwned?

No. Supabase's docs don't spell out the mechanism, but its Auth server uses Supabase's open-source [`hibp` library](https://github.com/supabase/hibp), which calls the Pwned Passwords range API. That API works by k-anonymity. Per the [Have I Been Pwned API docs](https://haveibeenpwned.com/API/v3#PwnedPasswords), you send only "the first 5 characters of either a SHA-1 or an NTLM hash". The API returns every hash suffix that starts with those 5 characters, hundreds of them, and the check for the full hash happens on your side. The password, and even its full hash, never leave the server.

## Where the warning comes from

### The Supabase Security Advisor

In the dashboard, under **Advisors**, then **Security Advisor**, the lint is called **Leaked Password Protection Disabled**. Its id is `auth_leaked_password_protection`, its level is WARN, and its detail line reads "Leaked password protection is currently disabled." Its description says "Supabase Auth prevents the use of compromised passwords by checking against HaveIBeenPwned.org. Enable this feature to enhance security."

The lint links to the [leaked password protection section](https://supabase.com/docs/guides/auth/password-security#password-strength-and-leaked-password-protection) of Supabase's docs. Unlike [RLS disabled in public](https://tuvio.dev/blog/supabase-rls-disabled-in-public), which is level ERROR, this one is a warning. It's about an Auth setting, not your tables, so no SQL will fix it.

### Lovable's security scan

Lovable's Quick scan runs every time you publish. Per [Lovable's security overview](https://docs.lovable.dev/features/security), its database review covers "tables without per-record access control (row-level security), access rules that let everyone through, and leaked-password protection turned off." So the same finding can show up in Lovable even if you never opened the Supabase dashboard.

## How to enable leaked password protection in Supabase

### Is leaked password protection a paid feature?

Yes. Supabase's docs say: "Leaked password protection is available on the Pro Plan and above." The [pricing page](https://supabase.com/pricing) lists it as not included on Free and included on Pro, Team and Enterprise. If your project is on the Free plan, you can't turn this toggle on. Skip to [the Free plan section](https://tuvio.dev/blog/supabase-leaked-password-protection#leaked-password-protection-on-the-supabase-free-plan).

### Turn on leaked password protection in the dashboard

1. Open your project and go to **Authentication**.
2. Under **Configuration**, open **Sign In / Providers**.
3. Open the **Email** provider.
4. Turn on **Prevent use of leaked passwords** and save.

The **Attack Protection** page under Authentication also shows whether it's enabled, with a button that takes you to the same Email provider settings. Then open the Security Advisor again. The warning should be gone once it re-runs.

### Supabase enable leaked password protection with the Management API

If you manage settings from scripts, the toggle is the `password_hibp_enabled` field on the auth config endpoint of the [Management API](https://supabase.com/docs/reference/api/v1-update-auth-service-config). The [rate limits guide](https://supabase.com/docs/guides/auth/rate-limits) uses the same endpoint, so the call has the same shape:

```bash
# Access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"

curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "password_hibp_enabled": true, "password_min_length": 10 }'
```

The CLI's `config.toml` is different. Its `[auth]` section has `minimum_password_length` and `password_requirements`, but the current template has no key for leaked password protection. If you keep auth config as code, set this one with the Management API or the dashboard. Self-hosted Supabase has its own switch, `GOTRUE_PASSWORD_HIBP_ENABLED`, in the Auth server's environment.

## Leaked password protection in Lovable

Lovable has two kinds of backend, and the setting lives in a different place for each. Per [Lovable's security docs](https://docs.lovable.dev/features/security):

- **Built-in backend (Lovable Cloud):** turn on **Password HIBP check** in **More**, then **Users**, then **Settings**, then **Email**. Lovable's [email authentication docs](https://docs.lovable.dev/features/email-auth) call it "the highest-value setting here: it blocks the passwords attackers try first, without making rules harder for everyone else."
- **Your own Supabase project connected to Lovable:** Lovable points you to the Supabase docs. Change it in your Supabase dashboard as above, and the Pro plan requirement applies to your Supabase project.

Lovable's docs don't mention a plan requirement for the built-in backend's HIBP check. If the toggle isn't available to you there, ask Lovable support.

## What leaked password protection does and doesn't cover

It runs when a password is set: on sign-up and on password change. Four limits matter.

**Existing users keep their passwords.** Turning it on doesn't scan the passwords already stored, which Supabase keeps only as bcrypt hashes. The docs say "Existing users can still sign in with their current password even if it doesn't meet the new, strengthened password requirements." In supabase-js, a successful `signInWithPassword` returns a `weakPassword` object next to the session when the password fails your rules. Its `reasons` can include `pwned`, so you can prompt that user to change it:

```ts
const { data, error } = await supabase.auth.signInWithPassword({ email, password })

if (data.weakPassword?.reasons.includes('pwned')) {
  // Signed in, but this password is in a breach list. Ask for a new one.
}
```

**OAuth and magic-link users have no password to check.** Google sign-in, GitHub sign-in and email one-time links skip it entirely. That's fine: there's no password to stuff.

**It doesn't stop guessing.** It blocks known-leaked passwords, not an attacker trying thousands of logins. For that you need limits on attempts.

**It doesn't replace the other password rules.** In the same Email provider settings, set **Minimum password length** (Supabase's docs say "Anything less than 8 characters is not recommended") and **Password requirements**, which can require digits, lowercase and uppercase letters, and symbols. These two work on the Free plan.

Then look at attack protection. Supabase Auth applies [rate limits](https://supabase.com/docs/guides/auth/rate-limits) to its endpoints, adjustable under **Authentication**, then **Rate Limits**. It also supports [CAPTCHA](https://supabase.com/docs/guides/auth/auth-captcha) with hCaptcha or Cloudflare Turnstile on sign-in, sign-up and password reset, turned on under **Attack Protection**. Supabase's password guide also suggests asking users to turn on [multi-factor authentication](https://supabase.com/docs/guides/auth/auth-mfa).

## Leaked password protection on the Supabase Free plan

You can run the same k-anonymity check yourself. The Pwned Passwords API needs no account: per [Have I Been Pwned's docs](https://haveibeenpwned.com/API/v3#PwnedPasswords), it "is freely accessible without the need for a subscription and API key", and it has no rate limit.

This file uses only `fetch` and Web Crypto, so it runs in the browser, in a Supabase Edge Function (Deno) or in Node 18 and later:

```ts
// pwned.ts: checks a password against Have I Been Pwned's Pwned Passwords.
// Only the first 5 characters of the password's SHA-1 hash leave this code.

async function sha1Hex(text: string): Promise<string> {
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(text));
  return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0"))
    .join("")
    .toUpperCase();
}

/** How many times the password appears in Pwned Passwords (0 = not found). */
export async function pwnedCount(password: string): Promise<number> {
  const hash = await sha1Hex(password);
  const prefix = hash.slice(0, 5);
  const suffix = hash.slice(5);

  const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, {
    headers: { "Add-Padding": "true" },
  });
  if (!res.ok) throw new Error(`Pwned Passwords returned ${res.status}`);

  for (const line of (await res.text()).split("\n")) {
    const [lineSuffix, count] = line.trim().split(":");
    // Padding lines have a count of 0, so they never count as a match.
    if (lineSuffix === suffix) return Number(count);
  }
  return 0;
}
```

The `Add-Padding: true` header pads every response with fake entries, so someone watching encrypted traffic can't tell which prefix you asked for from the response size. Per the docs, "Padded entries always have a password count of 0 and can be discarded once received." Run against the live API, `pwnedCount("password123")` returned 2,266,543 and a random 32-character string returned 0.

Call it right before you sign the user up, and again before `supabase.auth.updateUser({ password })` on your change-password form:

```ts
import { pwnedCount } from './pwned'

async function signUp(email: string, password: string) {
  let leaked = false
  try {
    leaked = (await pwnedCount(password)) > 0
  } catch {
    // API unreachable: let the sign-up through rather than lock people out.
  }
  if (leaked) {
    return { error: 'This password has appeared in a data breach. Please choose a different one.' }
  }
  return supabase.auth.signUp({ email, password })
}
```

If your app signs users up from a server route or an Edge Function instead, call `pwnedCount` there before `auth.signUp`. Have I Been Pwned's docs ask you to check once the whole password is typed, not on every keystroke.

Be clear about what this is: a check in your form, not in Supabase Auth. Someone calling the Auth API directly with your public key can skip it. That matters less than it sounds. The person skipping it is choosing a weak password for their own account. The real case you're covering, a normal user reusing an old password, goes through your form.

What about an Auth Hook? Supabase's [Password Verification Attempt hook](https://supabase.com/docs/guides/auth/auth-hooks/password-verification-hook) runs at sign-in and receives only the user id and whether the password was valid. It's for reacting to failed attempts, not for checking strength, and per the [Auth Hooks docs](https://supabase.com/docs/guides/auth/auth-hooks) it's a Teams and Enterprise feature. The Before User Created hook, available on Free, doesn't receive the password either.

## Is it safe to ignore "leaked password protection is currently disabled"?

Your data isn't exposed by this warning. It's a WARN, not an ERROR, and no table, key or row becomes readable because of it. If your app has no email and password sign-in at all, it barely applies.

But if users sign up with a password, credential stuffing is the attack this setting exists for, and it's cheap to run. One reused password can open an account and everything that account can see in your app. On Pro, turning it on takes a minute. On Free, set a minimum length of 8 or more, add the check above, and turn on CAPTCHA.

## Fix checklist

1. Check your plan. Pro and above can use the built-in toggle.
2. On Pro: **Authentication**, **Sign In / Providers**, **Email**, turn on **Prevent use of leaked passwords**.
3. On Lovable's built-in backend: **More**, **Users**, **Settings**, **Email**, turn on **Password HIBP check**.
4. On Free: add the `pwnedCount` check to your sign-up and change-password forms.
5. Set **Minimum password length** to 8 or more and pick **Password requirements**.
6. Look at **Rate Limits** and turn on CAPTCHA under **Attack Protection**.
7. Handle `weakPassword` on sign-in so existing users with leaked passwords get asked to change them.
8. Open the Security Advisor again and confirm the warning is gone, or note why you're keeping it.

## Other Supabase warnings worth fixing first

This warning is about user accounts. The ERROR-level ones are about your data, and those come first. If the Security Advisor also lists tables under **Table publicly accessible**, read [Supabase RLS disabled in public](https://tuvio.dev/blog/supabase-rls-disabled-in-public) and fix that before anything else. For writing the policies themselves, see [Supabase RLS policy examples](https://tuvio.dev/blog/supabase-rls-policy-examples). If you built with Lovable, [is Lovable safe](https://tuvio.dev/blog/is-lovable-safe) covers what its scans do and don't check.

Tuvio doesn't check this setting. It reads your repository and your public site pages, not your Supabase dashboard settings, so it can't see whether leaked password protection is on. What a free [scan](https://tuvio.dev/scan) does flag is the code side: a migration that turns RLS off on a table, policies that let anyone read every row, and keys committed to your code. A table where RLS was never turned on isn't flagged yet; the Security Advisor lists those. For the rest of what to look at before launch, see the [vibe coding security checklist](https://tuvio.dev/blog/vibe-coding-security-checklist).
