@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.mjs
CHANGED
|
@@ -2100,6 +2100,181 @@ function analyzeShellCommand(command) {
|
|
|
2100
2100
|
}
|
|
2101
2101
|
return { actions, paths, allTokens };
|
|
2102
2102
|
}
|
|
2103
|
+
function parseComponent(s) {
|
|
2104
|
+
if (!s) return null;
|
|
2105
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
2106
|
+
if (s === "0") return 0;
|
|
2107
|
+
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
2108
|
+
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
2111
|
+
function parseIpv4(input) {
|
|
2112
|
+
let s = input;
|
|
2113
|
+
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
2114
|
+
if (!s) return null;
|
|
2115
|
+
const parts = s.split(".");
|
|
2116
|
+
if (parts.length > 4) return null;
|
|
2117
|
+
const vals = [];
|
|
2118
|
+
for (const p of parts) {
|
|
2119
|
+
const v = parseComponent(p);
|
|
2120
|
+
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
2121
|
+
vals.push(v);
|
|
2122
|
+
}
|
|
2123
|
+
const n = vals.length;
|
|
2124
|
+
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
2125
|
+
const last = vals[n - 1];
|
|
2126
|
+
const remainingBytes = 4 - (n - 1);
|
|
2127
|
+
const limit = Math.pow(256, remainingBytes);
|
|
2128
|
+
if (last >= limit) return null;
|
|
2129
|
+
let value = last;
|
|
2130
|
+
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
2131
|
+
if (value > 4294967295) return null;
|
|
2132
|
+
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
2133
|
+
}
|
|
2134
|
+
function expandIpv6(input) {
|
|
2135
|
+
const s = input.toLowerCase();
|
|
2136
|
+
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
2137
|
+
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
2138
|
+
let head = s;
|
|
2139
|
+
let tailV4 = null;
|
|
2140
|
+
const lastColon = s.lastIndexOf(":");
|
|
2141
|
+
const afterLast = s.slice(lastColon + 1);
|
|
2142
|
+
if (afterLast.includes(".")) {
|
|
2143
|
+
const dotted = parseIpv4(afterLast);
|
|
2144
|
+
if (!dotted) return null;
|
|
2145
|
+
const o = dotted.split(".").map(Number);
|
|
2146
|
+
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
2147
|
+
head = s.slice(0, lastColon + 1) + "0";
|
|
2148
|
+
}
|
|
2149
|
+
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
2150
|
+
const toGroups = (part) => {
|
|
2151
|
+
if (!part) return [];
|
|
2152
|
+
const out = [];
|
|
2153
|
+
for (const g of part.split(":")) {
|
|
2154
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
2155
|
+
out.push(parseInt(g, 16));
|
|
2156
|
+
}
|
|
2157
|
+
return out;
|
|
2158
|
+
};
|
|
2159
|
+
const left = toGroups(lhs);
|
|
2160
|
+
if (left === null) return null;
|
|
2161
|
+
let right = [];
|
|
2162
|
+
if (rhs !== null) {
|
|
2163
|
+
const r = toGroups(rhs);
|
|
2164
|
+
if (r === null) return null;
|
|
2165
|
+
right = r;
|
|
2166
|
+
}
|
|
2167
|
+
if (tailV4) {
|
|
2168
|
+
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
2169
|
+
else left.splice(left.length - 1, 1, ...tailV4);
|
|
2170
|
+
}
|
|
2171
|
+
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
2172
|
+
if (rhs === null && groups.length !== 8) return null;
|
|
2173
|
+
if (rhs !== null && left.length + right.length > 8) return null;
|
|
2174
|
+
if (groups.length !== 8) return null;
|
|
2175
|
+
return groups;
|
|
2176
|
+
}
|
|
2177
|
+
function compressIpv6(g) {
|
|
2178
|
+
let bestStart = -1;
|
|
2179
|
+
let bestLen = 0;
|
|
2180
|
+
let i = 0;
|
|
2181
|
+
while (i < 8) {
|
|
2182
|
+
if (g[i] !== 0) {
|
|
2183
|
+
i++;
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
let j = i;
|
|
2187
|
+
while (j < 8 && g[j] === 0) j++;
|
|
2188
|
+
if (j - i > bestLen) {
|
|
2189
|
+
bestLen = j - i;
|
|
2190
|
+
bestStart = i;
|
|
2191
|
+
}
|
|
2192
|
+
i = j;
|
|
2193
|
+
}
|
|
2194
|
+
const hex = g.map((x) => x.toString(16));
|
|
2195
|
+
if (bestLen < 2) return hex.join(":");
|
|
2196
|
+
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
2197
|
+
}
|
|
2198
|
+
function normalizeIpLiteral(host) {
|
|
2199
|
+
try {
|
|
2200
|
+
if (typeof host !== "string") return null;
|
|
2201
|
+
let s = host.trim();
|
|
2202
|
+
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
2203
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
2204
|
+
const zone = s.indexOf("%");
|
|
2205
|
+
if (zone >= 0) s = s.slice(0, zone);
|
|
2206
|
+
if (!s) return null;
|
|
2207
|
+
if (s.includes(":")) {
|
|
2208
|
+
const g = expandIpv6(s);
|
|
2209
|
+
if (!g) return null;
|
|
2210
|
+
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
2211
|
+
if (mapped) {
|
|
2212
|
+
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
2213
|
+
}
|
|
2214
|
+
return compressIpv6(g);
|
|
2215
|
+
}
|
|
2216
|
+
return parseIpv4(s);
|
|
2217
|
+
} catch {
|
|
2218
|
+
return null;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
function isStrictGatedTier(tier) {
|
|
2222
|
+
return STRICT_TIERS.has(tier);
|
|
2223
|
+
}
|
|
2224
|
+
function ssrfReason(m, asWritten) {
|
|
2225
|
+
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2226
|
+
}
|
|
2227
|
+
function classifySsrf(host) {
|
|
2228
|
+
try {
|
|
2229
|
+
if (typeof host !== "string" || !host) return null;
|
|
2230
|
+
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
2231
|
+
const ip = normalizeIpLiteral(host);
|
|
2232
|
+
if (ip === null) {
|
|
2233
|
+
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
2234
|
+
}
|
|
2235
|
+
const hit = (tier, overridable) => ({
|
|
2236
|
+
tier,
|
|
2237
|
+
overridable,
|
|
2238
|
+
kind: "address",
|
|
2239
|
+
normalized: ip
|
|
2240
|
+
});
|
|
2241
|
+
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
2242
|
+
const o = v4Octets(ip);
|
|
2243
|
+
if (o) {
|
|
2244
|
+
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
2245
|
+
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
2246
|
+
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
2247
|
+
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
2248
|
+
if (o[0] === 127) return hit("private", true);
|
|
2249
|
+
if (o[0] === 10) return hit("private", true);
|
|
2250
|
+
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
2251
|
+
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
2252
|
+
return null;
|
|
2253
|
+
}
|
|
2254
|
+
const g = expandIpv6(ip);
|
|
2255
|
+
if (!g) return null;
|
|
2256
|
+
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
2257
|
+
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
2258
|
+
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
2259
|
+
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
2260
|
+
return null;
|
|
2261
|
+
} catch {
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
function ssrfFloor(tokens, opts = {}) {
|
|
2266
|
+
const exempt2 = new Set(
|
|
2267
|
+
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
2268
|
+
);
|
|
2269
|
+
for (const { token, binary } of tokens) {
|
|
2270
|
+
const m = classifySsrf(token);
|
|
2271
|
+
if (!m) continue;
|
|
2272
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2273
|
+
if (m.overridable && m.normalized && exempt2.has(m.normalized)) continue;
|
|
2274
|
+
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2275
|
+
}
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2103
2278
|
function hostMatches(host, pattern) {
|
|
2104
2279
|
const h = host.toLowerCase();
|
|
2105
2280
|
const p = pattern.toLowerCase().trim();
|
|
@@ -2115,15 +2290,19 @@ function matchesAny(host, patterns) {
|
|
|
2115
2290
|
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
2116
2291
|
return false;
|
|
2117
2292
|
}
|
|
2293
|
+
function isUniqueLocalV6(host) {
|
|
2294
|
+
const ip = normalizeIpLiteral(host);
|
|
2295
|
+
if (!ip || !ip.includes(":")) return false;
|
|
2296
|
+
const g = expandIpv6(ip);
|
|
2297
|
+
return g !== null && (g[0] & 65024) === 64512;
|
|
2298
|
+
}
|
|
2118
2299
|
function isPrivateHost(host) {
|
|
2119
|
-
const h = host.toLowerCase();
|
|
2120
|
-
|
|
2121
|
-
if (
|
|
2122
|
-
if (
|
|
2123
|
-
if (
|
|
2124
|
-
|
|
2125
|
-
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
2126
|
-
return false;
|
|
2300
|
+
const h = host.trim().toLowerCase();
|
|
2301
|
+
const m = classifySsrf(h);
|
|
2302
|
+
if (m) return m.kind === "address" && (m.tier === "private" || m.tier === "unspecified");
|
|
2303
|
+
if (h === "localhost") return true;
|
|
2304
|
+
if (PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s))) return true;
|
|
2305
|
+
return isUniqueLocalV6(h);
|
|
2127
2306
|
}
|
|
2128
2307
|
function evaluateEgress(dests, policy) {
|
|
2129
2308
|
if (!policy.enabled) return null;
|
|
@@ -2343,181 +2522,6 @@ function extractAllSshHosts(tokens) {
|
|
|
2343
2522
|
}
|
|
2344
2523
|
return [...hosts].filter(Boolean);
|
|
2345
2524
|
}
|
|
2346
|
-
function parseComponent(s) {
|
|
2347
|
-
if (!s) return null;
|
|
2348
|
-
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
2349
|
-
if (s === "0") return 0;
|
|
2350
|
-
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
2351
|
-
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
2352
|
-
return null;
|
|
2353
|
-
}
|
|
2354
|
-
function parseIpv4(input) {
|
|
2355
|
-
let s = input;
|
|
2356
|
-
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
2357
|
-
if (!s) return null;
|
|
2358
|
-
const parts = s.split(".");
|
|
2359
|
-
if (parts.length > 4) return null;
|
|
2360
|
-
const vals = [];
|
|
2361
|
-
for (const p of parts) {
|
|
2362
|
-
const v = parseComponent(p);
|
|
2363
|
-
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
2364
|
-
vals.push(v);
|
|
2365
|
-
}
|
|
2366
|
-
const n = vals.length;
|
|
2367
|
-
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
2368
|
-
const last = vals[n - 1];
|
|
2369
|
-
const remainingBytes = 4 - (n - 1);
|
|
2370
|
-
const limit = Math.pow(256, remainingBytes);
|
|
2371
|
-
if (last >= limit) return null;
|
|
2372
|
-
let value = last;
|
|
2373
|
-
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
2374
|
-
if (value > 4294967295) return null;
|
|
2375
|
-
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
2376
|
-
}
|
|
2377
|
-
function expandIpv6(input) {
|
|
2378
|
-
const s = input.toLowerCase();
|
|
2379
|
-
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
2380
|
-
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
2381
|
-
let head = s;
|
|
2382
|
-
let tailV4 = null;
|
|
2383
|
-
const lastColon = s.lastIndexOf(":");
|
|
2384
|
-
const afterLast = s.slice(lastColon + 1);
|
|
2385
|
-
if (afterLast.includes(".")) {
|
|
2386
|
-
const dotted = parseIpv4(afterLast);
|
|
2387
|
-
if (!dotted) return null;
|
|
2388
|
-
const o = dotted.split(".").map(Number);
|
|
2389
|
-
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
2390
|
-
head = s.slice(0, lastColon + 1) + "0";
|
|
2391
|
-
}
|
|
2392
|
-
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
2393
|
-
const toGroups = (part) => {
|
|
2394
|
-
if (!part) return [];
|
|
2395
|
-
const out = [];
|
|
2396
|
-
for (const g of part.split(":")) {
|
|
2397
|
-
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
2398
|
-
out.push(parseInt(g, 16));
|
|
2399
|
-
}
|
|
2400
|
-
return out;
|
|
2401
|
-
};
|
|
2402
|
-
const left = toGroups(lhs);
|
|
2403
|
-
if (left === null) return null;
|
|
2404
|
-
let right = [];
|
|
2405
|
-
if (rhs !== null) {
|
|
2406
|
-
const r = toGroups(rhs);
|
|
2407
|
-
if (r === null) return null;
|
|
2408
|
-
right = r;
|
|
2409
|
-
}
|
|
2410
|
-
if (tailV4) {
|
|
2411
|
-
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
2412
|
-
else left.splice(left.length - 1, 1, ...tailV4);
|
|
2413
|
-
}
|
|
2414
|
-
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
2415
|
-
if (rhs === null && groups.length !== 8) return null;
|
|
2416
|
-
if (rhs !== null && left.length + right.length > 8) return null;
|
|
2417
|
-
if (groups.length !== 8) return null;
|
|
2418
|
-
return groups;
|
|
2419
|
-
}
|
|
2420
|
-
function compressIpv6(g) {
|
|
2421
|
-
let bestStart = -1;
|
|
2422
|
-
let bestLen = 0;
|
|
2423
|
-
let i = 0;
|
|
2424
|
-
while (i < 8) {
|
|
2425
|
-
if (g[i] !== 0) {
|
|
2426
|
-
i++;
|
|
2427
|
-
continue;
|
|
2428
|
-
}
|
|
2429
|
-
let j = i;
|
|
2430
|
-
while (j < 8 && g[j] === 0) j++;
|
|
2431
|
-
if (j - i > bestLen) {
|
|
2432
|
-
bestLen = j - i;
|
|
2433
|
-
bestStart = i;
|
|
2434
|
-
}
|
|
2435
|
-
i = j;
|
|
2436
|
-
}
|
|
2437
|
-
const hex = g.map((x) => x.toString(16));
|
|
2438
|
-
if (bestLen < 2) return hex.join(":");
|
|
2439
|
-
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
2440
|
-
}
|
|
2441
|
-
function normalizeIpLiteral(host) {
|
|
2442
|
-
try {
|
|
2443
|
-
if (typeof host !== "string") return null;
|
|
2444
|
-
let s = host.trim();
|
|
2445
|
-
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
2446
|
-
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
2447
|
-
const zone = s.indexOf("%");
|
|
2448
|
-
if (zone >= 0) s = s.slice(0, zone);
|
|
2449
|
-
if (!s) return null;
|
|
2450
|
-
if (s.includes(":")) {
|
|
2451
|
-
const g = expandIpv6(s);
|
|
2452
|
-
if (!g) return null;
|
|
2453
|
-
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
2454
|
-
if (mapped) {
|
|
2455
|
-
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
2456
|
-
}
|
|
2457
|
-
return compressIpv6(g);
|
|
2458
|
-
}
|
|
2459
|
-
return parseIpv4(s);
|
|
2460
|
-
} catch {
|
|
2461
|
-
return null;
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
function isStrictGatedTier(tier) {
|
|
2465
|
-
return STRICT_TIERS.has(tier);
|
|
2466
|
-
}
|
|
2467
|
-
function ssrfReason(m, asWritten) {
|
|
2468
|
-
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2469
|
-
}
|
|
2470
|
-
function classifySsrf(host) {
|
|
2471
|
-
try {
|
|
2472
|
-
if (typeof host !== "string" || !host) return null;
|
|
2473
|
-
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
2474
|
-
const ip = normalizeIpLiteral(host);
|
|
2475
|
-
if (ip === null) {
|
|
2476
|
-
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
2477
|
-
}
|
|
2478
|
-
const hit = (tier, overridable) => ({
|
|
2479
|
-
tier,
|
|
2480
|
-
overridable,
|
|
2481
|
-
kind: "address",
|
|
2482
|
-
normalized: ip
|
|
2483
|
-
});
|
|
2484
|
-
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
2485
|
-
const o = v4Octets(ip);
|
|
2486
|
-
if (o) {
|
|
2487
|
-
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
2488
|
-
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
2489
|
-
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
2490
|
-
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
2491
|
-
if (o[0] === 127) return hit("private", true);
|
|
2492
|
-
if (o[0] === 10) return hit("private", true);
|
|
2493
|
-
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
2494
|
-
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
2495
|
-
return null;
|
|
2496
|
-
}
|
|
2497
|
-
const g = expandIpv6(ip);
|
|
2498
|
-
if (!g) return null;
|
|
2499
|
-
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
2500
|
-
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
2501
|
-
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
2502
|
-
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
2503
|
-
return null;
|
|
2504
|
-
} catch {
|
|
2505
|
-
return null;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
function ssrfFloor(tokens, opts = {}) {
|
|
2509
|
-
const exempt2 = new Set(
|
|
2510
|
-
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
2511
|
-
);
|
|
2512
|
-
for (const { token, binary } of tokens) {
|
|
2513
|
-
const m = classifySsrf(token);
|
|
2514
|
-
if (!m) continue;
|
|
2515
|
-
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2516
|
-
if (m.overridable && m.normalized && exempt2.has(m.normalized)) continue;
|
|
2517
|
-
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2518
|
-
}
|
|
2519
|
-
return null;
|
|
2520
|
-
}
|
|
2521
2525
|
function bareToolName(toolName) {
|
|
2522
2526
|
const parts = toolName.split("__");
|
|
2523
2527
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
@@ -3723,7 +3727,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3723
3727
|
}
|
|
3724
3728
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3725
3729
|
}
|
|
3726
|
-
var 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,
|
|
3730
|
+
var 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;
|
|
3727
3731
|
var init_dist = __esm({
|
|
3728
3732
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3729
3733
|
"use strict";
|
|
@@ -5161,6 +5165,36 @@ var init_dist = __esm({
|
|
|
5161
5165
|
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
5162
5166
|
NONE = { kind: "none" };
|
|
5163
5167
|
UNKNOWN = { kind: "unknown" };
|
|
5168
|
+
SSRF_MAX_HOST = 253;
|
|
5169
|
+
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
5170
|
+
"169.254.169.254",
|
|
5171
|
+
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
5172
|
+
"169.254.170.2",
|
|
5173
|
+
// AWS ECS task role
|
|
5174
|
+
"168.63.129.16",
|
|
5175
|
+
// Azure WireServer
|
|
5176
|
+
"fd00:ec2::254",
|
|
5177
|
+
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
5178
|
+
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
5179
|
+
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
5180
|
+
// live credential endpoint. It has to be named here, above the range check,
|
|
5181
|
+
// and it is the reason relaxing cgnat is safe.
|
|
5182
|
+
"100.100.100.200"
|
|
5183
|
+
]);
|
|
5184
|
+
STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
5185
|
+
METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
5186
|
+
v4Octets = (a) => {
|
|
5187
|
+
const p = a.split(".");
|
|
5188
|
+
return p.length === 4 ? p.map(Number) : null;
|
|
5189
|
+
};
|
|
5190
|
+
TIER_REASON = {
|
|
5191
|
+
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
5192
|
+
"link-local": "a link-local address",
|
|
5193
|
+
multicast: "a multicast address",
|
|
5194
|
+
unspecified: "the unspecified address, which reaches this host",
|
|
5195
|
+
cgnat: "a carrier-grade NAT address",
|
|
5196
|
+
private: "a loopback or private address"
|
|
5197
|
+
};
|
|
5164
5198
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
5165
5199
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
5166
5200
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -5184,6 +5218,7 @@ var init_dist = __esm({
|
|
|
5184
5218
|
"deb.debian.org",
|
|
5185
5219
|
"*.ubuntu.com"
|
|
5186
5220
|
];
|
|
5221
|
+
PRIVATE_HOST_SUFFIXES = [".local", ".internal", ".localhost"];
|
|
5187
5222
|
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
5188
5223
|
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
5189
5224
|
"curl",
|
|
@@ -5307,36 +5342,6 @@ var init_dist = __esm({
|
|
|
5307
5342
|
socat: /* @__PURE__ */ new Set([])
|
|
5308
5343
|
// socat uses address syntax, not flags — no value-flags
|
|
5309
5344
|
};
|
|
5310
|
-
SSRF_MAX_HOST = 253;
|
|
5311
|
-
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
5312
|
-
"169.254.169.254",
|
|
5313
|
-
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
5314
|
-
"169.254.170.2",
|
|
5315
|
-
// AWS ECS task role
|
|
5316
|
-
"168.63.129.16",
|
|
5317
|
-
// Azure WireServer
|
|
5318
|
-
"fd00:ec2::254",
|
|
5319
|
-
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
5320
|
-
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
5321
|
-
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
5322
|
-
// live credential endpoint. It has to be named here, above the range check,
|
|
5323
|
-
// and it is the reason relaxing cgnat is safe.
|
|
5324
|
-
"100.100.100.200"
|
|
5325
|
-
]);
|
|
5326
|
-
STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
5327
|
-
METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
5328
|
-
v4Octets = (a) => {
|
|
5329
|
-
const p = a.split(".");
|
|
5330
|
-
return p.length === 4 ? p.map(Number) : null;
|
|
5331
|
-
};
|
|
5332
|
-
TIER_REASON = {
|
|
5333
|
-
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
5334
|
-
"link-local": "a link-local address",
|
|
5335
|
-
multicast: "a multicast address",
|
|
5336
|
-
unspecified: "the unspecified address, which reaches this host",
|
|
5337
|
-
cgnat: "a carrier-grade NAT address",
|
|
5338
|
-
private: "a loopback or private address"
|
|
5339
|
-
};
|
|
5340
5345
|
DESTINATION_ARGS = /* @__PURE__ */ new Map([
|
|
5341
5346
|
["webfetch", ["url"]],
|
|
5342
5347
|
["fetch", ["url", "uri"]],
|
package/dist/dashboard.mjs
CHANGED
|
@@ -1944,7 +1944,7 @@ function matchCanaryArgs(args, values) {
|
|
|
1944
1944
|
return null;
|
|
1945
1945
|
}
|
|
1946
1946
|
}
|
|
1947
|
-
var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, 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, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN,
|
|
1947
|
+
var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, 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, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, SOURCE_COMMANDS, TERMINAL_ESCAPE_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, 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;
|
|
1948
1948
|
var init_dist = __esm({
|
|
1949
1949
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1950
1950
|
"use strict";
|
|
@@ -3136,7 +3136,6 @@ var init_dist = __esm({
|
|
|
3136
3136
|
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
3137
3137
|
NONE = { kind: "none" };
|
|
3138
3138
|
UNKNOWN = { kind: "unknown" };
|
|
3139
|
-
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
3140
3139
|
SSRF_MAX_HOST = 253;
|
|
3141
3140
|
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
3142
3141
|
"169.254.169.254",
|
|
@@ -3158,6 +3157,7 @@ var init_dist = __esm({
|
|
|
3158
3157
|
const p = a.split(".");
|
|
3159
3158
|
return p.length === 4 ? p.map(Number) : null;
|
|
3160
3159
|
};
|
|
3160
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
3161
3161
|
TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
3162
3162
|
aws_default = {
|
|
3163
3163
|
name: "aws",
|