Leaked passwords
Optional Have I Been Pwned check with k-anonymity: @virastack/password/pwned.
Usage
Off unless you call it. It uses the free Pwned Passwords range API; no key, no dependencies.
// app/signup/pwned-action.ts
"use server";
import { checkPwned, type PwnedResult } from "@virastack/password/pwned";
// From the server, Have I Been Pwned never sees the user's IP.
// Only the first 5 characters of the SHA-1 hash are sent.
export async function checkPassword(password: string): Promise<PwnedResult> {
return checkPwned(password, { timeout: 3000 });
}checkPwned(password, {
signal, // AbortSignal
timeout: 3000, // ms, default 5000
fetch, // custom fetch (tests, proxies)
})Result
| Result | Meaning |
|---|---|
{ status: "pwned", count } | Seen count times in breaches |
{ status: "safe" } | Not in the list |
{ status: "unknown", reason } | empty, unsupported (no crypto.subtle or fetch), network, timeout, aborted, rate-limited, http |
It never throws. On unknown, let the user continue (fail open).
Privacy and limits
- Only the first 5 characters of the password's SHA-1 hash are sent; the password and the full hash never leave. Responses are padded.
- Call it from the server. From a browser, Have I Been Pwned sees the user's IP.
crypto.subtleexists only on HTTPS and localhost; elsewhere the result isunknown.- Don't call it on every keystroke; check on submit.
