@dnsid-ai/transport 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,30 @@ const result = await fetchJson('https://agent.example/.well-known/dnsid-status.j
37
37
  const safeFetch = createSsrfSafeFetch({ dnsServer: process.env.DNSID_DNS_SERVER });
38
38
  ```
39
39
 
40
+ ## Single-send signed-response transport
41
+
42
+ `createResponseSignatureTransport(config?)` returns the `responseTransport`
43
+ function required by `HttpSignaturesProfile.createSignedFetch` when
44
+ `signing.responseSignature` is present. It accepts a prepared `Request` and returns
45
+ `{ response, peerCert }`. It performs one HTTPS request per call, reuses keep-alive
46
+ connections across calls, never follows redirects or retries, never caches, and
47
+ preserves encoded content bytes without transparent decompression. Request bodies
48
+ are sent with `Content-Length`. The certificate comes from the connection that
49
+ carried the response, so it can satisfy
50
+ `fl=mtls`; the profile still validates its SAN against the expected DNSid agent.
51
+
52
+ The transport honors the request abort signal and has a 30-second ceiling.
53
+ The signed client supplies a shared finite send/read/verification budget and
54
+ bounded content reads (8 MiB). Standalone transport consumers must also bound
55
+ body reads, cancel rejected responses, and not expose unverified content.
56
+ Protocol upgrades and tunnel establishment reject. HTTP errors and ordinary
57
+ redirect responses are returned to the profile for signature verification.
58
+
59
+ Custom DNS and CA settings follow `createDnsidFetch`. Like that function, this
60
+ transport does not filter private addresses: callers must select a trusted target
61
+ or inject a transport with their required network-address policy. It is separate
62
+ from WBA's SSRF-safe discovery transport and does not change that profile.
63
+
40
64
  Most Node applications should use this through `@dnsid-ai/sdk/node` or profile defaults.
41
65
 
42
66
  The default resolver queries the DNS servers returned by Node's `dns.getServers()` in order, using UDP with TCP fallback to capture the recursive server's remaining TXT TTL (capped at one day). `dnsServer` can override this list. On failures or empty answers it falls back to Node's native TXT lookup with TTL `0`, disabling identity caching for that result. Node's server list may omit macOS scoped/split-DNS routing, so even positive wire answers are not guaranteed to match the platform resolver in those setups. Neither path validates DNSSEC (`UNKNOWN`); SERVFAIL is a resolution error. `validated`/`required` DNSSEC modes still need an injected validating resolver. Cached verification still re-fetches status unless `verification.statusCheckInterval` is set.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  createDnsResolverFromServer: () => createDnsResolverFromServer,
35
35
  createDnsidFetch: () => createDnsidFetch,
36
36
  createLookup: () => createLookup,
37
+ createResponseSignatureTransport: () => createResponseSignatureTransport,
37
38
  createSsrfSafeFetch: () => createSsrfSafeFetch,
38
39
  fetchJson: () => fetchJson,
39
40
  formatDnsServer: () => formatDnsServer,
@@ -97,7 +98,7 @@ async function resolveTxtWithTtl(name, host, port, signal) {
97
98
  }
98
99
  async function query(name, host, port, signal) {
99
100
  const id = (0, import_node_crypto.randomInt)(65536);
100
- const request = {
101
+ const request2 = {
101
102
  type: "query",
102
103
  id,
103
104
  flags: import_dns_packet.default.RECURSION_DESIRED,
@@ -113,9 +114,9 @@ async function query(name, host, port, signal) {
113
114
  return null;
114
115
  }
115
116
  };
116
- const response = await udp(import_dns_packet.default.encode(request), host, port, signal, valid);
117
+ const response = await udp(import_dns_packet.default.encode(request2), host, port, signal, valid);
117
118
  if (!response.flag_tc) return response;
118
- const tcpResponse = valid(await tcp(import_dns_packet.default.streamEncode(request), host, port, signal));
119
+ const tcpResponse = valid(await tcp(import_dns_packet.default.streamEncode(request2), host, port, signal));
119
120
  if (!tcpResponse || tcpResponse.flag_tc) throw badResponse();
120
121
  return tcpResponse;
121
122
  }
@@ -178,6 +179,15 @@ function tcp(buf, host, port, signal) {
178
179
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
179
180
  var DEFAULT_MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
180
181
  var DNSID_USER_AGENT = "dnsid-ts";
182
+ function httpsTransportError(cause) {
183
+ const err = cause;
184
+ const isTls = err.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || err.code === "CERT_HAS_EXPIRED" || err.code === "ERR_TLS_CERT_ALTNAME_INVALID" || err.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
185
+ return new import_protocol2.VerificationError(`HTTPS fetch failed: ${err.message}`, {
186
+ code: import_protocol2.VerificationCode.TLSError,
187
+ transient: !isTls,
188
+ cause
189
+ });
190
+ }
181
191
  function createDnsidFetch(config) {
182
192
  if (!config.dnsServer && !config.caBundlePath) {
183
193
  return globalThis.fetch.bind(globalThis);
@@ -187,6 +197,76 @@ function createDnsidFetch(config) {
187
197
  if (config.caBundlePath) connect2.ca = [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")];
188
198
  return createFetchWithDispatcher(new import_undici.Agent({ connect: connect2 }));
189
199
  }
200
+ function createResponseSignatureTransport(config = {}) {
201
+ const lookup2 = config.dnsServer ? createLookup(config.dnsServer) : void 0;
202
+ const ca = config.caBundlePath ? [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")] : void 0;
203
+ const agent = new https.Agent({ keepAlive: true, lookup: lookup2, ca, rejectUnauthorized: true });
204
+ return async (request2) => {
205
+ const url = new URL(request2.url);
206
+ if (url.protocol !== "https:") throw new import_protocol2.ArgumentError("response authentication requires HTTPS");
207
+ const signal = AbortSignal.any([request2.signal, AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS * 3)]);
208
+ const content = request2.body ? Buffer.from(await request2.arrayBuffer()) : null;
209
+ const headers = Object.fromEntries(request2.headers);
210
+ if (content) headers["content-length"] = String(content.length);
211
+ return new Promise((resolve, reject) => {
212
+ const outgoing = https.request(url, {
213
+ agent,
214
+ signal,
215
+ method: request2.method,
216
+ headers
217
+ }, (incoming) => {
218
+ const cert = incoming.socket.getPeerCertificate();
219
+ const peerCert = {
220
+ notAfter: new Date(cert.valid_to),
221
+ san: (cert.subjectaltname ?? "").split(", ").filter((s) => s.startsWith("DNS:")).map((s) => s.slice(4))
222
+ };
223
+ const headers2 = new Headers();
224
+ for (let i = 0; i < incoming.rawHeaders.length; i += 2) headers2.append(incoming.rawHeaders[i], incoming.rawHeaders[i + 1]);
225
+ const status = incoming.statusCode;
226
+ if (status < 200 || status > 599) {
227
+ incoming.destroy();
228
+ reject(new import_protocol2.VerificationError("not a final HTTP response", { code: import_protocol2.VerificationCode.SignatureInvalid }));
229
+ return;
230
+ }
231
+ const noBody = request2.method === "HEAD" || [204, 205, 304].includes(status);
232
+ const iterator = incoming[Symbol.asyncIterator]();
233
+ let canceled = false;
234
+ incoming.on("error", () => {
235
+ });
236
+ const body = noBody ? null : new ReadableStream({
237
+ async pull(controller) {
238
+ try {
239
+ const { value, done } = await iterator.next();
240
+ if (canceled) return;
241
+ if (done) controller.close();
242
+ else controller.enqueue(new Uint8Array(value));
243
+ } catch (error) {
244
+ if (!canceled) controller.error(error);
245
+ }
246
+ },
247
+ cancel() {
248
+ canceled = true;
249
+ incoming.destroy();
250
+ outgoing.destroy();
251
+ }
252
+ });
253
+ if (noBody) incoming.resume();
254
+ resolve({ response: new Response(body, { status, statusText: incoming.statusMessage, headers: headers2 }), peerCert });
255
+ });
256
+ const fail = (cause) => reject(cause instanceof import_protocol2.VerificationError ? cause : httpsTransportError(cause));
257
+ outgoing.on("error", fail);
258
+ outgoing.on("upgrade", (_response, socket) => {
259
+ socket.destroy();
260
+ fail(new import_protocol2.VerificationError("protocol upgrades are unsupported", { code: import_protocol2.VerificationCode.SignatureInvalid }));
261
+ });
262
+ outgoing.on("connect", (_response, socket) => {
263
+ socket.destroy();
264
+ fail(new import_protocol2.VerificationError("tunnels are unsupported", { code: import_protocol2.VerificationCode.SignatureInvalid }));
265
+ });
266
+ outgoing.end(content ?? void 0);
267
+ });
268
+ };
269
+ }
190
270
  function createSsrfSafeFetch(config = {}, options = {}) {
191
271
  const privateAddressHosts = (options.privateAddressHosts ?? []).map(import_protocol2.normalizePrivateAddressHost);
192
272
  const connect2 = { lookup: createSsrfSafeLookup(config.dnsServer, privateAddressHosts) };
@@ -202,21 +282,21 @@ function createSsrfSafeFetch(config = {}, options = {}) {
202
282
  }
203
283
  if (options.decompress === false) {
204
284
  const { url: target, init: normalized } = normalizeFetchArgs(input, { ...init, redirect: "manual" });
205
- const request = new Request(target, normalized);
206
- const targetUrl = new URL(request.url);
285
+ const request2 = new Request(target, normalized);
286
+ const targetUrl = new URL(request2.url);
207
287
  const response = await dispatcher.request({
208
288
  origin: targetUrl.origin,
209
289
  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
290
+ method: request2.method,
291
+ headers: Object.fromEntries(request2.headers),
292
+ body: request2.body ? import_node_stream.Readable.fromWeb(request2.body) : void 0,
293
+ signal: request2.signal
214
294
  });
215
295
  const headers = new Headers();
216
296
  for (const [name, value] of Object.entries(response.headers)) {
217
297
  for (const item of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, item);
218
298
  }
219
- const noBody = request.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
299
+ const noBody = request2.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
220
300
  let body = null;
221
301
  if (noBody) await response.body.dump();
222
302
  else {
@@ -646,14 +726,7 @@ function fetchWithRedirects(url, opts, depth) {
646
726
  transient: true
647
727
  }));
648
728
  });
649
- req.on("error", (err) => {
650
- const nodeErr = err;
651
- const isTls = nodeErr.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || nodeErr.code === "CERT_HAS_EXPIRED" || nodeErr.code === "ERR_TLS_CERT_ALTNAME_INVALID" || nodeErr.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
652
- reject(new import_protocol2.VerificationError(`HTTPS fetch failed: ${err.message}`, {
653
- code: import_protocol2.VerificationCode.TLSError,
654
- transient: !isTls
655
- }));
656
- });
729
+ req.on("error", (err) => reject(httpsTransportError(err)));
657
730
  });
658
731
  }
659
732
  // Annotate the CommonJS export names for ESM import in node:
@@ -662,6 +735,7 @@ function fetchWithRedirects(url, opts, depth) {
662
735
  createDnsResolverFromServer,
663
736
  createDnsidFetch,
664
737
  createLookup,
738
+ createResponseSignatureTransport,
665
739
  createSsrfSafeFetch,
666
740
  fetchJson,
667
741
  formatDnsServer,
package/dist/index.d.cts CHANGED
@@ -70,6 +70,19 @@ interface SsrfSafeFetchOptions {
70
70
  * ```
71
71
  */
72
72
  declare function createDnsidFetch(config: TransportConfig): FetchLike;
73
+ /**
74
+ * Creates the single-send transport consumed by HTTP signatures' responseTransport option.
75
+ * Sends HTTPS once per call, without redirects, retries, caches, or content decoding, and
76
+ * reuses keep-alive connections across calls. Returns the certificate of the connection
77
+ * that carried this response. Request bodies are buffered and sent with Content-Length.
78
+ * The signed client bounds body reads to 8 MiB; standalone consumers must also bound reads
79
+ * and cancel rejected responses. DNS and additional CA settings follow createDnsidFetch;
80
+ * private addresses are not filtered and proxy environment variables are not used.
81
+ */
82
+ declare function createResponseSignatureTransport(config?: TransportConfig): (request: Request) => Promise<{
83
+ response: Response;
84
+ peerCert: TLSCertificate;
85
+ }>;
73
86
  /**
74
87
  * Creates a fetch function that rejects connections to private, loopback,
75
88
  * link-local, and other non-routable addresses (SSRF protection).
@@ -157,4 +170,4 @@ declare function createLookup(dnsServer: string): NonNullable<https.RequestOptio
157
170
  */
158
171
  declare function isUnsafeIp(address: string): boolean;
159
172
 
160
- export { type FetchLike, type FetchResult, type HTTPSFetchOptions, type SsrfSafeFetchOptions, createDefaultDnsResolver, createDnsResolverFromServer, createDnsidFetch, createLookup, createSsrfSafeFetch, fetchJson, formatDnsServer, isUnsafeIp, parseDnsServer };
173
+ export { type FetchLike, type FetchResult, type HTTPSFetchOptions, type SsrfSafeFetchOptions, createDefaultDnsResolver, createDnsResolverFromServer, createDnsidFetch, createLookup, createResponseSignatureTransport, createSsrfSafeFetch, fetchJson, formatDnsServer, isUnsafeIp, parseDnsServer };
package/dist/index.d.ts CHANGED
@@ -70,6 +70,19 @@ interface SsrfSafeFetchOptions {
70
70
  * ```
71
71
  */
72
72
  declare function createDnsidFetch(config: TransportConfig): FetchLike;
73
+ /**
74
+ * Creates the single-send transport consumed by HTTP signatures' responseTransport option.
75
+ * Sends HTTPS once per call, without redirects, retries, caches, or content decoding, and
76
+ * reuses keep-alive connections across calls. Returns the certificate of the connection
77
+ * that carried this response. Request bodies are buffered and sent with Content-Length.
78
+ * The signed client bounds body reads to 8 MiB; standalone consumers must also bound reads
79
+ * and cancel rejected responses. DNS and additional CA settings follow createDnsidFetch;
80
+ * private addresses are not filtered and proxy environment variables are not used.
81
+ */
82
+ declare function createResponseSignatureTransport(config?: TransportConfig): (request: Request) => Promise<{
83
+ response: Response;
84
+ peerCert: TLSCertificate;
85
+ }>;
73
86
  /**
74
87
  * Creates a fetch function that rejects connections to private, loopback,
75
88
  * link-local, and other non-routable addresses (SSRF protection).
@@ -157,4 +170,4 @@ declare function createLookup(dnsServer: string): NonNullable<https.RequestOptio
157
170
  */
158
171
  declare function isUnsafeIp(address: string): boolean;
159
172
 
160
- export { type FetchLike, type FetchResult, type HTTPSFetchOptions, type SsrfSafeFetchOptions, createDefaultDnsResolver, createDnsResolverFromServer, createDnsidFetch, createLookup, createSsrfSafeFetch, fetchJson, formatDnsServer, isUnsafeIp, parseDnsServer };
173
+ export { type FetchLike, type FetchResult, type HTTPSFetchOptions, type SsrfSafeFetchOptions, createDefaultDnsResolver, createDnsResolverFromServer, createDnsidFetch, createLookup, createResponseSignatureTransport, createSsrfSafeFetch, fetchJson, formatDnsServer, isUnsafeIp, parseDnsServer };
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ async function resolveTxtWithTtl(name, host, port, signal) {
55
55
  }
56
56
  async function query(name, host, port, signal) {
57
57
  const id = randomInt(65536);
58
- const request = {
58
+ const request2 = {
59
59
  type: "query",
60
60
  id,
61
61
  flags: packet.RECURSION_DESIRED,
@@ -71,9 +71,9 @@ async function query(name, host, port, signal) {
71
71
  return null;
72
72
  }
73
73
  };
74
- const response = await udp(packet.encode(request), host, port, signal, valid);
74
+ const response = await udp(packet.encode(request2), host, port, signal, valid);
75
75
  if (!response.flag_tc) return response;
76
- const tcpResponse = valid(await tcp(packet.streamEncode(request), host, port, signal));
76
+ const tcpResponse = valid(await tcp(packet.streamEncode(request2), host, port, signal));
77
77
  if (!tcpResponse || tcpResponse.flag_tc) throw badResponse();
78
78
  return tcpResponse;
79
79
  }
@@ -136,6 +136,15 @@ function tcp(buf, host, port, signal) {
136
136
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
137
137
  var DEFAULT_MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
138
138
  var DNSID_USER_AGENT = "dnsid-ts";
139
+ function httpsTransportError(cause) {
140
+ const err = cause;
141
+ const isTls = err.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || err.code === "CERT_HAS_EXPIRED" || err.code === "ERR_TLS_CERT_ALTNAME_INVALID" || err.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
142
+ return new VerificationError(`HTTPS fetch failed: ${err.message}`, {
143
+ code: VerificationCode.TLSError,
144
+ transient: !isTls,
145
+ cause
146
+ });
147
+ }
139
148
  function createDnsidFetch(config) {
140
149
  if (!config.dnsServer && !config.caBundlePath) {
141
150
  return globalThis.fetch.bind(globalThis);
@@ -145,6 +154,76 @@ function createDnsidFetch(config) {
145
154
  if (config.caBundlePath) connect2.ca = [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")];
146
155
  return createFetchWithDispatcher(new Agent2({ connect: connect2 }));
147
156
  }
157
+ function createResponseSignatureTransport(config = {}) {
158
+ const lookup2 = config.dnsServer ? createLookup(config.dnsServer) : void 0;
159
+ const ca = config.caBundlePath ? [...tls.rootCertificates, fs.readFileSync(config.caBundlePath, "utf8")] : void 0;
160
+ const agent = new https.Agent({ keepAlive: true, lookup: lookup2, ca, rejectUnauthorized: true });
161
+ return async (request2) => {
162
+ const url = new URL(request2.url);
163
+ if (url.protocol !== "https:") throw new ArgumentError("response authentication requires HTTPS");
164
+ const signal = AbortSignal.any([request2.signal, AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS * 3)]);
165
+ const content = request2.body ? Buffer.from(await request2.arrayBuffer()) : null;
166
+ const headers = Object.fromEntries(request2.headers);
167
+ if (content) headers["content-length"] = String(content.length);
168
+ return new Promise((resolve, reject) => {
169
+ const outgoing = https.request(url, {
170
+ agent,
171
+ signal,
172
+ method: request2.method,
173
+ headers
174
+ }, (incoming) => {
175
+ const cert = incoming.socket.getPeerCertificate();
176
+ const peerCert = {
177
+ notAfter: new Date(cert.valid_to),
178
+ san: (cert.subjectaltname ?? "").split(", ").filter((s) => s.startsWith("DNS:")).map((s) => s.slice(4))
179
+ };
180
+ const headers2 = new Headers();
181
+ for (let i = 0; i < incoming.rawHeaders.length; i += 2) headers2.append(incoming.rawHeaders[i], incoming.rawHeaders[i + 1]);
182
+ const status = incoming.statusCode;
183
+ if (status < 200 || status > 599) {
184
+ incoming.destroy();
185
+ reject(new VerificationError("not a final HTTP response", { code: VerificationCode.SignatureInvalid }));
186
+ return;
187
+ }
188
+ const noBody = request2.method === "HEAD" || [204, 205, 304].includes(status);
189
+ const iterator = incoming[Symbol.asyncIterator]();
190
+ let canceled = false;
191
+ incoming.on("error", () => {
192
+ });
193
+ const body = noBody ? null : new ReadableStream({
194
+ async pull(controller) {
195
+ try {
196
+ const { value, done } = await iterator.next();
197
+ if (canceled) return;
198
+ if (done) controller.close();
199
+ else controller.enqueue(new Uint8Array(value));
200
+ } catch (error) {
201
+ if (!canceled) controller.error(error);
202
+ }
203
+ },
204
+ cancel() {
205
+ canceled = true;
206
+ incoming.destroy();
207
+ outgoing.destroy();
208
+ }
209
+ });
210
+ if (noBody) incoming.resume();
211
+ resolve({ response: new Response(body, { status, statusText: incoming.statusMessage, headers: headers2 }), peerCert });
212
+ });
213
+ const fail = (cause) => reject(cause instanceof VerificationError ? cause : httpsTransportError(cause));
214
+ outgoing.on("error", fail);
215
+ outgoing.on("upgrade", (_response, socket) => {
216
+ socket.destroy();
217
+ fail(new VerificationError("protocol upgrades are unsupported", { code: VerificationCode.SignatureInvalid }));
218
+ });
219
+ outgoing.on("connect", (_response, socket) => {
220
+ socket.destroy();
221
+ fail(new VerificationError("tunnels are unsupported", { code: VerificationCode.SignatureInvalid }));
222
+ });
223
+ outgoing.end(content ?? void 0);
224
+ });
225
+ };
226
+ }
148
227
  function createSsrfSafeFetch(config = {}, options = {}) {
149
228
  const privateAddressHosts = (options.privateAddressHosts ?? []).map(normalizePrivateAddressHost);
150
229
  const connect2 = { lookup: createSsrfSafeLookup(config.dnsServer, privateAddressHosts) };
@@ -160,21 +239,21 @@ function createSsrfSafeFetch(config = {}, options = {}) {
160
239
  }
161
240
  if (options.decompress === false) {
162
241
  const { url: target, init: normalized } = normalizeFetchArgs(input, { ...init, redirect: "manual" });
163
- const request = new Request(target, normalized);
164
- const targetUrl = new URL(request.url);
242
+ const request2 = new Request(target, normalized);
243
+ const targetUrl = new URL(request2.url);
165
244
  const response = await dispatcher.request({
166
245
  origin: targetUrl.origin,
167
246
  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
247
+ method: request2.method,
248
+ headers: Object.fromEntries(request2.headers),
249
+ body: request2.body ? Readable.fromWeb(request2.body) : void 0,
250
+ signal: request2.signal
172
251
  });
173
252
  const headers = new Headers();
174
253
  for (const [name, value] of Object.entries(response.headers)) {
175
254
  for (const item of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, item);
176
255
  }
177
- const noBody = request.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
256
+ const noBody = request2.method === "HEAD" || [204, 205, 304].includes(response.statusCode);
178
257
  let body = null;
179
258
  if (noBody) await response.body.dump();
180
259
  else {
@@ -604,14 +683,7 @@ function fetchWithRedirects(url, opts, depth) {
604
683
  transient: true
605
684
  }));
606
685
  });
607
- req.on("error", (err) => {
608
- const nodeErr = err;
609
- const isTls = nodeErr.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || nodeErr.code === "CERT_HAS_EXPIRED" || nodeErr.code === "ERR_TLS_CERT_ALTNAME_INVALID" || nodeErr.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
610
- reject(new VerificationError(`HTTPS fetch failed: ${err.message}`, {
611
- code: VerificationCode.TLSError,
612
- transient: !isTls
613
- }));
614
- });
686
+ req.on("error", (err) => reject(httpsTransportError(err)));
615
687
  });
616
688
  }
617
689
  export {
@@ -619,6 +691,7 @@ export {
619
691
  createDnsResolverFromServer,
620
692
  createDnsidFetch,
621
693
  createLookup,
694
+ createResponseSignatureTransport,
622
695
  createSsrfSafeFetch,
623
696
  fetchJson,
624
697
  formatDnsServer,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dnsid-ai/transport",
3
3
  "author": "Known Systems AI, Inc.",
4
- "version": "0.32.0",
4
+ "version": "0.33.0",
5
5
  "description": "Node.js DNS and HTTPS transport implementations for DNSid.",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.ts",
@@ -41,7 +41,7 @@
41
41
  "typecheck": "tsc --noEmit"
42
42
  },
43
43
  "dependencies": {
44
- "@dnsid-ai/protocol": "^0.32.0",
44
+ "@dnsid-ai/protocol": "^0.33.0",
45
45
  "dns-packet": "5.6.1",
46
46
  "undici": "^8.12.3"
47
47
  },