@node9/proxy 2.16.0 → 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 +274 -284
- package/dist/cli.mjs +274 -284
- package/dist/dashboard.mjs +4 -4
- package/dist/index.js +220 -215
- package/dist/index.mjs +220 -215
- 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"]],
|
|
@@ -6838,14 +6843,14 @@ function getCredentials() {
|
|
|
6838
6843
|
const creds = JSON.parse(fs4.readFileSync(credPath, "utf-8"));
|
|
6839
6844
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
6840
6845
|
const profile = creds[profileName];
|
|
6841
|
-
if (profile?.apiKey) {
|
|
6846
|
+
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
6842
6847
|
return {
|
|
6843
6848
|
apiKey: profile.apiKey,
|
|
6844
6849
|
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6845
6850
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6846
6851
|
};
|
|
6847
6852
|
}
|
|
6848
|
-
if (creds.apiKey) {
|
|
6853
|
+
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
6849
6854
|
return {
|
|
6850
6855
|
apiKey: creds.apiKey,
|
|
6851
6856
|
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
@@ -9356,23 +9361,8 @@ var init_read_capped = __esm({
|
|
|
9356
9361
|
import fs14 from "fs";
|
|
9357
9362
|
import os11 from "os";
|
|
9358
9363
|
import path15 from "path";
|
|
9359
|
-
function validateApiUrl2(raw) {
|
|
9360
|
-
let u;
|
|
9361
|
-
try {
|
|
9362
|
-
u = new URL(raw);
|
|
9363
|
-
} catch {
|
|
9364
|
-
return null;
|
|
9365
|
-
}
|
|
9366
|
-
if (u.username || u.password) return null;
|
|
9367
|
-
if (u.protocol === "https:") return u;
|
|
9368
|
-
if (u.protocol === "http:") {
|
|
9369
|
-
const h = u.hostname;
|
|
9370
|
-
if (h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]") return u;
|
|
9371
|
-
}
|
|
9372
|
-
return null;
|
|
9373
|
-
}
|
|
9374
9364
|
function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
|
|
9375
|
-
const validated =
|
|
9365
|
+
const validated = validateApiUrl(creds.apiUrl);
|
|
9376
9366
|
if (!validated) {
|
|
9377
9367
|
try {
|
|
9378
9368
|
fs14.appendFileSync(
|
|
@@ -9553,6 +9543,7 @@ var init_cloud = __esm({
|
|
|
9553
9543
|
init_audit();
|
|
9554
9544
|
init_safe_text();
|
|
9555
9545
|
init_read_capped();
|
|
9546
|
+
init_api_url();
|
|
9556
9547
|
DLP_SAMPLE_MAX_LEN = 200;
|
|
9557
9548
|
DLP_PATTERN_MAX_LEN = 100;
|
|
9558
9549
|
KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
|
|
@@ -21560,37 +21551,13 @@ function resolveSyncIntervalMs(settings) {
|
|
|
21560
21551
|
return clamped * 1e3;
|
|
21561
21552
|
}
|
|
21562
21553
|
function readCredentials() {
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
const credPath = path41.join(os36.homedir(), ".node9", "credentials.json");
|
|
21571
|
-
const creds = JSON.parse(fs42.readFileSync(credPath, "utf-8"));
|
|
21572
|
-
const profileName = process.env.NODE9_PROFILE ?? "default";
|
|
21573
|
-
const profile = creds[profileName];
|
|
21574
|
-
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
21575
|
-
return {
|
|
21576
|
-
apiKey: profile.apiKey,
|
|
21577
|
-
apiUrl: typeof profile.apiUrl === "string" ? (
|
|
21578
|
-
// Credentials store the firewall base URL (e.g.
|
|
21579
|
-
// `https://api.node9.ai/api/v1/intercept`) so existing CLI
|
|
21580
|
-
// calls keep working. Sync lives at `/intercept/policies/sync`
|
|
21581
|
-
// — append the suffix when the stored URL ends in `/intercept`.
|
|
21582
|
-
// Anything else is taken as-is so users can override the full
|
|
21583
|
-
// URL via NODE9_API_URL or a non-standard apiUrl.
|
|
21584
|
-
/\/intercept$/.test(profile.apiUrl) ? profile.apiUrl + "/policies/sync" : profile.apiUrl
|
|
21585
|
-
) : DEFAULT_API_URL2
|
|
21586
|
-
};
|
|
21587
|
-
}
|
|
21588
|
-
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
21589
|
-
return { apiKey: creds.apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
21590
|
-
}
|
|
21591
|
-
} catch {
|
|
21592
|
-
}
|
|
21593
|
-
return null;
|
|
21554
|
+
const creds = getCredentials();
|
|
21555
|
+
if (!creds) return null;
|
|
21556
|
+
const base = creds.apiUrl.replace(/\/+$/, "");
|
|
21557
|
+
return {
|
|
21558
|
+
apiKey: creds.apiKey,
|
|
21559
|
+
apiUrl: /\/intercept$/.test(base) ? base + "/policies/sync" : base
|
|
21560
|
+
};
|
|
21594
21561
|
}
|
|
21595
21562
|
function readCachedEtag() {
|
|
21596
21563
|
try {
|
|
@@ -22208,7 +22175,7 @@ function startForensicBroadcast() {
|
|
|
22208
22175
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
22209
22176
|
recurring.unref();
|
|
22210
22177
|
}
|
|
22211
|
-
var FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile,
|
|
22178
|
+
var FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
22212
22179
|
var init_sync = __esm({
|
|
22213
22180
|
"src/daemon/sync.ts"() {
|
|
22214
22181
|
"use strict";
|
|
@@ -22227,7 +22194,6 @@ var init_sync = __esm({
|
|
|
22227
22194
|
init_state2();
|
|
22228
22195
|
init_audit();
|
|
22229
22196
|
init_machine_id();
|
|
22230
|
-
init_api_url();
|
|
22231
22197
|
FINDING_TO_SIGNAL3 = {
|
|
22232
22198
|
dlp: "dlpFindings",
|
|
22233
22199
|
pii: "piiFindings",
|
|
@@ -22242,7 +22208,6 @@ var init_sync = __esm({
|
|
|
22242
22208
|
};
|
|
22243
22209
|
rulesCacheFile = () => path41.join(os36.homedir(), ".node9", "rules-cache.json");
|
|
22244
22210
|
rulesCacheBackupFile = () => path41.join(os36.homedir(), ".node9", "rules-cache.last-good.json");
|
|
22245
|
-
DEFAULT_API_URL2 = `${DEFAULT_API_URL}/policies/sync`;
|
|
22246
22211
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
22247
22212
|
MIN_INTERVAL_SECONDS = 15;
|
|
22248
22213
|
MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
@@ -22353,7 +22318,7 @@ function buildWireRows(chunk2) {
|
|
|
22353
22318
|
return { rows, consumed: lastNl + 1 };
|
|
22354
22319
|
}
|
|
22355
22320
|
function buildBatchEndpoint(rawApiUrl) {
|
|
22356
|
-
const validated =
|
|
22321
|
+
const validated = validateApiUrl(rawApiUrl);
|
|
22357
22322
|
if (!validated) return null;
|
|
22358
22323
|
const base = validated.toString().replace(/\/$/, "").replace(/\/policies\/sync$/, "");
|
|
22359
22324
|
return `${base}/audit/batch`;
|
|
@@ -24318,12 +24283,20 @@ function resolveNode9Binary() {
|
|
|
24318
24283
|
function xmlEscape(s) {
|
|
24319
24284
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
24320
24285
|
}
|
|
24286
|
+
function daemonServiceEnv() {
|
|
24287
|
+
const env = { NODE9_AUTO_STARTED: "1" };
|
|
24288
|
+
const allow = process.env[HOST_ALLOW_ENV]?.trim();
|
|
24289
|
+
if (allow && /^[A-Za-z0-9.*,_ -]+$/.test(allow)) env[HOST_ALLOW_ENV] = allow;
|
|
24290
|
+
return env;
|
|
24291
|
+
}
|
|
24321
24292
|
function launchdPlist(binaryPath) {
|
|
24322
24293
|
const logDir = path47.join(os42.homedir(), ".node9");
|
|
24323
24294
|
const nodePath = xmlEscape(process.execPath);
|
|
24324
24295
|
const scriptPath = xmlEscape(binaryPath);
|
|
24325
24296
|
const outLog = xmlEscape(path47.join(logDir, "daemon.log"));
|
|
24326
24297
|
const errLog = xmlEscape(path47.join(logDir, "daemon-error.log"));
|
|
24298
|
+
const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
24299
|
+
<string>${xmlEscape(v)}</string>`).join("\n");
|
|
24327
24300
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
24328
24301
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
24329
24302
|
<plist version="1.0">
|
|
@@ -24348,8 +24321,7 @@ function launchdPlist(binaryPath) {
|
|
|
24348
24321
|
<string>${errLog}</string>
|
|
24349
24322
|
<key>EnvironmentVariables</key>
|
|
24350
24323
|
<dict>
|
|
24351
|
-
|
|
24352
|
-
<string>1</string>
|
|
24324
|
+
${envEntries}
|
|
24353
24325
|
</dict>
|
|
24354
24326
|
</dict>
|
|
24355
24327
|
</plist>
|
|
@@ -24378,6 +24350,7 @@ function isLaunchdInstalled() {
|
|
|
24378
24350
|
return fs48.existsSync(LAUNCHD_PLIST);
|
|
24379
24351
|
}
|
|
24380
24352
|
function systemdUnit(binaryPath) {
|
|
24353
|
+
const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
24381
24354
|
return `[Unit]
|
|
24382
24355
|
Description=node9 approval daemon
|
|
24383
24356
|
After=network.target
|
|
@@ -24387,7 +24360,7 @@ Type=simple
|
|
|
24387
24360
|
ExecStart=${process.execPath} ${binaryPath} daemon
|
|
24388
24361
|
Restart=on-failure
|
|
24389
24362
|
RestartSec=10s
|
|
24390
|
-
|
|
24363
|
+
${envLines}
|
|
24391
24364
|
|
|
24392
24365
|
[Install]
|
|
24393
24366
|
WantedBy=default.target
|
|
@@ -24443,7 +24416,10 @@ function windowsLauncherVbs(nodePath, scriptPath) {
|
|
|
24443
24416
|
"' Auto-generated by node9 - starts the approval daemon with no console window.",
|
|
24444
24417
|
"' Recreated by `node9 daemon install`; removed by `node9 daemon uninstall`.",
|
|
24445
24418
|
'Set sh = CreateObject("Wscript.Shell")',
|
|
24446
|
-
|
|
24419
|
+
// Values are vetted by daemonServiceEnv: no double quote can reach here.
|
|
24420
|
+
...Object.entries(daemonServiceEnv()).map(
|
|
24421
|
+
([k, v]) => `sh.Environment("PROCESS")("${k}") = "${v}"`
|
|
24422
|
+
),
|
|
24447
24423
|
`sh.Run """${nodePath}"" ""${scriptPath}"" daemon", 0, False`,
|
|
24448
24424
|
""
|
|
24449
24425
|
].join("\r\n");
|
|
@@ -24650,6 +24626,7 @@ var LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT, STARTUP_FILE,
|
|
|
24650
24626
|
var init_service = __esm({
|
|
24651
24627
|
"src/daemon/service.ts"() {
|
|
24652
24628
|
"use strict";
|
|
24629
|
+
init_api_url();
|
|
24653
24630
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
24654
24631
|
LAUNCHD_PLIST = path47.join(os42.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
24655
24632
|
SYSTEMD_UNIT_DIR = path47.join(os42.homedir(), ".config", "systemd", "user");
|
|
@@ -52313,12 +52290,18 @@ import * as os44 from "os";
|
|
|
52313
52290
|
import chalk11 from "chalk";
|
|
52314
52291
|
|
|
52315
52292
|
// src/auth/cloud-endpoints.ts
|
|
52293
|
+
init_api_url();
|
|
52316
52294
|
var PROD_BASE = "https://api.node9.ai/api/v1";
|
|
52317
52295
|
function resolveCloudEndpoint(pathSuffix, override) {
|
|
52296
|
+
const raw = override || process.env.NODE9_API_URL;
|
|
52297
|
+
if (!raw) return PROD_BASE + pathSuffix;
|
|
52298
|
+
if (!validateApiUrl(raw)) {
|
|
52299
|
+
throw new Error(
|
|
52300
|
+
`${override ? "--api-url" : "NODE9_API_URL"} "${raw.slice(0, 200)}" is not an allowed node9 host. For a self-hosted control plane set ${HOST_ALLOW_ENV}=<your-domain>.`
|
|
52301
|
+
);
|
|
52302
|
+
}
|
|
52318
52303
|
if (override) return override;
|
|
52319
|
-
|
|
52320
|
-
if (base) return base.replace(/\/intercept\/?$/, "") + pathSuffix;
|
|
52321
|
-
return PROD_BASE + pathSuffix;
|
|
52304
|
+
return raw.replace(/\/intercept\/?$/, "") + pathSuffix;
|
|
52322
52305
|
}
|
|
52323
52306
|
|
|
52324
52307
|
// src/auth/device-login.ts
|
|
@@ -52479,14 +52462,27 @@ import * as os45 from "os";
|
|
|
52479
52462
|
import * as path49 from "path";
|
|
52480
52463
|
import chalk12 from "chalk";
|
|
52481
52464
|
init_safe_text();
|
|
52465
|
+
init_api_url();
|
|
52482
52466
|
async function revokeSelf(creds) {
|
|
52483
|
-
|
|
52467
|
+
let rejected = null;
|
|
52468
|
+
const base = safeApiUrl(creds.apiUrl, (raw) => {
|
|
52469
|
+
rejected = String(raw).slice(0, 200);
|
|
52470
|
+
}).replace(/\/$/, "");
|
|
52471
|
+
const url = base + "/machines/self/disconnect";
|
|
52484
52472
|
try {
|
|
52485
52473
|
const r = await postJson2(url, {}, creds.apiKey);
|
|
52486
52474
|
return { outcome: "revoked", name: r.name };
|
|
52487
52475
|
} catch (e) {
|
|
52488
52476
|
const msg = e instanceof Error ? e.message : String(e);
|
|
52489
|
-
if (/HTTP 401/.test(msg))
|
|
52477
|
+
if (/HTTP 401/.test(msg)) {
|
|
52478
|
+
if (rejected) {
|
|
52479
|
+
return {
|
|
52480
|
+
outcome: "unreachable",
|
|
52481
|
+
detail: `stored apiUrl ${rejected} is not an allowed host (set ${HOST_ALLOW_ENV} for self-hosted); the revoke went to ${base} and was refused`
|
|
52482
|
+
};
|
|
52483
|
+
}
|
|
52484
|
+
return { outcome: "already" };
|
|
52485
|
+
}
|
|
52490
52486
|
return { outcome: "unreachable", detail: msg };
|
|
52491
52487
|
}
|
|
52492
52488
|
}
|
|
@@ -52506,10 +52502,7 @@ function registerLogoutCommand(program2) {
|
|
|
52506
52502
|
console.log(chalk12.gray("Not logged in \u2014 nothing to disconnect."));
|
|
52507
52503
|
return;
|
|
52508
52504
|
}
|
|
52509
|
-
const res = await revokeSelf({
|
|
52510
|
-
apiKey: entry.apiKey,
|
|
52511
|
-
apiUrl: entry.apiUrl || "https://api.node9.ai/api/v1/intercept"
|
|
52512
|
-
});
|
|
52505
|
+
const res = await revokeSelf({ apiKey: entry.apiKey, apiUrl: entry.apiUrl });
|
|
52513
52506
|
if (res.outcome === "revoked") {
|
|
52514
52507
|
console.log(
|
|
52515
52508
|
chalk12.green(
|
|
@@ -62635,10 +62628,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
62635
62628
|
const prof = JSON.parse(credRaw)[process.env.NODE9_PROFILE || "default"];
|
|
62636
62629
|
if (prof?.apiKey) {
|
|
62637
62630
|
console.log(chalk45.bold("Disconnecting from the cloud..."));
|
|
62638
|
-
const r = await revokeSelf({
|
|
62639
|
-
apiKey: prof.apiKey,
|
|
62640
|
-
apiUrl: prof.apiUrl || "https://api.node9.ai/api/v1/intercept"
|
|
62641
|
-
});
|
|
62631
|
+
const r = await revokeSelf({ apiKey: prof.apiKey, apiUrl: prof.apiUrl });
|
|
62642
62632
|
if (r.outcome === "revoked") {
|
|
62643
62633
|
console.log(chalk45.green(" \u2705 Machine disconnected \u2014 its key is revoked"));
|
|
62644
62634
|
} else if (r.outcome === "already") {
|