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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/dist/cli.cjs +328 -65
- package/dist/cli.mjs +310 -47
- package/dist/pm.cjs +309 -46
- package/dist/pm.js +309 -46
- package/dist/pm.mjs +309 -46
- package/package.json +1 -1
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
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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 =
|
|
3026
|
+
res.statusCode = 403;
|
|
2796
3027
|
}
|
|
2797
|
-
res.end(`PM proxy
|
|
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(
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
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
|
|
3087
|
+
"content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
|
|
2839
3088
|
});
|
|
2840
|
-
socket.end(`PM proxy
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|