elit 3.6.8 → 3.6.9

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/pm.js CHANGED
@@ -5203,6 +5203,217 @@ error: ${text}`);
5203
5203
  var import_node_http = __require("http");
5204
5204
  var import_node_https = __require("https");
5205
5205
  var import_node_net = __require("net");
5206
+ var import_promises = __require("dns/promises");
5207
+ var BLOCKED_IPV4_PREFIXES = [
5208
+ "0.",
5209
+ "10.",
5210
+ "100.64.",
5211
+ "100.65.",
5212
+ "100.66.",
5213
+ "100.67.",
5214
+ "100.68.",
5215
+ "100.69.",
5216
+ "100.70.",
5217
+ "100.71.",
5218
+ "100.72.",
5219
+ "100.73.",
5220
+ "100.74.",
5221
+ "100.75.",
5222
+ "100.76.",
5223
+ "100.77.",
5224
+ "100.78.",
5225
+ "100.79.",
5226
+ "100.80.",
5227
+ "100.81.",
5228
+ "100.82.",
5229
+ "100.83.",
5230
+ "100.84.",
5231
+ "100.85.",
5232
+ "100.86.",
5233
+ "100.87.",
5234
+ "100.88.",
5235
+ "100.89.",
5236
+ "100.90.",
5237
+ "100.91.",
5238
+ "100.92.",
5239
+ "100.93.",
5240
+ "100.94.",
5241
+ "100.95.",
5242
+ "100.96.",
5243
+ "100.97.",
5244
+ "100.98.",
5245
+ "100.99.",
5246
+ "100.100.",
5247
+ "100.101.",
5248
+ "100.102.",
5249
+ "100.103.",
5250
+ "100.104.",
5251
+ "100.105.",
5252
+ "100.106.",
5253
+ "100.107.",
5254
+ "100.108.",
5255
+ "100.109.",
5256
+ "100.110.",
5257
+ "100.111.",
5258
+ "100.112.",
5259
+ "100.113.",
5260
+ "100.114.",
5261
+ "100.115.",
5262
+ "100.116.",
5263
+ "100.117.",
5264
+ "100.118.",
5265
+ "100.119.",
5266
+ "100.120.",
5267
+ "100.121.",
5268
+ "100.122.",
5269
+ "100.123.",
5270
+ "100.124.",
5271
+ "100.125.",
5272
+ "100.126.",
5273
+ "100.127.",
5274
+ "127.",
5275
+ "169.254.",
5276
+ "172.16.",
5277
+ "172.17.",
5278
+ "172.18.",
5279
+ "172.19.",
5280
+ "172.20.",
5281
+ "172.21.",
5282
+ "172.22.",
5283
+ "172.23.",
5284
+ "172.24.",
5285
+ "172.25.",
5286
+ "172.26.",
5287
+ "172.27.",
5288
+ "172.28.",
5289
+ "172.29.",
5290
+ "172.30.",
5291
+ "172.31.",
5292
+ "192.0.2.",
5293
+ "192.88.99.",
5294
+ "192.168.",
5295
+ "198.18.",
5296
+ "198.19.",
5297
+ "198.51.100.",
5298
+ "203.0.113.",
5299
+ "224.",
5300
+ "225.",
5301
+ "226.",
5302
+ "227.",
5303
+ "228.",
5304
+ "229.",
5305
+ "230.",
5306
+ "231.",
5307
+ "232.",
5308
+ "233.",
5309
+ "234.",
5310
+ "235.",
5311
+ "236.",
5312
+ "237.",
5313
+ "238.",
5314
+ "239.",
5315
+ "240.",
5316
+ "241.",
5317
+ "242.",
5318
+ "243.",
5319
+ "244.",
5320
+ "245.",
5321
+ "246.",
5322
+ "247.",
5323
+ "248.",
5324
+ "249.",
5325
+ "250.",
5326
+ "251.",
5327
+ "252.",
5328
+ "253.",
5329
+ "254.",
5330
+ "255."
5331
+ ];
5332
+ var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
5333
+ function isBlockedIpv4(hostname) {
5334
+ const octets = hostname.split(".");
5335
+ if (octets.length !== 4) return false;
5336
+ const joined = hostname;
5337
+ return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
5338
+ }
5339
+ function isBlockedIpv6(hostname) {
5340
+ const lower = hostname.toLowerCase();
5341
+ if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
5342
+ return true;
5343
+ }
5344
+ const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
5345
+ if (ffffMatch) {
5346
+ return isBlockedIpv4(ffffMatch[1]);
5347
+ }
5348
+ const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
5349
+ if (compatMatch) {
5350
+ return isBlockedIpv4(compatMatch[1]);
5351
+ }
5352
+ return false;
5353
+ }
5354
+ function isSafeHostname(hostname) {
5355
+ if (!hostname) return false;
5356
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
5357
+ if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
5358
+ if (hostname.includes(":")) return !isBlockedIpv6(hostname);
5359
+ return true;
5360
+ }
5361
+ async function safeResolveHostname(hostname) {
5362
+ try {
5363
+ const result = await (0, import_promises.lookup)(hostname);
5364
+ const ip = result.address;
5365
+ if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
5366
+ throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
5367
+ }
5368
+ return ip;
5369
+ } catch (error) {
5370
+ if (error instanceof Error && error.message.includes("blocked address")) {
5371
+ throw error;
5372
+ }
5373
+ throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
5374
+ }
5375
+ }
5376
+ async function validateProxyTargetUrl(target) {
5377
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
5378
+ throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
5379
+ }
5380
+ const hostname = target.hostname;
5381
+ if (!isSafeHostname(hostname)) {
5382
+ throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
5383
+ }
5384
+ if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
5385
+ await safeResolveHostname(hostname);
5386
+ }
5387
+ }
5388
+ function sanitizeProxyRequestPath(requestUrl) {
5389
+ if (!requestUrl || requestUrl === "/") return "/";
5390
+ try {
5391
+ const normalizedInput = requestUrl.replace(/\\/g, "/");
5392
+ const parsed = new URL(normalizedInput, "http://placeholder");
5393
+ if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
5394
+ return "/";
5395
+ }
5396
+ const pathname = parsed.pathname || "/";
5397
+ let decodedPathname = pathname;
5398
+ try {
5399
+ decodedPathname = decodeURIComponent(pathname);
5400
+ } catch {
5401
+ return "/";
5402
+ }
5403
+ const lowerPath = pathname.toLowerCase();
5404
+ if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
5405
+ return "/";
5406
+ }
5407
+ const segments = decodedPathname.split("/");
5408
+ if (segments.some((segment) => segment === "." || segment === "..")) {
5409
+ return "/";
5410
+ }
5411
+ const sanitized = pathname + parsed.search;
5412
+ return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
5413
+ } catch {
5414
+ return "/";
5415
+ }
5416
+ }
5206
5417
  function resolvePmProxyHost(proxy) {
5207
5418
  return proxy.host?.trim() || "0.0.0.0";
5208
5419
  }
@@ -5292,6 +5503,9 @@ error: ${text}`);
5292
5503
  nextTargetIndex = (nextTargetIndex + 1) % targets.length;
5293
5504
  return target;
5294
5505
  };
5506
+ const validateTarget = async (target) => {
5507
+ await validateProxyTargetUrl(target);
5508
+ };
5295
5509
  const server = (0, import_node_http.createServer)((req, res) => {
5296
5510
  const target = pickTarget();
5297
5511
  if (!target) {
@@ -5299,29 +5513,45 @@ error: ${text}`);
5299
5513
  res.end("PM proxy target is not ready.");
5300
5514
  return;
5301
5515
  }
5516
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
5302
5517
  const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5303
- const targetUrl = new URL(req.url || "/", target);
5304
5518
  const headers = buildPmProxyHeaders(req.headers, target.host);
5305
- const proxyReq = requestLib(targetUrl, {
5306
- method: req.method,
5307
- headers
5308
- }, (proxyRes) => {
5309
- const outgoingHeaders = {};
5310
- for (const [key, value] of Object.entries(proxyRes.headers)) {
5311
- if (value !== void 0) {
5312
- outgoingHeaders[key] = value;
5519
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
5520
+ res.statusCode = 400;
5521
+ res.end("PM proxy rejected unsafe target protocol.");
5522
+ return;
5523
+ }
5524
+ validateTarget(target).then(() => {
5525
+ const proxyReq = requestLib({
5526
+ protocol: target.protocol,
5527
+ hostname: target.hostname,
5528
+ port: target.port || void 0,
5529
+ path: sanitizedPath,
5530
+ method: req.method,
5531
+ headers
5532
+ }, (proxyRes) => {
5533
+ const outgoingHeaders = {};
5534
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
5535
+ if (value !== void 0) {
5536
+ outgoingHeaders[key] = value;
5537
+ }
5313
5538
  }
5314
- }
5315
- res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
5316
- proxyRes.pipe(res);
5317
- });
5318
- proxyReq.on("error", (error) => {
5539
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
5540
+ proxyRes.pipe(res);
5541
+ });
5542
+ proxyReq.on("error", (error) => {
5543
+ if (!res.headersSent) {
5544
+ res.statusCode = 502;
5545
+ }
5546
+ res.end(`PM proxy error: ${error.message}`);
5547
+ });
5548
+ req.pipe(proxyReq);
5549
+ }).catch((error) => {
5319
5550
  if (!res.headersSent) {
5320
- res.statusCode = 502;
5551
+ res.statusCode = 403;
5321
5552
  }
5322
- res.end(`PM proxy error: ${error.message}`);
5553
+ res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
5323
5554
  });
5324
- req.pipe(proxyReq);
5325
5555
  });
5326
5556
  server.on("upgrade", (req, socket, head) => {
5327
5557
  const target = pickTarget();
@@ -5333,38 +5563,56 @@ error: ${text}`);
5333
5563
  socket.destroy();
5334
5564
  return;
5335
5565
  }
5566
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
5336
5567
  const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5337
- const targetUrl = new URL(req.url || "/", target);
5338
- const proxyReq = requestLib(targetUrl, {
5339
- method: req.method,
5340
- headers: buildPmProxyHeaders(req.headers, target.host)
5341
- });
5342
- proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
5343
- writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
5344
- if (head.length > 0) {
5345
- proxySocket.write(head);
5346
- }
5347
- if (proxyHead.length > 0) {
5348
- socket.write(proxyHead);
5349
- }
5350
- socket.on("error", () => proxySocket.destroy());
5351
- proxySocket.on("error", () => socket.destroy());
5352
- proxySocket.pipe(socket);
5353
- socket.pipe(proxySocket);
5354
- });
5355
- proxyReq.on("response", (proxyRes) => {
5356
- writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
5357
- proxyRes.pipe(socket);
5358
- });
5359
- proxyReq.on("error", (error) => {
5360
- writeRawHttpResponse(socket, 502, "Bad Gateway", {
5568
+ const targetUrl = new URL(sanitizedPath, target);
5569
+ if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
5570
+ writeRawHttpResponse(socket, 400, "Bad Request", {
5571
+ connection: "close",
5572
+ "content-length": 0
5573
+ });
5574
+ socket.destroy();
5575
+ return;
5576
+ }
5577
+ validateTarget(target).then(() => {
5578
+ const proxyReq = requestLib(targetUrl, {
5579
+ method: req.method,
5580
+ headers: buildPmProxyHeaders(req.headers, target.host)
5581
+ });
5582
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
5583
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
5584
+ if (head.length > 0) {
5585
+ proxySocket.write(head);
5586
+ }
5587
+ if (proxyHead.length > 0) {
5588
+ socket.write(proxyHead);
5589
+ }
5590
+ socket.on("error", () => proxySocket.destroy());
5591
+ proxySocket.on("error", () => socket.destroy());
5592
+ proxySocket.pipe(socket);
5593
+ socket.pipe(proxySocket);
5594
+ });
5595
+ proxyReq.on("response", (proxyRes) => {
5596
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
5597
+ proxyRes.pipe(socket);
5598
+ });
5599
+ proxyReq.on("error", (error) => {
5600
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
5601
+ connection: "close",
5602
+ "content-type": "text/plain; charset=utf-8",
5603
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
5604
+ });
5605
+ socket.end(`PM proxy error: ${error.message}`);
5606
+ });
5607
+ proxyReq.end();
5608
+ }).catch((error) => {
5609
+ writeRawHttpResponse(socket, 403, "Forbidden", {
5361
5610
  connection: "close",
5362
5611
  "content-type": "text/plain; charset=utf-8",
5363
- "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
5612
+ "content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
5364
5613
  });
5365
- socket.end(`PM proxy error: ${error.message}`);
5614
+ socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
5366
5615
  });
5367
- proxyReq.end();
5368
5616
  });
5369
5617
  await new Promise((resolve7, reject) => {
5370
5618
  server.once("error", reject);
@@ -5372,10 +5620,25 @@ error: ${text}`);
5372
5620
  });
5373
5621
  return {
5374
5622
  setTarget(targetUrl) {
5375
- setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
5623
+ if (targetUrl) {
5624
+ const parsed = new URL(targetUrl);
5625
+ validateProxyTargetUrl(parsed).then(() => {
5626
+ setResolvedTargets([parsed]);
5627
+ }).catch((error) => {
5628
+ console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
5629
+ setResolvedTargets([]);
5630
+ });
5631
+ } else {
5632
+ setResolvedTargets([]);
5633
+ }
5376
5634
  },
5377
5635
  setTargets(targetUrls) {
5378
- setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
5636
+ Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
5637
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
5638
+ }).catch((error) => {
5639
+ console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
5640
+ setResolvedTargets([]);
5641
+ });
5379
5642
  },
5380
5643
  close() {
5381
5644
  return new Promise((resolve7, reject) => {