@cardanowall/sdk-ts 0.3.0 → 0.4.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/dist/client/index.cjs +1140 -363
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +42 -5
- package/dist/client/index.d.ts +42 -5
- package/dist/client/index.js +1138 -365
- package/dist/client/index.js.map +1 -1
- package/dist/conformance/cli.cjs +4400 -2121
- package/dist/conformance/cli.cjs.map +1 -1
- package/dist/conformance/cli.js +4401 -2122
- package/dist/conformance/cli.js.map +1 -1
- package/dist/fetch/index.cjs +33 -14
- package/dist/fetch/index.cjs.map +1 -1
- package/dist/fetch/index.d.cts +2 -2
- package/dist/fetch/index.d.ts +2 -2
- package/dist/fetch/index.js +32 -15
- package/dist/fetch/index.js.map +1 -1
- package/dist/{fetch-outbound-BT5-NiYN.d.cts → fetch-outbound-dOK3ZxYa.d.cts} +7 -3
- package/dist/{fetch-outbound-BT5-NiYN.d.ts → fetch-outbound-dOK3ZxYa.d.ts} +7 -3
- package/dist/hash/index.cjs +1 -1
- package/dist/hash/index.cjs.map +1 -1
- package/dist/hash/index.js +1 -1
- package/dist/hash/index.js.map +1 -1
- package/dist/identity/index.cjs +356 -230
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.d.cts +3 -2
- package/dist/identity/index.d.ts +3 -2
- package/dist/identity/index.js +356 -230
- package/dist/identity/index.js.map +1 -1
- package/dist/index.cjs +5474 -2518
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +5454 -2514
- package/dist/index.js.map +1 -1
- package/dist/merkle/index.cjs +1 -1
- package/dist/merkle/index.js +1 -1
- package/dist/types-Cexm4VH9.d.cts +119 -0
- package/dist/types-CgoBub9J.d.ts +119 -0
- package/dist/{types-DGsZTMuZ.d.cts → types-Dp4wUSFI.d.cts} +220 -1
- package/dist/{types-DGsZTMuZ.d.ts → types-Dp4wUSFI.d.ts} +220 -1
- package/dist/verifier/index.cjs +4419 -2147
- package/dist/verifier/index.cjs.map +1 -1
- package/dist/verifier/index.d.cts +159 -111
- package/dist/verifier/index.d.ts +159 -111
- package/dist/verifier/index.js +4407 -2143
- package/dist/verifier/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/types-B8Q3gW54.d.ts +0 -123
- package/dist/types-CLXdbjqr.d.cts +0 -123
package/dist/fetch/index.cjs
CHANGED
|
@@ -12,6 +12,12 @@ var DenyHostError = class extends Error {
|
|
|
12
12
|
this.url = url;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
function isDenyHostError(e) {
|
|
16
|
+
return e instanceof DenyHostError || typeof e === "object" && e !== null && e.code === "SERVICE_INDEPENDENCE_VIOLATION";
|
|
17
|
+
}
|
|
18
|
+
function isBodyTooLargeError(e) {
|
|
19
|
+
return e instanceof BodyTooLargeError || typeof e === "object" && e !== null && e.code === "OUTBOUND_BODY_TOO_LARGE";
|
|
20
|
+
}
|
|
15
21
|
var UnsupportedProtocolError = class extends Error {
|
|
16
22
|
code = "UNSUPPORTED_PROTOCOL";
|
|
17
23
|
protocol;
|
|
@@ -117,12 +123,23 @@ var defaultFetchOutbound = async (url, opts) => {
|
|
|
117
123
|
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
118
124
|
const init = {
|
|
119
125
|
method: opts.method,
|
|
120
|
-
signal: controller.signal
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
// Redirects are never followed — deny-host and protocol validation ran
|
|
128
|
+
// against the original URL only, so a 3xx from an allowed host could
|
|
129
|
+
// otherwise pivot the fetch to any target (e.g. `302 Location:
|
|
130
|
+
// http://127.0.0.1/…`) behind the verifier's back. Every target must be
|
|
131
|
+
// validated, so a redirect is a fetch failure; all SDKs behave
|
|
132
|
+
// identically. A readable 3xx flows through as a non-2xx status and the
|
|
133
|
+
// caller's attempt handling marks it failed, like a 5xx.
|
|
134
|
+
redirect: "manual"
|
|
121
135
|
};
|
|
122
136
|
if (opts.headers) init.headers = { ...opts.headers };
|
|
123
137
|
if (opts.body !== void 0) init.body = opts.body;
|
|
124
138
|
try {
|
|
125
139
|
const res = await fetch(url, init);
|
|
140
|
+
if (res.type === "opaqueredirect") {
|
|
141
|
+
throw new Error(`redirect refused (opaqueredirect): ${url} answered with a redirect`);
|
|
142
|
+
}
|
|
126
143
|
const declared = res.headers.get("content-length");
|
|
127
144
|
if (declared !== null) {
|
|
128
145
|
const declaredLen = Number(declared);
|
|
@@ -182,9 +199,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
182
199
|
audit.push({
|
|
183
200
|
url,
|
|
184
201
|
method: "GET",
|
|
185
|
-
status:
|
|
202
|
+
status: null,
|
|
186
203
|
bytes: 0,
|
|
187
|
-
|
|
204
|
+
durationMs: 0,
|
|
188
205
|
purpose: opts.purpose
|
|
189
206
|
});
|
|
190
207
|
throw new Error(
|
|
@@ -196,9 +213,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
196
213
|
audit.push({
|
|
197
214
|
url,
|
|
198
215
|
method: "GET",
|
|
199
|
-
status:
|
|
216
|
+
status: null,
|
|
200
217
|
bytes: 0,
|
|
201
|
-
|
|
218
|
+
durationMs: 0,
|
|
202
219
|
purpose: opts.purpose
|
|
203
220
|
});
|
|
204
221
|
throw new UnsupportedProtocolError(protocol ?? "", url);
|
|
@@ -207,9 +224,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
207
224
|
audit.push({
|
|
208
225
|
url,
|
|
209
226
|
method: "GET",
|
|
210
|
-
status:
|
|
227
|
+
status: null,
|
|
211
228
|
bytes: 0,
|
|
212
|
-
|
|
229
|
+
durationMs: 0,
|
|
213
230
|
purpose: opts.purpose
|
|
214
231
|
});
|
|
215
232
|
throw new UnsupportedMethodError(opts.method, url);
|
|
@@ -220,9 +237,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
220
237
|
audit.push({
|
|
221
238
|
url,
|
|
222
239
|
method: opts.method,
|
|
223
|
-
status:
|
|
240
|
+
status: null,
|
|
224
241
|
bytes: 0,
|
|
225
|
-
|
|
242
|
+
durationMs: 0,
|
|
226
243
|
purpose: opts.purpose
|
|
227
244
|
});
|
|
228
245
|
throw new DenyHostError(canonicaliseHost(host), url);
|
|
@@ -240,7 +257,7 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
240
257
|
method: opts.method,
|
|
241
258
|
status: result.status,
|
|
242
259
|
bytes: result.bytes.byteLength,
|
|
243
|
-
|
|
260
|
+
durationMs: result.durationMs,
|
|
244
261
|
purpose: opts.purpose
|
|
245
262
|
});
|
|
246
263
|
if (retryableStatuses.includes(result.status) && retries > 0) {
|
|
@@ -258,9 +275,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
258
275
|
audit.push({
|
|
259
276
|
url,
|
|
260
277
|
method: opts.method,
|
|
261
|
-
status:
|
|
278
|
+
status: null,
|
|
262
279
|
bytes: 0,
|
|
263
|
-
|
|
280
|
+
durationMs,
|
|
264
281
|
purpose: opts.purpose
|
|
265
282
|
});
|
|
266
283
|
throw e;
|
|
@@ -268,9 +285,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
268
285
|
audit.push({
|
|
269
286
|
url,
|
|
270
287
|
method: opts.method,
|
|
271
|
-
status:
|
|
288
|
+
status: null,
|
|
272
289
|
bytes: 0,
|
|
273
|
-
|
|
290
|
+
durationMs,
|
|
274
291
|
purpose: opts.purpose
|
|
275
292
|
});
|
|
276
293
|
lastError = e;
|
|
@@ -329,6 +346,8 @@ exports.UnsupportedProtocolError = UnsupportedProtocolError;
|
|
|
329
346
|
exports.defaultFetchOutbound = defaultFetchOutbound;
|
|
330
347
|
exports.denyHostsFetch = denyHostsFetch;
|
|
331
348
|
exports.fetchOutbound = fetchOutbound;
|
|
349
|
+
exports.isBodyTooLargeError = isBodyTooLargeError;
|
|
350
|
+
exports.isDenyHostError = isDenyHostError;
|
|
332
351
|
exports.matchesDenyList = matchesDenyList;
|
|
333
352
|
exports.wrapFetchOutbound = wrapFetchOutbound;
|
|
334
353
|
//# sourceMappingURL=index.cjs.map
|
package/dist/fetch/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fetch/fetch-outbound.ts","../../src/fetch/deny-hosts.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Canonical outbound HTTP wrapper: deny-list short-circuit, protocol/method\n// allowlist, bounded timeout, exp-backoff retry with jitter, audit trail.\n\n// Universal loopback deny-host list a service-independent verifier MUST reject\n// so a record can never be made to \"verify\" only because it reached a loopback\n// address. This default carries no operator-specific entries: a deployment that\n// wants to forbid its own gateway/viewer hosts appends those at construction\n// time. Producers SHOULD pass this through `denyHosts` on every verifier\n// invocation; the wrapper accepts arbitrary lists but exports the canonical\n// loopback set so callers don't duplicate it inline. (RFC-1918 / link-local IP\n// ranges are blocked separately by the SSRF guard, not by this name list.)\nexport const DENY_HOSTS_DEFAULT: ReadonlyArray<string> = ['localhost', '127.0.0.1'];\n\n// Every outbound call carries a purpose tag from the closed set\n// `{cardano, arweave, ipfs}` (the three v1 gateway-chain purposes).\n// `https` is a transitional legacy tag for non-storage HTTPS\n// auxiliaries; new code SHOULD pick one of the three normative purposes.\n// `webhook` is the user-supplied-URL purpose: it triggers the SSRF guard\n// (DNS resolution + IP range check + connection pinning + redirect-chain\n// re-checking + body-size cap), and MUST be used for any fetch where the\n// target URL came from end-user input.\nexport type HttpPurpose = 'cardano' | 'arweave' | 'ipfs' | 'https' | 'webhook';\nexport type HttpMethod = 'GET' | 'POST';\n\nexport interface FetchOutboundOptions {\n readonly method: HttpMethod;\n readonly purpose: HttpPurpose;\n readonly headers?: Readonly<Record<string, string>>;\n readonly body?: string;\n // Hard cap on the response body the primitive will buffer. Gateway content\n // (ar:// / ipfs:// / https) is producer-chosen and therefore UNTRUSTED — the\n // verifier never trusts the producer — so a malicious gateway could otherwise\n // stream unbounded bytes into memory. Omit to use DEFAULT_OUTBOUND_MAX_BYTES.\n readonly maxBytes?: number;\n}\n\nexport interface FetchOutboundResult {\n readonly status: number;\n readonly bytes: Uint8Array;\n readonly durationMs: number;\n}\n\nexport type FetchOutbound = (\n url: string,\n opts: FetchOutboundOptions,\n) => Promise<FetchOutboundResult>;\n\n// Audit-log entry for one outbound HTTP fetch. Field names are snake_case so\n// the record can land directly on `VerifyReport.http_calls[]` (which IS the\n// wire shape) without a key-renaming pass.\nexport interface HttpCallRecord {\n readonly url: string;\n readonly method: HttpMethod;\n readonly status: number;\n readonly bytes: number;\n readonly duration_ms: number;\n readonly purpose: HttpPurpose;\n}\n\nexport interface RetryConfig {\n readonly timeoutMs?: number;\n readonly retries?: number;\n readonly retryableStatuses?: ReadonlyArray<number>;\n}\n\nexport interface WrapFetchOutboundConfig extends RetryConfig {\n readonly denyHosts?: ReadonlyArray<string>;\n}\n\nexport class DenyHostError extends Error {\n readonly code = 'SERVICE_INDEPENDENCE_VIOLATION';\n readonly host: string;\n readonly url: string;\n constructor(host: string, url: string) {\n super(`SERVICE_INDEPENDENCE_VIOLATION: host \"${host}\" is in denyHosts (url=${url})`);\n this.name = 'DenyHostError';\n this.host = host;\n this.url = url;\n }\n}\n\nexport class UnsupportedProtocolError extends Error {\n readonly code = 'UNSUPPORTED_PROTOCOL';\n readonly protocol: string;\n readonly url: string;\n constructor(protocol: string, url: string) {\n super(`UNSUPPORTED_PROTOCOL: \"${protocol}\" not in {http:, https:} (url=${url})`);\n this.name = 'UnsupportedProtocolError';\n this.protocol = protocol;\n this.url = url;\n }\n}\n\nexport class UnsupportedMethodError extends Error {\n readonly code = 'UNSUPPORTED_METHOD';\n readonly method: string;\n readonly url: string;\n constructor(method: string, url: string) {\n super(`UNSUPPORTED_METHOD: \"${method}\" not in {GET, POST} (url=${url})`);\n this.name = 'UnsupportedMethodError';\n this.method = method;\n this.url = url;\n }\n}\n\nexport class BodyTooLargeError extends Error {\n readonly code = 'OUTBOUND_BODY_TOO_LARGE';\n readonly url: string;\n readonly limitBytes: number;\n constructor(url: string, limitBytes: number) {\n super(`OUTBOUND_BODY_TOO_LARGE: response exceeded ${limitBytes} bytes (url=${url})`);\n this.name = 'BodyTooLargeError';\n this.url = url;\n this.limitBytes = limitBytes;\n }\n}\n\nexport class OutboundExhaustedError extends Error {\n readonly code = 'OUTBOUND_EXHAUSTED';\n readonly url: string;\n readonly attempts: number;\n readonly lastStatus: number | undefined;\n readonly lastError: Error | undefined;\n constructor(args: {\n url: string;\n attempts: number;\n lastStatus?: number | undefined;\n lastError?: Error | undefined;\n }) {\n super(\n `OUTBOUND_EXHAUSTED: ${args.attempts} attempts exhausted (url=${args.url}, lastStatus=${args.lastStatus ?? '-'})`,\n );\n this.name = 'OutboundExhaustedError';\n this.url = args.url;\n this.attempts = args.attempts;\n this.lastStatus = args.lastStatus;\n this.lastError = args.lastError;\n }\n}\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n// Default response-body cap for the verifier's gateway fetches. 64 MiB sits\n// well above any single sealed-PoE ciphertext or merkle-leaf payload a verifier\n// would realistically recompute a hash over, while bounding the memory a hostile\n// gateway can force the verifier to allocate for one request. Callers that\n// legitimately handle larger content raise it per-call via `opts.maxBytes`.\nexport const DEFAULT_OUTBOUND_MAX_BYTES = 64 * 1024 * 1024;\nexport const DEFAULT_RETRYABLE_STATUSES: ReadonlyArray<number> = [502, 503, 504];\nconst BACKOFF_BASE_MS: ReadonlyArray<number> = [1000, 2000, 4000];\nconst JITTER_RATIO = 0.25;\n\nfunction canonicaliseHost(host: string): string {\n return host.replace(/^\\[/, '').replace(/\\]$/, '').replace(/\\.$/, '').toLowerCase();\n}\n\nexport function matchesDenyList(host: string, denyHosts: ReadonlyArray<string>): boolean {\n const h = canonicaliseHost(host);\n for (const raw of denyHosts) {\n const pattern = raw.replace(/\\.$/, '').toLowerCase();\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2);\n if (h.endsWith('.' + suffix)) return true;\n continue;\n }\n if (h === pattern) return true;\n if (pattern === 'localhost') {\n if (h === '::1' || h === '0.0.0.0' || h === '169.254.169.254') return true;\n }\n if (pattern === '127.0.0.1') {\n if (/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n }\n }\n return false;\n}\n\nfunction parseProtocol(url: string): string | null {\n try {\n return new URL(url).protocol;\n } catch {\n return null;\n }\n}\n\nfunction isAllowedMethod(method: string): method is HttpMethod {\n return method === 'GET' || method === 'POST';\n}\n\nfunction backoffJitteredMs(attemptIndex: number): number {\n const idx = Math.min(attemptIndex, BACKOFF_BASE_MS.length - 1);\n const base = BACKOFF_BASE_MS[idx] ?? BACKOFF_BASE_MS[BACKOFF_BASE_MS.length - 1]!;\n const jitter = 1 + (Math.random() - 0.5) * 2 * JITTER_RATIO;\n return base * jitter;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport const defaultFetchOutbound: FetchOutbound = async (url, opts) => {\n const t0 = Date.now();\n const maxBytes = opts.maxBytes ?? DEFAULT_OUTBOUND_MAX_BYTES;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);\n const init: RequestInit = {\n method: opts.method,\n signal: controller.signal,\n };\n if (opts.headers) init.headers = { ...opts.headers };\n if (opts.body !== undefined) init.body = opts.body;\n try {\n // allow-raw-fetch: canonical defaultFetchOutbound — single egress point\n const res = await fetch(url, init);\n\n // Fast path: a truthful Content-Length over the cap lets us bail before\n // reading a single body byte. A lying/absent header is still caught by the\n // streaming counter below — the header is an optimisation, not the guard.\n const declared = res.headers.get('content-length');\n if (declared !== null) {\n const declaredLen = Number(declared);\n if (Number.isFinite(declaredLen) && declaredLen > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n }\n\n const bytes = await readBodyCapped(res, url, maxBytes, controller);\n return { status: res.status, bytes, durationMs: Date.now() - t0 };\n } finally {\n clearTimeout(timeout);\n }\n};\n\n// Stream the response body, aborting the underlying request the instant the\n// running byte count exceeds `maxBytes`. This is the actual OOM guard: a\n// gateway that withholds or lies about Content-Length still cannot make us\n// buffer more than the cap, because we stop reading and tear the socket down.\nasync function readBodyCapped(\n res: Response,\n url: string,\n maxBytes: number,\n controller: AbortController,\n): Promise<Uint8Array> {\n const body = res.body;\n if (body === null) {\n // No stream (e.g. a 204, or a fetch polyfill that buffered eagerly). Fall\n // back to arrayBuffer but still enforce the cap on the materialised length.\n const buf = await res.arrayBuffer();\n if (buf.byteLength > maxBytes) {\n throw new BodyTooLargeError(url, maxBytes);\n }\n return new Uint8Array(buf);\n }\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n total += value.byteLength;\n if (total > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function wrapFetchOutbound(\n inner: FetchOutbound,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined = undefined,\n): FetchOutbound {\n // Accept either a denyHosts array (positional) or the full config object.\n const normConfig: WrapFetchOutboundConfig =\n config === undefined\n ? {}\n : Array.isArray(config)\n ? { denyHosts: config as ReadonlyArray<string> }\n : (config as WrapFetchOutboundConfig);\n\n const denyHosts = normConfig.denyHosts ?? [];\n // Default retries=0 (single attempt). Callers opt in via explicit `retries`;\n // the top-level `fetchOutbound` entrypoint forwards caller config.\n const retries = normConfig.retries ?? 0;\n const retryableStatuses = normConfig.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES;\n\n return async (url, opts) => {\n // The `webhook` purpose has bespoke requirements (DNS pinning,\n // per-hop redirect re-checking, body-size cap) that the generic\n // wrapper cannot satisfy. Force callers to use `fetchWebhook`\n // instead of silently accepting the call here.\n if (opts.purpose === 'webhook') {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new Error(\n `webhook purpose must be sent via fetchWebhook, not fetchOutbound (url=${url})`,\n );\n }\n\n // Protocol allowlist.\n const protocol = parseProtocol(url);\n if (protocol !== 'http:' && protocol !== 'https:') {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedProtocolError(protocol ?? '', url);\n }\n\n // Method allowlist.\n if (!isAllowedMethod(opts.method)) {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedMethodError(opts.method, url);\n }\n\n // Deny-list short-circuit.\n if (denyHosts.length > 0) {\n const host = new URL(url).hostname;\n if (matchesDenyList(host, denyHosts)) {\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new DenyHostError(canonicaliseHost(host), url);\n }\n }\n\n // Retry loop. retries=0 → single attempt, return-or-rethrow original.\n let lastStatus: number | undefined;\n let lastError: Error | undefined;\n const totalAttempts = retries + 1;\n for (let attempt = 1; attempt <= totalAttempts; attempt++) {\n const t0 = Date.now();\n try {\n const result = await inner(url, opts);\n audit.push({\n url,\n method: opts.method,\n status: result.status,\n bytes: result.bytes.byteLength,\n duration_ms: result.durationMs,\n purpose: opts.purpose,\n });\n if (retryableStatuses.includes(result.status) && retries > 0) {\n lastStatus = result.status;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n return result;\n } catch (e) {\n const durationMs = Date.now() - t0;\n if (\n e instanceof DenyHostError ||\n e instanceof UnsupportedProtocolError ||\n e instanceof UnsupportedMethodError\n ) {\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: durationMs,\n purpose: opts.purpose,\n });\n throw e;\n }\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: durationMs,\n purpose: opts.purpose,\n });\n lastError = e as Error;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n }\n // Single-attempt mode re-throws the original verbatim so callers can match\n // by identity; retry mode wraps the terminal failure in OutboundExhaustedError.\n if (retries === 0 && lastError !== undefined) {\n throw lastError;\n }\n throw new OutboundExhaustedError({ url, attempts: totalAttempts, lastStatus, lastError });\n };\n}\n\nexport async function fetchOutbound(\n url: string,\n opts: FetchOutboundOptions,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig = {},\n): Promise<FetchOutboundResult> {\n const wrapped = wrapFetchOutbound(defaultFetchOutbound, audit, config);\n return wrapped(url, opts);\n}\n","// Public denyHostsFetch surface — thin adapter over the canonical\n// fetchOutbound primitive in ./fetch-outbound.ts.\n\nimport {\n DenyHostError,\n type FetchOutbound,\n type FetchOutboundOptions,\n type HttpCallRecord,\n type HttpMethod,\n UnsupportedMethodError,\n UnsupportedProtocolError,\n wrapFetchOutbound,\n} from './fetch-outbound';\n\nexport { DenyHostError, UnsupportedMethodError, UnsupportedProtocolError };\n\nexport type HttpCall = HttpCallRecord;\n\nexport type DenyHostsFetchOptions = {\n readonly denyHosts: readonly string[];\n readonly audit: HttpCall[];\n readonly purpose: HttpCall['purpose'];\n readonly fetchImpl?: typeof fetch;\n};\n\nexport async function denyHostsFetch(\n url: string,\n init: RequestInit | undefined,\n opts: DenyHostsFetchOptions,\n): Promise<Response> {\n // Forward the raw method so the canonical wrap surfaces\n // UnsupportedMethodError instead of silently rewriting to GET.\n const rawMethod = (init?.method ?? 'GET') as HttpMethod;\n const headers = init?.headers as Record<string, string> | undefined;\n const bodyValue = init?.body;\n const body: string | undefined = typeof bodyValue === 'string' ? bodyValue : undefined;\n const fetchImpl: typeof fetch = opts.fetchImpl ?? globalThis.fetch;\n\n const innerOpts: FetchOutboundOptions = headers\n ? body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, headers, body }\n : { method: rawMethod, purpose: opts.purpose, headers }\n : body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, body }\n : { method: rawMethod, purpose: opts.purpose };\n\n let storedResponse: Response | null = null;\n const inner: FetchOutbound = async (innerUrl, innerOptsArg) => {\n const t0 = Date.now();\n const reqInit: RequestInit = { method: innerOptsArg.method };\n if (innerOptsArg.headers) reqInit.headers = { ...innerOptsArg.headers };\n if (innerOptsArg.body !== undefined) reqInit.body = innerOptsArg.body;\n const response = await fetchImpl(innerUrl, reqInit);\n storedResponse = response;\n const buf = await response.clone().arrayBuffer();\n return {\n status: response.status,\n bytes: new Uint8Array(buf),\n durationMs: Date.now() - t0,\n };\n };\n\n const wrapped = wrapFetchOutbound(inner, opts.audit, { denyHosts: opts.denyHosts });\n await wrapped(url, innerOpts);\n return storedResponse!;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/fetch/fetch-outbound.ts","../../src/fetch/deny-hosts.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Canonical outbound HTTP wrapper: deny-list short-circuit, protocol/method\n// allowlist, bounded timeout, exp-backoff retry with jitter, audit trail.\n\n// Universal loopback deny-host list a service-independent verifier MUST reject\n// so a record can never be made to \"verify\" only because it reached a loopback\n// address. This default carries no operator-specific entries: a deployment that\n// wants to forbid its own gateway/viewer hosts appends those at construction\n// time. Producers SHOULD pass this through `denyHosts` on every verifier\n// invocation; the wrapper accepts arbitrary lists but exports the canonical\n// loopback set so callers don't duplicate it inline. (RFC-1918 / link-local IP\n// ranges are blocked separately by the SSRF guard, not by this name list.)\nexport const DENY_HOSTS_DEFAULT: ReadonlyArray<string> = ['localhost', '127.0.0.1'];\n\n// Every outbound call carries a purpose tag from the closed set\n// `{cardano, arweave, ipfs}` (the three v1 gateway-chain purposes).\n// `https` is a transitional legacy tag for non-storage HTTPS\n// auxiliaries; new code SHOULD pick one of the three normative purposes.\n// `webhook` is the user-supplied-URL purpose: it triggers the SSRF guard\n// (DNS resolution + IP range check + connection pinning + redirect-chain\n// re-checking + body-size cap), and MUST be used for any fetch where the\n// target URL came from end-user input.\nexport type HttpPurpose = 'cardano' | 'arweave' | 'ipfs' | 'https' | 'webhook';\nexport type HttpMethod = 'GET' | 'POST';\n\nexport interface FetchOutboundOptions {\n readonly method: HttpMethod;\n readonly purpose: HttpPurpose;\n readonly headers?: Readonly<Record<string, string>>;\n readonly body?: string;\n // Hard cap on the response body the primitive will buffer. Gateway content\n // (ar:// / ipfs:// / https) is producer-chosen and therefore UNTRUSTED — the\n // verifier never trusts the producer — so a malicious gateway could otherwise\n // stream unbounded bytes into memory. Omit to use DEFAULT_OUTBOUND_MAX_BYTES.\n readonly maxBytes?: number;\n}\n\nexport interface FetchOutboundResult {\n readonly status: number;\n readonly bytes: Uint8Array;\n readonly durationMs: number;\n}\n\nexport type FetchOutbound = (\n url: string,\n opts: FetchOutboundOptions,\n) => Promise<FetchOutboundResult>;\n\n// Audit-log entry for one outbound HTTP fetch. The field set and names match\n// the verifier report's audit-trail entry exactly, so the record lands on\n// `VerifyReport.auditTrail[]` without a key-renaming pass. `status` is the\n// HTTP status when a response was received and `null` when none was (refused\n// call, transport failure).\nexport interface HttpCallRecord {\n readonly url: string;\n readonly method: HttpMethod;\n readonly status: number | null;\n readonly bytes: number;\n readonly durationMs: number;\n readonly purpose: HttpPurpose;\n}\n\nexport interface RetryConfig {\n readonly timeoutMs?: number;\n readonly retries?: number;\n readonly retryableStatuses?: ReadonlyArray<number>;\n}\n\nexport interface WrapFetchOutboundConfig extends RetryConfig {\n readonly denyHosts?: ReadonlyArray<string>;\n}\n\nexport class DenyHostError extends Error {\n readonly code = 'SERVICE_INDEPENDENCE_VIOLATION';\n readonly host: string;\n readonly url: string;\n constructor(host: string, url: string) {\n super(`SERVICE_INDEPENDENCE_VIOLATION: host \"${host}\" is in denyHosts (url=${url})`);\n this.name = 'DenyHostError';\n this.host = host;\n this.url = url;\n }\n}\n\n// The typed errors discriminate on their stable `code` property, never on\n// class identity: the package ships several entry points in two module\n// formats, so a consumer's `BodyTooLargeError` (thrown by a custom transport\n// that imported it from another entry) is a different class object than the\n// verifier's. `instanceof` is kept as the fast path for the common\n// same-module case.\n\n/** Whether `e` is a deny-host refusal (`SERVICE_INDEPENDENCE_VIOLATION`). */\nexport function isDenyHostError(e: unknown): e is DenyHostError {\n return (\n e instanceof DenyHostError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as { code?: unknown }).code === 'SERVICE_INDEPENDENCE_VIOLATION')\n );\n}\n\n/** Whether `e` is a body-cap abort (`OUTBOUND_BODY_TOO_LARGE`). */\nexport function isBodyTooLargeError(e: unknown): e is BodyTooLargeError {\n return (\n e instanceof BodyTooLargeError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as { code?: unknown }).code === 'OUTBOUND_BODY_TOO_LARGE')\n );\n}\n\nexport class UnsupportedProtocolError extends Error {\n readonly code = 'UNSUPPORTED_PROTOCOL';\n readonly protocol: string;\n readonly url: string;\n constructor(protocol: string, url: string) {\n super(`UNSUPPORTED_PROTOCOL: \"${protocol}\" not in {http:, https:} (url=${url})`);\n this.name = 'UnsupportedProtocolError';\n this.protocol = protocol;\n this.url = url;\n }\n}\n\nexport class UnsupportedMethodError extends Error {\n readonly code = 'UNSUPPORTED_METHOD';\n readonly method: string;\n readonly url: string;\n constructor(method: string, url: string) {\n super(`UNSUPPORTED_METHOD: \"${method}\" not in {GET, POST} (url=${url})`);\n this.name = 'UnsupportedMethodError';\n this.method = method;\n this.url = url;\n }\n}\n\nexport class BodyTooLargeError extends Error {\n readonly code = 'OUTBOUND_BODY_TOO_LARGE';\n readonly url: string;\n readonly limitBytes: number;\n constructor(url: string, limitBytes: number) {\n super(`OUTBOUND_BODY_TOO_LARGE: response exceeded ${limitBytes} bytes (url=${url})`);\n this.name = 'BodyTooLargeError';\n this.url = url;\n this.limitBytes = limitBytes;\n }\n}\n\nexport class OutboundExhaustedError extends Error {\n readonly code = 'OUTBOUND_EXHAUSTED';\n readonly url: string;\n readonly attempts: number;\n readonly lastStatus: number | undefined;\n readonly lastError: Error | undefined;\n constructor(args: {\n url: string;\n attempts: number;\n lastStatus?: number | undefined;\n lastError?: Error | undefined;\n }) {\n super(\n `OUTBOUND_EXHAUSTED: ${args.attempts} attempts exhausted (url=${args.url}, lastStatus=${args.lastStatus ?? '-'})`,\n );\n this.name = 'OutboundExhaustedError';\n this.url = args.url;\n this.attempts = args.attempts;\n this.lastStatus = args.lastStatus;\n this.lastError = args.lastError;\n }\n}\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n// Default response-body cap for the verifier's gateway fetches. 64 MiB sits\n// well above any single sealed-PoE ciphertext or merkle-leaf payload a verifier\n// would realistically recompute a hash over, while bounding the memory a hostile\n// gateway can force the verifier to allocate for one request. Callers that\n// legitimately handle larger content raise it per-call via `opts.maxBytes`.\nexport const DEFAULT_OUTBOUND_MAX_BYTES = 64 * 1024 * 1024;\nexport const DEFAULT_RETRYABLE_STATUSES: ReadonlyArray<number> = [502, 503, 504];\nconst BACKOFF_BASE_MS: ReadonlyArray<number> = [1000, 2000, 4000];\nconst JITTER_RATIO = 0.25;\n\nfunction canonicaliseHost(host: string): string {\n return host.replace(/^\\[/, '').replace(/\\]$/, '').replace(/\\.$/, '').toLowerCase();\n}\n\nexport function matchesDenyList(host: string, denyHosts: ReadonlyArray<string>): boolean {\n const h = canonicaliseHost(host);\n for (const raw of denyHosts) {\n const pattern = raw.replace(/\\.$/, '').toLowerCase();\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2);\n if (h.endsWith('.' + suffix)) return true;\n continue;\n }\n if (h === pattern) return true;\n if (pattern === 'localhost') {\n if (h === '::1' || h === '0.0.0.0' || h === '169.254.169.254') return true;\n }\n if (pattern === '127.0.0.1') {\n if (/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n }\n }\n return false;\n}\n\nfunction parseProtocol(url: string): string | null {\n try {\n return new URL(url).protocol;\n } catch {\n return null;\n }\n}\n\nfunction isAllowedMethod(method: string): method is HttpMethod {\n return method === 'GET' || method === 'POST';\n}\n\nfunction backoffJitteredMs(attemptIndex: number): number {\n const idx = Math.min(attemptIndex, BACKOFF_BASE_MS.length - 1);\n const base = BACKOFF_BASE_MS[idx] ?? BACKOFF_BASE_MS[BACKOFF_BASE_MS.length - 1]!;\n const jitter = 1 + (Math.random() - 0.5) * 2 * JITTER_RATIO;\n return base * jitter;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport const defaultFetchOutbound: FetchOutbound = async (url, opts) => {\n const t0 = Date.now();\n const maxBytes = opts.maxBytes ?? DEFAULT_OUTBOUND_MAX_BYTES;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);\n const init: RequestInit = {\n method: opts.method,\n signal: controller.signal,\n // Redirects are never followed — deny-host and protocol validation ran\n // against the original URL only, so a 3xx from an allowed host could\n // otherwise pivot the fetch to any target (e.g. `302 Location:\n // http://127.0.0.1/…`) behind the verifier's back. Every target must be\n // validated, so a redirect is a fetch failure; all SDKs behave\n // identically. A readable 3xx flows through as a non-2xx status and the\n // caller's attempt handling marks it failed, like a 5xx.\n redirect: 'manual',\n };\n if (opts.headers) init.headers = { ...opts.headers };\n if (opts.body !== undefined) init.body = opts.body;\n try {\n // allow-raw-fetch: canonical defaultFetchOutbound — single egress point\n const res = await fetch(url, init);\n\n // Browser runtimes surface a refused redirect as an opaque response\n // (type 'opaqueredirect', status 0) with no readable status or body;\n // there is nothing to report from it, so it fails like a transport error.\n if (res.type === 'opaqueredirect') {\n throw new Error(`redirect refused (opaqueredirect): ${url} answered with a redirect`);\n }\n\n // Fast path: a truthful Content-Length over the cap lets us bail before\n // reading a single body byte. A lying/absent header is still caught by the\n // streaming counter below — the header is an optimisation, not the guard.\n const declared = res.headers.get('content-length');\n if (declared !== null) {\n const declaredLen = Number(declared);\n if (Number.isFinite(declaredLen) && declaredLen > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n }\n\n const bytes = await readBodyCapped(res, url, maxBytes, controller);\n return { status: res.status, bytes, durationMs: Date.now() - t0 };\n } finally {\n clearTimeout(timeout);\n }\n};\n\n// Stream the response body, aborting the underlying request the instant the\n// running byte count exceeds `maxBytes`. This is the actual OOM guard: a\n// gateway that withholds or lies about Content-Length still cannot make us\n// buffer more than the cap, because we stop reading and tear the socket down.\nasync function readBodyCapped(\n res: Response,\n url: string,\n maxBytes: number,\n controller: AbortController,\n): Promise<Uint8Array> {\n const body = res.body;\n if (body === null) {\n // No stream (e.g. a 204, or a fetch polyfill that buffered eagerly). Fall\n // back to arrayBuffer but still enforce the cap on the materialised length.\n const buf = await res.arrayBuffer();\n if (buf.byteLength > maxBytes) {\n throw new BodyTooLargeError(url, maxBytes);\n }\n return new Uint8Array(buf);\n }\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n total += value.byteLength;\n if (total > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function wrapFetchOutbound(\n inner: FetchOutbound,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined = undefined,\n): FetchOutbound {\n // Accept either a denyHosts array (positional) or the full config object.\n const normConfig: WrapFetchOutboundConfig =\n config === undefined\n ? {}\n : Array.isArray(config)\n ? { denyHosts: config as ReadonlyArray<string> }\n : (config as WrapFetchOutboundConfig);\n\n const denyHosts = normConfig.denyHosts ?? [];\n // Default retries=0 (single attempt). Callers opt in via explicit `retries`;\n // the top-level `fetchOutbound` entrypoint forwards caller config.\n const retries = normConfig.retries ?? 0;\n const retryableStatuses = normConfig.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES;\n\n return async (url, opts) => {\n // The `webhook` purpose has bespoke requirements (DNS pinning,\n // per-hop redirect re-checking, body-size cap) that the generic\n // wrapper cannot satisfy. Force callers to use `fetchWebhook`\n // instead of silently accepting the call here.\n if (opts.purpose === 'webhook') {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new Error(\n `webhook purpose must be sent via fetchWebhook, not fetchOutbound (url=${url})`,\n );\n }\n\n // Protocol allowlist.\n const protocol = parseProtocol(url);\n if (protocol !== 'http:' && protocol !== 'https:') {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedProtocolError(protocol ?? '', url);\n }\n\n // Method allowlist.\n if (!isAllowedMethod(opts.method)) {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedMethodError(opts.method, url);\n }\n\n // Deny-list short-circuit.\n if (denyHosts.length > 0) {\n const host = new URL(url).hostname;\n if (matchesDenyList(host, denyHosts)) {\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new DenyHostError(canonicaliseHost(host), url);\n }\n }\n\n // Retry loop. retries=0 → single attempt, return-or-rethrow original.\n let lastStatus: number | undefined;\n let lastError: Error | undefined;\n const totalAttempts = retries + 1;\n for (let attempt = 1; attempt <= totalAttempts; attempt++) {\n const t0 = Date.now();\n try {\n const result = await inner(url, opts);\n audit.push({\n url,\n method: opts.method,\n status: result.status,\n bytes: result.bytes.byteLength,\n durationMs: result.durationMs,\n purpose: opts.purpose,\n });\n if (retryableStatuses.includes(result.status) && retries > 0) {\n lastStatus = result.status;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n return result;\n } catch (e) {\n const durationMs = Date.now() - t0;\n if (\n e instanceof DenyHostError ||\n e instanceof UnsupportedProtocolError ||\n e instanceof UnsupportedMethodError\n ) {\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs,\n purpose: opts.purpose,\n });\n throw e;\n }\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs,\n purpose: opts.purpose,\n });\n lastError = e as Error;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n }\n // Single-attempt mode re-throws the original verbatim so callers can match\n // by identity; retry mode wraps the terminal failure in OutboundExhaustedError.\n if (retries === 0 && lastError !== undefined) {\n throw lastError;\n }\n throw new OutboundExhaustedError({ url, attempts: totalAttempts, lastStatus, lastError });\n };\n}\n\nexport async function fetchOutbound(\n url: string,\n opts: FetchOutboundOptions,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig = {},\n): Promise<FetchOutboundResult> {\n const wrapped = wrapFetchOutbound(defaultFetchOutbound, audit, config);\n return wrapped(url, opts);\n}\n","// Public denyHostsFetch surface — thin adapter over the canonical\n// fetchOutbound primitive in ./fetch-outbound.ts.\n\nimport {\n DenyHostError,\n type FetchOutbound,\n type FetchOutboundOptions,\n type HttpCallRecord,\n type HttpMethod,\n UnsupportedMethodError,\n UnsupportedProtocolError,\n wrapFetchOutbound,\n} from './fetch-outbound';\n\nexport { DenyHostError, UnsupportedMethodError, UnsupportedProtocolError };\n\nexport type HttpCall = HttpCallRecord;\n\nexport type DenyHostsFetchOptions = {\n readonly denyHosts: readonly string[];\n readonly audit: HttpCall[];\n readonly purpose: HttpCall['purpose'];\n readonly fetchImpl?: typeof fetch;\n};\n\nexport async function denyHostsFetch(\n url: string,\n init: RequestInit | undefined,\n opts: DenyHostsFetchOptions,\n): Promise<Response> {\n // Forward the raw method so the canonical wrap surfaces\n // UnsupportedMethodError instead of silently rewriting to GET.\n const rawMethod = (init?.method ?? 'GET') as HttpMethod;\n const headers = init?.headers as Record<string, string> | undefined;\n const bodyValue = init?.body;\n const body: string | undefined = typeof bodyValue === 'string' ? bodyValue : undefined;\n const fetchImpl: typeof fetch = opts.fetchImpl ?? globalThis.fetch;\n\n const innerOpts: FetchOutboundOptions = headers\n ? body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, headers, body }\n : { method: rawMethod, purpose: opts.purpose, headers }\n : body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, body }\n : { method: rawMethod, purpose: opts.purpose };\n\n let storedResponse: Response | null = null;\n const inner: FetchOutbound = async (innerUrl, innerOptsArg) => {\n const t0 = Date.now();\n const reqInit: RequestInit = { method: innerOptsArg.method };\n if (innerOptsArg.headers) reqInit.headers = { ...innerOptsArg.headers };\n if (innerOptsArg.body !== undefined) reqInit.body = innerOptsArg.body;\n const response = await fetchImpl(innerUrl, reqInit);\n storedResponse = response;\n const buf = await response.clone().arrayBuffer();\n return {\n status: response.status,\n bytes: new Uint8Array(buf),\n durationMs: Date.now() - t0,\n };\n };\n\n const wrapped = wrapFetchOutbound(inner, opts.audit, { denyHosts: opts.denyHosts });\n await wrapped(url, innerOpts);\n return storedResponse!;\n}\n"]}
|
package/dist/fetch/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HttpCallRecord } from '../fetch-outbound-
|
|
2
|
-
export { B as BodyTooLargeError, D as DEFAULT_OUTBOUND_MAX_BYTES, b as DenyHostError, F as FetchOutbound, c as FetchOutboundOptions, d as FetchOutboundResult, e as HttpMethod, f as HttpPurpose, O as OutboundExhaustedError, R as RetryConfig, U as UnsupportedMethodError, g as UnsupportedProtocolError, W as WrapFetchOutboundConfig, h as defaultFetchOutbound, i as fetchOutbound, m as matchesDenyList, w as wrapFetchOutbound } from '../fetch-outbound-
|
|
1
|
+
import { H as HttpCallRecord } from '../fetch-outbound-dOK3ZxYa.cjs';
|
|
2
|
+
export { B as BodyTooLargeError, D as DEFAULT_OUTBOUND_MAX_BYTES, b as DenyHostError, F as FetchOutbound, c as FetchOutboundOptions, d as FetchOutboundResult, e as HttpMethod, f as HttpPurpose, O as OutboundExhaustedError, R as RetryConfig, U as UnsupportedMethodError, g as UnsupportedProtocolError, W as WrapFetchOutboundConfig, h as defaultFetchOutbound, i as fetchOutbound, j as isBodyTooLargeError, k as isDenyHostError, m as matchesDenyList, w as wrapFetchOutbound } from '../fetch-outbound-dOK3ZxYa.cjs';
|
|
3
3
|
|
|
4
4
|
type HttpCall = HttpCallRecord;
|
|
5
5
|
type DenyHostsFetchOptions = {
|
package/dist/fetch/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HttpCallRecord } from '../fetch-outbound-
|
|
2
|
-
export { B as BodyTooLargeError, D as DEFAULT_OUTBOUND_MAX_BYTES, b as DenyHostError, F as FetchOutbound, c as FetchOutboundOptions, d as FetchOutboundResult, e as HttpMethod, f as HttpPurpose, O as OutboundExhaustedError, R as RetryConfig, U as UnsupportedMethodError, g as UnsupportedProtocolError, W as WrapFetchOutboundConfig, h as defaultFetchOutbound, i as fetchOutbound, m as matchesDenyList, w as wrapFetchOutbound } from '../fetch-outbound-
|
|
1
|
+
import { H as HttpCallRecord } from '../fetch-outbound-dOK3ZxYa.js';
|
|
2
|
+
export { B as BodyTooLargeError, D as DEFAULT_OUTBOUND_MAX_BYTES, b as DenyHostError, F as FetchOutbound, c as FetchOutboundOptions, d as FetchOutboundResult, e as HttpMethod, f as HttpPurpose, O as OutboundExhaustedError, R as RetryConfig, U as UnsupportedMethodError, g as UnsupportedProtocolError, W as WrapFetchOutboundConfig, h as defaultFetchOutbound, i as fetchOutbound, j as isBodyTooLargeError, k as isDenyHostError, m as matchesDenyList, w as wrapFetchOutbound } from '../fetch-outbound-dOK3ZxYa.js';
|
|
3
3
|
|
|
4
4
|
type HttpCall = HttpCallRecord;
|
|
5
5
|
type DenyHostsFetchOptions = {
|
package/dist/fetch/index.js
CHANGED
|
@@ -10,6 +10,12 @@ var DenyHostError = class extends Error {
|
|
|
10
10
|
this.url = url;
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
+
function isDenyHostError(e) {
|
|
14
|
+
return e instanceof DenyHostError || typeof e === "object" && e !== null && e.code === "SERVICE_INDEPENDENCE_VIOLATION";
|
|
15
|
+
}
|
|
16
|
+
function isBodyTooLargeError(e) {
|
|
17
|
+
return e instanceof BodyTooLargeError || typeof e === "object" && e !== null && e.code === "OUTBOUND_BODY_TOO_LARGE";
|
|
18
|
+
}
|
|
13
19
|
var UnsupportedProtocolError = class extends Error {
|
|
14
20
|
code = "UNSUPPORTED_PROTOCOL";
|
|
15
21
|
protocol;
|
|
@@ -115,12 +121,23 @@ var defaultFetchOutbound = async (url, opts) => {
|
|
|
115
121
|
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
116
122
|
const init = {
|
|
117
123
|
method: opts.method,
|
|
118
|
-
signal: controller.signal
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
// Redirects are never followed — deny-host and protocol validation ran
|
|
126
|
+
// against the original URL only, so a 3xx from an allowed host could
|
|
127
|
+
// otherwise pivot the fetch to any target (e.g. `302 Location:
|
|
128
|
+
// http://127.0.0.1/…`) behind the verifier's back. Every target must be
|
|
129
|
+
// validated, so a redirect is a fetch failure; all SDKs behave
|
|
130
|
+
// identically. A readable 3xx flows through as a non-2xx status and the
|
|
131
|
+
// caller's attempt handling marks it failed, like a 5xx.
|
|
132
|
+
redirect: "manual"
|
|
119
133
|
};
|
|
120
134
|
if (opts.headers) init.headers = { ...opts.headers };
|
|
121
135
|
if (opts.body !== void 0) init.body = opts.body;
|
|
122
136
|
try {
|
|
123
137
|
const res = await fetch(url, init);
|
|
138
|
+
if (res.type === "opaqueredirect") {
|
|
139
|
+
throw new Error(`redirect refused (opaqueredirect): ${url} answered with a redirect`);
|
|
140
|
+
}
|
|
124
141
|
const declared = res.headers.get("content-length");
|
|
125
142
|
if (declared !== null) {
|
|
126
143
|
const declaredLen = Number(declared);
|
|
@@ -180,9 +197,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
180
197
|
audit.push({
|
|
181
198
|
url,
|
|
182
199
|
method: "GET",
|
|
183
|
-
status:
|
|
200
|
+
status: null,
|
|
184
201
|
bytes: 0,
|
|
185
|
-
|
|
202
|
+
durationMs: 0,
|
|
186
203
|
purpose: opts.purpose
|
|
187
204
|
});
|
|
188
205
|
throw new Error(
|
|
@@ -194,9 +211,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
194
211
|
audit.push({
|
|
195
212
|
url,
|
|
196
213
|
method: "GET",
|
|
197
|
-
status:
|
|
214
|
+
status: null,
|
|
198
215
|
bytes: 0,
|
|
199
|
-
|
|
216
|
+
durationMs: 0,
|
|
200
217
|
purpose: opts.purpose
|
|
201
218
|
});
|
|
202
219
|
throw new UnsupportedProtocolError(protocol ?? "", url);
|
|
@@ -205,9 +222,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
205
222
|
audit.push({
|
|
206
223
|
url,
|
|
207
224
|
method: "GET",
|
|
208
|
-
status:
|
|
225
|
+
status: null,
|
|
209
226
|
bytes: 0,
|
|
210
|
-
|
|
227
|
+
durationMs: 0,
|
|
211
228
|
purpose: opts.purpose
|
|
212
229
|
});
|
|
213
230
|
throw new UnsupportedMethodError(opts.method, url);
|
|
@@ -218,9 +235,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
218
235
|
audit.push({
|
|
219
236
|
url,
|
|
220
237
|
method: opts.method,
|
|
221
|
-
status:
|
|
238
|
+
status: null,
|
|
222
239
|
bytes: 0,
|
|
223
|
-
|
|
240
|
+
durationMs: 0,
|
|
224
241
|
purpose: opts.purpose
|
|
225
242
|
});
|
|
226
243
|
throw new DenyHostError(canonicaliseHost(host), url);
|
|
@@ -238,7 +255,7 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
238
255
|
method: opts.method,
|
|
239
256
|
status: result.status,
|
|
240
257
|
bytes: result.bytes.byteLength,
|
|
241
|
-
|
|
258
|
+
durationMs: result.durationMs,
|
|
242
259
|
purpose: opts.purpose
|
|
243
260
|
});
|
|
244
261
|
if (retryableStatuses.includes(result.status) && retries > 0) {
|
|
@@ -256,9 +273,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
256
273
|
audit.push({
|
|
257
274
|
url,
|
|
258
275
|
method: opts.method,
|
|
259
|
-
status:
|
|
276
|
+
status: null,
|
|
260
277
|
bytes: 0,
|
|
261
|
-
|
|
278
|
+
durationMs,
|
|
262
279
|
purpose: opts.purpose
|
|
263
280
|
});
|
|
264
281
|
throw e;
|
|
@@ -266,9 +283,9 @@ function wrapFetchOutbound(inner, audit, config = void 0) {
|
|
|
266
283
|
audit.push({
|
|
267
284
|
url,
|
|
268
285
|
method: opts.method,
|
|
269
|
-
status:
|
|
286
|
+
status: null,
|
|
270
287
|
bytes: 0,
|
|
271
|
-
|
|
288
|
+
durationMs,
|
|
272
289
|
purpose: opts.purpose
|
|
273
290
|
});
|
|
274
291
|
lastError = e;
|
|
@@ -318,6 +335,6 @@ async function denyHostsFetch(url, init, opts) {
|
|
|
318
335
|
return storedResponse;
|
|
319
336
|
}
|
|
320
337
|
|
|
321
|
-
export { BodyTooLargeError, DEFAULT_OUTBOUND_MAX_BYTES, DenyHostError, OutboundExhaustedError, UnsupportedMethodError, UnsupportedProtocolError, defaultFetchOutbound, denyHostsFetch, fetchOutbound, matchesDenyList, wrapFetchOutbound };
|
|
338
|
+
export { BodyTooLargeError, DEFAULT_OUTBOUND_MAX_BYTES, DenyHostError, OutboundExhaustedError, UnsupportedMethodError, UnsupportedProtocolError, defaultFetchOutbound, denyHostsFetch, fetchOutbound, isBodyTooLargeError, isDenyHostError, matchesDenyList, wrapFetchOutbound };
|
|
322
339
|
//# sourceMappingURL=index.js.map
|
|
323
340
|
//# sourceMappingURL=index.js.map
|
package/dist/fetch/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fetch/fetch-outbound.ts","../../src/fetch/deny-hosts.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Canonical outbound HTTP wrapper: deny-list short-circuit, protocol/method\n// allowlist, bounded timeout, exp-backoff retry with jitter, audit trail.\n\n// Universal loopback deny-host list a service-independent verifier MUST reject\n// so a record can never be made to \"verify\" only because it reached a loopback\n// address. This default carries no operator-specific entries: a deployment that\n// wants to forbid its own gateway/viewer hosts appends those at construction\n// time. Producers SHOULD pass this through `denyHosts` on every verifier\n// invocation; the wrapper accepts arbitrary lists but exports the canonical\n// loopback set so callers don't duplicate it inline. (RFC-1918 / link-local IP\n// ranges are blocked separately by the SSRF guard, not by this name list.)\nexport const DENY_HOSTS_DEFAULT: ReadonlyArray<string> = ['localhost', '127.0.0.1'];\n\n// Every outbound call carries a purpose tag from the closed set\n// `{cardano, arweave, ipfs}` (the three v1 gateway-chain purposes).\n// `https` is a transitional legacy tag for non-storage HTTPS\n// auxiliaries; new code SHOULD pick one of the three normative purposes.\n// `webhook` is the user-supplied-URL purpose: it triggers the SSRF guard\n// (DNS resolution + IP range check + connection pinning + redirect-chain\n// re-checking + body-size cap), and MUST be used for any fetch where the\n// target URL came from end-user input.\nexport type HttpPurpose = 'cardano' | 'arweave' | 'ipfs' | 'https' | 'webhook';\nexport type HttpMethod = 'GET' | 'POST';\n\nexport interface FetchOutboundOptions {\n readonly method: HttpMethod;\n readonly purpose: HttpPurpose;\n readonly headers?: Readonly<Record<string, string>>;\n readonly body?: string;\n // Hard cap on the response body the primitive will buffer. Gateway content\n // (ar:// / ipfs:// / https) is producer-chosen and therefore UNTRUSTED — the\n // verifier never trusts the producer — so a malicious gateway could otherwise\n // stream unbounded bytes into memory. Omit to use DEFAULT_OUTBOUND_MAX_BYTES.\n readonly maxBytes?: number;\n}\n\nexport interface FetchOutboundResult {\n readonly status: number;\n readonly bytes: Uint8Array;\n readonly durationMs: number;\n}\n\nexport type FetchOutbound = (\n url: string,\n opts: FetchOutboundOptions,\n) => Promise<FetchOutboundResult>;\n\n// Audit-log entry for one outbound HTTP fetch. Field names are snake_case so\n// the record can land directly on `VerifyReport.http_calls[]` (which IS the\n// wire shape) without a key-renaming pass.\nexport interface HttpCallRecord {\n readonly url: string;\n readonly method: HttpMethod;\n readonly status: number;\n readonly bytes: number;\n readonly duration_ms: number;\n readonly purpose: HttpPurpose;\n}\n\nexport interface RetryConfig {\n readonly timeoutMs?: number;\n readonly retries?: number;\n readonly retryableStatuses?: ReadonlyArray<number>;\n}\n\nexport interface WrapFetchOutboundConfig extends RetryConfig {\n readonly denyHosts?: ReadonlyArray<string>;\n}\n\nexport class DenyHostError extends Error {\n readonly code = 'SERVICE_INDEPENDENCE_VIOLATION';\n readonly host: string;\n readonly url: string;\n constructor(host: string, url: string) {\n super(`SERVICE_INDEPENDENCE_VIOLATION: host \"${host}\" is in denyHosts (url=${url})`);\n this.name = 'DenyHostError';\n this.host = host;\n this.url = url;\n }\n}\n\nexport class UnsupportedProtocolError extends Error {\n readonly code = 'UNSUPPORTED_PROTOCOL';\n readonly protocol: string;\n readonly url: string;\n constructor(protocol: string, url: string) {\n super(`UNSUPPORTED_PROTOCOL: \"${protocol}\" not in {http:, https:} (url=${url})`);\n this.name = 'UnsupportedProtocolError';\n this.protocol = protocol;\n this.url = url;\n }\n}\n\nexport class UnsupportedMethodError extends Error {\n readonly code = 'UNSUPPORTED_METHOD';\n readonly method: string;\n readonly url: string;\n constructor(method: string, url: string) {\n super(`UNSUPPORTED_METHOD: \"${method}\" not in {GET, POST} (url=${url})`);\n this.name = 'UnsupportedMethodError';\n this.method = method;\n this.url = url;\n }\n}\n\nexport class BodyTooLargeError extends Error {\n readonly code = 'OUTBOUND_BODY_TOO_LARGE';\n readonly url: string;\n readonly limitBytes: number;\n constructor(url: string, limitBytes: number) {\n super(`OUTBOUND_BODY_TOO_LARGE: response exceeded ${limitBytes} bytes (url=${url})`);\n this.name = 'BodyTooLargeError';\n this.url = url;\n this.limitBytes = limitBytes;\n }\n}\n\nexport class OutboundExhaustedError extends Error {\n readonly code = 'OUTBOUND_EXHAUSTED';\n readonly url: string;\n readonly attempts: number;\n readonly lastStatus: number | undefined;\n readonly lastError: Error | undefined;\n constructor(args: {\n url: string;\n attempts: number;\n lastStatus?: number | undefined;\n lastError?: Error | undefined;\n }) {\n super(\n `OUTBOUND_EXHAUSTED: ${args.attempts} attempts exhausted (url=${args.url}, lastStatus=${args.lastStatus ?? '-'})`,\n );\n this.name = 'OutboundExhaustedError';\n this.url = args.url;\n this.attempts = args.attempts;\n this.lastStatus = args.lastStatus;\n this.lastError = args.lastError;\n }\n}\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n// Default response-body cap for the verifier's gateway fetches. 64 MiB sits\n// well above any single sealed-PoE ciphertext or merkle-leaf payload a verifier\n// would realistically recompute a hash over, while bounding the memory a hostile\n// gateway can force the verifier to allocate for one request. Callers that\n// legitimately handle larger content raise it per-call via `opts.maxBytes`.\nexport const DEFAULT_OUTBOUND_MAX_BYTES = 64 * 1024 * 1024;\nexport const DEFAULT_RETRYABLE_STATUSES: ReadonlyArray<number> = [502, 503, 504];\nconst BACKOFF_BASE_MS: ReadonlyArray<number> = [1000, 2000, 4000];\nconst JITTER_RATIO = 0.25;\n\nfunction canonicaliseHost(host: string): string {\n return host.replace(/^\\[/, '').replace(/\\]$/, '').replace(/\\.$/, '').toLowerCase();\n}\n\nexport function matchesDenyList(host: string, denyHosts: ReadonlyArray<string>): boolean {\n const h = canonicaliseHost(host);\n for (const raw of denyHosts) {\n const pattern = raw.replace(/\\.$/, '').toLowerCase();\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2);\n if (h.endsWith('.' + suffix)) return true;\n continue;\n }\n if (h === pattern) return true;\n if (pattern === 'localhost') {\n if (h === '::1' || h === '0.0.0.0' || h === '169.254.169.254') return true;\n }\n if (pattern === '127.0.0.1') {\n if (/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n }\n }\n return false;\n}\n\nfunction parseProtocol(url: string): string | null {\n try {\n return new URL(url).protocol;\n } catch {\n return null;\n }\n}\n\nfunction isAllowedMethod(method: string): method is HttpMethod {\n return method === 'GET' || method === 'POST';\n}\n\nfunction backoffJitteredMs(attemptIndex: number): number {\n const idx = Math.min(attemptIndex, BACKOFF_BASE_MS.length - 1);\n const base = BACKOFF_BASE_MS[idx] ?? BACKOFF_BASE_MS[BACKOFF_BASE_MS.length - 1]!;\n const jitter = 1 + (Math.random() - 0.5) * 2 * JITTER_RATIO;\n return base * jitter;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport const defaultFetchOutbound: FetchOutbound = async (url, opts) => {\n const t0 = Date.now();\n const maxBytes = opts.maxBytes ?? DEFAULT_OUTBOUND_MAX_BYTES;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);\n const init: RequestInit = {\n method: opts.method,\n signal: controller.signal,\n };\n if (opts.headers) init.headers = { ...opts.headers };\n if (opts.body !== undefined) init.body = opts.body;\n try {\n // allow-raw-fetch: canonical defaultFetchOutbound — single egress point\n const res = await fetch(url, init);\n\n // Fast path: a truthful Content-Length over the cap lets us bail before\n // reading a single body byte. A lying/absent header is still caught by the\n // streaming counter below — the header is an optimisation, not the guard.\n const declared = res.headers.get('content-length');\n if (declared !== null) {\n const declaredLen = Number(declared);\n if (Number.isFinite(declaredLen) && declaredLen > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n }\n\n const bytes = await readBodyCapped(res, url, maxBytes, controller);\n return { status: res.status, bytes, durationMs: Date.now() - t0 };\n } finally {\n clearTimeout(timeout);\n }\n};\n\n// Stream the response body, aborting the underlying request the instant the\n// running byte count exceeds `maxBytes`. This is the actual OOM guard: a\n// gateway that withholds or lies about Content-Length still cannot make us\n// buffer more than the cap, because we stop reading and tear the socket down.\nasync function readBodyCapped(\n res: Response,\n url: string,\n maxBytes: number,\n controller: AbortController,\n): Promise<Uint8Array> {\n const body = res.body;\n if (body === null) {\n // No stream (e.g. a 204, or a fetch polyfill that buffered eagerly). Fall\n // back to arrayBuffer but still enforce the cap on the materialised length.\n const buf = await res.arrayBuffer();\n if (buf.byteLength > maxBytes) {\n throw new BodyTooLargeError(url, maxBytes);\n }\n return new Uint8Array(buf);\n }\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n total += value.byteLength;\n if (total > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function wrapFetchOutbound(\n inner: FetchOutbound,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined = undefined,\n): FetchOutbound {\n // Accept either a denyHosts array (positional) or the full config object.\n const normConfig: WrapFetchOutboundConfig =\n config === undefined\n ? {}\n : Array.isArray(config)\n ? { denyHosts: config as ReadonlyArray<string> }\n : (config as WrapFetchOutboundConfig);\n\n const denyHosts = normConfig.denyHosts ?? [];\n // Default retries=0 (single attempt). Callers opt in via explicit `retries`;\n // the top-level `fetchOutbound` entrypoint forwards caller config.\n const retries = normConfig.retries ?? 0;\n const retryableStatuses = normConfig.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES;\n\n return async (url, opts) => {\n // The `webhook` purpose has bespoke requirements (DNS pinning,\n // per-hop redirect re-checking, body-size cap) that the generic\n // wrapper cannot satisfy. Force callers to use `fetchWebhook`\n // instead of silently accepting the call here.\n if (opts.purpose === 'webhook') {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new Error(\n `webhook purpose must be sent via fetchWebhook, not fetchOutbound (url=${url})`,\n );\n }\n\n // Protocol allowlist.\n const protocol = parseProtocol(url);\n if (protocol !== 'http:' && protocol !== 'https:') {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedProtocolError(protocol ?? '', url);\n }\n\n // Method allowlist.\n if (!isAllowedMethod(opts.method)) {\n audit.push({\n url,\n method: 'GET',\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedMethodError(opts.method, url);\n }\n\n // Deny-list short-circuit.\n if (denyHosts.length > 0) {\n const host = new URL(url).hostname;\n if (matchesDenyList(host, denyHosts)) {\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: 0,\n purpose: opts.purpose,\n });\n throw new DenyHostError(canonicaliseHost(host), url);\n }\n }\n\n // Retry loop. retries=0 → single attempt, return-or-rethrow original.\n let lastStatus: number | undefined;\n let lastError: Error | undefined;\n const totalAttempts = retries + 1;\n for (let attempt = 1; attempt <= totalAttempts; attempt++) {\n const t0 = Date.now();\n try {\n const result = await inner(url, opts);\n audit.push({\n url,\n method: opts.method,\n status: result.status,\n bytes: result.bytes.byteLength,\n duration_ms: result.durationMs,\n purpose: opts.purpose,\n });\n if (retryableStatuses.includes(result.status) && retries > 0) {\n lastStatus = result.status;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n return result;\n } catch (e) {\n const durationMs = Date.now() - t0;\n if (\n e instanceof DenyHostError ||\n e instanceof UnsupportedProtocolError ||\n e instanceof UnsupportedMethodError\n ) {\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: durationMs,\n purpose: opts.purpose,\n });\n throw e;\n }\n audit.push({\n url,\n method: opts.method,\n status: 0,\n bytes: 0,\n duration_ms: durationMs,\n purpose: opts.purpose,\n });\n lastError = e as Error;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n }\n // Single-attempt mode re-throws the original verbatim so callers can match\n // by identity; retry mode wraps the terminal failure in OutboundExhaustedError.\n if (retries === 0 && lastError !== undefined) {\n throw lastError;\n }\n throw new OutboundExhaustedError({ url, attempts: totalAttempts, lastStatus, lastError });\n };\n}\n\nexport async function fetchOutbound(\n url: string,\n opts: FetchOutboundOptions,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig = {},\n): Promise<FetchOutboundResult> {\n const wrapped = wrapFetchOutbound(defaultFetchOutbound, audit, config);\n return wrapped(url, opts);\n}\n","// Public denyHostsFetch surface — thin adapter over the canonical\n// fetchOutbound primitive in ./fetch-outbound.ts.\n\nimport {\n DenyHostError,\n type FetchOutbound,\n type FetchOutboundOptions,\n type HttpCallRecord,\n type HttpMethod,\n UnsupportedMethodError,\n UnsupportedProtocolError,\n wrapFetchOutbound,\n} from './fetch-outbound';\n\nexport { DenyHostError, UnsupportedMethodError, UnsupportedProtocolError };\n\nexport type HttpCall = HttpCallRecord;\n\nexport type DenyHostsFetchOptions = {\n readonly denyHosts: readonly string[];\n readonly audit: HttpCall[];\n readonly purpose: HttpCall['purpose'];\n readonly fetchImpl?: typeof fetch;\n};\n\nexport async function denyHostsFetch(\n url: string,\n init: RequestInit | undefined,\n opts: DenyHostsFetchOptions,\n): Promise<Response> {\n // Forward the raw method so the canonical wrap surfaces\n // UnsupportedMethodError instead of silently rewriting to GET.\n const rawMethod = (init?.method ?? 'GET') as HttpMethod;\n const headers = init?.headers as Record<string, string> | undefined;\n const bodyValue = init?.body;\n const body: string | undefined = typeof bodyValue === 'string' ? bodyValue : undefined;\n const fetchImpl: typeof fetch = opts.fetchImpl ?? globalThis.fetch;\n\n const innerOpts: FetchOutboundOptions = headers\n ? body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, headers, body }\n : { method: rawMethod, purpose: opts.purpose, headers }\n : body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, body }\n : { method: rawMethod, purpose: opts.purpose };\n\n let storedResponse: Response | null = null;\n const inner: FetchOutbound = async (innerUrl, innerOptsArg) => {\n const t0 = Date.now();\n const reqInit: RequestInit = { method: innerOptsArg.method };\n if (innerOptsArg.headers) reqInit.headers = { ...innerOptsArg.headers };\n if (innerOptsArg.body !== undefined) reqInit.body = innerOptsArg.body;\n const response = await fetchImpl(innerUrl, reqInit);\n storedResponse = response;\n const buf = await response.clone().arrayBuffer();\n return {\n status: response.status,\n bytes: new Uint8Array(buf),\n durationMs: Date.now() - t0,\n };\n };\n\n const wrapped = wrapFetchOutbound(inner, opts.audit, { denyHosts: opts.denyHosts });\n await wrapped(url, innerOpts);\n return storedResponse!;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/fetch/fetch-outbound.ts","../../src/fetch/deny-hosts.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Canonical outbound HTTP wrapper: deny-list short-circuit, protocol/method\n// allowlist, bounded timeout, exp-backoff retry with jitter, audit trail.\n\n// Universal loopback deny-host list a service-independent verifier MUST reject\n// so a record can never be made to \"verify\" only because it reached a loopback\n// address. This default carries no operator-specific entries: a deployment that\n// wants to forbid its own gateway/viewer hosts appends those at construction\n// time. Producers SHOULD pass this through `denyHosts` on every verifier\n// invocation; the wrapper accepts arbitrary lists but exports the canonical\n// loopback set so callers don't duplicate it inline. (RFC-1918 / link-local IP\n// ranges are blocked separately by the SSRF guard, not by this name list.)\nexport const DENY_HOSTS_DEFAULT: ReadonlyArray<string> = ['localhost', '127.0.0.1'];\n\n// Every outbound call carries a purpose tag from the closed set\n// `{cardano, arweave, ipfs}` (the three v1 gateway-chain purposes).\n// `https` is a transitional legacy tag for non-storage HTTPS\n// auxiliaries; new code SHOULD pick one of the three normative purposes.\n// `webhook` is the user-supplied-URL purpose: it triggers the SSRF guard\n// (DNS resolution + IP range check + connection pinning + redirect-chain\n// re-checking + body-size cap), and MUST be used for any fetch where the\n// target URL came from end-user input.\nexport type HttpPurpose = 'cardano' | 'arweave' | 'ipfs' | 'https' | 'webhook';\nexport type HttpMethod = 'GET' | 'POST';\n\nexport interface FetchOutboundOptions {\n readonly method: HttpMethod;\n readonly purpose: HttpPurpose;\n readonly headers?: Readonly<Record<string, string>>;\n readonly body?: string;\n // Hard cap on the response body the primitive will buffer. Gateway content\n // (ar:// / ipfs:// / https) is producer-chosen and therefore UNTRUSTED — the\n // verifier never trusts the producer — so a malicious gateway could otherwise\n // stream unbounded bytes into memory. Omit to use DEFAULT_OUTBOUND_MAX_BYTES.\n readonly maxBytes?: number;\n}\n\nexport interface FetchOutboundResult {\n readonly status: number;\n readonly bytes: Uint8Array;\n readonly durationMs: number;\n}\n\nexport type FetchOutbound = (\n url: string,\n opts: FetchOutboundOptions,\n) => Promise<FetchOutboundResult>;\n\n// Audit-log entry for one outbound HTTP fetch. The field set and names match\n// the verifier report's audit-trail entry exactly, so the record lands on\n// `VerifyReport.auditTrail[]` without a key-renaming pass. `status` is the\n// HTTP status when a response was received and `null` when none was (refused\n// call, transport failure).\nexport interface HttpCallRecord {\n readonly url: string;\n readonly method: HttpMethod;\n readonly status: number | null;\n readonly bytes: number;\n readonly durationMs: number;\n readonly purpose: HttpPurpose;\n}\n\nexport interface RetryConfig {\n readonly timeoutMs?: number;\n readonly retries?: number;\n readonly retryableStatuses?: ReadonlyArray<number>;\n}\n\nexport interface WrapFetchOutboundConfig extends RetryConfig {\n readonly denyHosts?: ReadonlyArray<string>;\n}\n\nexport class DenyHostError extends Error {\n readonly code = 'SERVICE_INDEPENDENCE_VIOLATION';\n readonly host: string;\n readonly url: string;\n constructor(host: string, url: string) {\n super(`SERVICE_INDEPENDENCE_VIOLATION: host \"${host}\" is in denyHosts (url=${url})`);\n this.name = 'DenyHostError';\n this.host = host;\n this.url = url;\n }\n}\n\n// The typed errors discriminate on their stable `code` property, never on\n// class identity: the package ships several entry points in two module\n// formats, so a consumer's `BodyTooLargeError` (thrown by a custom transport\n// that imported it from another entry) is a different class object than the\n// verifier's. `instanceof` is kept as the fast path for the common\n// same-module case.\n\n/** Whether `e` is a deny-host refusal (`SERVICE_INDEPENDENCE_VIOLATION`). */\nexport function isDenyHostError(e: unknown): e is DenyHostError {\n return (\n e instanceof DenyHostError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as { code?: unknown }).code === 'SERVICE_INDEPENDENCE_VIOLATION')\n );\n}\n\n/** Whether `e` is a body-cap abort (`OUTBOUND_BODY_TOO_LARGE`). */\nexport function isBodyTooLargeError(e: unknown): e is BodyTooLargeError {\n return (\n e instanceof BodyTooLargeError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as { code?: unknown }).code === 'OUTBOUND_BODY_TOO_LARGE')\n );\n}\n\nexport class UnsupportedProtocolError extends Error {\n readonly code = 'UNSUPPORTED_PROTOCOL';\n readonly protocol: string;\n readonly url: string;\n constructor(protocol: string, url: string) {\n super(`UNSUPPORTED_PROTOCOL: \"${protocol}\" not in {http:, https:} (url=${url})`);\n this.name = 'UnsupportedProtocolError';\n this.protocol = protocol;\n this.url = url;\n }\n}\n\nexport class UnsupportedMethodError extends Error {\n readonly code = 'UNSUPPORTED_METHOD';\n readonly method: string;\n readonly url: string;\n constructor(method: string, url: string) {\n super(`UNSUPPORTED_METHOD: \"${method}\" not in {GET, POST} (url=${url})`);\n this.name = 'UnsupportedMethodError';\n this.method = method;\n this.url = url;\n }\n}\n\nexport class BodyTooLargeError extends Error {\n readonly code = 'OUTBOUND_BODY_TOO_LARGE';\n readonly url: string;\n readonly limitBytes: number;\n constructor(url: string, limitBytes: number) {\n super(`OUTBOUND_BODY_TOO_LARGE: response exceeded ${limitBytes} bytes (url=${url})`);\n this.name = 'BodyTooLargeError';\n this.url = url;\n this.limitBytes = limitBytes;\n }\n}\n\nexport class OutboundExhaustedError extends Error {\n readonly code = 'OUTBOUND_EXHAUSTED';\n readonly url: string;\n readonly attempts: number;\n readonly lastStatus: number | undefined;\n readonly lastError: Error | undefined;\n constructor(args: {\n url: string;\n attempts: number;\n lastStatus?: number | undefined;\n lastError?: Error | undefined;\n }) {\n super(\n `OUTBOUND_EXHAUSTED: ${args.attempts} attempts exhausted (url=${args.url}, lastStatus=${args.lastStatus ?? '-'})`,\n );\n this.name = 'OutboundExhaustedError';\n this.url = args.url;\n this.attempts = args.attempts;\n this.lastStatus = args.lastStatus;\n this.lastError = args.lastError;\n }\n}\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n// Default response-body cap for the verifier's gateway fetches. 64 MiB sits\n// well above any single sealed-PoE ciphertext or merkle-leaf payload a verifier\n// would realistically recompute a hash over, while bounding the memory a hostile\n// gateway can force the verifier to allocate for one request. Callers that\n// legitimately handle larger content raise it per-call via `opts.maxBytes`.\nexport const DEFAULT_OUTBOUND_MAX_BYTES = 64 * 1024 * 1024;\nexport const DEFAULT_RETRYABLE_STATUSES: ReadonlyArray<number> = [502, 503, 504];\nconst BACKOFF_BASE_MS: ReadonlyArray<number> = [1000, 2000, 4000];\nconst JITTER_RATIO = 0.25;\n\nfunction canonicaliseHost(host: string): string {\n return host.replace(/^\\[/, '').replace(/\\]$/, '').replace(/\\.$/, '').toLowerCase();\n}\n\nexport function matchesDenyList(host: string, denyHosts: ReadonlyArray<string>): boolean {\n const h = canonicaliseHost(host);\n for (const raw of denyHosts) {\n const pattern = raw.replace(/\\.$/, '').toLowerCase();\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2);\n if (h.endsWith('.' + suffix)) return true;\n continue;\n }\n if (h === pattern) return true;\n if (pattern === 'localhost') {\n if (h === '::1' || h === '0.0.0.0' || h === '169.254.169.254') return true;\n }\n if (pattern === '127.0.0.1') {\n if (/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n }\n }\n return false;\n}\n\nfunction parseProtocol(url: string): string | null {\n try {\n return new URL(url).protocol;\n } catch {\n return null;\n }\n}\n\nfunction isAllowedMethod(method: string): method is HttpMethod {\n return method === 'GET' || method === 'POST';\n}\n\nfunction backoffJitteredMs(attemptIndex: number): number {\n const idx = Math.min(attemptIndex, BACKOFF_BASE_MS.length - 1);\n const base = BACKOFF_BASE_MS[idx] ?? BACKOFF_BASE_MS[BACKOFF_BASE_MS.length - 1]!;\n const jitter = 1 + (Math.random() - 0.5) * 2 * JITTER_RATIO;\n return base * jitter;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport const defaultFetchOutbound: FetchOutbound = async (url, opts) => {\n const t0 = Date.now();\n const maxBytes = opts.maxBytes ?? DEFAULT_OUTBOUND_MAX_BYTES;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);\n const init: RequestInit = {\n method: opts.method,\n signal: controller.signal,\n // Redirects are never followed — deny-host and protocol validation ran\n // against the original URL only, so a 3xx from an allowed host could\n // otherwise pivot the fetch to any target (e.g. `302 Location:\n // http://127.0.0.1/…`) behind the verifier's back. Every target must be\n // validated, so a redirect is a fetch failure; all SDKs behave\n // identically. A readable 3xx flows through as a non-2xx status and the\n // caller's attempt handling marks it failed, like a 5xx.\n redirect: 'manual',\n };\n if (opts.headers) init.headers = { ...opts.headers };\n if (opts.body !== undefined) init.body = opts.body;\n try {\n // allow-raw-fetch: canonical defaultFetchOutbound — single egress point\n const res = await fetch(url, init);\n\n // Browser runtimes surface a refused redirect as an opaque response\n // (type 'opaqueredirect', status 0) with no readable status or body;\n // there is nothing to report from it, so it fails like a transport error.\n if (res.type === 'opaqueredirect') {\n throw new Error(`redirect refused (opaqueredirect): ${url} answered with a redirect`);\n }\n\n // Fast path: a truthful Content-Length over the cap lets us bail before\n // reading a single body byte. A lying/absent header is still caught by the\n // streaming counter below — the header is an optimisation, not the guard.\n const declared = res.headers.get('content-length');\n if (declared !== null) {\n const declaredLen = Number(declared);\n if (Number.isFinite(declaredLen) && declaredLen > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n }\n\n const bytes = await readBodyCapped(res, url, maxBytes, controller);\n return { status: res.status, bytes, durationMs: Date.now() - t0 };\n } finally {\n clearTimeout(timeout);\n }\n};\n\n// Stream the response body, aborting the underlying request the instant the\n// running byte count exceeds `maxBytes`. This is the actual OOM guard: a\n// gateway that withholds or lies about Content-Length still cannot make us\n// buffer more than the cap, because we stop reading and tear the socket down.\nasync function readBodyCapped(\n res: Response,\n url: string,\n maxBytes: number,\n controller: AbortController,\n): Promise<Uint8Array> {\n const body = res.body;\n if (body === null) {\n // No stream (e.g. a 204, or a fetch polyfill that buffered eagerly). Fall\n // back to arrayBuffer but still enforce the cap on the materialised length.\n const buf = await res.arrayBuffer();\n if (buf.byteLength > maxBytes) {\n throw new BodyTooLargeError(url, maxBytes);\n }\n return new Uint8Array(buf);\n }\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n total += value.byteLength;\n if (total > maxBytes) {\n controller.abort();\n throw new BodyTooLargeError(url, maxBytes);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function wrapFetchOutbound(\n inner: FetchOutbound,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined = undefined,\n): FetchOutbound {\n // Accept either a denyHosts array (positional) or the full config object.\n const normConfig: WrapFetchOutboundConfig =\n config === undefined\n ? {}\n : Array.isArray(config)\n ? { denyHosts: config as ReadonlyArray<string> }\n : (config as WrapFetchOutboundConfig);\n\n const denyHosts = normConfig.denyHosts ?? [];\n // Default retries=0 (single attempt). Callers opt in via explicit `retries`;\n // the top-level `fetchOutbound` entrypoint forwards caller config.\n const retries = normConfig.retries ?? 0;\n const retryableStatuses = normConfig.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES;\n\n return async (url, opts) => {\n // The `webhook` purpose has bespoke requirements (DNS pinning,\n // per-hop redirect re-checking, body-size cap) that the generic\n // wrapper cannot satisfy. Force callers to use `fetchWebhook`\n // instead of silently accepting the call here.\n if (opts.purpose === 'webhook') {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new Error(\n `webhook purpose must be sent via fetchWebhook, not fetchOutbound (url=${url})`,\n );\n }\n\n // Protocol allowlist.\n const protocol = parseProtocol(url);\n if (protocol !== 'http:' && protocol !== 'https:') {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedProtocolError(protocol ?? '', url);\n }\n\n // Method allowlist.\n if (!isAllowedMethod(opts.method)) {\n audit.push({\n url,\n method: 'GET',\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new UnsupportedMethodError(opts.method, url);\n }\n\n // Deny-list short-circuit.\n if (denyHosts.length > 0) {\n const host = new URL(url).hostname;\n if (matchesDenyList(host, denyHosts)) {\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs: 0,\n purpose: opts.purpose,\n });\n throw new DenyHostError(canonicaliseHost(host), url);\n }\n }\n\n // Retry loop. retries=0 → single attempt, return-or-rethrow original.\n let lastStatus: number | undefined;\n let lastError: Error | undefined;\n const totalAttempts = retries + 1;\n for (let attempt = 1; attempt <= totalAttempts; attempt++) {\n const t0 = Date.now();\n try {\n const result = await inner(url, opts);\n audit.push({\n url,\n method: opts.method,\n status: result.status,\n bytes: result.bytes.byteLength,\n durationMs: result.durationMs,\n purpose: opts.purpose,\n });\n if (retryableStatuses.includes(result.status) && retries > 0) {\n lastStatus = result.status;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n return result;\n } catch (e) {\n const durationMs = Date.now() - t0;\n if (\n e instanceof DenyHostError ||\n e instanceof UnsupportedProtocolError ||\n e instanceof UnsupportedMethodError\n ) {\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs,\n purpose: opts.purpose,\n });\n throw e;\n }\n audit.push({\n url,\n method: opts.method,\n status: null,\n bytes: 0,\n durationMs,\n purpose: opts.purpose,\n });\n lastError = e as Error;\n if (attempt < totalAttempts) {\n await sleep(backoffJitteredMs(attempt - 1));\n continue;\n }\n break;\n }\n }\n // Single-attempt mode re-throws the original verbatim so callers can match\n // by identity; retry mode wraps the terminal failure in OutboundExhaustedError.\n if (retries === 0 && lastError !== undefined) {\n throw lastError;\n }\n throw new OutboundExhaustedError({ url, attempts: totalAttempts, lastStatus, lastError });\n };\n}\n\nexport async function fetchOutbound(\n url: string,\n opts: FetchOutboundOptions,\n audit: HttpCallRecord[],\n config: WrapFetchOutboundConfig = {},\n): Promise<FetchOutboundResult> {\n const wrapped = wrapFetchOutbound(defaultFetchOutbound, audit, config);\n return wrapped(url, opts);\n}\n","// Public denyHostsFetch surface — thin adapter over the canonical\n// fetchOutbound primitive in ./fetch-outbound.ts.\n\nimport {\n DenyHostError,\n type FetchOutbound,\n type FetchOutboundOptions,\n type HttpCallRecord,\n type HttpMethod,\n UnsupportedMethodError,\n UnsupportedProtocolError,\n wrapFetchOutbound,\n} from './fetch-outbound';\n\nexport { DenyHostError, UnsupportedMethodError, UnsupportedProtocolError };\n\nexport type HttpCall = HttpCallRecord;\n\nexport type DenyHostsFetchOptions = {\n readonly denyHosts: readonly string[];\n readonly audit: HttpCall[];\n readonly purpose: HttpCall['purpose'];\n readonly fetchImpl?: typeof fetch;\n};\n\nexport async function denyHostsFetch(\n url: string,\n init: RequestInit | undefined,\n opts: DenyHostsFetchOptions,\n): Promise<Response> {\n // Forward the raw method so the canonical wrap surfaces\n // UnsupportedMethodError instead of silently rewriting to GET.\n const rawMethod = (init?.method ?? 'GET') as HttpMethod;\n const headers = init?.headers as Record<string, string> | undefined;\n const bodyValue = init?.body;\n const body: string | undefined = typeof bodyValue === 'string' ? bodyValue : undefined;\n const fetchImpl: typeof fetch = opts.fetchImpl ?? globalThis.fetch;\n\n const innerOpts: FetchOutboundOptions = headers\n ? body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, headers, body }\n : { method: rawMethod, purpose: opts.purpose, headers }\n : body !== undefined\n ? { method: rawMethod, purpose: opts.purpose, body }\n : { method: rawMethod, purpose: opts.purpose };\n\n let storedResponse: Response | null = null;\n const inner: FetchOutbound = async (innerUrl, innerOptsArg) => {\n const t0 = Date.now();\n const reqInit: RequestInit = { method: innerOptsArg.method };\n if (innerOptsArg.headers) reqInit.headers = { ...innerOptsArg.headers };\n if (innerOptsArg.body !== undefined) reqInit.body = innerOptsArg.body;\n const response = await fetchImpl(innerUrl, reqInit);\n storedResponse = response;\n const buf = await response.clone().arrayBuffer();\n return {\n status: response.status,\n bytes: new Uint8Array(buf),\n durationMs: Date.now() - t0,\n };\n };\n\n const wrapped = wrapFetchOutbound(inner, opts.audit, { denyHosts: opts.denyHosts });\n await wrapped(url, innerOpts);\n return storedResponse!;\n}\n"]}
|
|
@@ -17,9 +17,9 @@ type FetchOutbound = (url: string, opts: FetchOutboundOptions) => Promise<FetchO
|
|
|
17
17
|
interface HttpCallRecord {
|
|
18
18
|
readonly url: string;
|
|
19
19
|
readonly method: HttpMethod;
|
|
20
|
-
readonly status: number;
|
|
20
|
+
readonly status: number | null;
|
|
21
21
|
readonly bytes: number;
|
|
22
|
-
readonly
|
|
22
|
+
readonly durationMs: number;
|
|
23
23
|
readonly purpose: HttpPurpose;
|
|
24
24
|
}
|
|
25
25
|
interface RetryConfig {
|
|
@@ -36,6 +36,10 @@ declare class DenyHostError extends Error {
|
|
|
36
36
|
readonly url: string;
|
|
37
37
|
constructor(host: string, url: string);
|
|
38
38
|
}
|
|
39
|
+
/** Whether `e` is a deny-host refusal (`SERVICE_INDEPENDENCE_VIOLATION`). */
|
|
40
|
+
declare function isDenyHostError(e: unknown): e is DenyHostError;
|
|
41
|
+
/** Whether `e` is a body-cap abort (`OUTBOUND_BODY_TOO_LARGE`). */
|
|
42
|
+
declare function isBodyTooLargeError(e: unknown): e is BodyTooLargeError;
|
|
39
43
|
declare class UnsupportedProtocolError extends Error {
|
|
40
44
|
readonly code = "UNSUPPORTED_PROTOCOL";
|
|
41
45
|
readonly protocol: string;
|
|
@@ -73,4 +77,4 @@ declare const defaultFetchOutbound: FetchOutbound;
|
|
|
73
77
|
declare function wrapFetchOutbound(inner: FetchOutbound, audit: HttpCallRecord[], config?: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined): FetchOutbound;
|
|
74
78
|
declare function fetchOutbound(url: string, opts: FetchOutboundOptions, audit: HttpCallRecord[], config?: WrapFetchOutboundConfig): Promise<FetchOutboundResult>;
|
|
75
79
|
|
|
76
|
-
export { BodyTooLargeError as B, DEFAULT_OUTBOUND_MAX_BYTES as D, type FetchOutbound as F, type HttpCallRecord as H, OutboundExhaustedError as O, type RetryConfig as R, UnsupportedMethodError as U, type WrapFetchOutboundConfig as W, DENY_HOSTS_DEFAULT as a, DenyHostError as b, type FetchOutboundOptions as c, type FetchOutboundResult as d, type HttpMethod as e, type HttpPurpose as f, UnsupportedProtocolError as g, defaultFetchOutbound as h, fetchOutbound as i, matchesDenyList as m, wrapFetchOutbound as w };
|
|
80
|
+
export { BodyTooLargeError as B, DEFAULT_OUTBOUND_MAX_BYTES as D, type FetchOutbound as F, type HttpCallRecord as H, OutboundExhaustedError as O, type RetryConfig as R, UnsupportedMethodError as U, type WrapFetchOutboundConfig as W, DENY_HOSTS_DEFAULT as a, DenyHostError as b, type FetchOutboundOptions as c, type FetchOutboundResult as d, type HttpMethod as e, type HttpPurpose as f, UnsupportedProtocolError as g, defaultFetchOutbound as h, fetchOutbound as i, isBodyTooLargeError as j, isDenyHostError as k, matchesDenyList as m, wrapFetchOutbound as w };
|
|
@@ -17,9 +17,9 @@ type FetchOutbound = (url: string, opts: FetchOutboundOptions) => Promise<FetchO
|
|
|
17
17
|
interface HttpCallRecord {
|
|
18
18
|
readonly url: string;
|
|
19
19
|
readonly method: HttpMethod;
|
|
20
|
-
readonly status: number;
|
|
20
|
+
readonly status: number | null;
|
|
21
21
|
readonly bytes: number;
|
|
22
|
-
readonly
|
|
22
|
+
readonly durationMs: number;
|
|
23
23
|
readonly purpose: HttpPurpose;
|
|
24
24
|
}
|
|
25
25
|
interface RetryConfig {
|
|
@@ -36,6 +36,10 @@ declare class DenyHostError extends Error {
|
|
|
36
36
|
readonly url: string;
|
|
37
37
|
constructor(host: string, url: string);
|
|
38
38
|
}
|
|
39
|
+
/** Whether `e` is a deny-host refusal (`SERVICE_INDEPENDENCE_VIOLATION`). */
|
|
40
|
+
declare function isDenyHostError(e: unknown): e is DenyHostError;
|
|
41
|
+
/** Whether `e` is a body-cap abort (`OUTBOUND_BODY_TOO_LARGE`). */
|
|
42
|
+
declare function isBodyTooLargeError(e: unknown): e is BodyTooLargeError;
|
|
39
43
|
declare class UnsupportedProtocolError extends Error {
|
|
40
44
|
readonly code = "UNSUPPORTED_PROTOCOL";
|
|
41
45
|
readonly protocol: string;
|
|
@@ -73,4 +77,4 @@ declare const defaultFetchOutbound: FetchOutbound;
|
|
|
73
77
|
declare function wrapFetchOutbound(inner: FetchOutbound, audit: HttpCallRecord[], config?: WrapFetchOutboundConfig | ReadonlyArray<string> | undefined): FetchOutbound;
|
|
74
78
|
declare function fetchOutbound(url: string, opts: FetchOutboundOptions, audit: HttpCallRecord[], config?: WrapFetchOutboundConfig): Promise<FetchOutboundResult>;
|
|
75
79
|
|
|
76
|
-
export { BodyTooLargeError as B, DEFAULT_OUTBOUND_MAX_BYTES as D, type FetchOutbound as F, type HttpCallRecord as H, OutboundExhaustedError as O, type RetryConfig as R, UnsupportedMethodError as U, type WrapFetchOutboundConfig as W, DENY_HOSTS_DEFAULT as a, DenyHostError as b, type FetchOutboundOptions as c, type FetchOutboundResult as d, type HttpMethod as e, type HttpPurpose as f, UnsupportedProtocolError as g, defaultFetchOutbound as h, fetchOutbound as i, matchesDenyList as m, wrapFetchOutbound as w };
|
|
80
|
+
export { BodyTooLargeError as B, DEFAULT_OUTBOUND_MAX_BYTES as D, type FetchOutbound as F, type HttpCallRecord as H, OutboundExhaustedError as O, type RetryConfig as R, UnsupportedMethodError as U, type WrapFetchOutboundConfig as W, DENY_HOSTS_DEFAULT as a, DenyHostError as b, type FetchOutboundOptions as c, type FetchOutboundResult as d, type HttpMethod as e, type HttpPurpose as f, UnsupportedProtocolError as g, defaultFetchOutbound as h, fetchOutbound as i, isBodyTooLargeError as j, isDenyHostError as k, matchesDenyList as m, wrapFetchOutbound as w };
|