@web-ts-toolkit/express-request-attestation 0.48.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.
package/index.d.mts ADDED
@@ -0,0 +1,712 @@
1
+ import { P as ProofTiming, R as ResolvedTimePolicy } from './time-policy-DlER-7rf.mjs';
2
+ export { A as ATTESTATION_ERROR_RESPONSE_HEADER, a as AttestationEnvelope, b as AttestationMacInput, c as AttestationProtocolError, D as DEFAULT_ATTESTATION_HEADER, d as DEFAULT_CLOCK_SKEW_MS, e as DEFAULT_CLUSTER_CLOCK_GUARD_MS, f as DEFAULT_MAX_AGE_MS, g as DEFAULT_MAX_BODY_BYTES, E as EMPTY_BODY_SHA256_HEX, G as GLOBAL_MAX_RETENTION_MS, K as KEY_BYTES_LENGTH, M as MAC_BYTES_LENGTH, h as MAC_INPUT_LABEL, i as MAX_BODY_BYTES_HARD_CAP, j as MAX_CLOCK_SKEW_MS, k as MAX_CLUSTER_CLOCK_GUARD_MS, l as MAX_CONTENT_TYPE_BYTES, m as MAX_ENCODED_TRANSACTION_ID_BYTES, n as MAX_HEADER_NAME_BYTES, o as MAX_KEY_ID_LENGTH, p as MAX_MAX_AGE_MS, q as MAX_PUBLIC_ORIGIN_BYTES, r as MAX_REPLAY_KEY_BYTES, s as MAX_REPLAY_NAMESPACE_LENGTH, t as MAX_REQUEST_TARGET_BYTES, u as MIN_CLOCK_SKEW_MS, v as MIN_CLUSTER_CLOCK_GUARD_MS, w as MIN_MAX_AGE_MS, x as MIN_REPLAY_KEY_BYTES, y as MacInputFields, N as NONCE_BYTES_LENGTH, z as PROTOCOL_VERSION, B as ProofTimingDecision, C as REPLAY_KEY_PREFIX, F as RequestSigner, H as RequestSignerOptions, I as RetentionAdmission, S as SignInput, T as TimePolicyOptions, J as TransactionIdFields, L as assertTimestampMs, O as base64urlDecode, Q as base64urlEncode, U as buildMacInputBytes, V as buildReplayKeyPreimageBytes, W as canonicalJsonBytes, X as canonicalizeRequestTarget, Y as checkRetentionAdmissible, Z as decodeTransactionId, _ as encodeTransactionId, $ as evaluateProofTiming, a0 as extractSingleHeaderValue, a1 as maxStoreAdmissionMs, a2 as maxVerifierRemainingMs, a3 as normalizeContentType, a4 as normalizeMethod, a5 as parseCanonicalJsonArray, a6 as resolveTimePolicy, a7 as utf8ByteLength, a8 as utf8Decode, a9 as utf8Encode, aa as validateBodyHashHex, ab as validateHeaderName, ac as validateKeyBytes, ad as validateKeyId, ae as validateMac, af as validateNonceHex, ag as validatePublicOrigin, ah as validateReplayKey, ai as validateReplayNamespace } from './time-policy-DlER-7rf.mjs';
3
+ import { Router } from 'express';
4
+
5
+ /**
6
+ * ATT-01 server-side interfaces and fixed HTTP errors (task section 4.5).
7
+ *
8
+ * Server-specific ONLY: this module may reference Express/Redis types but is
9
+ * imported exclusively by the `.` entry. The `./signer` entry never imports
10
+ * it, so browser declarations stay free of server types.
11
+ */
12
+ /** One accepted key: exactly 32 bytes with an explicit acceptance window. */
13
+ interface AttestationKeyEntry {
14
+ readonly keyId: string;
15
+ /** Exactly 32 bytes; snapshots are owned/copy-isolated. */
16
+ readonly key: Uint8Array;
17
+ /** Nonnegative safe-integer epoch ms; `acceptFrom < acceptUntil`. */
18
+ readonly acceptFrom: number;
19
+ readonly acceptUntil: number;
20
+ }
21
+ /** Keyring snapshot returned by `AttestationKeyProvider.getSnapshot()`. */
22
+ interface AttestationKeySnapshot {
23
+ /** Must name a present active entry. At most 16 retained keys. */
24
+ readonly currentKeyId: string;
25
+ readonly keys: readonly AttestationKeyEntry[];
26
+ }
27
+ /** Input snapshot for static/rotating providers (owned/copied on entry). */
28
+ interface AttestationKeySnapshotInput {
29
+ readonly currentKeyId: string;
30
+ readonly keys: readonly {
31
+ readonly keyId: string;
32
+ readonly key: Uint8Array;
33
+ readonly acceptFrom: number;
34
+ readonly acceptUntil: number;
35
+ }[];
36
+ }
37
+ /**
38
+ * Typed key provider. Returns an owned, copy-isolated snapshot (or a promise
39
+ * of one). Provider/read failure is operational `503`, never a stale-key
40
+ * client error. External cron/KMS/config code owns provisioning and refresh;
41
+ * there is no default polling timer.
42
+ */
43
+ interface AttestationKeyProvider {
44
+ getSnapshot(): AttestationKeySnapshot | Promise<AttestationKeySnapshot>;
45
+ }
46
+ /** Rotating provider with explicit atomic replacement (ATT-05). */
47
+ interface AttestationRotatingKeyProvider extends AttestationKeyProvider {
48
+ replace(snapshot: AttestationKeySnapshotInput): void;
49
+ }
50
+ /** Atomic bounded replay-store reservation input. */
51
+ interface AttestationStoreReserveInput {
52
+ /** Opaque key: `att:v1:` + SHA-256 hex of `[namespace, origin, nonceHex]`. */
53
+ readonly replayKey: string;
54
+ /** Absolute retention deadline (safe-integer epoch ms, section 4.4). */
55
+ readonly retainUntilMs: number;
56
+ }
57
+ type AttestationStoreReserveResult = 'reserved' | 'duplicate' | 'expired';
58
+ /**
59
+ * Bounded replay store. `reserve` returns `duplicate` for an admitted nonce,
60
+ * `expired` when the retention deadline has elapsed, and throws typed errors
61
+ * on capacity exhaustion or unavailability. The adapter never interprets
62
+ * caller-supplied nonce strings as already authenticated.
63
+ */
64
+ interface AttestationStore {
65
+ reserve(input: AttestationStoreReserveInput): Promise<AttestationStoreReserveResult>;
66
+ }
67
+ /**
68
+ * Narrow structural Redis client contract (ATT-04). The library never creates,
69
+ * connects, or closes clients and never imports a Redis driver; callers
70
+ * inject any compatible client (e.g. `node-redis`). Redis stays a dev
71
+ * dependency for integration tests, not a runtime dependency.
72
+ */
73
+ interface AttestationRedisClient {
74
+ sendCommand(args: string[]): Promise<unknown>;
75
+ }
76
+ /** Options for `createRequestAttestationMiddleware` (implemented in ATT-02). */
77
+ interface CreateRequestAttestationMiddlewareOptions {
78
+ /** Static canonical HTTPS origin (loopback http allowed in development). */
79
+ readonly publicOrigin: string;
80
+ readonly replayNamespace: string;
81
+ /** Upstream-stripped prefix to re-prepend after `req.originalUrl`. */
82
+ readonly publicPathPrefix?: string;
83
+ readonly keyProvider: AttestationKeyProvider;
84
+ readonly store: AttestationStore;
85
+ readonly maxAgeMs?: number;
86
+ readonly clockSkewMs?: number;
87
+ readonly clusterClockGuardMs?: number;
88
+ readonly headerName?: string;
89
+ readonly now?: () => number;
90
+ /** Bounds async provider/store waits; default 5000 ms. */
91
+ readonly operationTimeoutMs?: number;
92
+ }
93
+ /** Options for `createAttestationBodyCapture` (implemented in ATT-02). */
94
+ interface CreateAttestationBodyCaptureOptions {
95
+ /** Parser/body budget, default 1 MiB, hard cap 16 MiB. */
96
+ readonly maxBodyBytes?: number;
97
+ }
98
+ /**
99
+ * Package-owned body-capture integration: an Express parser `verify`
100
+ * callback storing digest/count in per-request state plus a narrow error
101
+ * handler mapping parser size/encoding failures to section 4.5 errors.
102
+ */
103
+ interface AttestationBodyCapture {
104
+ readonly verify: (req: unknown, res: unknown, body: unknown, encoding: string) => void;
105
+ readonly errorHandler: (err: unknown, req: unknown, res: unknown, next: (err?: unknown) => void) => void;
106
+ }
107
+ /** Options for `createSignerBundleRouter` (implemented in ATT-05). */
108
+ interface CreateSignerBundleRouterOptions {
109
+ readonly publicOrigin: string;
110
+ readonly replayNamespace: string;
111
+ readonly keyProvider: AttestationKeyProvider;
112
+ /** Relative mount base path used only to publish external URLs. */
113
+ readonly basePath?: string;
114
+ readonly publicPathPrefix?: string;
115
+ readonly now?: () => number;
116
+ readonly operationTimeoutMs?: number;
117
+ }
118
+ /** Options for `createMemoryAttestationStore` (implemented in ATT-03). */
119
+ interface CreateMemoryAttestationStoreOptions {
120
+ readonly maxEntries?: number;
121
+ readonly now?: () => number;
122
+ }
123
+ /** Options for `createRedisAttestationStore` (implemented in ATT-04). */
124
+ interface CreateRedisAttestationStoreOptions {
125
+ readonly client: AttestationRedisClient;
126
+ readonly keyPrefix?: string;
127
+ readonly maxEntries?: number;
128
+ }
129
+ /** Fixed section 4.5 error codes with their HTTP status and message. */
130
+ type AttestationErrorCode = 'ATTESTATION_MISSING' | 'ATTESTATION_MALFORMED' | 'ATTESTATION_STALE_KEY' | 'ATTESTATION_EXPIRED' | 'ATTESTATION_FUTURE' | 'ATTESTATION_INVALID_SIGNATURE' | 'ATTESTATION_REPLAY' | 'ATTESTATION_BODY_TOO_LARGE' | 'ATTESTATION_UNSUPPORTED_ENCODING' | 'ATTESTATION_BODY_CAPTURE_REQUIRED' | 'ATTESTATION_INTERNAL_ERROR' | 'ATTESTATION_KEYS_UNAVAILABLE' | 'ATTESTATION_REPLAY_UNAVAILABLE';
131
+ /**
132
+ * Typed attestation failure carrying a fixed code/status/message. Responses
133
+ * use `{ code, message }` JSON with `Cache-Control: no-store` and never echo
134
+ * raw keys, nonces, signatures, bodies, provider errors, or requested IDs.
135
+ */
136
+ declare class AttestationError extends Error {
137
+ readonly name = "AttestationError";
138
+ readonly code: AttestationErrorCode;
139
+ readonly status: number;
140
+ constructor(code: AttestationErrorCode);
141
+ }
142
+ declare function isAttestationError(error: unknown): error is AttestationError;
143
+ /** Response payload shape for attestation failures (no raw diagnostics). */
144
+ declare function toAttestationErrorPayload(error: AttestationError): {
145
+ readonly code: AttestationErrorCode;
146
+ readonly message: string;
147
+ };
148
+
149
+ interface AttestationDiagnosticEvent {
150
+ readonly code: AttestationErrorCode;
151
+ readonly status: number;
152
+ }
153
+ type AttestationDiagnosticHook = (event: AttestationDiagnosticEvent) => void;
154
+ type CreateRequestAttestationMiddlewareOptionsWithDiagnostic = CreateRequestAttestationMiddlewareOptions & {
155
+ readonly onDiagnostic?: AttestationDiagnosticHook;
156
+ };
157
+ /**
158
+ * Create the verify-before-reserve Express guard.
159
+ *
160
+ * Required `publicOrigin`/`replayNamespace`/services are validated once and
161
+ * frozen; key snapshots are owned per request while service references stay
162
+ * live. Async provider/store waits are bounded by `operationTimeoutMs`
163
+ * (default 5000 ms, allowed 1-60000 ms); a late settlement after the bound
164
+ * can never resume the rejected request, call `next`, or release a possibly
165
+ * committed reservation.
166
+ */
167
+ declare function createRequestAttestationMiddleware(options: CreateRequestAttestationMiddlewareOptionsWithDiagnostic): (req: unknown, res: unknown, next: (err?: unknown) => void) => void;
168
+
169
+ interface AttestationBodyRecord {
170
+ readonly digestHex: string;
171
+ readonly byteLength: number;
172
+ }
173
+ interface AttestationBodyCaptureWithRecord extends AttestationBodyCapture {
174
+ readonly getRecord: (req: unknown) => AttestationBodyRecord | undefined;
175
+ readonly maxBodyBytes: number;
176
+ }
177
+ /**
178
+ * Read the package-owned capture record for `req`, or `undefined` when the
179
+ * configured parser never invoked `verify` for this request. Returns a frozen
180
+ * copy; caller mutation cannot corrupt stored state. Never trusts
181
+ * `req.body` or any client-supplied digest field.
182
+ */
183
+ declare function getAttestationBodyRecord(req: unknown): AttestationBodyRecord | undefined;
184
+ /**
185
+ * Create the parser `verify` callback + narrow error mapper.
186
+ *
187
+ * `maxBodyBytes` defaults to 1 MiB and must be a positive safe integer up to
188
+ * the v1 hard cap of 16 MiB. Configure the SAME value as the Express parser
189
+ * `limit` option and always use `inflate: false`. Applications may choose a
190
+ * smaller limit. Over-limit bodies fail in the parser (mapped to 413 before
191
+ * the guard) before any replay allocation.
192
+ */
193
+ declare function createAttestationBodyCapture(options?: CreateAttestationBodyCaptureOptions): AttestationBodyCaptureWithRecord;
194
+
195
+ interface AttestationVerifyInput {
196
+ readonly headerValue: string | readonly string[] | undefined;
197
+ readonly method: unknown;
198
+ readonly originalUrl: unknown;
199
+ readonly publicPathPrefix?: string;
200
+ readonly contentTypeHeader: string | readonly string[] | undefined | null;
201
+ readonly contentEncodingHeader: string | readonly string[] | undefined | null;
202
+ readonly bodyDigestHex: unknown;
203
+ readonly snapshot: AttestationKeySnapshot;
204
+ readonly nowMs: number;
205
+ readonly policy: ResolvedTimePolicy;
206
+ readonly replayNamespace: string;
207
+ readonly publicOrigin: string;
208
+ }
209
+ interface AttestationVerifiedProof {
210
+ readonly keyId: string;
211
+ readonly timestampMs: number;
212
+ readonly nonceHex: string;
213
+ readonly keyEntry: AttestationKeyEntry;
214
+ readonly timing: ProofTiming;
215
+ readonly replayKey: string;
216
+ readonly retainUntilMs: number;
217
+ readonly method: string;
218
+ readonly requestTarget: string;
219
+ readonly contentType: string;
220
+ readonly bodyHashHex: string;
221
+ }
222
+ /** Validate + copy-isolate a provider snapshot. Malformed => KEYS_UNAVAILABLE. */
223
+ declare function copyAttestationSnapshot(snapshot: unknown): AttestationKeySnapshot;
224
+ /** Re-evaluate timing for a known key entry. Throws fixed FUTURE/EXPIRED/STALE. */
225
+ declare function checkProofTiming(input: {
226
+ readonly timestampMs: number;
227
+ readonly nowMs: number;
228
+ readonly keyEntry: Pick<AttestationKeyEntry, 'acceptFrom' | 'acceptUntil'>;
229
+ readonly policy: ResolvedTimePolicy;
230
+ }): ProofTiming;
231
+ /** Concatenate the trusted prefix with `originalUrl` and canonicalize. */
232
+ declare function resolveRequestTarget(originalUrl: unknown, publicPathPrefix: string): string;
233
+ /** Single final Content-Type value. Duplicate/controls => MALFORMED. */
234
+ declare function resolveContentType(header: string | readonly string[] | undefined | null): string;
235
+ /** V1 accepts identity / absent Content-Encoding only. */
236
+ declare function assertAllowedContentEncoding(header: string | readonly string[] | undefined | null): void;
237
+ /** Opaque replay key `att:v1:` + SHA-256 of `[namespace, origin, nonceHex]`. */
238
+ declare function deriveReplayKey(input: {
239
+ readonly replayNamespace: string;
240
+ readonly publicOrigin: string;
241
+ readonly nonceHex: string;
242
+ }): string;
243
+ /**
244
+ * Pure pre-reservation verification. Performs header/codec, snapshot lookup,
245
+ * time, target, content-type/encoding, body digest, and HMAC stages in order
246
+ * and returns the owned timing + replay key for the middleware to reserve.
247
+ */
248
+ declare function verifyAttestationProof(input: AttestationVerifyInput): AttestationVerifiedProof;
249
+
250
+ /**
251
+ * ATT-03 bounded atomic memory replay store (task section 4.4).
252
+ *
253
+ * Admission contract (shared with the future ATT-04 Redis adapter):
254
+ *
255
+ * - `reserve({ replayKey, retainUntilMs })` returns `'reserved' | 'duplicate' | 'expired'`.
256
+ * - The canonical deadline field is `retainUntilMs` (see `AttestationStoreReserveInput`).
257
+ * A `retainUntil` alias is accepted at runtime for callers written against the
258
+ * task-text shorthand; when both are present the canonical field wins.
259
+ * - Validation order per admission: input shape, store clock, opaque key bounds
260
+ * (1-256 printable ASCII), deadline shape (nonnegative safe integer), input
261
+ * expiry (`retainUntilMs <= now` returns `'expired'` with no allocation and no
262
+ * clamping), global retention cap (`retainUntilMs - now > 240000` rejects
263
+ * without allocating instead of truncating), then duplicate check, then bounded
264
+ * expiry reclamation, then capacity, then atomic insert.
265
+ * - Duplicate detection precedes capacity rejection: a live duplicate returns
266
+ * `'duplicate'` even when full. Duplicates never extend expiry and never
267
+ * allocate a new heap node.
268
+ * - Each admission reclaims the requested expired entry (when the same key holds
269
+ * one) plus at most 64 expired entries from the indexed expiry heap. No live
270
+ * entry is ever evicted, there is no interval timer, and there is no full-map
271
+ * rebuild or linear key scan.
272
+ * - An expired key is reservable again for a fresh proof whose deadline is
273
+ * valid; the old proof still fails the verifier's age rules (the store never
274
+ * sees signatures, only opaque already-derived replay keys).
275
+ *
276
+ * Sharing boundary: replay state is shared only by callers reusing this same
277
+ * store object in one process. Restart or state loss resets this replay memory.
278
+ * There is no multi-process coordination, durable exactly-once delivery, or
279
+ * cross-object sharing (independent store objects intentionally do not
280
+ * coordinate).
281
+ */
282
+
283
+ /**
284
+ * Shared capacity exhaustion: no live reservation is evicted and no proof is
285
+ * admitted without replay state. Handle like any replay-store failure: fail
286
+ * closed. Also thrown by the future Redis adapter so callers handle one error
287
+ * type across backends.
288
+ */
289
+ declare class AttestationCapacityError extends Error {
290
+ readonly name = "AttestationCapacityError";
291
+ constructor(message?: string);
292
+ }
293
+ /**
294
+ * Create a process-local bounded attestation replay store for local
295
+ * development and tests.
296
+ *
297
+ * Reservations are kept in memory, cleaned up opportunistically during
298
+ * `reserve` calls, and lost when the Node.js process exits. Do not use this
299
+ * store when replay state must survive restarts or be shared by multiple
300
+ * application instances. Replay is shared only by callers reusing this same
301
+ * object; the store does not verify request signatures. Capacity defaults to
302
+ * 50000 entries and never evicts live entries.
303
+ */
304
+ declare function createMemoryAttestationStore(options?: CreateMemoryAttestationStoreOptions): AttestationStore;
305
+
306
+ /**
307
+ * ATT-04 shared bounded Redis replay admission (task sections 4.1/4.4).
308
+ *
309
+ * Admission contract (shared with ATT-03 memory; see `test/store-conformance.ts`):
310
+ *
311
+ * - `reserve({ replayKey, retainUntilMs })` returns
312
+ * `'reserved' | 'duplicate' | 'expired'`.
313
+ * - The canonical deadline field is `retainUntilMs`
314
+ * (see `AttestationStoreReserveInput`). A `retainUntil` alias is accepted at
315
+ * runtime for callers written against the task-text shorthand; when both are
316
+ * present the canonical field wins.
317
+ * - Malformed inputs (non-object, bad opaque key bounds 1–256 printable
318
+ * ASCII, non-safe-integer deadlines) reject with `AttestationProtocolError`
319
+ * WITHOUT touching Redis (no allocation, `sendCommand` never called).
320
+ * - Live/overlong enforcement uses Redis server `TIME` inside one atomic Lua
321
+ * admission (`src/stores/redis-script.ts`): `retainUntil <= now` returns
322
+ * `'expired'`; `retainUntil - now > 240000` throws `AttestationProtocolError`
323
+ * instead of truncating; duplicates return `'duplicate'` even when full and
324
+ * never change their score; at most 64 expired entries plus the requested
325
+ * expired one are reclaimed per admission; shared `ZCARD` capacity is checked
326
+ * before `ZADD`; the ledger is `PEXPIREAT`ed through its latest retained
327
+ * score so idle state is reclaimed without shortening any member.
328
+ * - Capacity throws the shared `AttestationCapacityError` (same class as
329
+ * memory, so callers handle one type across backends). Wrong Redis data
330
+ * type, invalid script replies, and transport/script failures throw the
331
+ * typed operational `AttestationRedisStoreError`. Never silently accept
332
+ * without a committed (`ZADD`) reservation.
333
+ *
334
+ * Wiring:
335
+ *
336
+ * - `createRedisAttestationStore({ client, keyPrefix = 'wtt-attestation',
337
+ * maxEntries = 50000 })`. The public client type is the narrow structural
338
+ * `sendCommand(args: string[]): Promise<unknown>` contract compatible with
339
+ * `node-redis`; the library never creates, connects, or closes clients and
340
+ * never imports a Redis driver (driver is a dev dependency for tests only).
341
+ * - One same-slot sorted set per shared prefix
342
+ * (`<keyPrefix>:attestation-proofs`) holds opaque keys + absolute scores.
343
+ * All clients sharing a prefix MUST configure identical `maxEntries`;
344
+ * mixed capacities admit under the smallest view and reject under the
345
+ * largest, breaking the shared bound.
346
+ * - Steady-state `EVALSHA`; on cold start / definite `NOSCRIPT`
347
+ * nonexecution the runner `SCRIPT LOAD`s and retries exactly once. Uncertain
348
+ * network timeouts are never retried as though nothing committed: a timeout
349
+ * may have committed server-side but never executes a handler or falls back
350
+ * to memory (the middleware maps it to `ATTESTATION_REPLAY_UNAVAILABLE`
351
+ * without calling `next`). No non-atomic `GET`/`SET`, no separate capacity
352
+ * counter, no `nowSkewToleranceMs` hidden clamp — the adapter receives
353
+ * section 4.4's exact `retainUntil`.
354
+ *
355
+ * Capacity / failure / rate notes (no fabricated bytes-per-entry benchmark):
356
+ *
357
+ * - Size the ledger as unique accepted requests/second × maximum retention
358
+ * (default at most 50000 ms at the store including the behind-verifier
359
+ * guard, global cap 240000 ms) plus headroom; actual Redis memory and
360
+ * throughput require measurement on your deployment.
361
+ * - Redis eviction, restore/state loss, or failover can erase replay
362
+ * reservations. When relying on window-wide admission history, use a
363
+ * suitably configured dedicated `noeviction` deployment with persistence /
364
+ * replication appropriate to your recovery objectives; after state loss a
365
+ * fresh proof is required and old proofs may be admitted once more (they
366
+ * still fail verifier age rules once stale).
367
+ * - Sharing is one ledger key per prefix: independent prefixes (and memory
368
+ * objects) intentionally do not coordinate. Restarts do not reset Redis
369
+ * state, but `SCRIPT FLUSH` only forces a reload, never a second admission.
370
+ */
371
+
372
+ /** Default namespace for the single same-slot ledger key. */
373
+ declare const REDIS_ATTESTATION_STORE_DEFAULT_KEY_PREFIX: "wtt-attestation";
374
+ /** Default capacity: at most this many live reservations per shared prefix. */
375
+ declare const REDIS_ATTESTATION_STORE_DEFAULT_MAX_ENTRIES: 50000;
376
+ /**
377
+ * Typed operational failure for the Redis replay adapter: wrong Redis data
378
+ * type, invalid script replies, and transport/script failures. Capacity uses
379
+ * the shared `AttestationCapacityError`; malformed inputs use
380
+ * `AttestationProtocolError`. The middleware maps both operational failures
381
+ * to fixed `ATTESTATION_REPLAY_UNAVAILABLE` without calling `next`, falling
382
+ * back to memory, or releasing a possibly committed reservation.
383
+ */
384
+ declare class AttestationRedisStoreError extends Error {
385
+ readonly name = "AttestationRedisStoreError";
386
+ constructor(message?: string);
387
+ }
388
+ /**
389
+ * Create a shared bounded attestation replay store over one same-slot Redis
390
+ * sorted-set ledger.
391
+ *
392
+ * Pass an already-connected client (or compatible `sendCommand` adapter); the
393
+ * store never connects, disconnects, or attaches listeners. All clients of a
394
+ * prefix must use identical `maxEntries`. See the module header for capacity,
395
+ * failure, and lifecycle notes.
396
+ */
397
+ declare function createRedisAttestationStore(options: CreateRedisAttestationStoreOptions): AttestationStore;
398
+
399
+ /**
400
+ * MONGO-01 MongoDB replay adapter (task sections 4.1/4.4).
401
+ *
402
+ * Admission contract (shared with memory/Redis; see `test/store-conformance.ts`):
403
+ *
404
+ * - `reserve({ replayKey, retainUntilMs })` returns
405
+ * `'reserved' | 'duplicate' | 'expired'`.
406
+ * - The canonical deadline field is `retainUntilMs`
407
+ * (see `AttestationStoreReserveInput`). A `retainUntil` alias is accepted at
408
+ * runtime for callers written against the task-text shorthand; when both are
409
+ * present the canonical field wins.
410
+ * - Validation order per admission: input shape, store clock, opaque key
411
+ * bounds (1-256 printable ASCII), deadline shape (nonnegative safe
412
+ * integer), input expiry (`retainUntilMs <= now` returns `'expired'` with
413
+ * no allocation and no clamping), global retention cap
414
+ * (`retainUntilMs - now > 240000` rejects without allocating instead of
415
+ * truncating), then duplicate check, then bounded expiry reclamation, then
416
+ * capacity, then atomic insert.
417
+ * - Duplicate detection precedes capacity rejection: a live duplicate returns
418
+ * `'duplicate'` even when full. Duplicates never extend expiry and never
419
+ * rescore.
420
+ * - Each admission reclaims the requested expired entry (when the same key
421
+ * holds one) plus at most 64 expired entries from the indexed accounting
422
+ * ledger. No live entry is ever evicted and there is no interval timer.
423
+ * - An expired key is reservable again for a fresh proof whose deadline is
424
+ * valid; the old proof still fails the verifier's age rules (the store never
425
+ * sees signatures, only opaque already-derived replay keys).
426
+ *
427
+ * Storage design (follows the vault MongoDB store's DPoP replay
428
+ * reservations module):
429
+ *
430
+ * - One proof collection with unique `_id`s (`{ _id: replayKey,
431
+ * retainUntilMs }`) plus a separate non-TTL capacity/accounting collection
432
+ * (`{ _id, kind, replayKey?, retainUntilMs? }`) with one shared capacity
433
+ * row (`{ _id: 'capacity' }` holding `entries`/`maxEntries`/`revision`).
434
+ * - Multi-document snapshot transaction per admission: duplicate check,
435
+ * bounded expiry reclaim (64), capacity check, insert. Duplicates never
436
+ * extend expiry; no live eviction.
437
+ * - Do not rely on a Mongo TTL index for correctness (coarse granularity);
438
+ * the indexed, non-TTL accounting ledger is the source of truth. Create a
439
+ * non-TTL index on `retainUntilMs` (for example
440
+ * `{ kind: 1, retainUntilMs: 1 }` on the capacity collection) for the
441
+ * bounded expiry queries; never add a TTL index to the accounting
442
+ * collection.
443
+ * - App clock with a synchronized-clock requirement across instances (Mongo
444
+ * has no `TIME` equivalent): all verifiers/stores sharing one database must
445
+ * use synchronized clocks within the configured `clusterClockGuardMs`, and
446
+ * an invalid clock fails closed without allocating or touching the
447
+ * database.
448
+ *
449
+ * Wiring:
450
+ *
451
+ * - `createMongoAttestationStore({ db, proofsCollectionName?,
452
+ * capacityCollectionName?, maxEntries?, now? })`. The `Db`/collection
453
+ * handles are retained live without cloning, connecting, or closing; the
454
+ * caller owns the underlying `MongoClient` lifecycle (connect/close).
455
+ * Production deployments require a replica set (transactions).
456
+ * - The `mongodb` driver is a dev dependency for tests only. The library
457
+ * uses narrow structural `Db`/collection/session types and never imports a
458
+ * driver at runtime, so browser-only `/signer` consumers never need it.
459
+ * - Capacity throws the shared `AttestationCapacityError` (same class as
460
+ * memory/Redis). Wrong collection types, transaction failures, and
461
+ * malformed persisted rows throw the narrow `AttestationMongoStoreError`.
462
+ * Malformed inputs throw `AttestationProtocolError` without touching the
463
+ * database. The middleware already maps unknown store failures to fixed
464
+ * `ATTESTATION_REPLAY_UNAVAILABLE` without calling `next`; do not change
465
+ * middleware for this adapter.
466
+ *
467
+ * Sharing boundary: replay state is shared by all clients using the same
468
+ * database/collections with identical `maxEntries`. Independent databases or
469
+ * collection pairs intentionally do not coordinate. Mixed capacities break
470
+ * the shared bound; configure identically everywhere.
471
+ */
472
+
473
+ /** Default capacity: at most this many live replay reservations per database. */
474
+ declare const MONGO_ATTESTATION_STORE_DEFAULT_MAX_ENTRIES: 50000;
475
+ /** Default proof collection name (unique `_id`s, no TTL). */
476
+ declare const MONGO_ATTESTATION_STORE_DEFAULT_PROOFS_COLLECTION: "attestation_proofs";
477
+ /** Default capacity/accounting collection name (non-TTL ledger). */
478
+ declare const MONGO_ATTESTATION_STORE_DEFAULT_CAPACITY_COLLECTION: "attestation_replay_capacity";
479
+ /**
480
+ * Typed operational failure for the Mongo replay adapter: transport,
481
+ * topology, transaction, and persisted-row inconsistency failures. Capacity
482
+ * uses the shared `AttestationCapacityError`; malformed inputs use
483
+ * `AttestationProtocolError`. The middleware maps this error to fixed
484
+ * `ATTESTATION_REPLAY_UNAVAILABLE` without calling `next`, falling back to
485
+ * memory, or releasing a possibly committed reservation.
486
+ */
487
+ declare class AttestationMongoStoreError extends Error {
488
+ readonly name = "AttestationMongoStoreError";
489
+ constructor(message?: string);
490
+ }
491
+ /** Narrow structural session: only `withTransaction`/`endSession` are used. */
492
+ interface MongoAttestationStoreSession {
493
+ withTransaction<T>(fn: () => Promise<T>, options?: unknown): Promise<T | undefined>;
494
+ endSession(): Promise<void>;
495
+ }
496
+ /** Narrow structural collection: only the replay-admission methods are used. */
497
+ interface MongoAttestationStoreCollection {
498
+ readonly collectionName: string;
499
+ findOne(filter?: unknown, options?: unknown): Promise<unknown>;
500
+ findOneAndUpdate(filter: unknown, update: unknown, options?: unknown): Promise<unknown>;
501
+ insertOne(doc: unknown, options?: unknown): Promise<unknown>;
502
+ deleteOne(filter?: unknown, options?: unknown): Promise<unknown>;
503
+ deleteMany(filter?: unknown, options?: unknown): Promise<unknown>;
504
+ replaceOne(filter: unknown, replacement: unknown, options?: unknown): Promise<unknown>;
505
+ updateOne(filter: unknown, update: unknown, options?: unknown): Promise<unknown>;
506
+ find(filter?: unknown, options?: unknown): {
507
+ sort(spec: unknown): {
508
+ limit(n: number): {
509
+ toArray(): Promise<unknown[]>;
510
+ };
511
+ };
512
+ };
513
+ }
514
+ /** Narrow structural database handle: `collection` plus session creation. */
515
+ interface MongoAttestationStoreDb {
516
+ collection(name: string): MongoAttestationStoreCollection;
517
+ readonly client: {
518
+ startSession(): MongoAttestationStoreSession;
519
+ };
520
+ }
521
+ /** Options for `createMongoAttestationStore` (MONGO-01). */
522
+ interface CreateMongoAttestationStoreOptions {
523
+ /** Database handle from an already-connected caller-owned client. */
524
+ readonly db: MongoAttestationStoreDb;
525
+ /** Proof collection name; default `attestation_proofs`. */
526
+ readonly proofsCollectionName?: string;
527
+ /** Capacity/accounting collection name; default `attestation_replay_capacity`. */
528
+ readonly capacityCollectionName?: string;
529
+ /** Positive safe-integer shared capacity; default 50000. */
530
+ readonly maxEntries?: number;
531
+ /** App clock; default `Date.now`. Must stay synchronized across instances. */
532
+ readonly now?: () => number;
533
+ }
534
+ /**
535
+ * Create a shared bounded attestation replay store over two MongoDB
536
+ * collections.
537
+ *
538
+ * Pass a `Db` from an already-connected caller-owned `MongoClient`; the
539
+ * store never connects, disconnects, or attaches listeners. All clients
540
+ * sharing one database/collections must configure identical `maxEntries`
541
+ * and synchronized clocks. Production deployments require a replica set
542
+ * (transactions). Create a non-TTL index on `retainUntilMs` for the bounded
543
+ * expiry queries; never add a TTL index to the accounting collection.
544
+ */
545
+ declare function createMongoAttestationStore(options: CreateMongoAttestationStoreOptions): AttestationStore;
546
+
547
+ /**
548
+ * ATT-05 owned key rotation (task sections 4.1/4.6).
549
+ *
550
+ * - `generateAttestationKey(keyId, { acceptFrom, acceptUntil })` creates one
551
+ * random 32-byte entry for an explicit acceptance window.
552
+ * - `createStaticKeyProvider(snapshot)` owns an immutable snapshot.
553
+ * - `createRotatingKeyProvider(snapshot)` supports explicit atomic
554
+ * `replace(snapshot)`; external cron/KMS/config code owns provisioning and
555
+ * refresh. No default polling timer, no hardcoded grace policy.
556
+ *
557
+ * Snapshot rules (section 4.1):
558
+ *
559
+ * - Snapshots have `currentKeyId` plus a bounded list of 1-16 entries
560
+ * `{ keyId, key: Uint8Array(32), acceptFrom, acceptUntil }`.
561
+ * - Key bytes are exactly 32 bytes; times are nonnegative safe-integer epoch
562
+ * milliseconds with `acceptFrom < acceptUntil`; identifiers match
563
+ * `[A-Za-z0-9_-]{1,64}`.
564
+ * - `currentKeyId` must name a present entry. Snapshots are owned and
565
+ * copy-isolated: input bytes are copied on entry, output bytes are copied
566
+ * on every `getSnapshot()`, so later caller mutation (including in-place
567
+ * `Uint8Array` writes) cannot change provider state and provider state
568
+ * cannot be mutated through a returned snapshot.
569
+ * - Key IDs are unique across the application's provisioning history.
570
+ * Providers reject remapping an ID that is present in their retained
571
+ * snapshot (same ID with different bytes or a different acceptance
572
+ * interval) without growing an unbounded historical ID ledger. Generate
573
+ * unique version/random identifiers and never reintroduce a retired ID with
574
+ * different material. Atomic replacement controls subsequent snapshots; it
575
+ * is not cancellation of requests already using an owned preflight
576
+ * snapshot.
577
+ * - Rejected replacements leave the previous valid snapshot intact
578
+ * (atomic). Provider/read failure is operational `503` at the verifier,
579
+ * never a stale-key client error (see `verify.ts`).
580
+ *
581
+ * Runbook (finite acceptance, application-configured):
582
+ *
583
+ * - Prepare new material on **all** verifiers, publish current
584
+ * metadata/module, retain old material to its explicit deadline, retire
585
+ * only after overlap requirements. Do not make one instance the
586
+ * authoritative random-key generator for an uncoordinated cluster.
587
+ * - Finite acceptance/grace is application-configured, not automatically
588
+ * `2 x rotation interval`.
589
+ *
590
+ * Public HMAC material delivered in JavaScript stays accessible to every
591
+ * caller despite rotation; rotation limits how long old material is
592
+ * accepted, it does not stop a caller from downloading the next version.
593
+ *
594
+ * No import-time network requests, timers, key generation, or storage
595
+ * access. Construction performs no I/O (`sideEffects: false`).
596
+ */
597
+
598
+ /**
599
+ * Create one random 32-byte key entry for an explicit acceptance window.
600
+ * Uses `node:crypto` randomness; the returned entry owns its bytes (frozen).
601
+ */
602
+ declare function generateAttestationKey(keyId: string, window: {
603
+ readonly acceptFrom: number;
604
+ readonly acceptUntil: number;
605
+ }): AttestationKeyEntry;
606
+ /**
607
+ * Create an immutable static key provider. The input snapshot is copied on
608
+ * entry; every `getSnapshot()` returns a fresh owned copy. No timers,
609
+ * network, or storage work occurs at construction or on reads.
610
+ */
611
+ declare function createStaticKeyProvider(snapshot: AttestationKeySnapshotInput): AttestationKeyProvider;
612
+ /**
613
+ * Create an explicit-replacement rotating key provider. `replace(snapshot)`
614
+ * validates the new snapshot shape first, then rejects atomically when any
615
+ * ID retained in the current snapshot would be remapped to different bytes
616
+ * or a different acceptance interval; the previous valid snapshot survives
617
+ * rejected replacements. Retirement (dropping an ID) and introduction of
618
+ * fresh IDs are allowed. No historical tombstone ledger is kept: an ID that
619
+ * is no longer retained may be reintroduced, but provisioners must never
620
+ * reintroduce a retired ID with different material (generate unique
621
+ * version/random identifiers).
622
+ */
623
+ declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput): AttestationRotatingKeyProvider;
624
+
625
+ /**
626
+ * ATT-05 relative signer-asset router (task sections 4.1/4.6).
627
+ *
628
+ * Server-only: may import Express and Node built-ins. Never imported by the
629
+ * `./signer` browser entry.
630
+ *
631
+ * - `createSignerBundleRouter({ publicOrigin, replayNamespace, keyProvider,
632
+ * basePath = '/attestation', publicPathPrefix, now, operationTimeoutMs })`
633
+ * returns a **relative** Express router. It does not mount itself at
634
+ * `basePath`; the application mounts it (for example
635
+ * `app.use('/attestation', router)`). `basePath` plus the trusted
636
+ * `publicPathPrefix` are used solely to publish correct external URLs
637
+ * (`publicOrigin + publicPathPrefix + basePath + /signer.<hash>.mjs`).
638
+ * Request headers (`Host`, `Forwarded`, `X-Forwarded-*`) are never used
639
+ * for URL construction.
640
+ * - Routes: `GET /signer-meta`, `GET /signer.<contenthash>.mjs`,
641
+ * `GET /runtime.<contenthash>.mjs`. The runtime is the actual built browser
642
+ * ESM entry from this installed package (see `signer-assets.ts` for CJS/ESM
643
+ * + release-flattening lookup); the 64-hex content hash identifies exact
644
+ * immutable bytes. Never serve different content from the same URL.
645
+ * - Key-specific modules import the runtime via relative
646
+ * `./runtime.<hash>.mjs` and export `signer: RequestSigner` from
647
+ * `createRequestSigner`. Key bytes use a simple canonical encoded literal;
648
+ * no XOR/obfuscation is claimed as protection. All generated imports
649
+ * resolve in a browser without an npm bare-module map.
650
+ * - Metadata is bounded JSON
651
+ * `{ version: 1, keyId, signerUrl, publicOrigin, replayNamespace }` with
652
+ * `Cache-Control: no-store`, serving the current active key only. The
653
+ * current entry must be present and active at `nowMs`; otherwise metadata
654
+ * fails with `503 ATTESTATION_KEYS_UNAVAILABLE`.
655
+ * - Immutable assets use `Content-Type: text/javascript; charset=utf-8`,
656
+ * `X-Content-Type-Options: nosniff`, and
657
+ * `Cache-Control: public, max-age=31536000, immutable`. Unknown hashes
658
+ * return controlled `404`; syntactically valid but not currently retained
659
+ * hashes return `410`; neither redirects to latest. CORS/CSP policy is
660
+ * application-configured: this router sets no `Access-Control-*` headers
661
+ * and grants no broad credentialed access. Documented host requirements:
662
+ * serve metadata/modules over the pinned `publicOrigin`, allow
663
+ * `script-src` for the module URLs, and configure CORS only as the
664
+ * application requires (same-origin by default; cross-origin deployments
665
+ * must explicitly allow `GET` metadata/module fetches which use
666
+ * `cache: no-store`, `credentials: omit`, `redirect: error`).
667
+ * - Asset generation is bounded by the retained snapshot (max 16 keys) plus
668
+ * fixed runtime (512 KiB) / module (8 KiB) / metadata (4096 byte) caps.
669
+ * Key modules are derived deterministically per request from the current
670
+ * snapshot; no unbounded retired-asset cache is kept. Dropping a retired
671
+ * key's asset is safe because the client performs at most one bounded
672
+ * discovery refetch on a module-phase retirement race.
673
+ *
674
+ * Runbook: prepare new material on **all** verifiers, publish current
675
+ * metadata/module, retain old material to its explicit deadline, retire only
676
+ * after overlap. Old modules remain usable while their keys are retained.
677
+ * Public asset caching does not make distributed material confidential.
678
+ *
679
+ * No timers, network requests, or storage work at construction. Per-request
680
+ * provider waits are bounded by `operationTimeoutMs` (default 5000 ms);
681
+ * late settlement cannot resume the request.
682
+ */
683
+
684
+ /**
685
+ * Create the relative signer-asset router. The caller mounts it at
686
+ * `basePath` (for example `app.use('/attestation', router)`); this factory
687
+ * never mounts itself. All external URLs are built from trusted
688
+ * configuration (`publicOrigin` + `publicPathPrefix` + `basePath`), never
689
+ * from request headers.
690
+ */
691
+ declare function createSignerBundleRouter(options: CreateSignerBundleRouterOptions): Router;
692
+
693
+ /**
694
+ * SHA-256 hex digest of the exact submitted body bytes. An actually empty
695
+ * request hashes zero bytes (`EMPTY_BODY_SHA256_HEX`). The signed bytes and
696
+ * the captured bytes must be the same representation (parser capture uses
697
+ * `inflate: false`; identity/no content-encoding only).
698
+ */
699
+ declare function hashBodySha256Hex(body: Uint8Array): string;
700
+ /**
701
+ * Derive the opaque replay key `att:v1:` + SHA-256 hex of
702
+ * `JSON([replayNamespace, publicOrigin, nonceHex])`. The key carries no
703
+ * instance ID, path, token, session, or key ID, so nonce single-use spans
704
+ * accepted rotations in one protection space.
705
+ */
706
+ declare function deriveAttestationReplayKey(input: {
707
+ readonly replayNamespace: string;
708
+ readonly publicOrigin: string;
709
+ readonly nonceHex: string;
710
+ }): string;
711
+
712
+ export { type AttestationBodyCapture, type AttestationBodyCaptureWithRecord, type AttestationBodyRecord, AttestationCapacityError, type AttestationDiagnosticEvent, type AttestationDiagnosticHook, AttestationError, type AttestationErrorCode, type AttestationKeyEntry, type AttestationKeyProvider, type AttestationKeySnapshot, type AttestationKeySnapshotInput, AttestationMongoStoreError, type AttestationRedisClient, AttestationRedisStoreError, type AttestationRotatingKeyProvider, type AttestationStore, type AttestationStoreReserveInput, type AttestationStoreReserveResult, type AttestationVerifiedProof, type AttestationVerifyInput, type CreateAttestationBodyCaptureOptions, type CreateMemoryAttestationStoreOptions, type CreateMongoAttestationStoreOptions, type CreateRedisAttestationStoreOptions, type CreateRequestAttestationMiddlewareOptions, type CreateRequestAttestationMiddlewareOptionsWithDiagnostic, type CreateSignerBundleRouterOptions, MONGO_ATTESTATION_STORE_DEFAULT_CAPACITY_COLLECTION, MONGO_ATTESTATION_STORE_DEFAULT_MAX_ENTRIES, MONGO_ATTESTATION_STORE_DEFAULT_PROOFS_COLLECTION, type MongoAttestationStoreCollection, type MongoAttestationStoreDb, type MongoAttestationStoreSession, ProofTiming, REDIS_ATTESTATION_STORE_DEFAULT_KEY_PREFIX, REDIS_ATTESTATION_STORE_DEFAULT_MAX_ENTRIES, ResolvedTimePolicy, assertAllowedContentEncoding, checkProofTiming, copyAttestationSnapshot, createAttestationBodyCapture, createMemoryAttestationStore, createMongoAttestationStore, createRedisAttestationStore, createRequestAttestationMiddleware, createRotatingKeyProvider, createSignerBundleRouter, createStaticKeyProvider, deriveAttestationReplayKey, deriveReplayKey, generateAttestationKey, getAttestationBodyRecord, hashBodySha256Hex, isAttestationError, resolveContentType, resolveRequestTarget, toAttestationErrorPayload, verifyAttestationProof };