dbsc-toolkit 2.0.0 → 2.0.2
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.
- package/README.md +50 -257
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,36 +5,33 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## The problem this solves
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
The library exposes both paths as a single `tier` string your route handlers gate on:
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
**New here?** Read [HOW-IT-WORKS.md](./HOW-IT-WORKS.md) for the 15-minute walk-through.
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
## Live demo
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
Try it: <https://dbsc-toolkit.onrender.com/>
|
|
36
33
|
|
|
37
|
-
|
|
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
|
-
|
|
42
|
+
Pick the framework adapter and storage you actually use (each is an optional peer dependency):
|
|
46
43
|
|
|
47
44
|
```sh
|
|
48
|
-
# Express +
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
```html
|
|
78
|
+
<script type="module">
|
|
79
|
+
import { initBoundDbsc } from "/dbsc-client/index.js";
|
|
80
|
+
initBoundDbsc();
|
|
81
|
+
</script>
|
|
82
|
+
```
|
|
100
83
|
|
|
101
|
-
|
|
84
|
+
(Serve `node_modules/dbsc-toolkit/dist/client/` as a static directory; the demo shows the pattern.)
|
|
102
85
|
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
120
|
-
| `dbsc-toolkit/storage/memory` |
|
|
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
|
-
|
|
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,24 @@ 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
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
+
- **API reference:** [docs/api-reference.md](./docs/api-reference.md)
|
|
121
|
+
- **Adapters (Express / Fastify / Hono / Next.js + write your own):** [docs/adapters.md](./docs/adapters.md)
|
|
122
|
+
- **Storage (memory / Redis / Postgres):** [docs/storage.md](./docs/storage.md)
|
|
123
|
+
- **Telemetry hooks:** [docs/telemetry.md](./docs/telemetry.md)
|
|
124
|
+
- **Deployment (Render / Fly / Vercel / Cloudflare / nginx):** [docs/deployment.md](./docs/deployment.md)
|
|
125
|
+
- **Security best practices:** [docs/security/best-practices.md](./docs/security/best-practices.md)
|
|
126
|
+
- **Threat model:** [docs/security/threat-model.md](./docs/security/threat-model.md)
|
|
127
|
+
- **Troubleshooting:** [docs/troubleshooting.md](./docs/troubleshooting.md)
|
|
333
128
|
|
|
334
|
-
|
|
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.
|
|
129
|
+
## Status
|
|
337
130
|
|
|
338
|
-
|
|
131
|
+
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
132
|
|
|
340
133
|
## License
|
|
341
134
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbsc-toolkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Server-side Device Bound Session Credentials (DBSC) for Node.js plus a Web Crypto polyfill for Firefox, Safari, and older Chromium. Cookie-theft protection on every modern browser.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"./storage/postgres": {
|
|
43
43
|
"import": "./dist/storage/postgres/index.js",
|
|
44
44
|
"types": "./dist/storage/postgres/index.d.ts"
|
|
45
|
-
}
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
46
47
|
},
|
|
47
48
|
"files": [
|
|
48
49
|
"dist",
|