@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 +48 -2
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +48 -2
- package/package.json +2 -2
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
|
|
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
|
|
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.
|
|
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.
|
|
41
|
+
"@dnsid-ai/protocol": "^0.28.0",
|
|
42
42
|
"dns-packet": "5.6.1",
|
|
43
43
|
"undici": "^8.11.0"
|
|
44
44
|
},
|