Skip to content

feat(hash): pwned-password check (Have I Been Pwned, k-anonymity)#83

Merged
lyfuci merged 1 commit into
mainfrom
feat/hash-pwned-check
May 29, 2026
Merged

feat(hash): pwned-password check (Have I Been Pwned, k-anonymity)#83
lyfuci merged 1 commit into
mainfrom
feat/hash-pwned-check

Conversation

@lyfuci

@lyfuci lyfuci commented May 29, 2026

Copy link
Copy Markdown
Owner

What

A Pwned password check in the Hash tool: enter a password (text mode) and click Check to see whether it's appeared in known breach corpora — so you can avoid easily-cracked passwords. This is the rainbow-table-style "is this crackable?" check requested.

How — privacy-preserving by design

Uses the Have I Been Pwned: Pwned Passwords API with k-anonymity (src/lib/hibp.ts):

  1. SHA-1 the password locally.
  2. Send only the first 5 hex chars of the hash to …/range/{prefix} (a plain GET, no custom headers → a CORS simple request, no preflight).
  3. Match the returned suffix list in-page and read the breach count.

The password itself never leaves the browser — the server only sees a 5-char prefix shared by thousands of hashes.

  • Explicit-trigger only (a Check button), per the repo's no-silent-network rule (same pattern as IP Info / DNS). The result resets when the input is edited.
  • Strips the textarea's trailing newline before checking; internal/leading spaces are preserved (passwords can contain them).
  • Meaning-correct wording: "found in N known breaches" vs "not found in any known breach (not a strength guarantee)" — deliberately never says "safe"/"strong", since not-found ≠ strong.

Verification

  • pnpm typecheck · pnpm lint · pnpm build — clean; 208 tests green.
  • 5 unit tests with an injected fetch — including the FOUND case asserted on the real SHA-1 of password (5BAA6 / …68FD8), so the silent-failure bugs (lowercase hex, wrong slice, prefix-in-suffix — all of which look like "not found") can't pass.
  • Real end-to-end call in the COEP cross-origin-isolated app via headless Chrome — this is the load-bearing check that CORS + COEP + CSP all permit it: crossOriginIsolated=true, HTTP 200, password52,256,179, a random string → 0; the UI transitions Checking… → the breach count.

EN + zh-CN strings.

🤖 Generated with Claude Code

Add a "Pwned password check" to the Hash tool — tells the user whether the text
they typed has appeared in known breach corpora, so they can avoid easily
cracked passwords.

- src/lib/hibp.ts: pwnedPasswordCount() — SHA-1 the input locally, send only
  the first 5 hex chars to the HIBP range API (k-anonymity; a plain GET with no
  custom headers → a CORS "simple request", no preflight), and match the
  returned suffixes in-page. The password never leaves the browser — only a hash
  prefix shared by thousands of hashes does.
- Explicit-trigger only (a "Check" button) per the no-silent-network rule; the
  result resets when the input is edited. Strips the textarea's trailing newline
  before checking (internal/leading spaces are preserved — passwords can contain
  them).
- Meaning-correct wording: "found in N known breaches" vs "not found in any
  known breach (not a strength guarantee)" — never "safe"/"strong".

5 unit tests with an injected fetch, including the FOUND case asserted on the
REAL SHA-1 of "password" so the silent-failure bugs (lowercase hex / wrong
slice / prefix left in the suffix) can't pass as "not found". typecheck / lint /
build clean, 208 tests green. Verified the real call end-to-end in the
COEP-isolated app via headless Chrome (crossOriginIsolated=true, HTTP 200,
"password" → 52,256,179, random → 0; the UI renders the breach count).

EN + zh-CN strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lyfuci lyfuci merged commit 8eca341 into main May 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant