Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
# Tasks — `@suaveplan/hibp` (privacy-preserving HIBP client)
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/enterprise/hibp.md` end-to-end
- [x] 1.2 Read [HIBP API v3](https://haveibeenpwned.com/API/v3) — every endpoint, header, rate-limit, status-code anchor
- [x] 1.3 Read [Pwned Passwords range API](https://haveibeenpwned.com/API/v3#PwnedPasswords) and [`Add-Padding` notes](https://haveibeenpwned.com/API/v3#PwnedPasswordsPadding)
- [x] 1.4 Read Cloudflare _Validating Leaked Passwords with k-Anonymity_ post for the privacy threat model
- [x] 1.5 Read `openspec/AGENTS.md` Sections 13, 14, 14a
- [x] 1.6 Draft `proposal.md` from `openspec/templates/proposal.md`
- [x] 1.7 Fill in `## Source mapping` table — every requirement cites either an `enterprise/hibp.md` line range or a documented API v3 anchor
- [x] 1.8 Draft `specs/hibp/spec.md` from `openspec/templates/spec-delta.md`
- [x] 1.9 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
- [x] 1.10 `bun scripts/openspec-conformance.ts @suaveplan/hibp` clean
## 2. Package scaffold
- [x] 2.1 `packages/security/hibp/src/{range,breach,cache,client,errors,observability,privacy,schemas}/` directories created
- [x] 2.2 `package.json` — `@suaveplan/hibp@0.0.0`, `type: module`, `sideEffects: false`, deps via catalog/workspace refs (`@suaveplan/crypto-hash`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/timing`, `@opentelemetry/api`, `tslib`, `zod`)
- [x] 2.3 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json`
- [x] 2.4 `vite.config.ts` and `vitest.config.ts` (latter via `createTestConfig()` from `@suaveplan/testing/config`)
- [x] 2.5 `README.md` with package description, install snippet, quick-start, and `## Modules` table linking every co-located `.md`
## 3. Implementation — range API (k-anonymity)
- [x] 3.1 `src/range/range.ts` — `checkPassword(password, opts?)` SHA-1s the input, slices first 5 hex chars, dispatches range fetch, returns `{ pwned, count }`
- [x] 3.2 `src/range/range.ts` — sets `Add-Padding: true` on every outgoing request (mandatory; not configurable off)
- [x] 3.3 `src/range/range-mode.ts` — `RangeMode = "sha1" | "ntlm"`; NTLM appends `?mode=ntlm` and routes to the NTLM hash adapter
- [x] 3.4 `src/range/range-stream-parse.ts` — line streamer over `Response.body` via `TextDecoderStream` and newline framing; never collects to a single string
- [x] 3.5 `src/range/range-strict-frame.ts` — strict-mode validator rejecting lowercase hex / non-`/^[0-9A-F]{35}:[0-9]+$/` lines with `HIBP.MALFORMED_RESPONSE`
- [x] 3.6 `src/range/constant-time-compare.ts` — `constantTimeEqualsHex(a, b)` XOR-folds the full 35-char range with no early exit
- [x] 3.7 `src/range/differential-privacy.ts` — `padPrefixes(real, n, rng)` returning real plus N uniformly random sibling prefixes
- [x] 3.8 AbortSignal threaded through `checkPassword` and the inner streaming parser
## 4. Implementation — authenticated breach / paste API
- [x] 4.1 `src/breach/breach-client.ts` — `getBreachesForAccount(email, opts?)` builds `https://haveibeenpwned.com/api/v3/breachedaccount/{email}?truncateResponse=false&includeUnverified=true`; email URL-encoded; `hibp-api-key` header set
- [x] 4.2 `src/breach/breach-client.ts` — `getBreach(name)` against `/breach/{name}`
- [x] 4.3 `src/breach/breach-client.ts` — `getAllBreaches(opts?)` against `/breaches` with optional `domain` query param
- [x] 4.4 `src/breach/breach-client.ts` — `getPastesForAccount(email)` against `/pasteaccount/{email}`
- [x] 4.5 `src/breach/rate-limit-gate.ts` — `RateLimitGate` enforcing 1500 ms minimum gap on breach + paste endpoints; injectable `Clock`
- [x] 4.6 `src/breach/retry-after.ts` — `parseRetryAfter` honours both delta-seconds and HTTP-date forms; integrates with `@suaveplan/retry` `RetryPolicy`; cap at 60 s
- [x] 4.7 `src/breach/breach-client.ts` — never logs the email, never logs `hibp-api-key`, never echoes them into thrown errors
- [x] 4.8 AbortSignal threaded through every public method
## 5. Implementation — cache layer
- [x] 5.1 `src/cache/cache.ts` — `HibpCacheLike<V>` interface (`get`, `set`, `delete`, `size`)
- [x] 5.2 `src/cache/lru.ts` — `LruHibpCache` with default 10 000 entries, 24 h TTL, key normalization to uppercase hex
- [x] 5.3 `src/cache/null.ts` — `NullHibpCache` always-miss adapter
- [x] 5.4 `HibpClientOptions.cache?: HibpCacheLike` accepted; `cache: null` disables; absent uses `LruHibpCache` default
## 6. Implementation — schemas and errors
- [x] 6.1 `src/schemas/breach.schema.ts` — `BreachSchema` matching API v3 `Breach` shape (every field, including `IsStealerLog`)
- [x] 6.2 `src/schemas/paste.schema.ts` — `PasteSchema` matching API v3 `Paste` shape
- [x] 6.3 `src/schemas/pwned-result.schema.ts` — `PwnedResultSchema` (`pwned: boolean`, `count: int >= 0`)
- [x] 6.4 `src/client/options.schema.ts` — `HibpClientOptionsSchema` strict object; rejects `apiKey === ""`, `extraPrefixes < 0`, unknown keys
- [x] 6.5 `src/errors/codes.ts` — `defineCodes("HIBP", [...])` with all seven codes (`RATE_LIMITED`, `UNAUTHORIZED`, `NOT_FOUND`, `MALFORMED_RESPONSE`, `NETWORK_ERROR`, `ABORTED`, `PAYLOAD_TOO_LARGE`)
- [x] 6.6 `src/errors/hibp-error.ts` — `HibpError extends SuaveplanError<HibpErrorCode>` with stable JSON form
- [x] 6.7 Every code in the registry thrown by ≥1 production path (no decorative entries)
## 7. Implementation — observability
- [x] 7.1 `src/observability/metrics.ts` — `hibp.range.duration_ms` and `hibp.breach.duration_ms` histograms
- [x] 7.2 `src/observability/metrics.ts` — `hibp.range.outcome_total{outcome=pwned|clean|error}` counter
- [x] 7.3 `src/observability/metrics.ts` — `hibp.errors_total{code}` counter
- [x] 7.4 `src/observability/metrics.ts` — `hibp.rate_limit_throttled_total` counter
- [x] 7.5 Histograms recorded on every `checkPassword`, `getBreachesForAccount`, `getBreach`, `getAllBreaches`, `getPastesForAccount` exit path
## 8. Implementation — privacy assertions
- [x] 8.1 `src/privacy/assert-no-leak.ts` — scans an outgoing `Request` URL and body byte-by-byte for the full SHA-1 hash, the full password, and the configured email
- [x] 8.2 `HibpClientOptions.assertOutgoingRequestSafe?: boolean` opt-in production gate (default off, defence-in-depth)
- [x] 8.3 Privacy assertion thrown errors carry `HIBP.MALFORMED_RESPONSE` (privacy violation is a malformed-self-state condition)
## 9. Client orchestrator + barrels
- [x] 9.1 `src/client/client.ts` — `HibpClient` composes range + breach + cache + rate-limit + observability + privacy
- [x] 9.2 Constructor takes `HibpClientOptions` (api key, fetch, clock, rng, cache, rate-limit gate, observability, strict mode, differential-privacy mode, opt-in privacy assertions)
- [x] 9.3 `src/index.ts` — explicit named re-exports across all sub-namespaces (no `export *` per CLAUDE.md rule 10)
- [x] 9.4 `package.json` `exports` map covers every documented subpath
## 10. Documentation
- [x] 10.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
- [x] 10.2 `README.md` `## Modules` section links every co-located `.md`
- [x] 10.3 README documents the privacy contract verbatim (full password and full hash never leave the function)
- [x] 10.4 README documents the differential-privacy mode trade-offs
## 11. Tests (runner-neutral imports per CLAUDE.md rule 11)
- [x] 11.1 `range/range.test.ts` — k-anonymity slice, `Add-Padding` header, NTLM mode, OTel emission, AbortSignal
- [x] 11.2 `range/range-stream-parse.test.ts` — multi-chunk 30 KiB streaming decode without single-string buffering
- [x] 11.3 `range/range-strict-frame.test.ts` — strict-mode rejects lowercase / malformed `:count` / non-uppercase hex
- [x] 11.4 `range/range-mode.test.ts` — NTLM URL + adapter routing
- [x] 11.5 `range/constant-time-compare.test.ts` — every byte touched on both equal and unequal inputs; differential-timing test
- [x] 11.6 `range/differential-privacy.test.ts` — `padPrefixes` returns N+1 entries with real present
- [x] 11.7 `breach/breach-client.test.ts` — every endpoint URL, query, header; AbortSignal; Zod validation
- [x] 11.8 `breach/rate-limit-gate.test.ts` — 1500 ms gate; injectable clock; `hibp.rate_limit_throttled_total` increments
- [x] 11.9 `breach/retry-after.test.ts` — delta-seconds and HTTP-date forms; 60 s cap; `@suaveplan/retry` integration
- [x] 11.10 `cache/cache.test.ts` — interface type-level conformance
- [x] 11.11 `cache/lru.test.ts` — capacity eviction; TTL eviction; uppercase normalization
- [x] 11.12 `cache/null.test.ts` — always-miss; `set` is a no-op
- [x] 11.13 `client/client.test.ts` — full composition; `cache: null` disables; default `LruHibpCache`
- [x] 11.14 `client/options.schema.test.ts` — every Zod rejection path
- [x] 11.15 `schemas/breach.schema.test.ts` — recorded `/breach/Adobe` fixture round-trip
- [x] 11.16 `schemas/paste.schema.test.ts` — recorded `/pasteaccount/...` fixture round-trip
- [x] 11.17 `schemas/pwned-result.schema.test.ts` — accepts valid; rejects negative count
- [x] 11.18 `errors/codes.test.ts` — registry shape, no duplicate keys
- [x] 11.19 `errors/hibp-error.test.ts` — JSON serialization, `code` + `cause` carriage
- [x] 11.20 `observability/metrics.test.ts` — every histogram and counter against an in-memory `MeterProvider`
- [x] 11.21 `privacy/assert-no-leak.test.ts` — full SHA-1 → throws; prefix-only → passes; full password → throws; email → throws
- [x] 11.22 `privacy/range-no-leak.spec-conformance.test.ts` — 1 000-iteration `checkPassword("hunter2")` interception; zero leaks across all outgoing URLs and bodies
- [x] 11.23 `privacy/breach-no-log.spec-conformance.test.ts` — `LoggerLike` capture; email never appears in logs
- [x] 11.24 `index.test.ts` — every documented subpath export resolves
- [x] 11.25 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for network-bound integration cases — none expected since fetch is injected)
- [x] 11.26 Runner-neutral imports — CLAUDE.md §11 grep clean
## 12. Benchmarks (mitata)
- [x] 12.1 `src/__benchmarks__/range-cold.bench.ts` — cold range fetch + parse
- [x] 12.2 `src/__benchmarks__/range-warm-cache.bench.ts` — LRU hit path
- [x] 12.3 `src/__benchmarks__/range-parse-30kib.bench.ts` — streaming parser throughput on a 30 KiB padded fixture
- [x] 12.4 Baseline numbers captured in README
## 13. Pre-archive verification (the 5-gate triple)
- [x] 13.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info
- [x] 13.2 `bunx turbo lint typecheck test --filter=@suaveplan/hibp` — green
- [x] 13.3 `bunx turbo test:coverage --filter=@suaveplan/hibp` — 100/100/100/100
- [x] 13.4 `bun scripts/openspec-conformance.ts @suaveplan/hibp` — exit 0, no `error` findings
- [x] 13.5 `bunx turbo build --filter=@suaveplan/hibp` — green
## 14. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [x] 14.1 Generics threaded; `expectTypeOf` assertions on `HibpCacheLike<V>` and `HibpClientOptions`
- [x] 14.2 Zod schemas wired at every declared boundary (range result, breach response, paste response, options)
- [x] 14.3 Every error code in `errors/codes.ts` thrown by at least one production path
- [x] 14.4 Strict-mode framing tested both on (default) and off
- [x] 14.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
- [x] 14.6 Lifecycle dedup — N/A (`HibpClient` is stateless beyond its cache; cache `set` is idempotent and tested)
- [x] 14.7 No retroactive tick — every box above reflects work done in this change
## 15. Archive
- [x] 15.1 Fold spec delta into `openspec/specs/hibp/spec.md`
- [x] 15.2 Move `openspec/changes/enterprise-hibp/` → `openspec/archive/<yyyy-mm-dd>-enterprise-hibp/`
- [x] 15.3 Refresh `MANIFEST.md` (`bun scripts/folder-reorg/generate-manifest.ts`)
- [x] 15.4 Update `STATUS.md` row from Pending → Completed
- [x] 15.5 Update `CLAUDE.md` package listing under `#### Security / supply chain`