Password breach checking
Reject or warn on passwords found in known data breaches using the HaveIBeenPwned API.
KavachOS integrates with the HaveIBeenPwned Pwned Passwords API to detect compromised passwords at sign-up and password change. It uses the k-anonymity model — only the first 5 characters of the SHA-1 hash are sent to the API. Your users' actual passwords never leave your server.
Setup
import { createKavach } from 'kavachos';
import { emailPassword } from 'kavachos/plugins/email-password';
import { createHibpModule } from 'kavachos/modules/hibp';
const hibp = createHibpModule();
const kavach = await createKavach({
database: { provider: 'postgres', url: process.env.DATABASE_URL! },
secret: process.env.KAVACH_SECRET!,
baseUrl: 'https://auth.example.com',
plugins: [
emailPassword({
appUrl: 'https://app.example.com',
onPasswordValidate: hibp.enforce,
}),
],
});check() and enforce()
The module exposes two methods with different failure modes:
const hibp = createHibpModule();
// Returns { breached: true, count: 4830 } or { breached: false, count: 0 }
const result = await hibp.check('user-entered-password');
if (result.breached) {
console.warn(`Password seen ${result.count} times in breaches`);
}
// Throws KavachError with code PASSWORD_BREACHED if compromised
await hibp.enforce('user-entered-password');check() is useful when you want to warn the user without blocking them. enforce() integrates directly into the password validation lifecycle and blocks the request.
How k-anonymity works
1. SHA-1 hash the password → "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8"
2. Send only the first 5 chars → GET /range/5BAA6
3. API returns ~500 hashes with that prefix
4. Check if the rest of the hash appears in the list
5. If yes, the password has been breached — never sent the full hashThe full password hash is never transmitted. The API response contains partial hashes from other users' passwords, so the provider cannot determine which password you were checking.
Custom API key
The HIBP Passwords API is free and does not require authentication, but a paid key removes rate limits:
const hibp = createHibpModule({
apiKey: process.env.HIBP_API_KEY, // optional
timeout: 3000, // ms before the check is skipped, not failed
});If the HIBP API is unreachable, check() returns { breached: false } and enforce() passes through. The default behavior is fail-open so a slow network does not block your users from registering. Set failClosed: true to change this.
Configuration reference
Prop
Type