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.mjs CHANGED
@@ -2678,6 +2678,217 @@ function watch(paths, options) {
2678
2678
  import { createServer, request as httpRequest } from "http";
2679
2679
  import { request as httpsRequest } from "https";
2680
2680
  import { createServer as createNetServer } from "net";
2681
+ import { lookup as dnsLookup } from "dns/promises";
2682
+ var BLOCKED_IPV4_PREFIXES = [
2683
+ "0.",
2684
+ "10.",
2685
+ "100.64.",
2686
+ "100.65.",
2687
+ "100.66.",
2688
+ "100.67.",
2689
+ "100.68.",
2690
+ "100.69.",
2691
+ "100.70.",
2692
+ "100.71.",
2693
+ "100.72.",
2694
+ "100.73.",
2695
+ "100.74.",
2696
+ "100.75.",
2697
+ "100.76.",
2698
+ "100.77.",
2699
+ "100.78.",
2700
+ "100.79.",
2701
+ "100.80.",
2702
+ "100.81.",
2703
+ "100.82.",
2704
+ "100.83.",
2705
+ "100.84.",
2706
+ "100.85.",
2707
+ "100.86.",
2708
+ "100.87.",
2709
+ "100.88.",
2710
+ "100.89.",
2711
+ "100.90.",
2712
+ "100.91.",
2713
+ "100.92.",
2714
+ "100.93.",
2715
+ "100.94.",
2716
+ "100.95.",
2717
+ "100.96.",
2718
+ "100.97.",
2719
+ "100.98.",
2720
+ "100.99.",
2721
+ "100.100.",
2722
+ "100.101.",
2723
+ "100.102.",
2724
+ "100.103.",
2725
+ "100.104.",
2726
+ "100.105.",
2727
+ "100.106.",
2728
+ "100.107.",
2729
+ "100.108.",
2730
+ "100.109.",
2731
+ "100.110.",
2732
+ "100.111.",
2733
+ "100.112.",
2734
+ "100.113.",
2735
+ "100.114.",
2736
+ "100.115.",
2737
+ "100.116.",
2738
+ "100.117.",
2739
+ "100.118.",
2740
+ "100.119.",
2741
+ "100.120.",
2742
+ "100.121.",
2743
+ "100.122.",
2744
+ "100.123.",
2745
+ "100.124.",
2746
+ "100.125.",
2747
+ "100.126.",
2748
+ "100.127.",
2749
+ "127.",
2750
+ "169.254.",
2751
+ "172.16.",
2752
+ "172.17.",
2753
+ "172.18.",
2754
+ "172.19.",
2755
+ "172.20.",
2756
+ "172.21.",
2757
+ "172.22.",
2758
+ "172.23.",
2759
+ "172.24.",
2760
+ "172.25.",
2761
+ "172.26.",
2762
+ "172.27.",
2763
+ "172.28.",
2764
+ "172.29.",
2765
+ "172.30.",
2766
+ "172.31.",
2767
+ "192.0.2.",
2768
+ "192.88.99.",
2769
+ "192.168.",
2770
+ "198.18.",
2771
+ "198.19.",
2772
+ "198.51.100.",
2773
+ "203.0.113.",
2774
+ "224.",
2775
+ "225.",
2776
+ "226.",
2777
+ "227.",
2778
+ "228.",
2779
+ "229.",
2780
+ "230.",
2781
+ "231.",
2782
+ "232.",
2783
+ "233.",
2784
+ "234.",
2785
+ "235.",
2786
+ "236.",
2787
+ "237.",
2788
+ "238.",
2789
+ "239.",
2790
+ "240.",
2791
+ "241.",
2792
+ "242.",
2793
+ "243.",
2794
+ "244.",
2795
+ "245.",
2796
+ "246.",
2797
+ "247.",
2798
+ "248.",
2799
+ "249.",
2800
+ "250.",
2801
+ "251.",
2802
+ "252.",
2803
+ "253.",
2804
+ "254.",
2805
+ "255."
2806
+ ];
2807
+ var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
2808
+ function isBlockedIpv4(hostname) {
2809
+ const octets = hostname.split(".");
2810
+ if (octets.length !== 4) return false;
2811
+ const joined = hostname;
2812
+ return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
2813
+ }
2814
+ function isBlockedIpv6(hostname) {
2815
+ const lower = hostname.toLowerCase();
2816
+ if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
2817
+ return true;
2818
+ }
2819
+ const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
2820
+ if (ffffMatch) {
2821
+ return isBlockedIpv4(ffffMatch[1]);
2822
+ }
2823
+ const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
2824
+ if (compatMatch) {
2825
+ return isBlockedIpv4(compatMatch[1]);
2826
+ }
2827
+ return false;
2828
+ }
2829
+ function isSafeHostname(hostname) {
2830
+ if (!hostname) return false;
2831
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
2832
+ if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
2833
+ if (hostname.includes(":")) return !isBlockedIpv6(hostname);
2834
+ return true;
2835
+ }
2836
+ async function safeResolveHostname(hostname) {
2837
+ try {
2838
+ const result = await dnsLookup(hostname);
2839
+ const ip = result.address;
2840
+ if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
2841
+ throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
2842
+ }
2843
+ return ip;
2844
+ } catch (error) {
2845
+ if (error instanceof Error && error.message.includes("blocked address")) {
2846
+ throw error;
2847
+ }
2848
+ throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
2849
+ }
2850
+ }
2851
+ async function validateProxyTargetUrl(target) {
2852
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
2853
+ throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
2854
+ }
2855
+ const hostname = target.hostname;
2856
+ if (!isSafeHostname(hostname)) {
2857
+ throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
2858
+ }
2859
+ if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
2860
+ await safeResolveHostname(hostname);
2861
+ }
2862
+ }
2863
+ function sanitizeProxyRequestPath(requestUrl) {
2864
+ if (!requestUrl || requestUrl === "/") return "/";
2865
+ try {
2866
+ const normalizedInput = requestUrl.replace(/\\/g, "/");
2867
+ const parsed = new URL(normalizedInput, "http://placeholder");
2868
+ if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
2869
+ return "/";
2870
+ }
2871
+ const pathname = parsed.pathname || "/";
2872
+ let decodedPathname = pathname;
2873
+ try {
2874
+ decodedPathname = decodeURIComponent(pathname);
2875
+ } catch {
2876
+ return "/";
2877
+ }
2878
+ const lowerPath = pathname.toLowerCase();
2879
+ if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
2880
+ return "/";
2881
+ }
2882
+ const segments = decodedPathname.split("/");
2883
+ if (segments.some((segment) => segment === "." || segment === "..")) {
2884
+ return "/";
2885
+ }
2886
+ const sanitized = pathname + parsed.search;
2887
+ return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
2888
+ } catch {
2889
+ return "/";
2890
+ }
2891
+ }
2681
2892
  function resolvePmProxyHost(proxy) {
2682
2893
  return proxy.host?.trim() || "0.0.0.0";
2683
2894
  }
@@ -2767,6 +2978,9 @@ async function createPmProxyController(proxy) {
2767
2978
  nextTargetIndex = (nextTargetIndex + 1) % targets.length;
2768
2979
  return target;
2769
2980
  };
2981
+ const validateTarget = async (target) => {
2982
+ await validateProxyTargetUrl(target);
2983
+ };
2770
2984
  const server = createServer((req, res) => {
2771
2985
  const target = pickTarget();
2772
2986
  if (!target) {
@@ -2774,29 +2988,45 @@ async function createPmProxyController(proxy) {
2774
2988
  res.end("PM proxy target is not ready.");
2775
2989
  return;
2776
2990
  }
2991
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
2777
2992
  const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
2778
- const targetUrl = new URL(req.url || "/", target);
2779
2993
  const headers = buildPmProxyHeaders(req.headers, target.host);
2780
- const proxyReq = requestLib(targetUrl, {
2781
- method: req.method,
2782
- headers
2783
- }, (proxyRes) => {
2784
- const outgoingHeaders = {};
2785
- for (const [key, value] of Object.entries(proxyRes.headers)) {
2786
- if (value !== void 0) {
2787
- outgoingHeaders[key] = value;
2994
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
2995
+ res.statusCode = 400;
2996
+ res.end("PM proxy rejected unsafe target protocol.");
2997
+ return;
2998
+ }
2999
+ validateTarget(target).then(() => {
3000
+ const proxyReq = requestLib({
3001
+ protocol: target.protocol,
3002
+ hostname: target.hostname,
3003
+ port: target.port || void 0,
3004
+ path: sanitizedPath,
3005
+ method: req.method,
3006
+ headers
3007
+ }, (proxyRes) => {
3008
+ const outgoingHeaders = {};
3009
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
3010
+ if (value !== void 0) {
3011
+ outgoingHeaders[key] = value;
3012
+ }
2788
3013
  }
2789
- }
2790
- res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
2791
- proxyRes.pipe(res);
2792
- });
2793
- proxyReq.on("error", (error) => {
3014
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
3015
+ proxyRes.pipe(res);
3016
+ });
3017
+ proxyReq.on("error", (error) => {
3018
+ if (!res.headersSent) {
3019
+ res.statusCode = 502;
3020
+ }
3021
+ res.end(`PM proxy error: ${error.message}`);
3022
+ });
3023
+ req.pipe(proxyReq);
3024
+ }).catch((error) => {
2794
3025
  if (!res.headersSent) {
2795
- res.statusCode = 502;
3026
+ res.statusCode = 403;
2796
3027
  }
2797
- res.end(`PM proxy error: ${error.message}`);
3028
+ res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
2798
3029
  });
2799
- req.pipe(proxyReq);
2800
3030
  });
2801
3031
  server.on("upgrade", (req, socket, head) => {
2802
3032
  const target = pickTarget();
@@ -2808,38 +3038,56 @@ async function createPmProxyController(proxy) {
2808
3038
  socket.destroy();
2809
3039
  return;
2810
3040
  }
3041
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
2811
3042
  const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
2812
- const targetUrl = new URL(req.url || "/", target);
2813
- const proxyReq = requestLib(targetUrl, {
2814
- method: req.method,
2815
- headers: buildPmProxyHeaders(req.headers, target.host)
2816
- });
2817
- proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
2818
- writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
2819
- if (head.length > 0) {
2820
- proxySocket.write(head);
2821
- }
2822
- if (proxyHead.length > 0) {
2823
- socket.write(proxyHead);
2824
- }
2825
- socket.on("error", () => proxySocket.destroy());
2826
- proxySocket.on("error", () => socket.destroy());
2827
- proxySocket.pipe(socket);
2828
- socket.pipe(proxySocket);
2829
- });
2830
- proxyReq.on("response", (proxyRes) => {
2831
- writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
2832
- proxyRes.pipe(socket);
2833
- });
2834
- proxyReq.on("error", (error) => {
2835
- writeRawHttpResponse(socket, 502, "Bad Gateway", {
3043
+ const targetUrl = new URL(sanitizedPath, target);
3044
+ if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
3045
+ writeRawHttpResponse(socket, 400, "Bad Request", {
3046
+ connection: "close",
3047
+ "content-length": 0
3048
+ });
3049
+ socket.destroy();
3050
+ return;
3051
+ }
3052
+ validateTarget(target).then(() => {
3053
+ const proxyReq = requestLib(targetUrl, {
3054
+ method: req.method,
3055
+ headers: buildPmProxyHeaders(req.headers, target.host)
3056
+ });
3057
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
3058
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
3059
+ if (head.length > 0) {
3060
+ proxySocket.write(head);
3061
+ }
3062
+ if (proxyHead.length > 0) {
3063
+ socket.write(proxyHead);
3064
+ }
3065
+ socket.on("error", () => proxySocket.destroy());
3066
+ proxySocket.on("error", () => socket.destroy());
3067
+ proxySocket.pipe(socket);
3068
+ socket.pipe(proxySocket);
3069
+ });
3070
+ proxyReq.on("response", (proxyRes) => {
3071
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
3072
+ proxyRes.pipe(socket);
3073
+ });
3074
+ proxyReq.on("error", (error) => {
3075
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
3076
+ connection: "close",
3077
+ "content-type": "text/plain; charset=utf-8",
3078
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
3079
+ });
3080
+ socket.end(`PM proxy error: ${error.message}`);
3081
+ });
3082
+ proxyReq.end();
3083
+ }).catch((error) => {
3084
+ writeRawHttpResponse(socket, 403, "Forbidden", {
2836
3085
  connection: "close",
2837
3086
  "content-type": "text/plain; charset=utf-8",
2838
- "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
3087
+ "content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
2839
3088
  });
2840
- socket.end(`PM proxy error: ${error.message}`);
3089
+ socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
2841
3090
  });
2842
- proxyReq.end();
2843
3091
  });
2844
3092
  await new Promise((resolve7, reject) => {
2845
3093
  server.once("error", reject);
@@ -2847,10 +3095,25 @@ async function createPmProxyController(proxy) {
2847
3095
  });
2848
3096
  return {
2849
3097
  setTarget(targetUrl) {
2850
- setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
3098
+ if (targetUrl) {
3099
+ const parsed = new URL(targetUrl);
3100
+ validateProxyTargetUrl(parsed).then(() => {
3101
+ setResolvedTargets([parsed]);
3102
+ }).catch((error) => {
3103
+ console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
3104
+ setResolvedTargets([]);
3105
+ });
3106
+ } else {
3107
+ setResolvedTargets([]);
3108
+ }
2851
3109
  },
2852
3110
  setTargets(targetUrls) {
2853
- setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
3111
+ Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
3112
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
3113
+ }).catch((error) => {
3114
+ console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
3115
+ setResolvedTargets([]);
3116
+ });
2854
3117
  },
2855
3118
  close() {
2856
3119
  return new Promise((resolve7, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elit",
3
- "version": "3.6.8",
3
+ "version": "3.6.9",
4
4
  "description": "Optimized lightweight library for creating DOM elements with reactive state",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",