@node9/proxy 2.16.1 → 2.16.2
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/cli.js +219 -214
- package/dist/cli.mjs +219 -214
- package/dist/dashboard.mjs +2 -2
- package/dist/index.js +218 -213
- package/dist/index.mjs +218 -213
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2089,6 +2089,181 @@ function analyzeShellCommand(command) {
|
|
|
2089
2089
|
}
|
|
2090
2090
|
return { actions, paths, allTokens };
|
|
2091
2091
|
}
|
|
2092
|
+
function parseComponent(s) {
|
|
2093
|
+
if (!s) return null;
|
|
2094
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
2095
|
+
if (s === "0") return 0;
|
|
2096
|
+
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
2097
|
+
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
function parseIpv4(input) {
|
|
2101
|
+
let s = input;
|
|
2102
|
+
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
2103
|
+
if (!s) return null;
|
|
2104
|
+
const parts = s.split(".");
|
|
2105
|
+
if (parts.length > 4) return null;
|
|
2106
|
+
const vals = [];
|
|
2107
|
+
for (const p of parts) {
|
|
2108
|
+
const v = parseComponent(p);
|
|
2109
|
+
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
2110
|
+
vals.push(v);
|
|
2111
|
+
}
|
|
2112
|
+
const n = vals.length;
|
|
2113
|
+
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
2114
|
+
const last = vals[n - 1];
|
|
2115
|
+
const remainingBytes = 4 - (n - 1);
|
|
2116
|
+
const limit = Math.pow(256, remainingBytes);
|
|
2117
|
+
if (last >= limit) return null;
|
|
2118
|
+
let value = last;
|
|
2119
|
+
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
2120
|
+
if (value > 4294967295) return null;
|
|
2121
|
+
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
2122
|
+
}
|
|
2123
|
+
function expandIpv6(input) {
|
|
2124
|
+
const s = input.toLowerCase();
|
|
2125
|
+
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
2126
|
+
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
2127
|
+
let head = s;
|
|
2128
|
+
let tailV4 = null;
|
|
2129
|
+
const lastColon = s.lastIndexOf(":");
|
|
2130
|
+
const afterLast = s.slice(lastColon + 1);
|
|
2131
|
+
if (afterLast.includes(".")) {
|
|
2132
|
+
const dotted = parseIpv4(afterLast);
|
|
2133
|
+
if (!dotted) return null;
|
|
2134
|
+
const o = dotted.split(".").map(Number);
|
|
2135
|
+
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
2136
|
+
head = s.slice(0, lastColon + 1) + "0";
|
|
2137
|
+
}
|
|
2138
|
+
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
2139
|
+
const toGroups = (part) => {
|
|
2140
|
+
if (!part) return [];
|
|
2141
|
+
const out = [];
|
|
2142
|
+
for (const g of part.split(":")) {
|
|
2143
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
2144
|
+
out.push(parseInt(g, 16));
|
|
2145
|
+
}
|
|
2146
|
+
return out;
|
|
2147
|
+
};
|
|
2148
|
+
const left = toGroups(lhs);
|
|
2149
|
+
if (left === null) return null;
|
|
2150
|
+
let right = [];
|
|
2151
|
+
if (rhs !== null) {
|
|
2152
|
+
const r = toGroups(rhs);
|
|
2153
|
+
if (r === null) return null;
|
|
2154
|
+
right = r;
|
|
2155
|
+
}
|
|
2156
|
+
if (tailV4) {
|
|
2157
|
+
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
2158
|
+
else left.splice(left.length - 1, 1, ...tailV4);
|
|
2159
|
+
}
|
|
2160
|
+
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
2161
|
+
if (rhs === null && groups.length !== 8) return null;
|
|
2162
|
+
if (rhs !== null && left.length + right.length > 8) return null;
|
|
2163
|
+
if (groups.length !== 8) return null;
|
|
2164
|
+
return groups;
|
|
2165
|
+
}
|
|
2166
|
+
function compressIpv6(g) {
|
|
2167
|
+
let bestStart = -1;
|
|
2168
|
+
let bestLen = 0;
|
|
2169
|
+
let i = 0;
|
|
2170
|
+
while (i < 8) {
|
|
2171
|
+
if (g[i] !== 0) {
|
|
2172
|
+
i++;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
let j = i;
|
|
2176
|
+
while (j < 8 && g[j] === 0) j++;
|
|
2177
|
+
if (j - i > bestLen) {
|
|
2178
|
+
bestLen = j - i;
|
|
2179
|
+
bestStart = i;
|
|
2180
|
+
}
|
|
2181
|
+
i = j;
|
|
2182
|
+
}
|
|
2183
|
+
const hex = g.map((x) => x.toString(16));
|
|
2184
|
+
if (bestLen < 2) return hex.join(":");
|
|
2185
|
+
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
2186
|
+
}
|
|
2187
|
+
function normalizeIpLiteral(host) {
|
|
2188
|
+
try {
|
|
2189
|
+
if (typeof host !== "string") return null;
|
|
2190
|
+
let s = host.trim();
|
|
2191
|
+
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
2192
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
2193
|
+
const zone = s.indexOf("%");
|
|
2194
|
+
if (zone >= 0) s = s.slice(0, zone);
|
|
2195
|
+
if (!s) return null;
|
|
2196
|
+
if (s.includes(":")) {
|
|
2197
|
+
const g = expandIpv6(s);
|
|
2198
|
+
if (!g) return null;
|
|
2199
|
+
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
2200
|
+
if (mapped) {
|
|
2201
|
+
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
2202
|
+
}
|
|
2203
|
+
return compressIpv6(g);
|
|
2204
|
+
}
|
|
2205
|
+
return parseIpv4(s);
|
|
2206
|
+
} catch {
|
|
2207
|
+
return null;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
function isStrictGatedTier(tier) {
|
|
2211
|
+
return STRICT_TIERS.has(tier);
|
|
2212
|
+
}
|
|
2213
|
+
function ssrfReason(m, asWritten) {
|
|
2214
|
+
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2215
|
+
}
|
|
2216
|
+
function classifySsrf(host) {
|
|
2217
|
+
try {
|
|
2218
|
+
if (typeof host !== "string" || !host) return null;
|
|
2219
|
+
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
2220
|
+
const ip = normalizeIpLiteral(host);
|
|
2221
|
+
if (ip === null) {
|
|
2222
|
+
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
2223
|
+
}
|
|
2224
|
+
const hit = (tier, overridable) => ({
|
|
2225
|
+
tier,
|
|
2226
|
+
overridable,
|
|
2227
|
+
kind: "address",
|
|
2228
|
+
normalized: ip
|
|
2229
|
+
});
|
|
2230
|
+
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
2231
|
+
const o = v4Octets(ip);
|
|
2232
|
+
if (o) {
|
|
2233
|
+
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
2234
|
+
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
2235
|
+
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
2236
|
+
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
2237
|
+
if (o[0] === 127) return hit("private", true);
|
|
2238
|
+
if (o[0] === 10) return hit("private", true);
|
|
2239
|
+
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
2240
|
+
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
const g = expandIpv6(ip);
|
|
2244
|
+
if (!g) return null;
|
|
2245
|
+
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
2246
|
+
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
2247
|
+
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
2248
|
+
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
2249
|
+
return null;
|
|
2250
|
+
} catch {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function ssrfFloor(tokens, opts = {}) {
|
|
2255
|
+
const exempt2 = new Set(
|
|
2256
|
+
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
2257
|
+
);
|
|
2258
|
+
for (const { token, binary } of tokens) {
|
|
2259
|
+
const m = classifySsrf(token);
|
|
2260
|
+
if (!m) continue;
|
|
2261
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2262
|
+
if (m.overridable && m.normalized && exempt2.has(m.normalized)) continue;
|
|
2263
|
+
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2264
|
+
}
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2092
2267
|
function hostMatches(host, pattern) {
|
|
2093
2268
|
const h = host.toLowerCase();
|
|
2094
2269
|
const p = pattern.toLowerCase().trim();
|
|
@@ -2104,15 +2279,19 @@ function matchesAny(host, patterns) {
|
|
|
2104
2279
|
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
2105
2280
|
return false;
|
|
2106
2281
|
}
|
|
2282
|
+
function isUniqueLocalV6(host) {
|
|
2283
|
+
const ip = normalizeIpLiteral(host);
|
|
2284
|
+
if (!ip || !ip.includes(":")) return false;
|
|
2285
|
+
const g = expandIpv6(ip);
|
|
2286
|
+
return g !== null && (g[0] & 65024) === 64512;
|
|
2287
|
+
}
|
|
2107
2288
|
function isPrivateHost(host) {
|
|
2108
|
-
const h = host.toLowerCase();
|
|
2109
|
-
|
|
2110
|
-
if (
|
|
2111
|
-
if (
|
|
2112
|
-
if (
|
|
2113
|
-
|
|
2114
|
-
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
2115
|
-
return false;
|
|
2289
|
+
const h = host.trim().toLowerCase();
|
|
2290
|
+
const m = classifySsrf(h);
|
|
2291
|
+
if (m) return m.kind === "address" && (m.tier === "private" || m.tier === "unspecified");
|
|
2292
|
+
if (h === "localhost") return true;
|
|
2293
|
+
if (PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s))) return true;
|
|
2294
|
+
return isUniqueLocalV6(h);
|
|
2116
2295
|
}
|
|
2117
2296
|
function evaluateEgress(dests, policy) {
|
|
2118
2297
|
if (!policy.enabled) return null;
|
|
@@ -2332,181 +2511,6 @@ function extractAllSshHosts(tokens) {
|
|
|
2332
2511
|
}
|
|
2333
2512
|
return [...hosts].filter(Boolean);
|
|
2334
2513
|
}
|
|
2335
|
-
function parseComponent(s) {
|
|
2336
|
-
if (!s) return null;
|
|
2337
|
-
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
2338
|
-
if (s === "0") return 0;
|
|
2339
|
-
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
2340
|
-
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
2341
|
-
return null;
|
|
2342
|
-
}
|
|
2343
|
-
function parseIpv4(input) {
|
|
2344
|
-
let s = input;
|
|
2345
|
-
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
2346
|
-
if (!s) return null;
|
|
2347
|
-
const parts = s.split(".");
|
|
2348
|
-
if (parts.length > 4) return null;
|
|
2349
|
-
const vals = [];
|
|
2350
|
-
for (const p of parts) {
|
|
2351
|
-
const v = parseComponent(p);
|
|
2352
|
-
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
2353
|
-
vals.push(v);
|
|
2354
|
-
}
|
|
2355
|
-
const n = vals.length;
|
|
2356
|
-
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
2357
|
-
const last = vals[n - 1];
|
|
2358
|
-
const remainingBytes = 4 - (n - 1);
|
|
2359
|
-
const limit = Math.pow(256, remainingBytes);
|
|
2360
|
-
if (last >= limit) return null;
|
|
2361
|
-
let value = last;
|
|
2362
|
-
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
2363
|
-
if (value > 4294967295) return null;
|
|
2364
|
-
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
2365
|
-
}
|
|
2366
|
-
function expandIpv6(input) {
|
|
2367
|
-
const s = input.toLowerCase();
|
|
2368
|
-
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
2369
|
-
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
2370
|
-
let head = s;
|
|
2371
|
-
let tailV4 = null;
|
|
2372
|
-
const lastColon = s.lastIndexOf(":");
|
|
2373
|
-
const afterLast = s.slice(lastColon + 1);
|
|
2374
|
-
if (afterLast.includes(".")) {
|
|
2375
|
-
const dotted = parseIpv4(afterLast);
|
|
2376
|
-
if (!dotted) return null;
|
|
2377
|
-
const o = dotted.split(".").map(Number);
|
|
2378
|
-
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
2379
|
-
head = s.slice(0, lastColon + 1) + "0";
|
|
2380
|
-
}
|
|
2381
|
-
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
2382
|
-
const toGroups = (part) => {
|
|
2383
|
-
if (!part) return [];
|
|
2384
|
-
const out = [];
|
|
2385
|
-
for (const g of part.split(":")) {
|
|
2386
|
-
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
2387
|
-
out.push(parseInt(g, 16));
|
|
2388
|
-
}
|
|
2389
|
-
return out;
|
|
2390
|
-
};
|
|
2391
|
-
const left = toGroups(lhs);
|
|
2392
|
-
if (left === null) return null;
|
|
2393
|
-
let right = [];
|
|
2394
|
-
if (rhs !== null) {
|
|
2395
|
-
const r = toGroups(rhs);
|
|
2396
|
-
if (r === null) return null;
|
|
2397
|
-
right = r;
|
|
2398
|
-
}
|
|
2399
|
-
if (tailV4) {
|
|
2400
|
-
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
2401
|
-
else left.splice(left.length - 1, 1, ...tailV4);
|
|
2402
|
-
}
|
|
2403
|
-
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
2404
|
-
if (rhs === null && groups.length !== 8) return null;
|
|
2405
|
-
if (rhs !== null && left.length + right.length > 8) return null;
|
|
2406
|
-
if (groups.length !== 8) return null;
|
|
2407
|
-
return groups;
|
|
2408
|
-
}
|
|
2409
|
-
function compressIpv6(g) {
|
|
2410
|
-
let bestStart = -1;
|
|
2411
|
-
let bestLen = 0;
|
|
2412
|
-
let i = 0;
|
|
2413
|
-
while (i < 8) {
|
|
2414
|
-
if (g[i] !== 0) {
|
|
2415
|
-
i++;
|
|
2416
|
-
continue;
|
|
2417
|
-
}
|
|
2418
|
-
let j = i;
|
|
2419
|
-
while (j < 8 && g[j] === 0) j++;
|
|
2420
|
-
if (j - i > bestLen) {
|
|
2421
|
-
bestLen = j - i;
|
|
2422
|
-
bestStart = i;
|
|
2423
|
-
}
|
|
2424
|
-
i = j;
|
|
2425
|
-
}
|
|
2426
|
-
const hex = g.map((x) => x.toString(16));
|
|
2427
|
-
if (bestLen < 2) return hex.join(":");
|
|
2428
|
-
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
2429
|
-
}
|
|
2430
|
-
function normalizeIpLiteral(host) {
|
|
2431
|
-
try {
|
|
2432
|
-
if (typeof host !== "string") return null;
|
|
2433
|
-
let s = host.trim();
|
|
2434
|
-
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
2435
|
-
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
2436
|
-
const zone = s.indexOf("%");
|
|
2437
|
-
if (zone >= 0) s = s.slice(0, zone);
|
|
2438
|
-
if (!s) return null;
|
|
2439
|
-
if (s.includes(":")) {
|
|
2440
|
-
const g = expandIpv6(s);
|
|
2441
|
-
if (!g) return null;
|
|
2442
|
-
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
2443
|
-
if (mapped) {
|
|
2444
|
-
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
2445
|
-
}
|
|
2446
|
-
return compressIpv6(g);
|
|
2447
|
-
}
|
|
2448
|
-
return parseIpv4(s);
|
|
2449
|
-
} catch {
|
|
2450
|
-
return null;
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
function isStrictGatedTier(tier) {
|
|
2454
|
-
return STRICT_TIERS.has(tier);
|
|
2455
|
-
}
|
|
2456
|
-
function ssrfReason(m, asWritten) {
|
|
2457
|
-
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2458
|
-
}
|
|
2459
|
-
function classifySsrf(host) {
|
|
2460
|
-
try {
|
|
2461
|
-
if (typeof host !== "string" || !host) return null;
|
|
2462
|
-
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
2463
|
-
const ip = normalizeIpLiteral(host);
|
|
2464
|
-
if (ip === null) {
|
|
2465
|
-
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
2466
|
-
}
|
|
2467
|
-
const hit = (tier, overridable) => ({
|
|
2468
|
-
tier,
|
|
2469
|
-
overridable,
|
|
2470
|
-
kind: "address",
|
|
2471
|
-
normalized: ip
|
|
2472
|
-
});
|
|
2473
|
-
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
2474
|
-
const o = v4Octets(ip);
|
|
2475
|
-
if (o) {
|
|
2476
|
-
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
2477
|
-
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
2478
|
-
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
2479
|
-
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
2480
|
-
if (o[0] === 127) return hit("private", true);
|
|
2481
|
-
if (o[0] === 10) return hit("private", true);
|
|
2482
|
-
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
2483
|
-
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
2484
|
-
return null;
|
|
2485
|
-
}
|
|
2486
|
-
const g = expandIpv6(ip);
|
|
2487
|
-
if (!g) return null;
|
|
2488
|
-
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
2489
|
-
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
2490
|
-
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
2491
|
-
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
2492
|
-
return null;
|
|
2493
|
-
} catch {
|
|
2494
|
-
return null;
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
function ssrfFloor(tokens, opts = {}) {
|
|
2498
|
-
const exempt2 = new Set(
|
|
2499
|
-
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
2500
|
-
);
|
|
2501
|
-
for (const { token, binary } of tokens) {
|
|
2502
|
-
const m = classifySsrf(token);
|
|
2503
|
-
if (!m) continue;
|
|
2504
|
-
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2505
|
-
if (m.overridable && m.normalized && exempt2.has(m.normalized)) continue;
|
|
2506
|
-
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2507
|
-
}
|
|
2508
|
-
return null;
|
|
2509
|
-
}
|
|
2510
2514
|
function bareToolName(toolName) {
|
|
2511
2515
|
const parts = toolName.split("__");
|
|
2512
2516
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
@@ -3712,7 +3716,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3712
3716
|
}
|
|
3713
3717
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3714
3718
|
}
|
|
3715
|
-
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN,
|
|
3719
|
+
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
|
|
3716
3720
|
var init_dist = __esm({
|
|
3717
3721
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3718
3722
|
"use strict";
|
|
@@ -5157,6 +5161,36 @@ var init_dist = __esm({
|
|
|
5157
5161
|
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
5158
5162
|
NONE = { kind: "none" };
|
|
5159
5163
|
UNKNOWN = { kind: "unknown" };
|
|
5164
|
+
SSRF_MAX_HOST = 253;
|
|
5165
|
+
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
5166
|
+
"169.254.169.254",
|
|
5167
|
+
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
5168
|
+
"169.254.170.2",
|
|
5169
|
+
// AWS ECS task role
|
|
5170
|
+
"168.63.129.16",
|
|
5171
|
+
// Azure WireServer
|
|
5172
|
+
"fd00:ec2::254",
|
|
5173
|
+
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
5174
|
+
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
5175
|
+
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
5176
|
+
// live credential endpoint. It has to be named here, above the range check,
|
|
5177
|
+
// and it is the reason relaxing cgnat is safe.
|
|
5178
|
+
"100.100.100.200"
|
|
5179
|
+
]);
|
|
5180
|
+
STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
5181
|
+
METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
5182
|
+
v4Octets = (a) => {
|
|
5183
|
+
const p = a.split(".");
|
|
5184
|
+
return p.length === 4 ? p.map(Number) : null;
|
|
5185
|
+
};
|
|
5186
|
+
TIER_REASON = {
|
|
5187
|
+
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
5188
|
+
"link-local": "a link-local address",
|
|
5189
|
+
multicast: "a multicast address",
|
|
5190
|
+
unspecified: "the unspecified address, which reaches this host",
|
|
5191
|
+
cgnat: "a carrier-grade NAT address",
|
|
5192
|
+
private: "a loopback or private address"
|
|
5193
|
+
};
|
|
5160
5194
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
5161
5195
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
5162
5196
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -5180,6 +5214,7 @@ var init_dist = __esm({
|
|
|
5180
5214
|
"deb.debian.org",
|
|
5181
5215
|
"*.ubuntu.com"
|
|
5182
5216
|
];
|
|
5217
|
+
PRIVATE_HOST_SUFFIXES = [".local", ".internal", ".localhost"];
|
|
5183
5218
|
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
5184
5219
|
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
5185
5220
|
"curl",
|
|
@@ -5303,36 +5338,6 @@ var init_dist = __esm({
|
|
|
5303
5338
|
socat: /* @__PURE__ */ new Set([])
|
|
5304
5339
|
// socat uses address syntax, not flags — no value-flags
|
|
5305
5340
|
};
|
|
5306
|
-
SSRF_MAX_HOST = 253;
|
|
5307
|
-
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
5308
|
-
"169.254.169.254",
|
|
5309
|
-
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
5310
|
-
"169.254.170.2",
|
|
5311
|
-
// AWS ECS task role
|
|
5312
|
-
"168.63.129.16",
|
|
5313
|
-
// Azure WireServer
|
|
5314
|
-
"fd00:ec2::254",
|
|
5315
|
-
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
5316
|
-
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
5317
|
-
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
5318
|
-
// live credential endpoint. It has to be named here, above the range check,
|
|
5319
|
-
// and it is the reason relaxing cgnat is safe.
|
|
5320
|
-
"100.100.100.200"
|
|
5321
|
-
]);
|
|
5322
|
-
STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
5323
|
-
METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
5324
|
-
v4Octets = (a) => {
|
|
5325
|
-
const p = a.split(".");
|
|
5326
|
-
return p.length === 4 ? p.map(Number) : null;
|
|
5327
|
-
};
|
|
5328
|
-
TIER_REASON = {
|
|
5329
|
-
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
5330
|
-
"link-local": "a link-local address",
|
|
5331
|
-
multicast: "a multicast address",
|
|
5332
|
-
unspecified: "the unspecified address, which reaches this host",
|
|
5333
|
-
cgnat: "a carrier-grade NAT address",
|
|
5334
|
-
private: "a loopback or private address"
|
|
5335
|
-
};
|
|
5336
5341
|
DESTINATION_ARGS = /* @__PURE__ */ new Map([
|
|
5337
5342
|
["webfetch", ["url"]],
|
|
5338
5343
|
["fetch", ["url", "uri"]],
|