@bluefields/primitives 0.2.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/LICENSE +202 -0
- package/dist/canonicalize.d.ts +37 -0
- package/dist/canonicalize.js +196 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/env.d.ts +7 -0
- package/dist/env.js +15 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +63 -0
- package/dist/logger.js +118 -0
- package/dist/logger.js.map +1 -0
- package/dist/robots-txt.d.ts +65 -0
- package/dist/robots-txt.js +250 -0
- package/dist/robots-txt.js.map +1 -0
- package/dist/ssrf.d.ts +77 -0
- package/dist/ssrf.js +432 -0
- package/dist/ssrf.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +51 -0
package/dist/ssrf.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* SSRF-safe URL validation and HTTP client (Reviews #6, #7, #10).
|
|
4
|
+
*
|
|
5
|
+
* Every URL that crosses a trust boundary into our platform — customer
|
|
6
|
+
* webhook URLs, customer watch URLs, MCP scrape_url arguments — must pass
|
|
7
|
+
* through `validateUrlForExternalRequest` before we make any outbound
|
|
8
|
+
* request. The validator is the first line of defense; `createSsrfSafeHttpClient`
|
|
9
|
+
* is the second, catching DNS rebinding by re-validating at connect time.
|
|
10
|
+
*
|
|
11
|
+
* This module is reused widely. Two rules to keep in mind when extending it:
|
|
12
|
+
* 1. Default-deny. Add new categories to the deny side; don't allowlist exceptions.
|
|
13
|
+
* 2. Validate, don't sanitize. We reject bad URLs; we never massage them into
|
|
14
|
+
* "fixed" URLs and proceed.
|
|
15
|
+
*/
|
|
16
|
+
import { lookup as dnsLookup } from 'node:dns/promises';
|
|
17
|
+
import { isIPv4, isIPv6 } from 'node:net';
|
|
18
|
+
import { Agent, ProxyAgent, fetch as undiciFetch } from 'undici';
|
|
19
|
+
export class SsrfValidationError extends Error {
|
|
20
|
+
reason;
|
|
21
|
+
constructor(message, reason) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'SsrfValidationError';
|
|
24
|
+
this.reason = reason;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Hostnames that are known metadata services — reject before DNS even runs.
|
|
28
|
+
const METADATA_HOSTNAMES = new Set([
|
|
29
|
+
'metadata.google.internal',
|
|
30
|
+
'metadata.goog',
|
|
31
|
+
'metadata',
|
|
32
|
+
]);
|
|
33
|
+
// Internal-only domains the platform owns — reject before DNS.
|
|
34
|
+
// Matches both the bare domain and any subdomain.
|
|
35
|
+
const INTERNAL_DOMAIN_SUFFIXES = ['bluefields.dev', 'internal.bluefields.dev'];
|
|
36
|
+
const HTTPS_PROTOCOL = 'https:';
|
|
37
|
+
/**
|
|
38
|
+
* Validate that `url` is safe to use for an outbound HTTPS request.
|
|
39
|
+
*
|
|
40
|
+
* Throws `SsrfValidationError` on any rejection. Callers should treat the
|
|
41
|
+
* error as a 4xx validation failure (not a 5xx server error).
|
|
42
|
+
*/
|
|
43
|
+
export async function validateUrlForExternalRequest(url) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = new URL(url);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new SsrfValidationError(`URL did not parse: ${url}`, 'invalid_url');
|
|
50
|
+
}
|
|
51
|
+
if (parsed.protocol !== HTTPS_PROTOCOL) {
|
|
52
|
+
throw new SsrfValidationError(`unsupported protocol: ${parsed.protocol} (only https is allowed)`, 'unsupported_protocol');
|
|
53
|
+
}
|
|
54
|
+
// WHATWG URL returns IPv6 hostnames in bracketed form (`[::1]`); strip
|
|
55
|
+
// brackets so `isIPv6` and the IP-range predicates see the bare literal.
|
|
56
|
+
// Also lowercase defensively even though the parser already normalizes it.
|
|
57
|
+
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
58
|
+
if (METADATA_HOSTNAMES.has(hostname)) {
|
|
59
|
+
throw new SsrfValidationError(`metadata endpoint blocked: ${hostname}`, 'metadata_endpoint');
|
|
60
|
+
}
|
|
61
|
+
for (const suffix of INTERNAL_DOMAIN_SUFFIXES) {
|
|
62
|
+
if (hostname === suffix || hostname.endsWith(`.${suffix}`)) {
|
|
63
|
+
throw new SsrfValidationError(`internal domain blocked: ${hostname}`, 'internal_domain');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// If the URL is an IP literal (brackets already stripped above), validate
|
|
67
|
+
// it directly without DNS.
|
|
68
|
+
if (isIPv4(hostname) || isIPv6(hostname)) {
|
|
69
|
+
if (isPrivateOrReservedIp(hostname)) {
|
|
70
|
+
throw new SsrfValidationError(`private/reserved IP blocked: ${hostname}`, 'private_ip');
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Resolve via DNS. `all: true` surfaces every record so we can catch the
|
|
75
|
+
// multi-A-record attack where one address is public and another is private.
|
|
76
|
+
// The `as const` is required for TS to pick the LookupAllOptions overload.
|
|
77
|
+
let addresses;
|
|
78
|
+
try {
|
|
79
|
+
addresses = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
throw new SsrfValidationError(`DNS lookup failed for ${hostname}: ${err.message}`, 'dns_failure');
|
|
83
|
+
}
|
|
84
|
+
for (const addr of addresses) {
|
|
85
|
+
if (isPrivateOrReservedIp(addr.address)) {
|
|
86
|
+
throw new SsrfValidationError(`${hostname} resolves to private/reserved IP ${addr.address}`, 'private_ip');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns true if `ip` is in any private, reserved, loopback, link-local,
|
|
92
|
+
* multicast, broadcast, documentation, or AWS-metadata range — anything we
|
|
93
|
+
* don't want our platform's egress to reach. Exported so tests can verify
|
|
94
|
+
* each category and other callers (e.g. attestation manifest integrity
|
|
95
|
+
* checks on `proxy_egress_ip`) can reuse the same predicate.
|
|
96
|
+
*/
|
|
97
|
+
export function isPrivateOrReservedIp(ip) {
|
|
98
|
+
if (isIPv4(ip))
|
|
99
|
+
return isIPv4PrivateOrReserved(ip);
|
|
100
|
+
if (isIPv6(ip))
|
|
101
|
+
return isIPv6PrivateOrReserved(ip);
|
|
102
|
+
// Unrecognized format — fail closed.
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
function ipv4ToUint32(ip) {
|
|
106
|
+
const parts = ip.split('.');
|
|
107
|
+
if (parts.length !== 4)
|
|
108
|
+
return Number.NaN;
|
|
109
|
+
let n = 0;
|
|
110
|
+
for (const p of parts) {
|
|
111
|
+
if (!/^\d{1,3}$/.test(p))
|
|
112
|
+
return Number.NaN;
|
|
113
|
+
const v = Number(p);
|
|
114
|
+
if (v < 0 || v > 255)
|
|
115
|
+
return Number.NaN;
|
|
116
|
+
n = (n << 8) | v;
|
|
117
|
+
}
|
|
118
|
+
return n >>> 0;
|
|
119
|
+
}
|
|
120
|
+
function isIPv4PrivateOrReserved(ip) {
|
|
121
|
+
const n = ipv4ToUint32(ip);
|
|
122
|
+
if (!Number.isFinite(n))
|
|
123
|
+
return true;
|
|
124
|
+
// 0.0.0.0/8 — "this network" / unspecified
|
|
125
|
+
if (n <= 0x00ffffff)
|
|
126
|
+
return true;
|
|
127
|
+
// 10.0.0.0/8
|
|
128
|
+
if (n >= 0x0a000000 && n <= 0x0affffff)
|
|
129
|
+
return true;
|
|
130
|
+
// 100.64.0.0/10 — Carrier-grade NAT (RFC 6598)
|
|
131
|
+
if (n >= 0x64400000 && n <= 0x647fffff)
|
|
132
|
+
return true;
|
|
133
|
+
// 127.0.0.0/8 — loopback
|
|
134
|
+
if (n >= 0x7f000000 && n <= 0x7fffffff)
|
|
135
|
+
return true;
|
|
136
|
+
// 169.254.0.0/16 — link-local (includes 169.254.169.254 metadata)
|
|
137
|
+
if (n >= 0xa9fe0000 && n <= 0xa9feffff)
|
|
138
|
+
return true;
|
|
139
|
+
// 172.16.0.0/12
|
|
140
|
+
if (n >= 0xac100000 && n <= 0xac1fffff)
|
|
141
|
+
return true;
|
|
142
|
+
// 192.0.0.0/24 — IETF Protocol Assignments
|
|
143
|
+
if (n >= 0xc0000000 && n <= 0xc00000ff)
|
|
144
|
+
return true;
|
|
145
|
+
// 192.0.2.0/24 — TEST-NET-1
|
|
146
|
+
if (n >= 0xc0000200 && n <= 0xc00002ff)
|
|
147
|
+
return true;
|
|
148
|
+
// 192.168.0.0/16
|
|
149
|
+
if (n >= 0xc0a80000 && n <= 0xc0a8ffff)
|
|
150
|
+
return true;
|
|
151
|
+
// 198.18.0.0/15 — benchmarking
|
|
152
|
+
if (n >= 0xc6120000 && n <= 0xc613ffff)
|
|
153
|
+
return true;
|
|
154
|
+
// 198.51.100.0/24 — TEST-NET-2
|
|
155
|
+
if (n >= 0xc6336400 && n <= 0xc63364ff)
|
|
156
|
+
return true;
|
|
157
|
+
// 203.0.113.0/24 — TEST-NET-3
|
|
158
|
+
if (n >= 0xcb007100 && n <= 0xcb0071ff)
|
|
159
|
+
return true;
|
|
160
|
+
// 224.0.0.0/4 — multicast
|
|
161
|
+
if (n >= 0xe0000000 && n <= 0xefffffff)
|
|
162
|
+
return true;
|
|
163
|
+
// 240.0.0.0/4 — reserved (includes 255.255.255.255 broadcast)
|
|
164
|
+
if (n >= 0xf0000000)
|
|
165
|
+
return true;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Expand any valid IPv6 textual representation (including shorthand and
|
|
170
|
+
* IPv4-mapped forms) into a 16-byte buffer. Returns null on malformed input.
|
|
171
|
+
*
|
|
172
|
+
* Implemented inline rather than via a library because the policy "default
|
|
173
|
+
* deny on parse failure" is integral to the SSRF defense — a bug in a
|
|
174
|
+
* permissive parser would silently allow attacker-controlled inputs.
|
|
175
|
+
*/
|
|
176
|
+
function expandIPv6(ip) {
|
|
177
|
+
let cleaned = ip.toLowerCase();
|
|
178
|
+
// Strip surrounding brackets if present.
|
|
179
|
+
if (cleaned.startsWith('[') && cleaned.endsWith(']')) {
|
|
180
|
+
cleaned = cleaned.slice(1, -1);
|
|
181
|
+
}
|
|
182
|
+
// Strip zone identifier (% suffix) — link-local sometimes carries it.
|
|
183
|
+
const zoneIdx = cleaned.indexOf('%');
|
|
184
|
+
if (zoneIdx !== -1)
|
|
185
|
+
cleaned = cleaned.slice(0, zoneIdx);
|
|
186
|
+
// Detect IPv4 tail (e.g. ::ffff:192.0.2.1 or ::192.0.2.1).
|
|
187
|
+
const lastColon = cleaned.lastIndexOf(':');
|
|
188
|
+
const tail = lastColon === -1 ? '' : cleaned.slice(lastColon + 1);
|
|
189
|
+
let head = cleaned;
|
|
190
|
+
let v4Bytes = null;
|
|
191
|
+
if (tail.includes('.')) {
|
|
192
|
+
if (!isIPv4(tail))
|
|
193
|
+
return null;
|
|
194
|
+
const v4 = ipv4ToUint32(tail);
|
|
195
|
+
if (!Number.isFinite(v4))
|
|
196
|
+
return null;
|
|
197
|
+
v4Bytes = [(v4 >>> 24) & 0xff, (v4 >>> 16) & 0xff, (v4 >>> 8) & 0xff, v4 & 0xff];
|
|
198
|
+
head = cleaned.slice(0, lastColon);
|
|
199
|
+
}
|
|
200
|
+
// Split on the at-most-one "::" run.
|
|
201
|
+
const parts = head.split('::');
|
|
202
|
+
if (parts.length > 2)
|
|
203
|
+
return null;
|
|
204
|
+
const left = parts[0] ? parts[0].split(':') : [];
|
|
205
|
+
const right = parts.length === 2 && parts[1] ? parts[1].split(':') : [];
|
|
206
|
+
const wantWords = v4Bytes ? 6 : 8;
|
|
207
|
+
let words;
|
|
208
|
+
if (parts.length === 1) {
|
|
209
|
+
if (left.length !== wantWords)
|
|
210
|
+
return null;
|
|
211
|
+
words = left;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const missing = wantWords - left.length - right.length;
|
|
215
|
+
if (missing < 0)
|
|
216
|
+
return null;
|
|
217
|
+
words = [...left, ...new Array(missing).fill('0'), ...right];
|
|
218
|
+
}
|
|
219
|
+
const out = new Uint8Array(16);
|
|
220
|
+
for (let i = 0; i < words.length; i++) {
|
|
221
|
+
const w = words[i];
|
|
222
|
+
if (!/^[0-9a-f]{1,4}$/.test(w))
|
|
223
|
+
return null;
|
|
224
|
+
const v = Number.parseInt(w, 16);
|
|
225
|
+
if (!Number.isInteger(v) || v < 0 || v > 0xffff)
|
|
226
|
+
return null;
|
|
227
|
+
out[i * 2] = (v >>> 8) & 0xff;
|
|
228
|
+
out[i * 2 + 1] = v & 0xff;
|
|
229
|
+
}
|
|
230
|
+
if (v4Bytes) {
|
|
231
|
+
out[12] = v4Bytes[0];
|
|
232
|
+
out[13] = v4Bytes[1];
|
|
233
|
+
out[14] = v4Bytes[2];
|
|
234
|
+
out[15] = v4Bytes[3];
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
function isIPv6PrivateOrReserved(ip) {
|
|
239
|
+
const b = expandIPv6(ip);
|
|
240
|
+
if (!b)
|
|
241
|
+
return true;
|
|
242
|
+
// :: — unspecified
|
|
243
|
+
let allZero = true;
|
|
244
|
+
for (let i = 0; i < 16; i++) {
|
|
245
|
+
if (b[i] !== 0) {
|
|
246
|
+
allZero = false;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (allZero)
|
|
251
|
+
return true;
|
|
252
|
+
// ::1 — loopback
|
|
253
|
+
let loopback = true;
|
|
254
|
+
for (let i = 0; i < 15; i++) {
|
|
255
|
+
if (b[i] !== 0) {
|
|
256
|
+
loopback = false;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (loopback && b[15] === 1)
|
|
261
|
+
return true;
|
|
262
|
+
// ::ffff:0:0/96 — IPv4-mapped. Bytes 0-9 = 0, bytes 10-11 = 0xff. Tail
|
|
263
|
+
// is an IPv4 address; defer to the IPv4 predicate so we catch e.g.
|
|
264
|
+
// ::ffff:127.0.0.1 as loopback.
|
|
265
|
+
let mappedPrefix = true;
|
|
266
|
+
for (let i = 0; i < 10; i++) {
|
|
267
|
+
if (b[i] !== 0) {
|
|
268
|
+
mappedPrefix = false;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (mappedPrefix && b[10] === 0xff && b[11] === 0xff) {
|
|
273
|
+
const v4 = `${b[12]}.${b[13]}.${b[14]}.${b[15]}`;
|
|
274
|
+
return isIPv4PrivateOrReserved(v4);
|
|
275
|
+
}
|
|
276
|
+
// 64:ff9b::/96 — IPv4/IPv6 translation (RFC 6052)
|
|
277
|
+
if (b[0] === 0x00 && b[1] === 0x64 && b[2] === 0xff && b[3] === 0x9b) {
|
|
278
|
+
let zerosOk = true;
|
|
279
|
+
for (let i = 4; i < 12; i++) {
|
|
280
|
+
if (b[i] !== 0) {
|
|
281
|
+
zerosOk = false;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (zerosOk) {
|
|
286
|
+
const v4 = `${b[12]}.${b[13]}.${b[14]}.${b[15]}`;
|
|
287
|
+
return isIPv4PrivateOrReserved(v4);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// 100::/64 — discard-only address block (RFC 6666)
|
|
291
|
+
if (b[0] === 0x01 && b[1] === 0x00 && b[2] === 0 && b[3] === 0) {
|
|
292
|
+
let zerosOk = true;
|
|
293
|
+
for (let i = 4; i < 8; i++) {
|
|
294
|
+
if (b[i] !== 0) {
|
|
295
|
+
zerosOk = false;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (zerosOk)
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
// 2001:db8::/32 — documentation
|
|
303
|
+
if (b[0] === 0x20 && b[1] === 0x01 && b[2] === 0x0d && b[3] === 0xb8)
|
|
304
|
+
return true;
|
|
305
|
+
// fc00::/7 — Unique Local Addresses (covers fd00:ec2::/64 AWS IPv6 metadata)
|
|
306
|
+
if ((b[0] & 0xfe) === 0xfc)
|
|
307
|
+
return true;
|
|
308
|
+
// fe80::/10 — link-local
|
|
309
|
+
if (b[0] === 0xfe && (b[1] & 0xc0) === 0x80)
|
|
310
|
+
return true;
|
|
311
|
+
// ff00::/8 — multicast
|
|
312
|
+
if (b[0] === 0xff)
|
|
313
|
+
return true;
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Undici lookup callback that re-validates each candidate address at
|
|
318
|
+
* connect time. Exported for testability; created via
|
|
319
|
+
* `createSsrfSafeHttpClient` for production use.
|
|
320
|
+
*
|
|
321
|
+
* The signature matches Node's `dns.lookup` callback so undici can call it
|
|
322
|
+
* as its `connect.lookup`. We always do an `all: true` lookup internally so
|
|
323
|
+
* a multi-record DNS response can't slip a private IP past us by chance of
|
|
324
|
+
* record ordering — we only return a public one.
|
|
325
|
+
*/
|
|
326
|
+
export function ssrfSafeLookup(hostname, options, callback) {
|
|
327
|
+
// undici (and node:net.connect) may pass family as a number (0/4/6) or
|
|
328
|
+
// as the strings 'IPv4'/'IPv6'. Normalize to a number for dns.lookup.
|
|
329
|
+
let family = 0;
|
|
330
|
+
if (typeof options.family === 'number')
|
|
331
|
+
family = options.family;
|
|
332
|
+
else if (options.family === 'IPv4')
|
|
333
|
+
family = 4;
|
|
334
|
+
else if (options.family === 'IPv6')
|
|
335
|
+
family = 6;
|
|
336
|
+
dnsLookup(hostname, { all: true, family, verbatim: true }).then((addresses) => {
|
|
337
|
+
// Defend against malformed dns.lookup results that contain entries
|
|
338
|
+
// with no address (observed in some Node + Alpine combinations on
|
|
339
|
+
// hosts that have both IPv4 + IPv6 A records but one record family
|
|
340
|
+
// returns partial data). Drop those before checking private-IP-ness
|
|
341
|
+
// so we don't hand undici an `undefined` host.
|
|
342
|
+
const publicOnes = addresses.filter((a) => typeof a.address === 'string' &&
|
|
343
|
+
a.address.length > 0 &&
|
|
344
|
+
!isPrivateOrReservedIp(a.address));
|
|
345
|
+
if (publicOnes.length === 0) {
|
|
346
|
+
callback(new SsrfValidationError(`${hostname} has no public IP addresses at connect time`, 'private_ip'), '', 0);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// undici 6.x calls this lookup with options.all === true,
|
|
350
|
+
// expecting callback(err, Array<{address, family}>). Node's net
|
|
351
|
+
// module uses the legacy options.all === false form,
|
|
352
|
+
// callback(err, address, family). Honor both.
|
|
353
|
+
const wantAll = options.all === true;
|
|
354
|
+
if (wantAll) {
|
|
355
|
+
// biome-ignore lint/suspicious/noExplicitAny: array-form callback overload
|
|
356
|
+
callback(null, publicOnes);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const first = publicOnes[0];
|
|
360
|
+
callback(null, first.address, first.family);
|
|
361
|
+
}
|
|
362
|
+
}, (err) => callback(err, '', 0));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Diagnostic helper — fetch a URL through an arbitrary HTTP proxy.
|
|
366
|
+
* Used by the /debug/diagnostics/proxy-test endpoint to verify
|
|
367
|
+
* provider reachability from inside the api container without going
|
|
368
|
+
* through Patchright/Chromium. Not for production traffic; the
|
|
369
|
+
* standard fetcher path uses ssrfSafeFetch.
|
|
370
|
+
*
|
|
371
|
+
* The target `url` is SSRF-validated up front (it can't go through the
|
|
372
|
+
* connect-time `ssrfSafeLookup`, since the dispatcher connects to the proxy,
|
|
373
|
+
* not the target). The proxy itself is operator-supplied via the admin-gated
|
|
374
|
+
* diagnostic, so it isn't validated here.
|
|
375
|
+
*/
|
|
376
|
+
export async function proxyFetch(url, proxy, timeoutMs = 15_000) {
|
|
377
|
+
// Defense-in-depth: reject a private/metadata/internal target before we
|
|
378
|
+
// route it through the proxy (the connect-time lookup can't see it here).
|
|
379
|
+
await validateUrlForExternalRequest(url);
|
|
380
|
+
// Embed user:pass directly in the proxy URL — undici's ProxyAgent
|
|
381
|
+
// expects this style; the `token` option is for bearer-style tokens,
|
|
382
|
+
// not Basic auth.
|
|
383
|
+
const u = new URL(proxy.server);
|
|
384
|
+
u.username = encodeURIComponent(proxy.username);
|
|
385
|
+
u.password = encodeURIComponent(proxy.password);
|
|
386
|
+
const agent = new ProxyAgent({
|
|
387
|
+
uri: u.toString(),
|
|
388
|
+
connectTimeout: timeoutMs,
|
|
389
|
+
bodyTimeout: timeoutMs,
|
|
390
|
+
headersTimeout: timeoutMs,
|
|
391
|
+
});
|
|
392
|
+
// biome-ignore lint/suspicious/noExplicitAny: undici dispatcher extension
|
|
393
|
+
return undiciFetch(url, { dispatcher: agent });
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* SSRF-safe fetch helper — uses undici's fetch (not Node's global fetch)
|
|
397
|
+
* because Node's global fetch silently ignores the `dispatcher` field
|
|
398
|
+
* on RequestInit. Re-uses one dispatcher per process for efficiency.
|
|
399
|
+
*
|
|
400
|
+
* Callers that just want "fetch this URL, refuse if it resolves to a
|
|
401
|
+
* private IP" should use this instead of constructing the dispatcher
|
|
402
|
+
* themselves.
|
|
403
|
+
*/
|
|
404
|
+
let sharedSsrfDispatcher = null;
|
|
405
|
+
export async function ssrfSafeFetch(url, init) {
|
|
406
|
+
if (!sharedSsrfDispatcher)
|
|
407
|
+
sharedSsrfDispatcher = createSsrfSafeHttpClient();
|
|
408
|
+
// biome-ignore lint/suspicious/noExplicitAny: undici extension
|
|
409
|
+
return undiciFetch(url, {
|
|
410
|
+
...init,
|
|
411
|
+
dispatcher: sharedSsrfDispatcher,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Build an undici Agent that pins each connection to a public IP and
|
|
416
|
+
* times out at 10s. Use this dispatcher wherever the platform makes an
|
|
417
|
+
* outbound request on behalf of a customer (webhook delivery, fetcher
|
|
418
|
+
* Tier 0, MCP scrape_url backend).
|
|
419
|
+
*
|
|
420
|
+
* Redirects: undici re-runs the dispatcher (and thus our lookup) for each
|
|
421
|
+
* new host encountered, so a redirect to a private IP is rejected at
|
|
422
|
+
* connect time the same way the original URL would be. Callers should
|
|
423
|
+
* still pass `maxRedirections` explicitly per-request — the default is 0.
|
|
424
|
+
*/
|
|
425
|
+
export function createSsrfSafeHttpClient() {
|
|
426
|
+
return new Agent({
|
|
427
|
+
headersTimeout: 10_000,
|
|
428
|
+
bodyTimeout: 10_000,
|
|
429
|
+
connect: { lookup: ssrfSafeLookup },
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=ssrf.js.map
|
package/dist/ssrf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.js","sourceRoot":"","sources":["../src/ssrf.ts"],"names":[],"mappings":"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"}
|