@web-ts-toolkit/express-request-attestation 0.51.0 → 0.53.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/README.md +352 -56
- package/index.d.mts +212 -103
- package/index.d.ts +212 -103
- package/index.js +31 -56
- package/index.mjs +31 -56
- package/package.json +1 -1
- package/signer.d.mts +196 -93
- package/signer.d.ts +196 -93
- package/signer.js +454 -326
- package/signer.mjs +454 -326
- package/{time-policy-DlER-7rf.d.mts → time-policy-CquS0Zgm.d.mts} +46 -25
- package/{time-policy-DlER-7rf.d.ts → time-policy-CquS0Zgm.d.ts} +46 -25
package/index.d.ts
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import { P as ProofTiming, R as ResolvedTimePolicy } from './time-policy-
|
|
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-
|
|
1
|
+
import { P as ProofTiming, R as ResolvedTimePolicy } from './time-policy-CquS0Zgm.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-CquS0Zgm.js';
|
|
3
3
|
import { Router } from 'express';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* imported exclusively by the `.` entry. The `./signer` entry never imports
|
|
10
|
-
* it, so browser declarations stay free of server types.
|
|
6
|
+
* Server-side key, replay-store, and Express integration contracts, exported
|
|
7
|
+
* from `@web-ts-toolkit/express-request-attestation`. Browser consumers use
|
|
8
|
+
* the separate `/signer` entry, which needs no server dependencies or types.
|
|
11
9
|
*/
|
|
12
10
|
/** One accepted key: exactly 32 bytes with an explicit acceptance window. */
|
|
13
11
|
interface AttestationKeyEntry {
|
|
12
|
+
/** Unique provisioning ID matching `[A-Za-z0-9_-]{1,64}`; never remap a live ID. */
|
|
14
13
|
readonly keyId: string;
|
|
15
14
|
/** Exactly 32 bytes; snapshots are owned/copy-isolated. */
|
|
16
15
|
readonly key: Uint8Array;
|
|
17
|
-
/**
|
|
16
|
+
/** Inclusive activation, nonnegative safe-integer epoch ms; less than `acceptUntil`. */
|
|
18
17
|
readonly acceptFrom: number;
|
|
18
|
+
/** Exclusive retirement in epoch ms. Identical for this ID on every verifier in the protection space. */
|
|
19
19
|
readonly acceptUntil: number;
|
|
20
20
|
}
|
|
21
21
|
/** Keyring snapshot returned by `AttestationKeyProvider.getSnapshot()`. */
|
|
22
22
|
interface AttestationKeySnapshot {
|
|
23
|
-
/**
|
|
23
|
+
/** Names a present entry; discovery additionally requires it active at its post-provider clock. */
|
|
24
24
|
readonly currentKeyId: string;
|
|
25
|
+
/** 1–16 retained entries, with identical material/windows across the protection space. */
|
|
25
26
|
readonly keys: readonly AttestationKeyEntry[];
|
|
26
27
|
}
|
|
27
28
|
/** Input snapshot for static/rotating providers (owned/copied on entry). */
|
|
@@ -36,61 +37,87 @@ interface AttestationKeySnapshotInput {
|
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* Typed key provider. Returns an owned, copy-isolated snapshot (or a promise
|
|
39
|
-
* of one). Provider
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* of one). Provider throws, rejected reads, malformed snapshots, and timeouts
|
|
41
|
+
* become fixed `503 ATTESTATION_KEYS_UNAVAILABLE`, without a stale-key marker.
|
|
42
|
+
* External cron/KMS/config code owns provisioning, refresh, and dependency
|
|
43
|
+
* deadlines; there is no polling timer or cancellation of underlying work.
|
|
42
44
|
*/
|
|
43
45
|
interface AttestationKeyProvider {
|
|
44
46
|
getSnapshot(): AttestationKeySnapshot | Promise<AttestationKeySnapshot>;
|
|
45
47
|
}
|
|
46
|
-
/** Rotating provider with explicit atomic replacement
|
|
48
|
+
/** Rotating provider with explicit atomic replacement and copy-isolated reads. */
|
|
47
49
|
interface AttestationRotatingKeyProvider extends AttestationKeyProvider {
|
|
50
|
+
/** Rejects invalid/remapped retained IDs atomically; does not cancel requests using an earlier snapshot. */
|
|
48
51
|
replace(snapshot: AttestationKeySnapshotInput): void;
|
|
49
52
|
}
|
|
50
53
|
/** Atomic bounded replay-store reservation input. */
|
|
51
54
|
interface AttestationStoreReserveInput {
|
|
52
55
|
/** Opaque key: `att:v1:` + SHA-256 hex of `[namespace, origin, nonceHex]`. */
|
|
53
56
|
readonly replayKey: string;
|
|
54
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Absolute safe-integer epoch-ms deadline:
|
|
59
|
+
* `min(timestampMs + maxAgeMs + clockSkewMs, key.acceptUntil) + clusterClockGuardMs`.
|
|
60
|
+
* Retain until this deadline; never clamp it or extend it on a duplicate.
|
|
61
|
+
*/
|
|
55
62
|
readonly retainUntilMs: number;
|
|
56
63
|
}
|
|
64
|
+
/** One atomic admission: inserted, already live (no extension), or deadline already elapsed. */
|
|
57
65
|
type AttestationStoreReserveResult = 'reserved' | 'duplicate' | 'expired';
|
|
58
66
|
/**
|
|
59
67
|
* Bounded replay store. `reserve` returns `duplicate` for an admitted nonce,
|
|
60
68
|
* `expired` when the retention deadline has elapsed, and throws typed errors
|
|
61
|
-
* on capacity exhaustion or unavailability.
|
|
62
|
-
*
|
|
69
|
+
* on capacity exhaustion or unavailability. Verify proofs before reserving;
|
|
70
|
+
* the store does not verify signatures. Successful or possibly committed
|
|
71
|
+
* reservations are never released on handler failure, timeout, or lost response.
|
|
72
|
+
* Shared verifiers must use identical time policies and key acceptance windows.
|
|
73
|
+
* Direct calls have no package-level wait timeout; callers own driver deadlines.
|
|
63
74
|
*/
|
|
64
75
|
interface AttestationStore {
|
|
65
76
|
reserve(input: AttestationStoreReserveInput): Promise<AttestationStoreReserveResult>;
|
|
66
77
|
}
|
|
67
78
|
/**
|
|
68
|
-
* Narrow structural Redis client contract
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* dependency for integration tests, not a runtime dependency.
|
|
79
|
+
* Narrow structural Redis client contract. Inject an already-connected compatible
|
|
80
|
+
* client (for example `node-redis`). The caller owns connection, error listeners,
|
|
81
|
+
* command/queue deadlines, and shutdown; this package never imports a driver.
|
|
72
82
|
*/
|
|
73
83
|
interface AttestationRedisClient {
|
|
74
84
|
sendCommand(args: string[]): Promise<unknown>;
|
|
75
85
|
}
|
|
76
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Express guard configuration. All guards in one `(publicOrigin, replayNamespace)`
|
|
88
|
+
* share replay state, identical time policies, and identical per-key acceptance
|
|
89
|
+
* windows. Migrate live policy changes to a new namespace rather than mixing
|
|
90
|
+
* policies or extending old key deadlines over already-admitted proofs.
|
|
91
|
+
*/
|
|
77
92
|
interface CreateRequestAttestationMiddlewareOptions {
|
|
78
|
-
/** Static canonical HTTPS origin (loopback
|
|
93
|
+
/** Static canonical HTTPS origin (loopback HTTP allowed); never derive from request headers. */
|
|
79
94
|
readonly publicOrigin: string;
|
|
95
|
+
/** Protection-space identifier matching `[A-Za-z0-9_-]{1,128}`; must match signers. */
|
|
80
96
|
readonly replayNamespace: string;
|
|
81
|
-
/** Upstream-stripped prefix to
|
|
97
|
+
/** Upstream-stripped prefix to prepend to `req.originalUrl`; default empty, no trailing slash. */
|
|
82
98
|
readonly publicPathPrefix?: string;
|
|
99
|
+
/** Caller-owned live provider; each request validates and copies its bounded snapshot. */
|
|
83
100
|
readonly keyProvider: AttestationKeyProvider;
|
|
101
|
+
/** Atomic shared nonce admission after successful verification; no fallback on store failure. */
|
|
84
102
|
readonly store: AttestationStore;
|
|
103
|
+
/** Maximum proof age, default 30000 ms; safe integer in 1000–120000. */
|
|
85
104
|
readonly maxAgeMs?: number;
|
|
105
|
+
/** Client clock allowance, default 5000 ms; safe integer in 0–30000. */
|
|
86
106
|
readonly clockSkewMs?: number;
|
|
107
|
+
/** Maximum pairwise verifier/store clock difference, default 5000 ms (0–30000); retention only. */
|
|
87
108
|
readonly clusterClockGuardMs?: number;
|
|
109
|
+
/** Single proof header; default `x-client-transaction-id`. Must pass shared HTTP name validation. */
|
|
88
110
|
readonly headerName?: string;
|
|
111
|
+
/** Default `Date.now`; every call must return nonnegative safe-integer epoch ms or the guard fails with 500. */
|
|
89
112
|
readonly now?: () => number;
|
|
90
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Each async provider/store wait, default 5000 ms; integer range 1–60000.
|
|
115
|
+
* Does not cancel underlying work. Configure driver deadlines separately;
|
|
116
|
+
* a timed-out reservation may commit but can never resume the handler.
|
|
117
|
+
*/
|
|
91
118
|
readonly operationTimeoutMs?: number;
|
|
92
119
|
}
|
|
93
|
-
/**
|
|
120
|
+
/** Exact-byte capture for Express parsers; use the same limit and `inflate: false` on the parser. */
|
|
94
121
|
interface CreateAttestationBodyCaptureOptions {
|
|
95
122
|
/** Parser/body budget, default 1 MiB, hard cap 16 MiB. */
|
|
96
123
|
readonly maxBodyBytes?: number;
|
|
@@ -98,35 +125,51 @@ interface CreateAttestationBodyCaptureOptions {
|
|
|
98
125
|
/**
|
|
99
126
|
* Package-owned body-capture integration: an Express parser `verify`
|
|
100
127
|
* callback storing digest/count in per-request state plus a narrow error
|
|
101
|
-
* handler mapping parser size/encoding failures to
|
|
128
|
+
* handler mapping parser size/encoding failures to fixed 413/415 errors.
|
|
129
|
+
* These callbacks assign directly to Express parser/error-handler types.
|
|
102
130
|
*/
|
|
103
131
|
interface AttestationBodyCapture {
|
|
132
|
+
/** Attach as the parser's `verify` callback; observes bytes without changing `req.body`. */
|
|
104
133
|
readonly verify: (req: unknown, res: unknown, body: unknown, encoding: string) => void;
|
|
134
|
+
/** Mount after parsers and before the guard; other parser errors pass to `next(err)`. */
|
|
105
135
|
readonly errorHandler: (err: unknown, req: unknown, res: unknown, next: (err?: unknown) => void) => void;
|
|
106
136
|
}
|
|
107
|
-
/**
|
|
137
|
+
/** Trusted asset-distribution configuration; the caller owns mounting, CORS, and CSP. */
|
|
108
138
|
interface CreateSignerBundleRouterOptions {
|
|
139
|
+
/** Static canonical HTTPS origin (loopback HTTP allowed); never derived from Host/Forwarded headers. */
|
|
109
140
|
readonly publicOrigin: string;
|
|
141
|
+
/** Protection-space namespace matching the verifier and browser client. */
|
|
110
142
|
readonly replayNamespace: string;
|
|
143
|
+
/** Caller-owned provider; discovery publishes only the current key active after the provider read. */
|
|
111
144
|
readonly keyProvider: AttestationKeyProvider;
|
|
112
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Default `/attestation`. Mount the relative router at this path yourself.
|
|
147
|
+
* `/` publishes `/signer-meta`, `/signer.<hash>.mjs`, and `/runtime.<hash>.mjs`.
|
|
148
|
+
*/
|
|
113
149
|
readonly basePath?: string;
|
|
150
|
+
/** Prefix stripped by the upstream proxy, default empty; prepend once to published URLs, not the app mount. */
|
|
114
151
|
readonly publicPathPrefix?: string;
|
|
152
|
+
/** Default `Date.now`; nonnegative safe-integer epoch ms, checked before and after discovery's provider read. */
|
|
115
153
|
readonly now?: () => number;
|
|
154
|
+
/** Per-provider wait bound, default 5000 ms (1–60000); does not cancel provider work. Timeout returns fixed 503. */
|
|
116
155
|
readonly operationTimeoutMs?: number;
|
|
117
156
|
}
|
|
118
|
-
/**
|
|
157
|
+
/** Process-local replay storage, shared only by callers reusing the same store object. */
|
|
119
158
|
interface CreateMemoryAttestationStoreOptions {
|
|
159
|
+
/** Positive safe-integer capacity; default 50000. Live entries are never evicted. */
|
|
120
160
|
readonly maxEntries?: number;
|
|
161
|
+
/** Default `Date.now`; nonnegative safe-integer epoch ms, synchronized with verifier clocks. */
|
|
121
162
|
readonly now?: () => number;
|
|
122
163
|
}
|
|
123
|
-
/**
|
|
164
|
+
/** Shared Redis replay ledger; the caller owns the connected client and dependency deadlines. */
|
|
124
165
|
interface CreateRedisAttestationStoreOptions {
|
|
125
166
|
readonly client: AttestationRedisClient;
|
|
167
|
+
/** Default `wtt-attestation`; 1–256 characters. All instances in a protection space share this ledger prefix. */
|
|
126
168
|
readonly keyPrefix?: string;
|
|
169
|
+
/** Positive safe-integer capacity; default 50000, identical for every client sharing the prefix. */
|
|
127
170
|
readonly maxEntries?: number;
|
|
128
171
|
}
|
|
129
|
-
/** Fixed
|
|
172
|
+
/** Fixed verifier error codes; `AttestationError` supplies the stable HTTP status and message. */
|
|
130
173
|
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
174
|
/**
|
|
132
175
|
* Typed attestation failure carrying a fixed code/status/message. Responses
|
|
@@ -146,32 +189,51 @@ declare function toAttestationErrorPayload(error: AttestationError): {
|
|
|
146
189
|
readonly message: string;
|
|
147
190
|
};
|
|
148
191
|
|
|
192
|
+
/** Sanitized failure observation; excludes request/key/body/provider diagnostics. */
|
|
149
193
|
interface AttestationDiagnosticEvent {
|
|
150
194
|
readonly code: AttestationErrorCode;
|
|
151
195
|
readonly status: number;
|
|
152
196
|
}
|
|
153
197
|
type AttestationDiagnosticHook = (event: AttestationDiagnosticEvent) => void;
|
|
154
198
|
type CreateRequestAttestationMiddlewareOptionsWithDiagnostic = CreateRequestAttestationMiddlewareOptions & {
|
|
199
|
+
/** Optional synchronous observation of fixed code/status only; hook failure never changes admission. */
|
|
155
200
|
readonly onDiagnostic?: AttestationDiagnosticHook;
|
|
156
201
|
};
|
|
157
202
|
/**
|
|
158
203
|
* Create the verify-before-reserve Express guard.
|
|
159
204
|
*
|
|
205
|
+
* Mount after exact-byte parser capture and its error handler on selected API
|
|
206
|
+
* routes. The returned function assigns directly to `express.RequestHandler`.
|
|
207
|
+
* Missing/empty proofs fail with 403 ATTESTATION_MISSING; duplicate, comma-joined,
|
|
208
|
+
* oversized, or noncanonical proofs and raw duplicate Content-Type fields fail
|
|
209
|
+
* with 403 ATTESTATION_MALFORMED before provider/replay work. Only a stale-key failure
|
|
210
|
+
* carries `X-Attestation-Error: stale-key`; all provider read/snapshot failures
|
|
211
|
+
* use fixed 503 ATTESTATION_KEYS_UNAVAILABLE without that marker.
|
|
212
|
+
*
|
|
160
213
|
* Required `publicOrigin`/`replayNamespace`/services are validated once and
|
|
161
214
|
* frozen; key snapshots are owned per request while service references stay
|
|
162
215
|
* live. Async provider/store waits are bounded by `operationTimeoutMs`
|
|
163
216
|
* (default 5000 ms, allowed 1-60000 ms); a late settlement after the bound
|
|
164
217
|
* can never resume the rejected request, call `next`, or release a possibly
|
|
165
|
-
* committed reservation.
|
|
218
|
+
* committed reservation. Driver/provider cancellation and deadlines stay caller-owned.
|
|
219
|
+
*
|
|
220
|
+
* Public HMAC material does not authenticate a user/session. Auth before this
|
|
221
|
+
* guard can avoid replay allocation for unauthorized requests; guard-first can
|
|
222
|
+
* reject invalid proofs before auth work but reserves even if later auth fails.
|
|
223
|
+
* Reservations remain committed after downstream failure. Durable business
|
|
224
|
+
* idempotency and authorization are application responsibilities.
|
|
166
225
|
*/
|
|
167
226
|
declare function createRequestAttestationMiddleware(options: CreateRequestAttestationMiddlewareOptionsWithDiagnostic): (req: unknown, res: unknown, next: (err?: unknown) => void) => void;
|
|
168
227
|
|
|
228
|
+
/** Frozen observation of the exact parser bytes, never reconstructed from `req.body`. */
|
|
169
229
|
interface AttestationBodyRecord {
|
|
170
230
|
readonly digestHex: string;
|
|
171
231
|
readonly byteLength: number;
|
|
172
232
|
}
|
|
173
233
|
interface AttestationBodyCaptureWithRecord extends AttestationBodyCapture {
|
|
234
|
+
/** Returns a frozen copy, or undefined if no parser capture ran for this request. */
|
|
174
235
|
readonly getRecord: (req: unknown) => AttestationBodyRecord | undefined;
|
|
236
|
+
/** Validated byte budget; use the same value as the parser and browser wrapper limits. */
|
|
175
237
|
readonly maxBodyBytes: number;
|
|
176
238
|
}
|
|
177
239
|
/**
|
|
@@ -182,16 +244,22 @@ interface AttestationBodyCaptureWithRecord extends AttestationBodyCapture {
|
|
|
182
244
|
*/
|
|
183
245
|
declare function getAttestationBodyRecord(req: unknown): AttestationBodyRecord | undefined;
|
|
184
246
|
/**
|
|
185
|
-
* Create the parser `verify` callback
|
|
247
|
+
* Create the Express parser `verify` callback and narrow error handler.
|
|
186
248
|
*
|
|
187
249
|
* `maxBodyBytes` defaults to 1 MiB and must be a positive safe integer up to
|
|
188
250
|
* the v1 hard cap of 16 MiB. Configure the SAME value as the Express parser
|
|
189
251
|
* `limit` option and always use `inflate: false`. Applications may choose a
|
|
190
252
|
* smaller limit. Over-limit bodies fail in the parser (mapped to 413 before
|
|
191
|
-
* the guard) before any replay allocation.
|
|
253
|
+
* the guard) before any replay allocation. Pass `capture.verify` directly to
|
|
254
|
+
* `express.json`/`urlencoded`/`text`/`raw`; mount `capture.errorHandler` after
|
|
255
|
+
* parsers and before the guard. The callbacks are assignable to Express types.
|
|
256
|
+
* Size/encoding failures map to fixed 413/415; other parser errors go to
|
|
257
|
+
* `next(err)`. Capture observes the bytes without mutating `req.body`.
|
|
258
|
+
* Uncaptured nonempty bodies fail at the guard with BODY_CAPTURE_REQUIRED.
|
|
192
259
|
*/
|
|
193
260
|
declare function createAttestationBodyCapture(options?: CreateAttestationBodyCaptureOptions): AttestationBodyCaptureWithRecord;
|
|
194
261
|
|
|
262
|
+
/** Low-level verifier input. Supply trusted origin/policy/key state and a digest of captured exact bytes. */
|
|
195
263
|
interface AttestationVerifyInput {
|
|
196
264
|
readonly headerValue: string | readonly string[] | undefined;
|
|
197
265
|
readonly method: unknown;
|
|
@@ -206,6 +274,7 @@ interface AttestationVerifyInput {
|
|
|
206
274
|
readonly replayNamespace: string;
|
|
207
275
|
readonly publicOrigin: string;
|
|
208
276
|
}
|
|
277
|
+
/** Signature/timing verification result; no nonce has been reserved yet. */
|
|
209
278
|
interface AttestationVerifiedProof {
|
|
210
279
|
readonly keyId: string;
|
|
211
280
|
readonly timestampMs: number;
|
|
@@ -213,6 +282,7 @@ interface AttestationVerifiedProof {
|
|
|
213
282
|
readonly keyEntry: AttestationKeyEntry;
|
|
214
283
|
readonly timing: ProofTiming;
|
|
215
284
|
readonly replayKey: string;
|
|
285
|
+
/** Absolute epoch-ms replay retention deadline, including the cluster clock guard. */
|
|
216
286
|
readonly retainUntilMs: number;
|
|
217
287
|
readonly method: string;
|
|
218
288
|
readonly requestTarget: string;
|
|
@@ -230,7 +300,7 @@ declare function checkProofTiming(input: {
|
|
|
230
300
|
}): ProofTiming;
|
|
231
301
|
/** Concatenate the trusted prefix with `originalUrl` and canonicalize. */
|
|
232
302
|
declare function resolveRequestTarget(originalUrl: unknown, publicPathPrefix: string): string;
|
|
233
|
-
/** Single final Content-Type value. Duplicate/controls => MALFORMED. */
|
|
303
|
+
/** Single final Content-Type value. Duplicate fields/controls => MALFORMED; commas within one field are preserved. */
|
|
234
304
|
declare function resolveContentType(header: string | readonly string[] | undefined | null): string;
|
|
235
305
|
/** V1 accepts identity / absent Content-Encoding only. */
|
|
236
306
|
declare function assertAllowedContentEncoding(header: string | readonly string[] | undefined | null): void;
|
|
@@ -244,18 +314,21 @@ declare function deriveReplayKey(input: {
|
|
|
244
314
|
* Pure pre-reservation verification. Performs header/codec, snapshot lookup,
|
|
245
315
|
* time, target, content-type/encoding, body digest, and HMAC stages in order
|
|
246
316
|
* and returns the owned timing + replay key for the middleware to reserve.
|
|
317
|
+
* Always validates the raw proof first; malformed framing cannot be bypassed
|
|
318
|
+
* with a decoded envelope. Throws fixed `AttestationError` failures. This helper
|
|
319
|
+
* neither authenticates the caller nor reserves the nonce: prefer the Express
|
|
320
|
+
* guard for atomic admission and its final timing checks around asynchronous work.
|
|
247
321
|
*/
|
|
248
322
|
declare function verifyAttestationProof(input: AttestationVerifyInput): AttestationVerifiedProof;
|
|
249
323
|
|
|
250
324
|
/**
|
|
251
|
-
*
|
|
325
|
+
* Bounded atomic memory replay store.
|
|
252
326
|
*
|
|
253
|
-
* Admission contract
|
|
327
|
+
* Admission contract shared by the memory, Redis, and MongoDB stores:
|
|
254
328
|
*
|
|
255
329
|
* - `reserve({ replayKey, retainUntilMs })` returns `'reserved' | 'duplicate' | 'expired'`.
|
|
256
|
-
* -
|
|
257
|
-
*
|
|
258
|
-
* task-text shorthand; when both are present the canonical field wins.
|
|
330
|
+
* - `retainUntilMs` is the absolute epoch-ms deadline from verified proof timing,
|
|
331
|
+
* including the cluster clock guard, never a relative TTL.
|
|
259
332
|
* - Validation order per admission: input shape, store clock, opaque key bounds
|
|
260
333
|
* (1-256 printable ASCII), deadline shape (nonnegative safe integer), input
|
|
261
334
|
* expiry (`retainUntilMs <= now` returns `'expired'` with no allocation and no
|
|
@@ -283,8 +356,7 @@ declare function verifyAttestationProof(input: AttestationVerifyInput): Attestat
|
|
|
283
356
|
/**
|
|
284
357
|
* Shared capacity exhaustion: no live reservation is evicted and no proof is
|
|
285
358
|
* admitted without replay state. Handle like any replay-store failure: fail
|
|
286
|
-
* closed.
|
|
287
|
-
* type across backends.
|
|
359
|
+
* closed. All three built-in backends use this same error type.
|
|
288
360
|
*/
|
|
289
361
|
declare class AttestationCapacityError extends Error {
|
|
290
362
|
readonly name = "AttestationCapacityError";
|
|
@@ -299,27 +371,29 @@ declare class AttestationCapacityError extends Error {
|
|
|
299
371
|
* store when replay state must survive restarts or be shared by multiple
|
|
300
372
|
* application instances. Replay is shared only by callers reusing this same
|
|
301
373
|
* object; the store does not verify request signatures. Capacity defaults to
|
|
302
|
-
* 50000 entries and never evicts live entries.
|
|
374
|
+
* 50000 entries and never evicts live entries. Admission uses absolute
|
|
375
|
+
* `retainUntilMs`, rejects retention over 240000 ms rather than truncating it,
|
|
376
|
+
* and never extends a duplicate's deadline. Capacity throws
|
|
377
|
+
* `AttestationCapacityError`; malformed inputs/clock values throw
|
|
378
|
+
* `AttestationProtocolError`. Keep its clock synchronized with the verifier.
|
|
303
379
|
*/
|
|
304
380
|
declare function createMemoryAttestationStore(options?: CreateMemoryAttestationStoreOptions): AttestationStore;
|
|
305
381
|
|
|
306
382
|
/**
|
|
307
|
-
*
|
|
383
|
+
* Shared bounded Redis replay admission.
|
|
308
384
|
*
|
|
309
|
-
* Admission contract
|
|
385
|
+
* Admission contract shared with memory and MongoDB:
|
|
310
386
|
*
|
|
311
387
|
* - `reserve({ replayKey, retainUntilMs })` returns
|
|
312
388
|
* `'reserved' | 'duplicate' | 'expired'`.
|
|
313
|
-
* -
|
|
314
|
-
*
|
|
315
|
-
* runtime for callers written against the task-text shorthand; when both are
|
|
316
|
-
* present the canonical field wins.
|
|
389
|
+
* - `retainUntilMs` is the absolute epoch-ms deadline from verified proof timing,
|
|
390
|
+
* including the cluster clock guard, never a relative TTL.
|
|
317
391
|
* - Malformed inputs (non-object, bad opaque key bounds 1–256 printable
|
|
318
392
|
* ASCII, non-safe-integer deadlines) reject with `AttestationProtocolError`
|
|
319
393
|
* WITHOUT touching Redis (no allocation, `sendCommand` never called).
|
|
320
394
|
* - Live/overlong enforcement uses Redis server `TIME` inside one atomic Lua
|
|
321
|
-
* admission
|
|
322
|
-
* `'expired'`; `
|
|
395
|
+
* admission: `retainUntilMs <= nowMs` returns
|
|
396
|
+
* `'expired'`; `retainUntilMs - nowMs > 240000` throws `AttestationProtocolError`
|
|
323
397
|
* instead of truncating; duplicates return `'duplicate'` even when full and
|
|
324
398
|
* never change their score; at most 64 expired entries plus the requested
|
|
325
399
|
* expired one are reclaimed per admission; shared `ZCARD` capacity is checked
|
|
@@ -337,20 +411,18 @@ declare function createMemoryAttestationStore(options?: CreateMemoryAttestationS
|
|
|
337
411
|
* maxEntries = 50000 })`. The public client type is the narrow structural
|
|
338
412
|
* `sendCommand(args: string[]): Promise<unknown>` contract compatible with
|
|
339
413
|
* `node-redis`; the library never creates, connects, or closes clients and
|
|
340
|
-
* never imports a Redis driver
|
|
414
|
+
* never imports a Redis driver. Callers own command/queue deadlines and retries.
|
|
341
415
|
* - One same-slot sorted set per shared prefix
|
|
342
416
|
* (`<keyPrefix>:attestation-proofs`) holds opaque keys + absolute scores.
|
|
343
417
|
* All clients sharing a prefix MUST configure identical `maxEntries`;
|
|
344
|
-
* mixed capacities
|
|
345
|
-
* largest, breaking the shared bound.
|
|
418
|
+
* mixed capacities break the shared bound.
|
|
346
419
|
* - Steady-state `EVALSHA`; on cold start / definite `NOSCRIPT`
|
|
347
420
|
* nonexecution the runner `SCRIPT LOAD`s and retries exactly once. Uncertain
|
|
348
421
|
* network timeouts are never retried as though nothing committed: a timeout
|
|
349
422
|
* may have committed server-side but never executes a handler or falls back
|
|
350
423
|
* to memory (the middleware maps it to `ATTESTATION_REPLAY_UNAVAILABLE`
|
|
351
|
-
* without calling `next`).
|
|
352
|
-
*
|
|
353
|
-
* section 4.4's exact `retainUntil`.
|
|
424
|
+
* without calling `next`). The adapter preserves the verifier's exact
|
|
425
|
+
* `retainUntilMs` rather than clamping it.
|
|
354
426
|
*
|
|
355
427
|
* Capacity / failure / rate notes (no fabricated bytes-per-entry benchmark):
|
|
356
428
|
*
|
|
@@ -361,9 +433,8 @@ declare function createMemoryAttestationStore(options?: CreateMemoryAttestationS
|
|
|
361
433
|
* - Redis eviction, restore/state loss, or failover can erase replay
|
|
362
434
|
* reservations. When relying on window-wide admission history, use a
|
|
363
435
|
* suitably configured dedicated `noeviction` deployment with persistence /
|
|
364
|
-
* replication appropriate to your recovery objectives; after state loss
|
|
365
|
-
*
|
|
366
|
-
* still fail verifier age rules once stale).
|
|
436
|
+
* replication appropriate to your recovery objectives; after state loss old
|
|
437
|
+
* proofs may be admitted again while still valid under verifier age rules.
|
|
367
438
|
* - Sharing is one ledger key per prefix: independent prefixes (and memory
|
|
368
439
|
* objects) intentionally do not coordinate. Restarts do not reset Redis
|
|
369
440
|
* state, but `SCRIPT FLUSH` only forces a reload, never a second admission.
|
|
@@ -390,23 +461,30 @@ declare class AttestationRedisStoreError extends Error {
|
|
|
390
461
|
* sorted-set ledger.
|
|
391
462
|
*
|
|
392
463
|
* Pass an already-connected client (or compatible `sendCommand` adapter); the
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
464
|
+
* caller owns connection, listeners, command deadlines, and shutdown. Defaults:
|
|
465
|
+
* `keyPrefix: 'wtt-attestation'`, `maxEntries: 50000`. All clients sharing the
|
|
466
|
+
* prefix need identical capacity; verifiers in a protection space also need
|
|
467
|
+
* identical time policy/key windows and clocks within `clusterClockGuardMs`
|
|
468
|
+
* of Redis `TIME`.
|
|
469
|
+
*
|
|
470
|
+
* Absolute `retainUntilMs` scores never slide on duplicates; live entries are
|
|
471
|
+
* never evicted by this adapter. Capacity throws `AttestationCapacityError`,
|
|
472
|
+
* malformed inputs throw `AttestationProtocolError`, and dependency failures
|
|
473
|
+
* throw `AttestationRedisStoreError`. There is no direct-call wait timeout or
|
|
474
|
+
* retry after uncertain command execution. Eviction/state loss/failover can
|
|
475
|
+
* erase replay history; configure Redis persistence and noeviction accordingly.
|
|
396
476
|
*/
|
|
397
477
|
declare function createRedisAttestationStore(options: CreateRedisAttestationStoreOptions): AttestationStore;
|
|
398
478
|
|
|
399
479
|
/**
|
|
400
|
-
*
|
|
480
|
+
* Bounded transactional MongoDB replay adapter.
|
|
401
481
|
*
|
|
402
|
-
* Admission contract
|
|
482
|
+
* Admission contract shared with memory and Redis:
|
|
403
483
|
*
|
|
404
484
|
* - `reserve({ replayKey, retainUntilMs })` returns
|
|
405
485
|
* `'reserved' | 'duplicate' | 'expired'`.
|
|
406
|
-
* -
|
|
407
|
-
*
|
|
408
|
-
* runtime for callers written against the task-text shorthand; when both are
|
|
409
|
-
* present the canonical field wins.
|
|
486
|
+
* - `retainUntilMs` is the absolute epoch-ms deadline from verified proof timing,
|
|
487
|
+
* including the cluster clock guard, never a relative TTL.
|
|
410
488
|
* - Validation order per admission: input shape, store clock, opaque key
|
|
411
489
|
* bounds (1-256 printable ASCII), deadline shape (nonnegative safe
|
|
412
490
|
* integer), input expiry (`retainUntilMs <= now` returns `'expired'` with
|
|
@@ -424,8 +502,7 @@ declare function createRedisAttestationStore(options: CreateRedisAttestationStor
|
|
|
424
502
|
* valid; the old proof still fails the verifier's age rules (the store never
|
|
425
503
|
* sees signatures, only opaque already-derived replay keys).
|
|
426
504
|
*
|
|
427
|
-
* Storage design
|
|
428
|
-
* reservations module):
|
|
505
|
+
* Storage design:
|
|
429
506
|
*
|
|
430
507
|
* - One proof collection with unique `_id`s (`{ _id: replayKey,
|
|
431
508
|
* retainUntilMs }`) plus a separate non-TTL capacity/accounting collection
|
|
@@ -453,16 +530,14 @@ declare function createRedisAttestationStore(options: CreateRedisAttestationStor
|
|
|
453
530
|
* handles are retained live without cloning, connecting, or closing; the
|
|
454
531
|
* caller owns the underlying `MongoClient` lifecycle (connect/close).
|
|
455
532
|
* Production deployments require a replica set (transactions).
|
|
456
|
-
* - The
|
|
457
|
-
* uses narrow structural `Db`/collection/session types and never imports a
|
|
533
|
+
* - The library uses narrow structural `Db`/collection/session types and never imports a
|
|
458
534
|
* driver at runtime, so browser-only `/signer` consumers never need it.
|
|
459
535
|
* - Capacity throws the shared `AttestationCapacityError` (same class as
|
|
460
536
|
* memory/Redis). Wrong collection types, transaction failures, and
|
|
461
537
|
* malformed persisted rows throw the narrow `AttestationMongoStoreError`.
|
|
462
538
|
* Malformed inputs throw `AttestationProtocolError` without touching the
|
|
463
|
-
* database. The middleware
|
|
464
|
-
* `ATTESTATION_REPLAY_UNAVAILABLE` without calling `next
|
|
465
|
-
* middleware for this adapter.
|
|
539
|
+
* database. The middleware maps store failures to fixed
|
|
540
|
+
* `ATTESTATION_REPLAY_UNAVAILABLE` without calling `next`.
|
|
466
541
|
*
|
|
467
542
|
* Sharing boundary: replay state is shared by all clients using the same
|
|
468
543
|
* database/collections with identical `maxEntries`. Independent databases or
|
|
@@ -518,34 +593,50 @@ interface MongoAttestationStoreDb {
|
|
|
518
593
|
startSession(): MongoAttestationStoreSession;
|
|
519
594
|
};
|
|
520
595
|
}
|
|
521
|
-
/**
|
|
596
|
+
/** Caller-owned MongoDB replica-set connection, collection pair, capacity, and clock. */
|
|
522
597
|
interface CreateMongoAttestationStoreOptions {
|
|
523
598
|
/** Database handle from an already-connected caller-owned client. */
|
|
524
599
|
readonly db: MongoAttestationStoreDb;
|
|
525
600
|
/** Proof collection name; default `attestation_proofs`. */
|
|
526
601
|
readonly proofsCollectionName?: string;
|
|
527
|
-
/**
|
|
602
|
+
/**
|
|
603
|
+
* Default `attestation_replay_capacity`, distinct from the proof collection.
|
|
604
|
+
* Provision a non-TTL `{ kind: 1, retainUntilMs: 1 }` index before traffic.
|
|
605
|
+
* Never TTL-delete accounting rows; the adapter maintains them transactionally.
|
|
606
|
+
*/
|
|
528
607
|
readonly capacityCollectionName?: string;
|
|
529
|
-
/** Positive safe-integer shared capacity; default 50000. */
|
|
608
|
+
/** Positive safe-integer shared capacity; default 50000, identical for every client of this collection pair. */
|
|
530
609
|
readonly maxEntries?: number;
|
|
531
|
-
/**
|
|
610
|
+
/** Default `Date.now`; nonnegative safe-integer epoch ms, synchronized within the verifier's cluster clock guard. */
|
|
532
611
|
readonly now?: () => number;
|
|
533
612
|
}
|
|
534
613
|
/**
|
|
535
614
|
* Create a shared bounded attestation replay store over two MongoDB
|
|
536
615
|
* collections.
|
|
537
616
|
*
|
|
538
|
-
* Pass a `Db` from an already-connected caller-owned `MongoClient`;
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
617
|
+
* Pass a `Db` from an already-connected caller-owned `MongoClient`; replica-set
|
|
618
|
+
* transactions are required. Defaults: `attestation_proofs`,
|
|
619
|
+
* `attestation_replay_capacity`, `maxEntries: 50000`, and `Date.now`.
|
|
620
|
+
* All clients of the collection pair need identical capacity and synchronized
|
|
621
|
+
* clocks; verifiers in the protection space need identical time policy/key windows.
|
|
622
|
+
*
|
|
623
|
+
* Before traffic, use the driver's `createIndex({ kind: 1, retainUntilMs: 1 })`
|
|
624
|
+
* on the capacity collection with no `expireAfterSeconds`. The factory performs
|
|
625
|
+
* no database I/O/index creation. Never add TTL cleanup to the accounting ledger.
|
|
626
|
+
* Each admission checks duplicates, reclaims at most 64 expired rows plus the
|
|
627
|
+
* requested expired key, then inserts without live eviction or duplicate extension.
|
|
628
|
+
*
|
|
629
|
+
* The caller owns connection, server-selection/command/transaction deadlines,
|
|
630
|
+
* and shutdown. Per-admission sessions belong to the adapter, but driver retries
|
|
631
|
+
* and cleanup are not bounded by the middleware's waiting timeout. Capacity
|
|
632
|
+
* throws `AttestationCapacityError`; malformed inputs use `AttestationProtocolError`;
|
|
633
|
+
* dependency/row failures use `AttestationMongoStoreError`. State loss can erase
|
|
634
|
+
* replay history; this is not durable business idempotency or a failover guarantee.
|
|
544
635
|
*/
|
|
545
636
|
declare function createMongoAttestationStore(options: CreateMongoAttestationStoreOptions): AttestationStore;
|
|
546
637
|
|
|
547
638
|
/**
|
|
548
|
-
*
|
|
639
|
+
* Owned key rotation with explicit acceptance windows.
|
|
549
640
|
*
|
|
550
641
|
* - `generateAttestationKey(keyId, { acceptFrom, acceptUntil })` creates one
|
|
551
642
|
* random 32-byte entry for an explicit acceptance window.
|
|
@@ -554,7 +645,7 @@ declare function createMongoAttestationStore(options: CreateMongoAttestationStor
|
|
|
554
645
|
* `replace(snapshot)`; external cron/KMS/config code owns provisioning and
|
|
555
646
|
* refresh. No default polling timer, no hardcoded grace policy.
|
|
556
647
|
*
|
|
557
|
-
* Snapshot rules
|
|
648
|
+
* Snapshot rules:
|
|
558
649
|
*
|
|
559
650
|
* - Snapshots have `currentKeyId` plus a bounded list of 1-16 entries
|
|
560
651
|
* `{ keyId, key: Uint8Array(32), acceptFrom, acceptUntil }`.
|
|
@@ -576,7 +667,7 @@ declare function createMongoAttestationStore(options: CreateMongoAttestationStor
|
|
|
576
667
|
* snapshot.
|
|
577
668
|
* - Rejected replacements leave the previous valid snapshot intact
|
|
578
669
|
* (atomic). Provider/read failure is operational `503` at the verifier,
|
|
579
|
-
* never a stale-key client error
|
|
670
|
+
* never a stale-key client error.
|
|
580
671
|
*
|
|
581
672
|
* Runbook (finite acceptance, application-configured):
|
|
582
673
|
*
|
|
@@ -597,7 +688,10 @@ declare function createMongoAttestationStore(options: CreateMongoAttestationStor
|
|
|
597
688
|
|
|
598
689
|
/**
|
|
599
690
|
* Create one random 32-byte key entry for an explicit acceptance window.
|
|
600
|
-
* Uses `node:crypto` randomness
|
|
691
|
+
* Uses `node:crypto` randomness. Times are nonnegative safe-integer epoch ms
|
|
692
|
+
* with inclusive `acceptFrom` and exclusive `acceptUntil`. Distribute the same
|
|
693
|
+
* entry/window to every verifier; do not generate independent per-instance keys.
|
|
694
|
+
* The entry is frozen and owns its bytes; providers copy those bytes on input.
|
|
601
695
|
*/
|
|
602
696
|
declare function generateAttestationKey(keyId: string, window: {
|
|
603
697
|
readonly acceptFrom: number;
|
|
@@ -607,6 +701,8 @@ declare function generateAttestationKey(keyId: string, window: {
|
|
|
607
701
|
* Create an immutable static key provider. The input snapshot is copied on
|
|
608
702
|
* entry; every `getSnapshot()` returns a fresh owned copy. No timers,
|
|
609
703
|
* network, or storage work occurs at construction or on reads.
|
|
704
|
+
* Invalid snapshots throw `AttestationProtocolError`. Retain 1–16 keys;
|
|
705
|
+
* `currentKeyId` must name a present entry, which discovery checks for activity.
|
|
610
706
|
*/
|
|
611
707
|
declare function createStaticKeyProvider(snapshot: AttestationKeySnapshotInput): AttestationKeyProvider;
|
|
612
708
|
/**
|
|
@@ -618,12 +714,14 @@ declare function createStaticKeyProvider(snapshot: AttestationKeySnapshotInput):
|
|
|
618
714
|
* fresh IDs are allowed. No historical tombstone ledger is kept: an ID that
|
|
619
715
|
* is no longer retained may be reintroduced, but provisioners must never
|
|
620
716
|
* reintroduce a retired ID with different material (generate unique
|
|
621
|
-
* version/random identifiers).
|
|
717
|
+
* version/random identifiers). All verifiers need the same material, windows,
|
|
718
|
+
* and time policy. Provision/refresh externally; replacement starts no polling
|
|
719
|
+
* and does not cancel requests already holding a previous snapshot.
|
|
622
720
|
*/
|
|
623
721
|
declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput): AttestationRotatingKeyProvider;
|
|
624
722
|
|
|
625
723
|
/**
|
|
626
|
-
*
|
|
724
|
+
* Relative signer-asset router with content-addressed public modules.
|
|
627
725
|
*
|
|
628
726
|
* Server-only: may import Express and Node built-ins. Never imported by the
|
|
629
727
|
* `./signer` browser entry.
|
|
@@ -634,13 +732,12 @@ declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput
|
|
|
634
732
|
* `basePath`; the application mounts it (for example
|
|
635
733
|
* `app.use('/attestation', router)`). `basePath` plus the trusted
|
|
636
734
|
* `publicPathPrefix` are used solely to publish correct external URLs
|
|
637
|
-
* (`
|
|
735
|
+
* (a root `basePath: '/'` adds no extra slash before the asset name).
|
|
638
736
|
* Request headers (`Host`, `Forwarded`, `X-Forwarded-*`) are never used
|
|
639
737
|
* for URL construction.
|
|
640
738
|
* - Routes: `GET /signer-meta`, `GET /signer.<contenthash>.mjs`,
|
|
641
739
|
* `GET /runtime.<contenthash>.mjs`. The runtime is the actual built browser
|
|
642
|
-
* ESM entry from this installed package
|
|
643
|
-
* + release-flattening lookup); the 64-hex content hash identifies exact
|
|
740
|
+
* ESM entry from this installed package; the 64-hex content hash identifies exact
|
|
644
741
|
* immutable bytes. Never serve different content from the same URL.
|
|
645
742
|
* - Key-specific modules import the runtime via relative
|
|
646
743
|
* `./runtime.<hash>.mjs` and export `signer: RequestSigner` from
|
|
@@ -650,11 +747,12 @@ declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput
|
|
|
650
747
|
* - Metadata is bounded JSON
|
|
651
748
|
* `{ version: 1, keyId, signerUrl, publicOrigin, replayNamespace }` with
|
|
652
749
|
* `Cache-Control: no-store`, serving the current active key only. The
|
|
653
|
-
* current entry must be present and active at
|
|
654
|
-
* fails with `503 ATTESTATION_KEYS_UNAVAILABLE`.
|
|
750
|
+
* current entry must be present and active at the checked clock after the
|
|
751
|
+
* provider read; otherwise metadata fails with `503 ATTESTATION_KEYS_UNAVAILABLE`.
|
|
752
|
+
* Invalid or throwing clocks fail with `500 ATTESTATION_INTERNAL_ERROR`.
|
|
655
753
|
* - Immutable assets use `Content-Type: text/javascript; charset=utf-8`,
|
|
656
754
|
* `X-Content-Type-Options: nosniff`, and
|
|
657
|
-
* `Cache-Control: public, max-age=31536000, immutable`.
|
|
755
|
+
* `Cache-Control: public, max-age=31536000, immutable`. Malformed asset paths/hashes
|
|
658
756
|
* return controlled `404`; syntactically valid but not currently retained
|
|
659
757
|
* hashes return `410`; neither redirects to latest. CORS/CSP policy is
|
|
660
758
|
* application-configured: this router sets no `Access-Control-*` headers
|
|
@@ -662,14 +760,14 @@ declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput
|
|
|
662
760
|
* serve metadata/modules over the pinned `publicOrigin`, allow
|
|
663
761
|
* `script-src` for the module URLs, and configure CORS only as the
|
|
664
762
|
* application requires (same-origin by default; cross-origin deployments
|
|
665
|
-
* must explicitly allow `GET` metadata/module fetches
|
|
666
|
-
*
|
|
763
|
+
* must explicitly allow `GET` metadata/module fetches. Discovery uses
|
|
764
|
+
* no-store/omit/error; dynamic imports follow native module-fetch rules).
|
|
667
765
|
* - Asset generation is bounded by the retained snapshot (max 16 keys) plus
|
|
668
766
|
* fixed runtime (512 KiB) / module (8 KiB) / metadata (4096 byte) caps.
|
|
669
767
|
* Key modules are derived deterministically per request from the current
|
|
670
768
|
* snapshot; no unbounded retired-asset cache is kept. Dropping a retired
|
|
671
|
-
* key's asset
|
|
672
|
-
*
|
|
769
|
+
* key's asset must account for the client's one bounded discovery refetch
|
|
770
|
+
* on a module-phase retirement race; repeated retirement failures end the load.
|
|
673
771
|
*
|
|
674
772
|
* Runbook: prepare new material on **all** verifiers, publish current
|
|
675
773
|
* metadata/module, retain old material to its explicit deadline, retire only
|
|
@@ -686,7 +784,18 @@ declare function createRotatingKeyProvider(snapshot: AttestationKeySnapshotInput
|
|
|
686
784
|
* `basePath` (for example `app.use('/attestation', router)`); this factory
|
|
687
785
|
* never mounts itself. All external URLs are built from trusted
|
|
688
786
|
* configuration (`publicOrigin` + `publicPathPrefix` + `basePath`), never
|
|
689
|
-
* from request headers.
|
|
787
|
+
* from request headers. With `basePath: '/'`, assets are directly under the
|
|
788
|
+
* origin or configured stripped prefix; their runtime imports remain relative.
|
|
789
|
+
* The default base path is `/attestation`; root URLs are `/signer-meta`,
|
|
790
|
+
* `/signer.<hash>.mjs`, and `/runtime.<hash>.mjs`. With prefix `/prefix`, those
|
|
791
|
+
* external URLs begin `/prefix/` while the app still mounts at `/`.
|
|
792
|
+
* The caller owns actual upstream stripping and CORS/CSP configuration.
|
|
793
|
+
* Discovery checks the clock before provider work and current-key eligibility
|
|
794
|
+
* after it. Inactive keys/provider failures return fixed 503 errors; invalid or
|
|
795
|
+
* throwing clocks return a fixed 500 error. Both use `Cache-Control: no-store`.
|
|
796
|
+
* `operationTimeoutMs` bounds provider waiting (default 5000 ms, range 1–60000),
|
|
797
|
+
* without cancelling underlying provider work. Assets have immutable URLs and
|
|
798
|
+
* no redirect-to-latest behavior; only discovery chooses the current active key.
|
|
690
799
|
*/
|
|
691
800
|
declare function createSignerBundleRouter(options: CreateSignerBundleRouterOptions): Router;
|
|
692
801
|
|