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.cjs CHANGED
@@ -2796,6 +2796,217 @@ function watch(paths, options) {
2796
2796
  var import_node_http = require("http");
2797
2797
  var import_node_https = require("https");
2798
2798
  var import_node_net = require("net");
2799
+ var import_promises = require("dns/promises");
2800
+ var BLOCKED_IPV4_PREFIXES = [
2801
+ "0.",
2802
+ "10.",
2803
+ "100.64.",
2804
+ "100.65.",
2805
+ "100.66.",
2806
+ "100.67.",
2807
+ "100.68.",
2808
+ "100.69.",
2809
+ "100.70.",
2810
+ "100.71.",
2811
+ "100.72.",
2812
+ "100.73.",
2813
+ "100.74.",
2814
+ "100.75.",
2815
+ "100.76.",
2816
+ "100.77.",
2817
+ "100.78.",
2818
+ "100.79.",
2819
+ "100.80.",
2820
+ "100.81.",
2821
+ "100.82.",
2822
+ "100.83.",
2823
+ "100.84.",
2824
+ "100.85.",
2825
+ "100.86.",
2826
+ "100.87.",
2827
+ "100.88.",
2828
+ "100.89.",
2829
+ "100.90.",
2830
+ "100.91.",
2831
+ "100.92.",
2832
+ "100.93.",
2833
+ "100.94.",
2834
+ "100.95.",
2835
+ "100.96.",
2836
+ "100.97.",
2837
+ "100.98.",
2838
+ "100.99.",
2839
+ "100.100.",
2840
+ "100.101.",
2841
+ "100.102.",
2842
+ "100.103.",
2843
+ "100.104.",
2844
+ "100.105.",
2845
+ "100.106.",
2846
+ "100.107.",
2847
+ "100.108.",
2848
+ "100.109.",
2849
+ "100.110.",
2850
+ "100.111.",
2851
+ "100.112.",
2852
+ "100.113.",
2853
+ "100.114.",
2854
+ "100.115.",
2855
+ "100.116.",
2856
+ "100.117.",
2857
+ "100.118.",
2858
+ "100.119.",
2859
+ "100.120.",
2860
+ "100.121.",
2861
+ "100.122.",
2862
+ "100.123.",
2863
+ "100.124.",
2864
+ "100.125.",
2865
+ "100.126.",
2866
+ "100.127.",
2867
+ "127.",
2868
+ "169.254.",
2869
+ "172.16.",
2870
+ "172.17.",
2871
+ "172.18.",
2872
+ "172.19.",
2873
+ "172.20.",
2874
+ "172.21.",
2875
+ "172.22.",
2876
+ "172.23.",
2877
+ "172.24.",
2878
+ "172.25.",
2879
+ "172.26.",
2880
+ "172.27.",
2881
+ "172.28.",
2882
+ "172.29.",
2883
+ "172.30.",
2884
+ "172.31.",
2885
+ "192.0.2.",
2886
+ "192.88.99.",
2887
+ "192.168.",
2888
+ "198.18.",
2889
+ "198.19.",
2890
+ "198.51.100.",
2891
+ "203.0.113.",
2892
+ "224.",
2893
+ "225.",
2894
+ "226.",
2895
+ "227.",
2896
+ "228.",
2897
+ "229.",
2898
+ "230.",
2899
+ "231.",
2900
+ "232.",
2901
+ "233.",
2902
+ "234.",
2903
+ "235.",
2904
+ "236.",
2905
+ "237.",
2906
+ "238.",
2907
+ "239.",
2908
+ "240.",
2909
+ "241.",
2910
+ "242.",
2911
+ "243.",
2912
+ "244.",
2913
+ "245.",
2914
+ "246.",
2915
+ "247.",
2916
+ "248.",
2917
+ "249.",
2918
+ "250.",
2919
+ "251.",
2920
+ "252.",
2921
+ "253.",
2922
+ "254.",
2923
+ "255."
2924
+ ];
2925
+ var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
2926
+ function isBlockedIpv4(hostname) {
2927
+ const octets = hostname.split(".");
2928
+ if (octets.length !== 4) return false;
2929
+ const joined = hostname;
2930
+ return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
2931
+ }
2932
+ function isBlockedIpv6(hostname) {
2933
+ const lower = hostname.toLowerCase();
2934
+ if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
2935
+ return true;
2936
+ }
2937
+ const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
2938
+ if (ffffMatch) {
2939
+ return isBlockedIpv4(ffffMatch[1]);
2940
+ }
2941
+ const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
2942
+ if (compatMatch) {
2943
+ return isBlockedIpv4(compatMatch[1]);
2944
+ }
2945
+ return false;
2946
+ }
2947
+ function isSafeHostname(hostname) {
2948
+ if (!hostname) return false;
2949
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
2950
+ if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
2951
+ if (hostname.includes(":")) return !isBlockedIpv6(hostname);
2952
+ return true;
2953
+ }
2954
+ async function safeResolveHostname(hostname) {
2955
+ try {
2956
+ const result = await (0, import_promises.lookup)(hostname);
2957
+ const ip = result.address;
2958
+ if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
2959
+ throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
2960
+ }
2961
+ return ip;
2962
+ } catch (error) {
2963
+ if (error instanceof Error && error.message.includes("blocked address")) {
2964
+ throw error;
2965
+ }
2966
+ throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
2967
+ }
2968
+ }
2969
+ async function validateProxyTargetUrl(target) {
2970
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
2971
+ throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
2972
+ }
2973
+ const hostname = target.hostname;
2974
+ if (!isSafeHostname(hostname)) {
2975
+ throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
2976
+ }
2977
+ if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
2978
+ await safeResolveHostname(hostname);
2979
+ }
2980
+ }
2981
+ function sanitizeProxyRequestPath(requestUrl) {
2982
+ if (!requestUrl || requestUrl === "/") return "/";
2983
+ try {
2984
+ const normalizedInput = requestUrl.replace(/\\/g, "/");
2985
+ const parsed = new URL(normalizedInput, "http://placeholder");
2986
+ if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
2987
+ return "/";
2988
+ }
2989
+ const pathname = parsed.pathname || "/";
2990
+ let decodedPathname = pathname;
2991
+ try {
2992
+ decodedPathname = decodeURIComponent(pathname);
2993
+ } catch {
2994
+ return "/";
2995
+ }
2996
+ const lowerPath = pathname.toLowerCase();
2997
+ if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
2998
+ return "/";
2999
+ }
3000
+ const segments = decodedPathname.split("/");
3001
+ if (segments.some((segment) => segment === "." || segment === "..")) {
3002
+ return "/";
3003
+ }
3004
+ const sanitized = pathname + parsed.search;
3005
+ return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
3006
+ } catch {
3007
+ return "/";
3008
+ }
3009
+ }
2799
3010
  function resolvePmProxyHost(proxy) {
2800
3011
  return proxy.host?.trim() || "0.0.0.0";
2801
3012
  }
@@ -2885,6 +3096,9 @@ async function createPmProxyController(proxy) {
2885
3096
  nextTargetIndex = (nextTargetIndex + 1) % targets.length;
2886
3097
  return target;
2887
3098
  };
3099
+ const validateTarget = async (target) => {
3100
+ await validateProxyTargetUrl(target);
3101
+ };
2888
3102
  const server = (0, import_node_http.createServer)((req, res) => {
2889
3103
  const target = pickTarget();
2890
3104
  if (!target) {
@@ -2892,29 +3106,45 @@ async function createPmProxyController(proxy) {
2892
3106
  res.end("PM proxy target is not ready.");
2893
3107
  return;
2894
3108
  }
3109
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
2895
3110
  const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
2896
- const targetUrl = new URL(req.url || "/", target);
2897
3111
  const headers = buildPmProxyHeaders(req.headers, target.host);
2898
- const proxyReq = requestLib(targetUrl, {
2899
- method: req.method,
2900
- headers
2901
- }, (proxyRes) => {
2902
- const outgoingHeaders = {};
2903
- for (const [key, value] of Object.entries(proxyRes.headers)) {
2904
- if (value !== void 0) {
2905
- outgoingHeaders[key] = value;
3112
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
3113
+ res.statusCode = 400;
3114
+ res.end("PM proxy rejected unsafe target protocol.");
3115
+ return;
3116
+ }
3117
+ validateTarget(target).then(() => {
3118
+ const proxyReq = requestLib({
3119
+ protocol: target.protocol,
3120
+ hostname: target.hostname,
3121
+ port: target.port || void 0,
3122
+ path: sanitizedPath,
3123
+ method: req.method,
3124
+ headers
3125
+ }, (proxyRes) => {
3126
+ const outgoingHeaders = {};
3127
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
3128
+ if (value !== void 0) {
3129
+ outgoingHeaders[key] = value;
3130
+ }
2906
3131
  }
2907
- }
2908
- res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
2909
- proxyRes.pipe(res);
2910
- });
2911
- proxyReq.on("error", (error) => {
3132
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
3133
+ proxyRes.pipe(res);
3134
+ });
3135
+ proxyReq.on("error", (error) => {
3136
+ if (!res.headersSent) {
3137
+ res.statusCode = 502;
3138
+ }
3139
+ res.end(`PM proxy error: ${error.message}`);
3140
+ });
3141
+ req.pipe(proxyReq);
3142
+ }).catch((error) => {
2912
3143
  if (!res.headersSent) {
2913
- res.statusCode = 502;
3144
+ res.statusCode = 403;
2914
3145
  }
2915
- res.end(`PM proxy error: ${error.message}`);
3146
+ res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
2916
3147
  });
2917
- req.pipe(proxyReq);
2918
3148
  });
2919
3149
  server.on("upgrade", (req, socket, head) => {
2920
3150
  const target = pickTarget();
@@ -2926,38 +3156,56 @@ async function createPmProxyController(proxy) {
2926
3156
  socket.destroy();
2927
3157
  return;
2928
3158
  }
3159
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
2929
3160
  const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
2930
- const targetUrl = new URL(req.url || "/", target);
2931
- const proxyReq = requestLib(targetUrl, {
2932
- method: req.method,
2933
- headers: buildPmProxyHeaders(req.headers, target.host)
2934
- });
2935
- proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
2936
- writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
2937
- if (head.length > 0) {
2938
- proxySocket.write(head);
2939
- }
2940
- if (proxyHead.length > 0) {
2941
- socket.write(proxyHead);
2942
- }
2943
- socket.on("error", () => proxySocket.destroy());
2944
- proxySocket.on("error", () => socket.destroy());
2945
- proxySocket.pipe(socket);
2946
- socket.pipe(proxySocket);
2947
- });
2948
- proxyReq.on("response", (proxyRes) => {
2949
- writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
2950
- proxyRes.pipe(socket);
2951
- });
2952
- proxyReq.on("error", (error) => {
2953
- writeRawHttpResponse(socket, 502, "Bad Gateway", {
3161
+ const targetUrl = new URL(sanitizedPath, target);
3162
+ if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
3163
+ writeRawHttpResponse(socket, 400, "Bad Request", {
3164
+ connection: "close",
3165
+ "content-length": 0
3166
+ });
3167
+ socket.destroy();
3168
+ return;
3169
+ }
3170
+ validateTarget(target).then(() => {
3171
+ const proxyReq = requestLib(targetUrl, {
3172
+ method: req.method,
3173
+ headers: buildPmProxyHeaders(req.headers, target.host)
3174
+ });
3175
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
3176
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
3177
+ if (head.length > 0) {
3178
+ proxySocket.write(head);
3179
+ }
3180
+ if (proxyHead.length > 0) {
3181
+ socket.write(proxyHead);
3182
+ }
3183
+ socket.on("error", () => proxySocket.destroy());
3184
+ proxySocket.on("error", () => socket.destroy());
3185
+ proxySocket.pipe(socket);
3186
+ socket.pipe(proxySocket);
3187
+ });
3188
+ proxyReq.on("response", (proxyRes) => {
3189
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
3190
+ proxyRes.pipe(socket);
3191
+ });
3192
+ proxyReq.on("error", (error) => {
3193
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
3194
+ connection: "close",
3195
+ "content-type": "text/plain; charset=utf-8",
3196
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
3197
+ });
3198
+ socket.end(`PM proxy error: ${error.message}`);
3199
+ });
3200
+ proxyReq.end();
3201
+ }).catch((error) => {
3202
+ writeRawHttpResponse(socket, 403, "Forbidden", {
2954
3203
  connection: "close",
2955
3204
  "content-type": "text/plain; charset=utf-8",
2956
- "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
3205
+ "content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
2957
3206
  });
2958
- socket.end(`PM proxy error: ${error.message}`);
3207
+ socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
2959
3208
  });
2960
- proxyReq.end();
2961
3209
  });
2962
3210
  await new Promise((resolve7, reject) => {
2963
3211
  server.once("error", reject);
@@ -2965,10 +3213,25 @@ async function createPmProxyController(proxy) {
2965
3213
  });
2966
3214
  return {
2967
3215
  setTarget(targetUrl) {
2968
- setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
3216
+ if (targetUrl) {
3217
+ const parsed = new URL(targetUrl);
3218
+ validateProxyTargetUrl(parsed).then(() => {
3219
+ setResolvedTargets([parsed]);
3220
+ }).catch((error) => {
3221
+ console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
3222
+ setResolvedTargets([]);
3223
+ });
3224
+ } else {
3225
+ setResolvedTargets([]);
3226
+ }
2969
3227
  },
2970
3228
  setTargets(targetUrls) {
2971
- setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
3229
+ Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
3230
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
3231
+ }).catch((error) => {
3232
+ console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
3233
+ setResolvedTargets([]);
3234
+ });
2972
3235
  },
2973
3236
  close() {
2974
3237
  return new Promise((resolve7, reject) => {