# @suaveplan/hibp - Privacy-preserving Have I Been Pwned v3 client — k-anonymity range API, authenticated breach/paste API, LRU cache, differential-privacy mode ## Summary Privacy-preserving [Have I Been Pwned](https://haveibeenpwned.com/) v3 client. Universal — works in Node, Bun, Deno, browsers, edge workers. The only required runtime primitive is `fetch`. ## Documentation tree - [breach-client](./src/breach/breach-client.md) — `BreachClient` wraps the four authenticated HIBP v3 endpoints: `/breachedaccount/{email}`, `/breach/{name}`, `/breaches… - [rate-limit-gate](./src/breach/rate-limit-gate.md) — `RateLimitGate` enforces the HIBP v3 client-side rate-limit contract of one request per 1500 ms across the authenticate… - [retry-after](./src/breach/retry-after.md) — `parseRetryAfter` parses the `Retry-After` HTTP header per RFC 9110 §10.2.3 and returns the indicated delay in millisec… - [cache](./src/cache/cache.md) — _(no submodules)_ - lru — `LruHibpCache` is the default cache for `H… - [cache](./src/cache/cache.md) — _(no submodules)_ - lru — `LruHibpCache` is the default cache for `H… - [lru](./src/cache/lru.md) — `LruHibpCache` is the default cache for `HibpClient`. It holds up to `LRU_DEFAULT_CAPACITY` entries (10 000) with `L… - [null](./src/cache/null.md) — `NullHibpCache` is the always-miss adapter used by `HibpClient` when the caller explicitly opts out of caching (`cac… - [client](./src/client/client.md) — _(no submodules)_ - options.schema — `HibpClientOptionsSchema` is the Z… - [client](./src/client/client.md) — _(no submodules)_ - options.schema — `HibpClientOptionsSchema` is the Z… - [options.schema](./src/client/options.schema.md) — `HibpClientOptionsSchema` is the Zod boundary for the `HibpClient` constructor. It is `.strict()` — unknown keys cause… - [codes](./src/errors/codes.md) — Spec-canonical path for the HIBP error-code registry. This file is a thin re-export of the top-level `src/error-codes.t… - [hibp-error](./src/errors/hibp-error.md) — `HibpError` is the structured error class every failure path inside `@suaveplan/hibp` throws. It carries one of the sev… - [observability](./src/observability/metrics.md) — _(no submodules)_ - [metrics](./src/observability/metrics.md) — _(no submodules)_ - [privacy](./src/privacy/assert-no-leak.md) — _(no submodules)_ - [assert-no-leak](./src/privacy/assert-no-leak.md) — _(no submodules)_ - [range](./src/range/range.md) — _(no submodules)_ - [constant-time-compare](./src/range/constant-time-compare.md) — `constantTimeEqualsHex` compares two hex suffixes without early-exit on the first mismatching character. It is used by… - [differential-privacy](./src/range/differential-privacy.md) — `padPrefixes` generates the decoy prefixes used by the differential-privacy mode of `checkPassword`. Given the real 5-h… - [range-mode](./src/range/range-mode.md) — Houses the hash-mode dispatch utilities used by `checkPassword`: `hashSha1Prefix` (SHA-1 via `@noble/hashes/sha1`), `ha… - [range-stream-parse](./src/range/range-stream-parse.md) — Streams an HTTP response body line-by-line through a `TextDecoderStream` and a newline framer, yielding `{ suffix, coun… - [range-strict-frame](./src/range/range-strict-frame.md) — `parseRangeLine` is the per-line framer used by `streamParseRangeBody`. It enforces the documented Pwned Passwords line… - [range](./src/range/range.md) — _(no submodules)_ - [breach.schema](./src/schemas/breach.schema.md) — `BreachSchema` is the Zod schema mirroring the HIBP v3 `Breach` payload. It validates every documented field, so the re… - [paste.schema](./src/schemas/paste.schema.md) — `PasteSchema` validates the HIBP v3 `Paste` payload returned by `/pasteaccount/{email}`. Older paste records can omit `… - [pwned-result.schema](./src/schemas/pwned-result.schema.md) — `PwnedResultSchema` validates the runtime shape of every `checkPassword` result. It is the final boundary between the i… - [error-codes](./src/error-codes.md) — Hosts the canonical `HIBP_CODES` registry — exactly seven error codes that classify every failure path inside `@suavepl… - lru — `LruHibpCache` is the default cache for `H… - [cache](./src/cache/cache.md) — _(no submodules)_- lru — `LruHibpCache` is the default cache for `H… - [lru](./src/cache/lru.md) — `LruHibpCache` is the default cache for `HibpClient`. It holds up to `LRU_DEFAULT_CAPACITY` entries (10 000) with `L… - [null](./src/cache/null.md) — `NullHibpCache` is the always-miss adapter used by `HibpClient` when the caller explicitly opts out of caching (`cac… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [options.schema](./src/client/options.schema.md) — `HibpClientOptionsSchema` is the Zod boundary for the `HibpClient` constructor. It is `.strict()` — unknown keys cause… - [codes](./src/errors/codes.md) — Spec-canonical path for the HIBP error-code registry. This file is a thin re-export of the top-level `src/error-codes.t… - [hibp-error](./src/errors/hibp-error.md) — `HibpError` is the structured error class every failure path inside `@suaveplan/hibp` throws. It carries one of the sev… - [observability](./src/observability/metrics.md) — _(no submodules)_- [metrics](./src/observability/metrics.md) — _(no submodules)_- [privacy](./src/privacy/assert-no-leak.md) — _(no submodules)_- [assert-no-leak](./src/privacy/assert-no-leak.md) — _(no submodules)_- [range](./src/range/range.md) — _(no submodules)_- [constant-time-compare](./src/range/constant-time-compare.md) — `constantTimeEqualsHex` compares two hex suffixes without early-exit on the first mismatching character. It is used by… - [differential-privacy](./src/range/differential-privacy.md) — `padPrefixes` generates the decoy prefixes used by the differential-privacy mode of `checkPassword`. Given the real 5-h… - [range-mode](./src/range/range-mode.md) — Houses the hash-mode dispatch utilities used by `checkPassword`: `hashSha1Prefix` (SHA-1 via `@noble/hashes/sha1`), `ha… - [range-stream-parse](./src/range/range-stream-parse.md) — Streams an HTTP response body line-by-line through a `TextDecoderStream` and a newline framer, yielding `{ suffix, coun… - [range-strict-frame](./src/range/range-strict-frame.md) — `parseRangeLine` is the per-line framer used by `streamParseRangeBody`. It enforces the documented Pwned Passwords line… - [range](./src/range/range.md) — _(no submodules)_- [breach.schema](./src/schemas/breach.schema.md) — `BreachSchema` is the Zod schema mirroring the HIBP v3 `Breach` payload. It validates every documented field, so the re… - [paste.schema](./src/schemas/paste.schema.md) — `PasteSchema` validates the HIBP v3 `Paste` payload returned by `/pasteaccount/{email}`. Older paste records can omit `… - [pwned-result.schema](./src/schemas/pwned-result.schema.md) — `PwnedResultSchema` validates the runtime shape of every `checkPassword` result. It is the final boundary between the i… - [error-codes](./src/error-codes.md) — Hosts the canonical `HIBP_CODES` registry — exactly seven error codes that classify every failure path inside `@suavepl… - lru — `LruHibpCache` is the default cache for `H… - [cache](./src/cache/cache.md) — _(no submodules)_- lru — `LruHibpCache` is the default cache for `H… - [lru](./src/cache/lru.md) — `LruHibpCache` is the default cache for `HibpClient`. It holds up to `LRU_DEFAULT_CAPACITY` entries (10 000) with `L… - [null](./src/cache/null.md) — `NullHibpCache` is the always-miss adapter used by `HibpClient` when the caller explicitly opts out of caching (`cac… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [options.schema](./src/client/options.schema.md) — `HibpClientOptionsSchema` is the Zod boundary for the `HibpClient` constructor. It is `.strict()` — unknown keys cause… - [codes](./src/errors/codes.md) — Spec-canonical path for the HIBP error-code registry. This file is a thin re-export of the top-level `src/error-codes.t… - [hibp-error](./src/errors/hibp-error.md) — `HibpError` is the structured error class every failure path inside `@suaveplan/hibp` throws. It carries one of the sev… - [observability](./src/observability/metrics.md) — _(no submodules)_- [metrics](./src/observability/metrics.md) — _(no submodules)_- [privacy](./src/privacy/assert-no-leak.md) — _(no submodules)_- [assert-no-leak](./src/privacy/assert-no-leak.md) — _(no submodules)_- [range](./src/range/range.md) — _(no submodules)_- [constant-time-compare](./src/range/constant-time-compare.md) — `constantTimeEqualsHex` compares two hex suffixes without early-exit on the first mismatching character. It is used by… - [differential-privacy](./src/range/differential-privacy.md) — `padPrefixes` generates the decoy prefixes used by the differential-privacy mode of `checkPassword`. Given the real 5-h… - [range-mode](./src/range/range-mode.md) — Houses the hash-mode dispatch utilities used by `checkPassword`: `hashSha1Prefix` (SHA-1 via `@noble/hashes/sha1`), `ha… - [range-stream-parse](./src/range/range-stream-parse.md) — Streams an HTTP response body line-by-line through a `TextDecoderStream` and a newline framer, yielding `{ suffix, coun… - [range-strict-frame](./src/range/range-strict-frame.md) — `parseRangeLine` is the per-line framer used by `streamParseRangeBody`. It enforces the documented Pwned Passwords line… - [range](./src/range/range.md) — _(no submodules)_- [breach.schema](./src/schemas/breach.schema.md) — `BreachSchema` is the Zod schema mirroring the HIBP v3 `Breach` payload. It validates every documented field, so the re… - [paste.schema](./src/schemas/paste.schema.md) — `PasteSchema` validates the HIBP v3 `Paste` payload returned by `/pasteaccount/{email}`. Older paste records can omit `… - [pwned-result.schema](./src/schemas/pwned-result.schema.md) — `PwnedResultSchema` validates the runtime shape of every `checkPassword` result. It is the final boundary between the i… - [error-codes](./src/error-codes.md) — Hosts the canonical `HIBP_CODES` registry — exactly seven error codes that classify every failure path inside `@suavepl… - lru — `LruHibpCache` is the default cache for `H… - [cache](./src/cache/cache.md) — _(no submodules)_- lru — `LruHibpCache` is the default cache for `H… - [lru](./src/cache/lru.md) — `LruHibpCache` is the default cache for `HibpClient`. It holds up to `LRU_DEFAULT_CAPACITY` entries (10 000) with `L… - [null](./src/cache/null.md) — `NullHibpCache` is the always-miss adapter used by `HibpClient` when the caller explicitly opts out of caching (`cac… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [client](./src/client/client.md) — _(no submodules)_- options.schema — `HibpClientOptionsSchema` is the Z… - [options.schema](./src/client/options.schema.md) — `HibpClientOptionsSchema` is the Zod boundary for the `HibpClient` constructor. It is `.strict()` — unknown keys cause… - [codes](./src/errors/codes.md) — Spec-canonical path for the HIBP error-code registry. This file is a thin re-export of the top-level `src/error-codes.t… - [hibp-error](./src/errors/hibp-error.md) — `HibpError` is the structured error class every failure path inside `@suaveplan/hibp` throws. It carries one of the sev… - [observability](./src/observability/metrics.md) — _(no submodules)_- [metrics](./src/observability/metrics.md) — _(no submodules)_- [privacy](./src/privacy/assert-no-leak.md) — _(no submodules)_- [assert-no-leak](./src/privacy/assert-no-leak.md) — _(no submodules)_- [range](./src/range/range.md) — _(no submodules)_- [constant-time-compare](./src/range/constant-time-compare.md) — `constantTimeEqualsHex` compares two hex suffixes without early-exit on the first mismatching character. It is used by… - [differential-privacy](./src/range/differential-privacy.md) — `padPrefixes` generates the decoy prefixes used by the differential-privacy mode of `checkPassword`. Given the real 5-h… - [range-mode](./src/range/range-mode.md) — Houses the hash-mode dispatch utilities used by `checkPassword`: `hashSha1Prefix` (SHA-1 via `@noble/hashes/sha1`), `ha… - [range-stream-parse](./src/range/range-stream-parse.md) — Streams an HTTP response body line-by-line through a `TextDecoderStream` and a newline framer, yielding `{ suffix, coun… - [range-strict-frame](./src/range/range-strict-frame.md) — `parseRangeLine` is the per-line framer used by `streamParseRangeBody`. It enforces the documented Pwned Passwords line… - [range](./src/range/range.md) — _(no submodules)_- [breach.schema](./src/schemas/breach.schema.md) — `BreachSchema` is the Zod schema mirroring the HIBP v3 `Breach` payload. It validates every documented field, so the re… - [paste.schema](./src/schemas/paste.schema.md) — `PasteSchema` validates the HIBP v3 `Paste` payload returned by `/pasteaccount/{email}`. Older paste records can omit `… - [pwned-result.schema](./src/schemas/pwned-result.schema.md) — `PwnedResultSchema` validates the runtime shape of every `checkPassword` result. It is the final boundary between the i… - [error-codes](./src/error-codes.md) — Hosts the canonical `HIBP_CODES` registry — exactly seven error codes that classify every failure path inside `@suavepl… - [cache](./src/cache/cache.md) — _(no submodules)_- [lru](./src/cache/lru.md) — `LruHibpCache` is the default cache for `HibpClient`. It holds up to `LRU_DEFAULT_CAPACITY` entries (10 000) with `L… - [null](./src/cache/null.md) — `NullHibpCache` is the always-miss adapter used by `HibpClient` when the caller explicitly opts out of caching (`cac… - [client](./src/client/client.md) — _(no submodules)_ - [client](./src/client/client.md) — _(no submodules)_ - [options.schema](./src/client/options.schema.md) — `HibpClientOptionsSchema` is the Zod boundary for the `HibpClient` constructor. It is `.strict()` — unknown keys cause… - [codes](./src/errors/codes.md) — Spec-canonical path for the HIBP error-code registry. This file is a thin re-export of the top-level `src/error-codes.t… - [hibp-error](./src/errors/hibp-error.md) — `HibpError` is the structured error class every failure path inside `@suaveplan/hibp` throws. It carries one of the sev… - [observability](./src/observability/metrics.md) — _(no submodules)_ - [metrics](./src/observability/metrics.md) — _(no submodules)_- [privacy](./src/privacy/assert-no-leak.md) — _(no submodules)_ - [assert-no-leak](./src/privacy/assert-no-leak.md) — _(no submodules)_- [range](./src/range/range.md) — _(no submodules)_ - [constant-time-compare](./src/range/constant-time-compare.md) — `constantTimeEqualsHex` compares two hex suffixes without early-exit on the first mismatching character. It is used by… - [differential-privacy](./src/range/differential-privacy.md) — `padPrefixes` generates the decoy prefixes used by the differential-privacy mode of `checkPassword`. Given the real 5-h… - [range-mode](./src/range/range-mode.md) — Houses the hash-mode dispatch utilities used by `checkPassword`: `hashSha1Prefix` (SHA-1 via `@noble/hashes/sha1`), `ha… - [range-stream-parse](./src/range/range-stream-parse.md) — Streams an HTTP response body line-by-line through a `TextDecoderStream` and a newline framer, yielding `{ suffix, coun… - [range-strict-frame](./src/range/range-strict-frame.md) — `parseRangeLine` is the per-line framer used by `streamParseRangeBody`. It enforces the documented Pwned Passwords line… - [range](./src/range/range.md) — _(no submodules)_- [breach.schema](./src/schemas/breach.schema.md) — `BreachSchema` is the Zod schema mirroring the [HIBP v3 `Breach` payload](https://haveibeenpwned.com/API/v3#Breach). It… - [paste.schema](./src/schemas/paste.schema.md) — `PasteSchema` validates the [HIBP v3 `Paste` payload](https://haveibeenpwned.com/API/v3#PastesForAccount) returned by `… - [pwned-result.schema](./src/schemas/pwned-result.schema.md) — `PwnedResultSchema` validates the runtime shape of every `checkPassword` result. It is the final boundary between the i… - [error-codes](./src/error-codes.md) — Hosts the canonical `HIBP_CODES` registry — exactly seven error codes that classify every failure path inside `@suavepl… ## Privacy contract This package treats the password and its SHA-1 digest as secrets that must never leave the calling process. - Only the **first five hex characters** of the SHA-1 digest are transmitted to the Pwned Passwords range API ([k-anonymity](https://haveibeenpwned.com/API/v3#PwnedPasswords)). - Every range request carries the mandatory `Add-Padding: true` header so Cloudflare appends decoy lines, defeating response-size correlation attacks. - The full password, the full hash, the API key, and the email are never logged and never appear in any thrown error's serialized form. - An opt-in `assertOutgoingRequestSafe: true` switch wraps every outgoing fetch with a byte-by-byte leak check so callers can catch a regression before it lands in production. If any of those invariants ever break, that is a defect, not a feature request. ## Install ```bash bun add @suaveplan/hibp ``` ## Quick start ### Check a single password against the range API ```ts import { HibpClient } from "@suaveplan/hibp"; const client = new HibpClient(); const result = await client.checkPassword("hunter2"); // { pwned: true, count: 23_597 } ``` ### Pull the breach list for an account ```ts import { HibpClient } from "@suaveplan/hibp"; const client = new HibpClient({ apiKey: process.env.HIBP_API_KEY! }); const breaches = await client.getBreachesForAccount("user@example.com"); // Breach[] ``` ## Differential-privacy mode For deployments that want belt-and-braces defence on top of the standard `Add-Padding` padding, opt in to decoy prefixes. Every `checkPassword` call will dispatch the real 5-hex prefix plus N additional random sibling prefixes; only the real one's response is consulted. ```ts const client = new HibpClient({ differentialPrivacy: { extraPrefixes: 3 }, rng: myInjectableRng, }); ``` Trade-offs: - **Adds N extra round trips per check.** Default off because the `Add-Padding` header already defeats the documented correlation attack for almost every deployment. - **Decoy responses are discarded** but still count against any caching layer's bandwidth budget. Tune `extraPrefixes` to match the operational cost. ## Modules | Path | Purpose | | --- | --- | | [`src/client/client.md`](src/client/client.md) | `HibpClient` façade orchestrating range + breach + cache + observability | | [`src/client/options.schema.md`](src/client/options.schema.md) | Zod boundary for constructor options | | [`src/range/range.md`](src/range/range.md) | k-anonymity range API client | | [`src/range/range-mode.md`](src/range/range-mode.md) | SHA-1 / NTLM mode dispatch | | [`src/range/range-stream-parse.md`](src/range/range-stream-parse.md) | Streaming line parser | | [`src/range/range-strict-frame.md`](src/range/range-strict-frame.md) | Strict-mode line framing | | [`src/range/constant-time-compare.md`](src/range/constant-time-compare.md) | Constant-time suffix compare | | [`src/range/differential-privacy.md`](src/range/differential-privacy.md) | Decoy prefix generator | | [`src/breach/breach-client.md`](src/breach/breach-client.md) | Authenticated breach + paste API client | | [`src/breach/rate-limit-gate.md`](src/breach/rate-limit-gate.md) | 1500 ms client-side rate-limit gate | | [`src/breach/retry-after.md`](src/breach/retry-after.md) | RFC 9110 `Retry-After` parser | | [`src/cache/cache.md`](src/cache/cache.md) | `HibpCacheLike` interface | | [`src/cache/lru.md`](src/cache/lru.md) | Default LRU cache (10 000 / 24 h) | | [`src/cache/null.md`](src/cache/null.md) | Always-miss cache adapter | | [`src/errors/codes.md`](src/errors/codes.md) | Seven-code error registry | | [`src/errors/hibp-error.md`](src/errors/hibp-error.md) | `HibpError` base class | | [`src/error-codes.md`](src/error-codes.md) | Top-level code registration | | [`src/observability/metrics.md`](src/observability/metrics.md) | OTel histograms + counters | | [`src/privacy/assert-no-leak.md`](src/privacy/assert-no-leak.md) | Outgoing-request leak detector | | [`src/schemas/breach.schema.md`](src/schemas/breach.schema.md) | `BreachSchema` Zod schema | | [`src/schemas/paste.schema.md`](src/schemas/paste.schema.md) | `PasteSchema` Zod schema | | [`src/schemas/pwned-result.schema.md`](src/schemas/pwned-result.schema.md) | `PwnedResultSchema` Zod schema | ## Benchmarks | Benchmark | Description | | --- | --- | | `range-cold` | Cold range fetch + parse | | `range-warm-cache` | LRU hit path | | `range-parse-30kib` | Streaming parser throughput on a 30 KiB padded fixture | Run with `bun run benchmark` (see `src/__benchmarks__/`). ## License MIT