@dnsid-ai/transport 0.26.0 → 0.28.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/index.cjs CHANGED
@@ -46,6 +46,7 @@ var fs = __toESM(require("fs"), 1);
46
46
  var https = __toESM(require("https"), 1);
47
47
  var net2 = __toESM(require("net"), 1);
48
48
  var tls = __toESM(require("tls"), 1);
49
+ var import_node_stream = require("stream");
49
50
  var import_undici = require("undici");
50
51
  var import_protocol2 = require("@dnsid-ai/protocol");
51
52
 
@@ -190,7 +191,8 @@ function createSsrfSafeFetch(config = {}, options = {}) {
190
191
  const privateAddressHosts = (options.privateAddressHosts ?? []).map(import_protocol2.normalizePrivateAddressHost);
191
192
  const connect2 = { lookup: createSsrfSafeLookup(config.dnsServer, privateAddressHosts) };
192
193
  if (config.caBundlePath) connect2.ca = [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")];
193
- const fetchWithSafeLookup = createFetchWithDispatcher(new import_undici.Agent({ connect: connect2 }));
194
+ const dispatcher = new import_undici.Agent({ connect: connect2 });
195
+ const fetchWithSafeLookup = createFetchWithDispatcher(dispatcher);
194
196
  return async (input, init) => {
195
197
  const url = new URL(input instanceof Request ? input.url : input);
196
198
  if (isUnsafeIp(url.hostname)) {
@@ -198,6 +200,49 @@ function createSsrfSafeFetch(config = {}, options = {}) {
198
200
  code: import_protocol2.VerificationCode.TLSError
199
201
  });
200
202
  }
203
+ if (options.decompress === false) {
204
+ const { url: target, init: normalized } = normalizeFetchArgs(input, { ...init, redirect: "manual" });
205
+ const request = new Request(target, normalized);
206
+ const targetUrl = new URL(request.url);
207
+ const response = await dispatcher.request({
208
+ origin: targetUrl.origin,
209
+ path: targetUrl.pathname + targetUrl.search,
210
+ method: request.method,
211
+ headers: Object.fromEntries(request.headers),
212
+ body: request.body ? import_node_stream.Readable.fromWeb(request.body) : void 0,
213
+ signal: request.signal
214
+ });
215
+ const headers = new Headers();
216
+ for (const [name, value] of Object.entries(response.headers)) {
217
+ for (const item of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, item);
218
+ }
219
+ const noBody = request.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
220
+ let body = null;
221
+ if (noBody) await response.body.dump();
222
+ else {
223
+ const iterator = response.body[Symbol.asyncIterator]();
224
+ let canceled = false;
225
+ response.body.on("error", () => {
226
+ });
227
+ body = new ReadableStream({
228
+ async pull(controller) {
229
+ try {
230
+ const { value, done } = await iterator.next();
231
+ if (canceled) return;
232
+ if (done) controller.close();
233
+ else controller.enqueue(new Uint8Array(value));
234
+ } catch (error) {
235
+ if (!canceled) controller.error(error);
236
+ }
237
+ },
238
+ cancel() {
239
+ canceled = true;
240
+ response.body.destroy();
241
+ }
242
+ });
243
+ }
244
+ return new Response(body, { status: response.statusCode, statusText: response.statusText, headers });
245
+ }
201
246
  return fetchWithSafeLookup(input, { ...init, redirect: "manual" });
202
247
  };
203
248
  }
@@ -501,7 +546,8 @@ function fetchWithRedirects(url, opts, depth) {
501
546
  signal: opts.signal,
502
547
  rejectUnauthorized: true,
503
548
  headers: {
504
- "User-Agent": DNSID_USER_AGENT
549
+ "User-Agent": DNSID_USER_AGENT,
550
+ ...opts.revalidate ? { "Cache-Control": "no-cache" } : {}
505
551
  },
506
552
  lookup: createSsrfSafeLookup(opts.dnsServer, (opts.privateAddressHosts ?? []).map(import_protocol2.normalizePrivateAddressHost))
507
553
  };
package/dist/index.d.cts CHANGED
@@ -13,6 +13,8 @@ interface FetchResult {
13
13
  }
14
14
  /** Options controlling {@link fetchJson}. */
15
15
  interface HTTPSFetchOptions {
16
+ /** Bypass local caches and revalidate upstream evidence. */
17
+ revalidate?: boolean;
16
18
  /** Shared verification deadline/cancellation, including redirects. */
17
19
  signal?: AbortSignal;
18
20
  /** If set, the URL host (and every redirect host) must match this host exactly. */
@@ -33,6 +35,8 @@ interface HTTPSFetchOptions {
33
35
 
34
36
  /** Explicit exceptions for trusted test/private deployments using {@link createSsrfSafeFetch}. */
35
37
  interface SsrfSafeFetchOptions {
38
+ /** Set false to preserve encoded response bytes and Content-Encoding, for signed-directory verification. Default: true. */
39
+ decompress?: boolean;
36
40
  /**
37
41
  * Hostnames, or leading-dot suffixes such as `.test`, whose resolved RFC 1918/ULA private or
38
42
  * loopback addresses may be contacted. Link-local and other unsafe ranges remain blocked, IP-literal
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ interface FetchResult {
13
13
  }
14
14
  /** Options controlling {@link fetchJson}. */
15
15
  interface HTTPSFetchOptions {
16
+ /** Bypass local caches and revalidate upstream evidence. */
17
+ revalidate?: boolean;
16
18
  /** Shared verification deadline/cancellation, including redirects. */
17
19
  signal?: AbortSignal;
18
20
  /** If set, the URL host (and every redirect host) must match this host exactly. */
@@ -33,6 +35,8 @@ interface HTTPSFetchOptions {
33
35
 
34
36
  /** Explicit exceptions for trusted test/private deployments using {@link createSsrfSafeFetch}. */
35
37
  interface SsrfSafeFetchOptions {
38
+ /** Set false to preserve encoded response bytes and Content-Encoding, for signed-directory verification. Default: true. */
39
+ decompress?: boolean;
36
40
  /**
37
41
  * Hostnames, or leading-dot suffixes such as `.test`, whose resolved RFC 1918/ULA private or
38
42
  * loopback addresses may be contacted. Link-local and other unsafe ranges remain blocked, IP-literal
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import * as fs from "fs";
4
4
  import * as https from "https";
5
5
  import * as net2 from "net";
6
6
  import * as tls from "tls";
7
+ import { Readable } from "stream";
7
8
  import { Agent as Agent2, fetch as undiciFetch } from "undici";
8
9
  import { ArgumentError, DNSSECState as DNSSECState2, VerificationCode, VerificationError, normalizePrivateAddressHost } from "@dnsid-ai/protocol";
9
10
 
@@ -148,7 +149,8 @@ function createSsrfSafeFetch(config = {}, options = {}) {
148
149
  const privateAddressHosts = (options.privateAddressHosts ?? []).map(normalizePrivateAddressHost);
149
150
  const connect2 = { lookup: createSsrfSafeLookup(config.dnsServer, privateAddressHosts) };
150
151
  if (config.caBundlePath) connect2.ca = [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")];
151
- const fetchWithSafeLookup = createFetchWithDispatcher(new Agent2({ connect: connect2 }));
152
+ const dispatcher = new Agent2({ connect: connect2 });
153
+ const fetchWithSafeLookup = createFetchWithDispatcher(dispatcher);
152
154
  return async (input, init) => {
153
155
  const url = new URL(input instanceof Request ? input.url : input);
154
156
  if (isUnsafeIp(url.hostname)) {
@@ -156,6 +158,49 @@ function createSsrfSafeFetch(config = {}, options = {}) {
156
158
  code: VerificationCode.TLSError
157
159
  });
158
160
  }
161
+ if (options.decompress === false) {
162
+ const { url: target, init: normalized } = normalizeFetchArgs(input, { ...init, redirect: "manual" });
163
+ const request = new Request(target, normalized);
164
+ const targetUrl = new URL(request.url);
165
+ const response = await dispatcher.request({
166
+ origin: targetUrl.origin,
167
+ path: targetUrl.pathname + targetUrl.search,
168
+ method: request.method,
169
+ headers: Object.fromEntries(request.headers),
170
+ body: request.body ? Readable.fromWeb(request.body) : void 0,
171
+ signal: request.signal
172
+ });
173
+ const headers = new Headers();
174
+ for (const [name, value] of Object.entries(response.headers)) {
175
+ for (const item of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, item);
176
+ }
177
+ const noBody = request.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
178
+ let body = null;
179
+ if (noBody) await response.body.dump();
180
+ else {
181
+ const iterator = response.body[Symbol.asyncIterator]();
182
+ let canceled = false;
183
+ response.body.on("error", () => {
184
+ });
185
+ body = new ReadableStream({
186
+ async pull(controller) {
187
+ try {
188
+ const { value, done } = await iterator.next();
189
+ if (canceled) return;
190
+ if (done) controller.close();
191
+ else controller.enqueue(new Uint8Array(value));
192
+ } catch (error) {
193
+ if (!canceled) controller.error(error);
194
+ }
195
+ },
196
+ cancel() {
197
+ canceled = true;
198
+ response.body.destroy();
199
+ }
200
+ });
201
+ }
202
+ return new Response(body, { status: response.statusCode, statusText: response.statusText, headers });
203
+ }
159
204
  return fetchWithSafeLookup(input, { ...init, redirect: "manual" });
160
205
  };
161
206
  }
@@ -459,7 +504,8 @@ function fetchWithRedirects(url, opts, depth) {
459
504
  signal: opts.signal,
460
505
  rejectUnauthorized: true,
461
506
  headers: {
462
- "User-Agent": DNSID_USER_AGENT
507
+ "User-Agent": DNSID_USER_AGENT,
508
+ ...opts.revalidate ? { "Cache-Control": "no-cache" } : {}
463
509
  },
464
510
  lookup: createSsrfSafeLookup(opts.dnsServer, (opts.privateAddressHosts ?? []).map(normalizePrivateAddressHost))
465
511
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnsid-ai/transport",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Node.js DNS and HTTPS transport implementations for DNSid.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -38,7 +38,7 @@
38
38
  "typecheck": "tsc --noEmit"
39
39
  },
40
40
  "dependencies": {
41
- "@dnsid-ai/protocol": "^0.26.0",
41
+ "@dnsid-ai/protocol": "^0.28.0",
42
42
  "dns-packet": "5.6.1",
43
43
  "undici": "^8.11.0"
44
44
  },