dbsc-toolkit 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +51 -257
  2. package/dist/client/clockSync.d.ts +2 -0
  3. package/dist/client/clockSync.d.ts.map +1 -0
  4. package/dist/client/clockSync.js +17 -0
  5. package/dist/client/clockSync.js.map +1 -0
  6. package/dist/client/index.d.ts +2 -0
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +5 -0
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/keystore.d.ts +1 -0
  11. package/dist/client/keystore.d.ts.map +1 -1
  12. package/dist/client/keystore.js.map +1 -1
  13. package/dist/client/wrapFetch.d.ts +19 -0
  14. package/dist/client/wrapFetch.d.ts.map +1 -0
  15. package/dist/client/wrapFetch.js +27 -0
  16. package/dist/client/wrapFetch.js.map +1 -0
  17. package/dist/core/bound/index.d.ts +2 -0
  18. package/dist/core/bound/index.d.ts.map +1 -1
  19. package/dist/core/bound/index.js +1 -0
  20. package/dist/core/bound/index.js.map +1 -1
  21. package/dist/core/bound/proof.d.ts +15 -0
  22. package/dist/core/bound/proof.d.ts.map +1 -0
  23. package/dist/core/bound/proof.js +39 -0
  24. package/dist/core/bound/proof.js.map +1 -0
  25. package/dist/core/errors.d.ts +2 -0
  26. package/dist/core/errors.d.ts.map +1 -1
  27. package/dist/core/errors.js +2 -0
  28. package/dist/core/errors.js.map +1 -1
  29. package/dist/core/index.d.ts +3 -0
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +2 -0
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/express/index.d.ts +2 -0
  34. package/dist/express/index.d.ts.map +1 -1
  35. package/dist/express/index.js +4 -0
  36. package/dist/express/index.js.map +1 -1
  37. package/dist/express/proof.d.ts +19 -0
  38. package/dist/express/proof.d.ts.map +1 -0
  39. package/dist/express/proof.js +41 -0
  40. package/dist/express/proof.js.map +1 -0
  41. package/dist/fastify/index.d.ts +2 -0
  42. package/dist/fastify/index.d.ts.map +1 -1
  43. package/dist/fastify/index.js +4 -0
  44. package/dist/fastify/index.js.map +1 -1
  45. package/dist/fastify/proof.d.ts +13 -0
  46. package/dist/fastify/proof.d.ts.map +1 -0
  47. package/dist/fastify/proof.js +36 -0
  48. package/dist/fastify/proof.js.map +1 -0
  49. package/dist/hono/index.d.ts +2 -0
  50. package/dist/hono/index.d.ts.map +1 -1
  51. package/dist/hono/index.js +4 -0
  52. package/dist/hono/index.js.map +1 -1
  53. package/dist/hono/proof.d.ts +13 -0
  54. package/dist/hono/proof.d.ts.map +1 -0
  55. package/dist/hono/proof.js +37 -0
  56. package/dist/hono/proof.js.map +1 -0
  57. package/dist/nextjs/index.d.ts +2 -0
  58. package/dist/nextjs/index.d.ts.map +1 -1
  59. package/dist/nextjs/index.js +25 -8
  60. package/dist/nextjs/index.js.map +1 -1
  61. package/dist/nextjs/proof.d.ts +30 -0
  62. package/dist/nextjs/proof.d.ts.map +1 -0
  63. package/dist/nextjs/proof.js +43 -0
  64. package/dist/nextjs/proof.js.map +1 -0
  65. package/package.json +1 -1
package/README.md CHANGED
@@ -5,36 +5,33 @@
5
5
  [![License](https://img.shields.io/npm/l/dbsc-toolkit.svg)](./LICENSE)
6
6
  [![Node](https://img.shields.io/node/v/dbsc-toolkit.svg)](https://nodejs.org)
7
7
 
8
- Server-side implementation of [Device Bound Session Credentials](https://w3c.github.io/webappsec-dbsc/) (DBSC) for Node.js, with a silent Web Crypto polyfill for browsers that don't ship DBSC natively.
8
+ ## The problem this solves
9
9
 
10
- DBSC binds session cookies to a hardware-resident private key inside the browser. A stolen cookie is useless without that key which never leaves the user's device. Chromium 145+ does this natively. Firefox, Safari, and older Chromium browsers get the same cryptographic refresh-signing protection via a Web Crypto polyfill that activates automatically after login.
10
+ When a user logs in to your app, the server hands the browser a session cookie. From that point on, the cookie *is* the user. Every request carrying it gets treated as authenticated. That's the soft spot: if an attacker ever gets a copy of the cookie (via XSS, malware on the user's machine, a leaked log file, a misconfigured proxy), they paste it into their own browser and they *are* your user. No password prompt, no MFA, no second factor. Cookies are portable by design, and that portability is exactly what makes them stealable.
11
11
 
12
- | Browser | Tier | Key location |
13
- |---------|------|--------------|
14
- | Chromium 145+ (Chrome, Edge, Brave, Opera, Arc, Vivaldi) | `dbsc` | TPM / Secure Enclave / Android Keystore |
15
- | Firefox, Safari, older Chromium | `bound` | Browser keystore (non-extractable Web Crypto key) |
12
+ ## What this library does
16
13
 
17
- Both tiers defeat the entire "stolen cookie replayed from another device" class of attack. `dbsc` additionally defeats infostealer malware reading the browser profile directory the `bound` polyfill key is software-bound.
14
+ Device Bound Session Credentials ([DBSC](https://w3c.github.io/webappsec-dbsc/)) is a new W3C standard that breaks that portability. When the user logs in, the browser generates a private cryptographic key on the user's device, inside the TPM chip on Windows, the Secure Enclave on Macs, or the Android Keystore on phones. The public half goes to your server. Every few minutes the browser proves it still has the private key by signing a fresh server-issued challenge. A copied cookie pasted into another machine has no matching key, so refresh fails and the session dies within minutes.
18
15
 
19
- Verified end-to-end against Chrome 147 on Windows with a real TPM 2.0.
16
+ Chromium 145+ does this natively (Chrome, Edge, Brave, Opera, Arc, Vivaldi). For browsers that don't ship DBSC yet (Firefox, Safari, older Chromium), this library also includes a Web Crypto polyfill that delivers the same protection against remote cookie theft. It activates silently after login with no biometric prompt and no user interaction. The polyfill key lives in the browser's own keystore (IndexedDB) instead of a hardware chip, so it's a notch weaker against malware running on the user's own machine, but it still defeats every remote-theft scenario.
20
17
 
21
- **New here?** Read [HOW-IT-WORKS.md](./HOW-IT-WORKS.md) first.
18
+ The library exposes both paths as a single `tier` string your route handlers gate on:
22
19
 
23
- ## Live demo
24
-
25
- Try it: <https://dbsc-toolkit.onrender.com/>
20
+ | Browser | `tier` value | Where the private key lives |
21
+ |---------|--------------|------------------------------|
22
+ | Chromium 145+ (Chrome, Edge, Brave, Opera, Arc, Vivaldi) | `"dbsc"` | TPM / Secure Enclave / Android Keystore |
23
+ | Firefox, Safari, older Chromium | `"bound"` | Browser's own keystore (non-extractable IndexedDB key) |
24
+ | No active binding (logged out, polyfill not loaded, etc.) | `"none"` | n/a |
26
25
 
27
- Sign up, log in, then click **Check session**:
28
- - On Chromium 145+ you land on `tier: "dbsc"` within a second of login.
29
- - On Firefox/Safari/older Chromium you land on `tier: "bound"` within ~3 seconds (the polyfill activates silently).
26
+ This package is the server-side implementation: middleware for Express / Fastify / Hono / Next.js, storage adapters for memory / Redis / Postgres, and a small browser SDK that drives the polyfill on non-Chromium browsers.
30
27
 
31
- The demo uses a 60-second bound-cookie TTL so refresh fires fast — watch DevTools Network for `POST /dbsc/refresh` (native) or `POST /dbsc-bound/refresh` (polyfill) after the cookie expires. Source in [examples/express/](./examples/express/).
28
+ **New here?** Read [HOW-IT-WORKS.md](./HOW-IT-WORKS.md) for the 15-minute walk-through.
32
29
 
33
- The demo runs on `RedisStorage` (Upstash) by default, so sessions survive deploys, cold starts, and laptop reboots. Locally without `REDIS_URL`, it falls back to `MemoryStorage` — fine for one terminal session, wiped on every restart.
30
+ ## Live demo
34
31
 
35
- Two protected routes show the gating pattern: `/profile` requires `tier === "dbsc"` specifically (TPM-only flows); `/profile-soft` accepts either `"dbsc"` or `"bound"`.
32
+ Try it: <https://dbsc-toolkit.onrender.com/>
36
33
 
37
- > **Hitting `not authenticated` after a few login/logout cycles?** That's Chrome's DBSC quota — the browser's anti-abuse throttle. The demo surfaces it explicitly now (red banner + reason text in the response). To recover: `chrome://settings/clearBrowserData` Last hour Cookies and site data clear, or open an Incognito window. The quota is per `(browser install, origin)`, so production users (who log in once and stay logged in) essentially never trip it.
34
+ Sign up, log in, click **Check session**. Chromium 145+ lands on `tier: "dbsc"` within a second; Firefox/Safari land on `tier: "bound"` within ~3 seconds. The demo uses a 60-second bound-cookie TTL so refresh fires fast. Open DevTools Network and watch. Source in [examples/express/](./examples/express/).
38
35
 
39
36
  ## Install
40
37
 
@@ -42,20 +39,14 @@ Two protected routes show the gating pattern: `/profile` requires `tier === "dbs
42
39
  npm install dbsc-toolkit
43
40
  ```
44
41
 
45
- Then install whichever framework and storage you actually use. Each is an optional peer dependency — install only what you need.
42
+ Pick the framework adapter and storage you actually use (each is an optional peer dependency):
46
43
 
47
44
  ```sh
48
- # Express + in-memory storage (dev)
49
- npm install express cookie-parser
50
-
51
- # Or with Redis storage
52
- npm install express cookie-parser ioredis
53
-
54
- # Or with Postgres storage
55
- npm install express cookie-parser pg
45
+ npm install express cookie-parser ioredis # Express + Redis
46
+ npm install express cookie-parser pg # Express + Postgres
56
47
  ```
57
48
 
58
- ## Quick start — Express
49
+ ## Quick start
59
50
 
60
51
  ```ts
61
52
  import express from "express";
@@ -73,146 +64,44 @@ const storage = new MemoryStorage();
73
64
  app.use(dbsc({ storage }));
74
65
 
75
66
  app.post("/login", async (req, res) => {
76
- const sessionId = randomUUID();
77
- await bindSession(res, sessionId, storage, { userId: req.body.username });
67
+ await bindSession(res, randomUUID(), storage, { userId: req.body.username });
78
68
  res.json({ ok: true });
79
69
  });
80
70
 
81
71
  app.get("/me", (req, res) => res.json(res.locals.dbsc));
82
-
83
72
  app.listen(3000);
84
73
  ```
85
74
 
86
- `app.use(dbsc(...))` mounts `POST /dbsc/registration` and `POST /dbsc/refresh` automatically — Chrome drives both, your code never sees them. `bindSession()` is the one-liner you add to your login route: it writes the session row, issues a challenge, builds the registration header (both legacy + new names), and sets the two short-lived cookies Chrome needs to complete binding.
87
-
88
- Call `bindSession()` after you have verified the user's credentials — in the login route, or in a signup route that auto-logs the user in. Calling it in a bare signup that does not establish an authenticated session is not useful: there is no session to bind yet.
89
-
90
- ### The registration race after login
91
-
92
- Chrome posts to `/dbsc/registration` *after* the login response returns. The handshake includes TPM key generation, JWS signing, and a network round-trip, so it commonly takes 300 ms to 1.5 s — sometimes longer on a cold device. If the page immediately requests `/me` or `/payment` and gates on `tier === "dbsc"`, the check can land before registration completes and report `tier: "none"` even on a fully supported browser. Two ways to absorb this on the client:
93
-
94
- - **Status indicator with short polling.** After a successful login, poll a low-cost endpoint (`/me` works) every 500 ms for up to ~8 s — long enough to cover both native DBSC and the bound-polyfill activation window. Stop when `tier !== "none"`; show a small "Session bound" badge. The live demo uses this pattern — see `pollDbscReady` in [examples/express/src/server.js](./examples/express/src/server.js).
95
- - **One-shot auto-retry on the first call after login.** If a tier-gated request returns `tier: "none"` within the first few seconds of a fresh login, wait ~1 s and retry once. Cheap, invisible to the user, and avoids the false demotion entirely.
75
+ `app.use(dbsc(...))` mounts the protocol routes automatically; your code never sees them. `bindSession()` is the one-liner you add to your existing login route. For the polyfill to cover non-Chromium browsers, include this on your page:
96
76
 
97
- For server-driven flows (a payment route called from a server redirect immediately after login), either pattern works. The race only matters when the very first authenticated request is also a tier check; routine browsing past the first second is unaffected.
98
-
99
- A full demo with `/me`, `/logout`, and `/clear-cookies` is in [examples/express/src/server.js](./examples/express/src/server.js).
77
+ ```html
78
+ <script type="module">
79
+ import { initBoundDbsc } from "/dbsc-client/index.js";
80
+ initBoundDbsc();
81
+ </script>
82
+ ```
100
83
 
101
- ## Adding DBSC to an existing app
84
+ (Serve `node_modules/dbsc-toolkit/dist/client/` as a static directory; the demo shows the pattern.)
102
85
 
103
- If you already have a working session cookie and login route (Express-session, NextAuth, your own table — doesn't matter), DBSC slots in beside what you have. You don't migrate the session store and you don't rewrite login. Two patterns:
86
+ Full walk-through, including the post-login race and how to absorb it: [docs/getting-started.md](./docs/getting-started.md).
104
87
 
105
- - Add one `bindSession()` call at the end of your existing login.
106
- - Or set `autoBind` on the middleware and never touch login at all — DBSC binds users on their next page load.
88
+ ## Adding to an existing app
107
89
 
108
- Full integration story, per-route policy table, and rollout timeline in [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md).
90
+ Already have a session cookie? You don't migrate the store, you don't rewrite login. Add one `bindSession()` call at the end of your existing login route, or set `autoBind` on the middleware and never touch login at all. Per-route policy table and rollout timeline in [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md).
109
91
 
110
92
  ## Subpath imports
111
93
 
112
94
  | Import | What it is |
113
95
  |--------|------------|
114
- | `dbsc-toolkit` | Core types, crypto, protocol functions (native DBSC + bound polyfill) |
96
+ | `dbsc-toolkit` | Core types, crypto, protocol functions |
115
97
  | `dbsc-toolkit/express` | Express middleware |
116
98
  | `dbsc-toolkit/fastify` | Fastify plugin |
117
99
  | `dbsc-toolkit/hono` | Hono middleware |
118
100
  | `dbsc-toolkit/nextjs` | Next.js App Router middleware + handlers |
119
- | `dbsc-toolkit/client` | Browser SDK `initBoundDbsc()` for the polyfill |
120
- | `dbsc-toolkit/storage/memory` | In-memory storage (dev/test) |
121
- | `dbsc-toolkit/storage/redis` | Redis storage |
122
- | `dbsc-toolkit/storage/postgres` | Postgres storage |
123
-
124
- Tree-shaking eliminates anything you don't import.
125
-
126
- ## How a verified flow looks
127
-
128
- On Chromium 145+:
129
-
130
- 1. User hits `POST /login`. Server creates a session, issues a challenge, sets `Secure-Session-Registration` response header and two short-lived cookies (`__Host-dbsc-reg`, `__Host-dbsc-challenge`).
131
- 2. The browser immediately POSTs to `/dbsc/registration` with `Secure-Session-Response: <jws>`. The JWS carries the device public key signed by the matching private key (held in the platform's hardware key store).
132
- 3. Middleware verifies the JWS, stores the public key bound to the session, sets `__Host-dbsc-session` cookie. `tier` is now `"dbsc"`.
133
- 4. Every refresh cycle (default 10 min) the browser signs a fresh challenge with the hardware-resident key. A copied cookie cannot pass refresh — the attacker has no key.
134
-
135
- On Firefox / Safari / older Chromium (with the `initBoundDbsc()` client SDK loaded on the page):
136
-
137
- 1. Same `/login` response — the registration headers are sent, but the browser ignores them.
138
- 2. After a 3-second probe (waiting in case native DBSC is just slow), the client SDK generates a non-extractable ECDSA P-256 keypair via Web Crypto, exports the public key, and POSTs to `/dbsc-bound/registration` with the signed challenge.
139
- 3. Middleware verifies the signature against the JWK, stores the public key, sets `__Host-dbsc-session`. `tier` is now `"bound"`.
140
- 4. Every refresh cycle the SDK calls `/dbsc-bound/refresh` with a fresh signature. A copied cookie alone has no key in IndexedDB on the attacker's machine, so refresh fails.
141
-
142
- For the complete protocol walk-through with every header value and timing detail, see [HOW-IT-WORKS.md](./HOW-IT-WORKS.md). The bound polyfill protocol is documented in [docs/bound-polyfill.md](./docs/bound-polyfill.md).
143
-
144
- ## Using the tier to actually defend
145
-
146
- Setting up the middleware does not protect anything on its own. The library exposes a tier; **enforcing it is your responsibility**.
147
-
148
- Most routes should gate on `tier !== "none"`:
149
-
150
- ```ts
151
- app.get("/dashboard", (req, res) => {
152
- if (res.locals.dbsc.tier === "none") {
153
- return res.status(403).json({ error: "session not bound" });
154
- }
155
- // safe — request is bound to a hardware-resident or browser-resident key
156
- });
157
- ```
158
-
159
- For genuinely sensitive routes where you want the TPM-backed guarantee specifically (defeats infostealer malware, not just remote cookie theft), gate on `"dbsc"`:
160
-
161
- ```ts
162
- app.post("/payment", (req, res) => {
163
- if (res.locals.dbsc.tier !== "dbsc") {
164
- return res.status(403).json({ error: "hardware-bound session required" });
165
- }
166
- });
167
- ```
168
-
169
- If you skip the tier check entirely, a stolen cookie works just like before — the library bought you nothing. The whole point is the demotion: when a cookie is replayed without a valid refresh signature, tier drops to `"none"` and your gate refuses the request.
170
-
171
- Tier handling at a glance:
172
-
173
- - `tier === "dbsc"`: hardware-bound via TPM / Secure Enclave / Android Keystore. Full access.
174
- - `tier === "bound"`: software-bound via Web Crypto + IndexedDB. Defeats XSS, network capture, and cookie paste-to-other-machine. Does not defeat infostealer malware on the user's device.
175
- - `tier === "none"`: treat as unauthenticated for any route you care about. Force re-login, log a `session_stolen` candidate, depending on context.
176
-
177
- See [docs/security/best-practices.md](./docs/security/best-practices.md) for the full tier-policy guidance.
178
-
179
- ## Local testing
180
-
181
- You need HTTPS — `__Host-` cookies require it and Chrome rejects DBSC on plain HTTP. Two options:
182
-
183
- - Deploy somewhere that gives you HTTPS (Render, Fly, Railway, Cloudflare Tunnel). Easiest path. The live demo above runs on Render.
184
- - Run `local-ssl-proxy --source 3001 --target 3000` in front of your local server.
185
-
186
- If you deploy behind any reverse proxy (Render, Fly, Cloudflare, nginx), call `app.set("trust proxy", true)` in Express before mounting the DBSC middleware. Without it, `req.protocol` returns `http` even when the client connected over HTTPS, so the `scope.origin` in the registration response goes out with the wrong scheme and Chrome silently terminates the session. Fastify needs `Fastify({ trustProxy: true })`; Hono and Next.js derive origin from the request URL directly and don't need any flag.
187
-
188
- A working demo is in [examples/express/](./examples/express/).
189
-
190
- ## Framework support
191
-
192
- The library has two layers:
193
-
194
- **Core (`dbsc-toolkit`)** — pure protocol. No framework deps. Functions in, plain data out. Works anywhere Node.js runs.
195
-
196
- **Adapters** — thin wrappers for specific frameworks. Four shipped: Express, Fastify, Hono, Next.js.
101
+ | `dbsc-toolkit/client` | Browser SDK with `initBoundDbsc()` for the polyfill |
102
+ | `dbsc-toolkit/storage/{memory,redis,postgres}` | Storage adapters |
197
103
 
198
- If you use Koa, Hapi, raw `http`, Bun, or Deno call core directly:
199
-
200
- ```ts
201
- import {
202
- handleRefresh,
203
- handleRegistration,
204
- issueChallenge,
205
- readSessionResponseHeader,
206
- } from "dbsc-toolkit";
207
-
208
- const result = await handleRefresh({
209
- sessionId: getCookie(req, "__Host-dbsc-session"),
210
- secSessionResponseHeader: readSessionResponseHeader(req.headers),
211
- expectedJti: getCookie(req, "__Host-dbsc-challenge"),
212
- }, storage);
213
- ```
214
-
215
- `readSessionResponseHeader` reads `Secure-Session-Response` (the current spec name) with fallback to `Sec-Session-Response` (the legacy name) for older Chrome builds.
104
+ Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`, Bun, or Deno? Call core directly. See [docs/adapters.md](./docs/adapters.md).
216
105
 
217
106
  ## Protection tiers
218
107
 
@@ -222,120 +111,25 @@ const result = await handleRefresh({
222
111
  | `bound` | Web Crypto polyfill, non-extractable ECDSA P-256 key in IndexedDB | Cookie theft. Does not defeat infostealer malware on the user's machine. |
223
112
  | `none` | Plain cookie | Nothing the cookie itself doesn't already do |
224
113
 
225
- The tier is available at `res.locals.dbsc.tier` (Express), `req.dbsc.tier` (Fastify), `c.get("dbsc").tier` (Hono), and via `getDbscSession()` (Next.js). Use it to apply per-route policy for example, block payment flows when `tier !== "dbsc"` and accept any binding for everything else with `tier !== "none"`.
226
-
227
- ## Storage
228
-
229
- ### Redis
230
-
231
- ```ts
232
- import Redis from "ioredis";
233
- import { RedisStorage } from "dbsc-toolkit/storage/redis";
234
-
235
- const redis = new Redis(process.env.REDIS_URL);
236
- const storage = new RedisStorage(redis);
237
- ```
238
-
239
- ### PostgreSQL
240
-
241
- ```ts
242
- import { Pool } from "pg";
243
- import { PostgresStorage } from "dbsc-toolkit/storage/postgres";
244
-
245
- const pool = new Pool({ connectionString: process.env.DATABASE_URL });
246
- const storage = new PostgresStorage(pool);
247
- ```
248
-
249
- Run the migration before first use:
250
-
251
- ```sh
252
- psql $DATABASE_URL < node_modules/dbsc-toolkit/migrations/001_initial.sql
253
- ```
254
-
255
- ## Telemetry hooks
256
-
257
- ```ts
258
- app.use(dbsc({
259
- storage,
260
- onEvent: (event) => {
261
- if (event.type === "session_stolen") {
262
- alerting.trigger("dbsc.session_stolen", { sessionId: event.sessionId });
263
- }
264
- metrics.increment(`dbsc.${event.type}`, { tier: event.tier });
265
- },
266
- }));
267
- ```
268
-
269
- Event types: `registration`, `refresh`, `verification_failure`, `session_stolen`, `tier_change`.
270
-
271
- ## Skipped sessions
272
-
273
- Chrome may send a request without the bound credential and tell you why via the `Secure-Session-Skipped` header. The library parses it and exposes the entries on the request:
274
-
275
- ```ts
276
- app.get("/payment", (req, res) => {
277
- const skipped = res.locals.dbsc.skipped;
278
- if (skipped.some(s => s.reason === "quota_exceeded")) {
279
- // Chrome throttled DBSC registrations for this site, briefly
280
- // unsafe to assume the binding is fresh — fall back or step up
281
- return res.status(503).json({ error: "session binding temporarily unavailable" });
282
- }
283
- // ...
284
- });
285
- ```
286
-
287
- Reasons defined by the spec: `unreachable` (couldn't reach the refresh endpoint), `server_error` (refresh got a 5xx), `quota_exceeded` (browser's anti-abuse throttle). These are diagnostics from Chrome — your server cannot disable them, but it can react to them.
288
-
289
- The quota is scoped per `(browser install, origin)`, not per origin globally. A site with a million users has a million separate quota buckets — one user spamming logins on their own Chrome cannot drain quota for anyone else. In production with normal login-once-and-stay-logged-in behavior, registration runs once per user and `quota_exceeded` essentially never trips. You hit it during development because the test loop logs in and out on the same browser dozens of times in a few minutes. To recover during testing, clear site data for the origin (`chrome://settings/clearBrowserData` → last hour → cookies and site data) or test in a fresh Incognito window.
290
-
291
- ## Header naming
292
-
293
- The W3C draft renamed the headers from `Sec-Session-*` to `Secure-Session-*`. Chromium 145+ acts on the new names. The middleware reads both and writes both for compatibility. If you build response headers manually, send both:
294
-
295
- ```ts
296
- res.setHeader("Secure-Session-Registration", header);
297
- res.setHeader("Sec-Session-Registration", header);
298
- ```
299
-
300
- ## Security
301
-
302
- Defense-in-depth layer. Does not replace TLS, secure cookie flags, MFA, or server hardening.
303
-
304
- The `bound` polyfill defeats remote cookie theft but is not hardware-bound — the key lives in IndexedDB on the user's disk and can be read by infostealer malware with filesystem access on the user's machine. For routes that must defeat that threat, gate on `tier === "dbsc"` specifically. For everything else, `tier !== "none"` is the right gate.
305
-
306
- See [SECURITY.md](./SECURITY.md) for reporting vulnerabilities and [docs/security/threat-model.md](./docs/security/threat-model.md) for the per-tier STRIDE breakdown.
307
-
308
- ## Project status
309
-
310
- - Single package on npm: `dbsc-toolkit`
311
- - Native DBSC: Chromium 145+ on Windows (TPM 2.0) / macOS Apple Silicon (Secure Enclave) / Android (Keystore)
312
- - Bound polyfill: every browser with Web Crypto + IndexedDB — Firefox, Safari, older Chromium
313
- - Verified end-to-end on Chrome 147 / Windows / TPM 2.0 (other Chromium browsers and platforms should work but not independently verified)
314
- - No third-party security audit yet
315
-
316
- ## Production readiness
317
-
318
- Honest table — what you're getting and where the rough edges are.
114
+ The library exposes the tier; **enforcing it is your responsibility**. Gate most routes on `tier !== "none"`; gate genuinely sensitive routes (payments, password change, admin) on `tier === "dbsc"`. Full guidance in [docs/security/best-practices.md](./docs/security/best-practices.md).
319
115
 
320
- | Area | Status | Confidence |
321
- |------|--------|-----------|
322
- | Core protocol (registration + refresh + verification) | Stable | High — verified against real Chrome 147 + TPM 2.0 |
323
- | Bound polyfill (`/dbsc-bound/*` + client SDK) | New in v2.0.0 | Medium — unit-tested; cross-browser verification on the live demo |
324
- | Express adapter | Stable | High — used in the live demo, exercised on Render |
325
- | Fastify / Hono / Next.js adapters | Stable | Medium — unit tests pass, share core code with Express, not battle-tested in production |
326
- | `MemoryStorage` | Dev / test only | N/A — explicitly non-production |
327
- | `RedisStorage` | Stable | Medium — atomic challenge consume via Lua, tested locally |
328
- | `PostgresStorage` | Stable | Medium — migrations included, tested locally |
329
- | Security audit | None | — |
330
- | W3C spec stability | Draft, library tracks Chromium's implementation | Spec may evolve; expect occasional wire-format adjustments |
116
+ ## Going deeper
331
117
 
332
- **Should you use this in production?** Yes, with three conditions:
118
+ - **Concepts and protocol:** [HOW-IT-WORKS.md](./HOW-IT-WORKS.md)
119
+ - **Bound polyfill wire protocol:** [docs/bound-polyfill.md](./docs/bound-polyfill.md)
120
+ - **Per-request signing (close the Firefox/Safari ride-along gap on sensitive routes):** [docs/per-request-signing.md](./docs/per-request-signing.md)
121
+ - **API reference:** [docs/api-reference.md](./docs/api-reference.md)
122
+ - **Adapters (Express / Fastify / Hono / Next.js + write your own):** [docs/adapters.md](./docs/adapters.md)
123
+ - **Storage (memory / Redis / Postgres):** [docs/storage.md](./docs/storage.md)
124
+ - **Telemetry hooks:** [docs/telemetry.md](./docs/telemetry.md)
125
+ - **Deployment (Render / Fly / Vercel / Cloudflare / nginx):** [docs/deployment.md](./docs/deployment.md)
126
+ - **Security best practices:** [docs/security/best-practices.md](./docs/security/best-practices.md)
127
+ - **Threat model:** [docs/security/threat-model.md](./docs/security/threat-model.md)
128
+ - **Troubleshooting:** [docs/troubleshooting.md](./docs/troubleshooting.md)
333
129
 
334
- 1. **Use Redis or Postgres storage**, not memory. Memory storage on a server that ever restarts produces a broken loop where browsers hold cookies that no longer match any stored key.
335
- 2. **Treat it as defense-in-depth**, never the only auth layer. Your existing session cookie, password, MFA, rate limiting — all still required. This library raises the floor on session-replay attacks; it doesn't replace anything else.
336
- 3. **Pin a version.** Pin `dbsc-toolkit@~2.0.0` (patch updates only) and read the changelog before bumping. v2 dropped the HMAC and WebAuthn tiers — see CHANGELOG for the migration path.
130
+ ## Status
337
131
 
338
- The realistic adoption pattern: ship it as the second layer behind your existing auth. The bound polyfill means you don't have to lock non-Chromium users out. Gate genuinely high-value actions (payments, password change, admin) on `tier === "dbsc"`; gate everything else on `tier !== "none"`. See [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md).
132
+ Verified end-to-end on Chrome 147 / Windows / TPM 2.0. Native DBSC supported on Chromium 145+ across Windows, macOS Apple Silicon, and Android. The bound polyfill works on every browser with Web Crypto + IndexedDB (Firefox, Safari, older Chromium). No third-party security audit yet. Production-readiness table and adoption guidance: [HOW-IT-WORKS.md#production-readiness](./HOW-IT-WORKS.md#production-readiness).
339
133
 
340
134
  ## License
341
135
 
@@ -0,0 +1,2 @@
1
+ export declare function recordServerTime(response: Response): Promise<void>;
2
+ //# sourceMappingURL=clockSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clockSync.d.ts","sourceRoot":"","sources":["../../src/client/clockSync.ts"],"names":[],"mappings":"AAEA,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxE"}
@@ -0,0 +1,17 @@
1
+ import { getKeyRecord, setKeyRecord } from "./keystore.js";
2
+ export async function recordServerTime(response) {
3
+ const hdr = response.headers.get("X-Server-Time");
4
+ if (!hdr)
5
+ return;
6
+ const serverTime = Number(hdr);
7
+ if (!Number.isFinite(serverTime))
8
+ return;
9
+ const rec = await getKeyRecord().catch(() => null);
10
+ if (!rec)
11
+ return;
12
+ const offset = serverTime - Date.now();
13
+ if (rec.clockOffsetMs === offset)
14
+ return;
15
+ await setKeyRecord({ ...rec, clockOffsetMs: offset });
16
+ }
17
+ //# sourceMappingURL=clockSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clockSync.js","sourceRoot":"","sources":["../../src/client/clockSync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAkB;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO;IACzC,MAAM,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvC,IAAI,GAAG,CAAC,aAAa,KAAK,MAAM;QAAE,OAAO;IACzC,MAAM,YAAY,CAAC,EAAE,GAAG,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;AACxD,CAAC"}
@@ -1,3 +1,5 @@
1
+ export { wrapFetch } from "./wrapFetch.js";
2
+ export type { WrapFetchOptions } from "./wrapFetch.js";
1
3
  export interface InitBoundDbscOptions {
2
4
  statePath?: string;
3
5
  challengePath?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA0CD,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrF;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA0CD,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrF;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC"}
@@ -1,4 +1,6 @@
1
1
  import { clearKeyRecord, getKeyRecord, setKeyRecord } from "./keystore.js";
2
+ import { recordServerTime } from "./clockSync.js";
3
+ export { wrapFetch } from "./wrapFetch.js";
2
4
  const DEFAULTS = {
3
5
  statePath: "/dbsc-bound/state",
4
6
  challengePath: "/dbsc-bound/challenge",
@@ -59,6 +61,7 @@ export function stopBoundDbsc() {
59
61
  }
60
62
  async function fetchState(path) {
61
63
  const r = await fetch(path, { credentials: "include" });
64
+ await recordServerTime(r);
62
65
  return (await r.json());
63
66
  }
64
67
  async function runRegistration(sessionId, challenge, cfg) {
@@ -76,6 +79,7 @@ async function runRegistration(sessionId, challenge, cfg) {
76
79
  throw new Error(`bound registration failed: ${res.status}`);
77
80
  }
78
81
  await setKeyRecord({ sessionId, keyPair });
82
+ await recordServerTime(res);
79
83
  }
80
84
  async function runRefresh(cfg) {
81
85
  const rec = await getKeyRecord().catch(() => null);
@@ -94,6 +98,7 @@ async function runRefresh(cfg) {
94
98
  headers: { "Content-Type": "application/json" },
95
99
  body: JSON.stringify({ challenge, signature, timestamp }),
96
100
  });
101
+ await recordServerTime(res);
97
102
  return res.ok;
98
103
  }
99
104
  function scheduleRefresh(cfg, intervalMs) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  export interface KeyRecord {
2
2
  sessionId: string;
3
3
  keyPair: CryptoKeyPair;
4
+ clockOffsetMs?: number;
4
5
  }
5
6
  export declare function getKeyRecord(): Promise<KeyRecord | null>;
6
7
  export declare function setKeyRecord(rec: KeyRecord): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../../src/client/keystore.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;CACxB;AAgBD,wBAAsB,YAAY,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAQ9D;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAQpD"}
1
+ {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../../src/client/keystore.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAgBD,wBAAsB,YAAY,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAQ9D;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAQpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/client/keystore.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,cAAc,CAAC;AAC/B,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,MAAM,cAAc,GAAG,YAAY,CAAC;AAOpC,SAAS,MAAM;IACb,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACvC,GAAG,CAAC,eAAe,GAAG,GAAG,EAAE;YACzB,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YACtB,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9C,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC;QACF,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACvD,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC3D,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAE,GAAG,CAAC,MAAgC,IAAI,IAAI,CAAC,CAAC;QAC7E,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAc;IAC/C,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACpD,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAChC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAClD,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAChC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/client/keystore.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,cAAc,CAAC;AAC/B,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,MAAM,cAAc,GAAG,YAAY,CAAC;AAQpC,SAAS,MAAM;IACb,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACvC,GAAG,CAAC,eAAe,GAAG,GAAG,EAAE;YACzB,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YACtB,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9C,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC;QACF,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACvD,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC3D,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAE,GAAG,CAAC,MAAgC,IAAI,IAAI,CAAC,CAAC;QAC7E,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAc;IAC/C,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACpD,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAChC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACnD,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAClD,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QAChC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Wraps a `fetch` function so every outgoing request carries a fresh ECDSA P-256
3
+ * signature in the `X-Dbsc-Bound-Proof` header.
4
+ *
5
+ * Use this ONLY for calls to sensitive routes you've gated with
6
+ * `requireBoundProof()` on the server (payment, admin, password-change, etc).
7
+ * It is per-call by design — keep it out of `globalThis.fetch` so third-party
8
+ * SDKs (analytics, React Query, SWR, etc) keep using the native `fetch`.
9
+ *
10
+ * If no bound key is present in IndexedDB the wrapped fetch transparently falls
11
+ * back to the underlying fetch — Chromium native DBSC paths and the
12
+ * unauthenticated paths keep working.
13
+ */
14
+ export interface WrapFetchOptions {
15
+ fetch?: typeof fetch;
16
+ headerName?: string;
17
+ }
18
+ export declare function wrapFetch(opts?: WrapFetchOptions): typeof fetch;
19
+ //# sourceMappingURL=wrapFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapFetch.d.ts","sourceRoot":"","sources":["../../src/client/wrapFetch.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,KAAK,CA0BnE"}
@@ -0,0 +1,27 @@
1
+ import { getKeyRecord } from "./keystore.js";
2
+ export function wrapFetch(opts = {}) {
3
+ const base = opts.fetch ?? globalThis.fetch.bind(globalThis);
4
+ const headerName = opts.headerName ?? "X-Dbsc-Bound-Proof";
5
+ return (async (input, init = {}) => {
6
+ const rec = await getKeyRecord().catch(() => null);
7
+ if (!rec)
8
+ return base(input, init);
9
+ const url = new URL(typeof input === "string" || input instanceof URL ? input.toString() : input.url, typeof window !== "undefined" ? window.location.href : "http://localhost");
10
+ const method = (init.method ?? "GET").toUpperCase();
11
+ const offset = rec.clockOffsetMs ?? 0;
12
+ const ts = Date.now() + offset;
13
+ const message = `${rec.sessionId}.${method}.${url.pathname}.${ts}`;
14
+ const sigBytes = await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, rec.keyPair.privateKey, new TextEncoder().encode(message));
15
+ const sig = base64url(new Uint8Array(sigBytes));
16
+ const headers = new Headers(init.headers);
17
+ headers.set(headerName, `ts=${ts};sig=${sig}`);
18
+ return base(input, { ...init, headers, credentials: init.credentials ?? "include" });
19
+ });
20
+ }
21
+ function base64url(b) {
22
+ let s = "";
23
+ for (let i = 0; i < b.length; i++)
24
+ s += String.fromCharCode(b[i]);
25
+ return btoa(s).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
26
+ }
27
+ //# sourceMappingURL=wrapFetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapFetch.js","sourceRoot":"","sources":["../../src/client/wrapFetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAoB7C,MAAM,UAAU,SAAS,CAAC,OAAyB,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,oBAAoB,CAAC;IAE3D,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAEnC,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAChF,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAC1E,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,IAAI,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;QAC/B,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACnE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CACvC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAClC,GAAG,CAAC,OAAO,CAAC,UAAU,EACtB,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAClC,CAAC;QACF,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS,EAAE,CAAC,CAAC;IACvF,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED,SAAS,SAAS,CAAC,CAAa;IAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC3E,CAAC"}
@@ -3,4 +3,6 @@ export type { BoundRegistrationRequest, BoundRegistrationResult } from "./regist
3
3
  export { handleBoundRefresh } from "./refresh.js";
4
4
  export type { BoundRefreshRequest } from "./refresh.js";
5
5
  export { verifyP256Signature } from "./verify.js";
6
+ export { verifyBoundProof, parseProofHeader, BOUND_PROOF_HEADER } from "./proof.js";
7
+ export type { VerifyBoundProofRequest } from "./proof.js";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/bound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/bound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACpF,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { handleBoundRegistration } from "./registration.js";
2
2
  export { handleBoundRefresh } from "./refresh.js";
3
3
  export { verifyP256Signature } from "./verify.js";
4
+ export { verifyBoundProof, parseProofHeader, BOUND_PROOF_HEADER } from "./proof.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/bound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/bound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}