@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/LICENSE +201 -0
- package/README.md +505 -0
- package/index.d.mts +712 -0
- package/index.d.ts +712 -0
- package/index.js +2881 -0
- package/index.mjs +2850 -0
- package/package.json +64 -0
- package/signer.d.mts +351 -0
- package/signer.d.ts +351 -0
- package/signer.js +2064 -0
- package/signer.mjs +2041 -0
- package/time-policy-DlER-7rf.d.mts +385 -0
- package/time-policy-DlER-7rf.d.ts +385 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
import { P as ProofTiming, R as ResolvedTimePolicy } from './time-policy-DlER-7rf.js';
|
|
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.js';
|
|
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 };
|