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.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
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
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
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
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 =
|
|
3144
|
+
res.statusCode = 403;
|
|
2914
3145
|
}
|
|
2915
|
-
res.end(`PM proxy
|
|
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(
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
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
|
|
3205
|
+
"content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
|
|
2957
3206
|
});
|
|
2958
|
-
socket.end(`PM proxy
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|