@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.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"]],
|
|
@@ -6831,14 +6836,14 @@ function getCredentials() {
|
|
|
6831
6836
|
const creds = JSON.parse(import_fs4.default.readFileSync(credPath, "utf-8"));
|
|
6832
6837
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
6833
6838
|
const profile = creds[profileName];
|
|
6834
|
-
if (profile?.apiKey) {
|
|
6839
|
+
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
6835
6840
|
return {
|
|
6836
6841
|
apiKey: profile.apiKey,
|
|
6837
6842
|
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6838
6843
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6839
6844
|
};
|
|
6840
6845
|
}
|
|
6841
|
-
if (creds.apiKey) {
|
|
6846
|
+
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
6842
6847
|
return {
|
|
6843
6848
|
apiKey: creds.apiKey,
|
|
6844
6849
|
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
@@ -9352,23 +9357,8 @@ var init_read_capped = __esm({
|
|
|
9352
9357
|
});
|
|
9353
9358
|
|
|
9354
9359
|
// src/auth/cloud.ts
|
|
9355
|
-
function validateApiUrl2(raw) {
|
|
9356
|
-
let u;
|
|
9357
|
-
try {
|
|
9358
|
-
u = new URL(raw);
|
|
9359
|
-
} catch {
|
|
9360
|
-
return null;
|
|
9361
|
-
}
|
|
9362
|
-
if (u.username || u.password) return null;
|
|
9363
|
-
if (u.protocol === "https:") return u;
|
|
9364
|
-
if (u.protocol === "http:") {
|
|
9365
|
-
const h = u.hostname;
|
|
9366
|
-
if (h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]") return u;
|
|
9367
|
-
}
|
|
9368
|
-
return null;
|
|
9369
|
-
}
|
|
9370
9360
|
function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
|
|
9371
|
-
const validated =
|
|
9361
|
+
const validated = validateApiUrl(creds.apiUrl);
|
|
9372
9362
|
if (!validated) {
|
|
9373
9363
|
try {
|
|
9374
9364
|
import_fs14.default.appendFileSync(
|
|
@@ -9552,6 +9542,7 @@ var init_cloud = __esm({
|
|
|
9552
9542
|
init_audit();
|
|
9553
9543
|
init_safe_text();
|
|
9554
9544
|
init_read_capped();
|
|
9545
|
+
init_api_url();
|
|
9555
9546
|
DLP_SAMPLE_MAX_LEN = 200;
|
|
9556
9547
|
DLP_PATTERN_MAX_LEN = 100;
|
|
9557
9548
|
KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
|
|
@@ -21565,37 +21556,13 @@ function resolveSyncIntervalMs(settings) {
|
|
|
21565
21556
|
return clamped * 1e3;
|
|
21566
21557
|
}
|
|
21567
21558
|
function readCredentials() {
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
|
|
21571
|
-
|
|
21572
|
-
|
|
21573
|
-
|
|
21574
|
-
|
|
21575
|
-
const credPath = import_path38.default.join(import_os34.default.homedir(), ".node9", "credentials.json");
|
|
21576
|
-
const creds = JSON.parse(import_fs39.default.readFileSync(credPath, "utf-8"));
|
|
21577
|
-
const profileName = process.env.NODE9_PROFILE ?? "default";
|
|
21578
|
-
const profile = creds[profileName];
|
|
21579
|
-
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
21580
|
-
return {
|
|
21581
|
-
apiKey: profile.apiKey,
|
|
21582
|
-
apiUrl: typeof profile.apiUrl === "string" ? (
|
|
21583
|
-
// Credentials store the firewall base URL (e.g.
|
|
21584
|
-
// `https://api.node9.ai/api/v1/intercept`) so existing CLI
|
|
21585
|
-
// calls keep working. Sync lives at `/intercept/policies/sync`
|
|
21586
|
-
// — append the suffix when the stored URL ends in `/intercept`.
|
|
21587
|
-
// Anything else is taken as-is so users can override the full
|
|
21588
|
-
// URL via NODE9_API_URL or a non-standard apiUrl.
|
|
21589
|
-
/\/intercept$/.test(profile.apiUrl) ? profile.apiUrl + "/policies/sync" : profile.apiUrl
|
|
21590
|
-
) : DEFAULT_API_URL2
|
|
21591
|
-
};
|
|
21592
|
-
}
|
|
21593
|
-
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
21594
|
-
return { apiKey: creds.apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
21595
|
-
}
|
|
21596
|
-
} catch {
|
|
21597
|
-
}
|
|
21598
|
-
return null;
|
|
21559
|
+
const creds = getCredentials();
|
|
21560
|
+
if (!creds) return null;
|
|
21561
|
+
const base = creds.apiUrl.replace(/\/+$/, "");
|
|
21562
|
+
return {
|
|
21563
|
+
apiKey: creds.apiKey,
|
|
21564
|
+
apiUrl: /\/intercept$/.test(base) ? base + "/policies/sync" : base
|
|
21565
|
+
};
|
|
21599
21566
|
}
|
|
21600
21567
|
function readCachedEtag() {
|
|
21601
21568
|
try {
|
|
@@ -22213,7 +22180,7 @@ function startForensicBroadcast() {
|
|
|
22213
22180
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
22214
22181
|
recurring.unref();
|
|
22215
22182
|
}
|
|
22216
|
-
var import_fs39, import_https4, import_os34, import_path38, FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile,
|
|
22183
|
+
var import_fs39, import_https4, import_os34, import_path38, 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;
|
|
22217
22184
|
var init_sync = __esm({
|
|
22218
22185
|
"src/daemon/sync.ts"() {
|
|
22219
22186
|
"use strict";
|
|
@@ -22236,7 +22203,6 @@ var init_sync = __esm({
|
|
|
22236
22203
|
init_state2();
|
|
22237
22204
|
init_audit();
|
|
22238
22205
|
init_machine_id();
|
|
22239
|
-
init_api_url();
|
|
22240
22206
|
FINDING_TO_SIGNAL3 = {
|
|
22241
22207
|
dlp: "dlpFindings",
|
|
22242
22208
|
pii: "piiFindings",
|
|
@@ -22251,7 +22217,6 @@ var init_sync = __esm({
|
|
|
22251
22217
|
};
|
|
22252
22218
|
rulesCacheFile = () => import_path38.default.join(import_os34.default.homedir(), ".node9", "rules-cache.json");
|
|
22253
22219
|
rulesCacheBackupFile = () => import_path38.default.join(import_os34.default.homedir(), ".node9", "rules-cache.last-good.json");
|
|
22254
|
-
DEFAULT_API_URL2 = `${DEFAULT_API_URL}/policies/sync`;
|
|
22255
22220
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
22256
22221
|
MIN_INTERVAL_SECONDS = 15;
|
|
22257
22222
|
MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
@@ -22358,7 +22323,7 @@ function buildWireRows(chunk2) {
|
|
|
22358
22323
|
return { rows, consumed: lastNl + 1 };
|
|
22359
22324
|
}
|
|
22360
22325
|
function buildBatchEndpoint(rawApiUrl) {
|
|
22361
|
-
const validated =
|
|
22326
|
+
const validated = validateApiUrl(rawApiUrl);
|
|
22362
22327
|
if (!validated) return null;
|
|
22363
22328
|
const base = validated.toString().replace(/\/$/, "").replace(/\/policies\/sync$/, "");
|
|
22364
22329
|
return `${base}/audit/batch`;
|
|
@@ -24324,12 +24289,20 @@ function resolveNode9Binary() {
|
|
|
24324
24289
|
function xmlEscape(s) {
|
|
24325
24290
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
24326
24291
|
}
|
|
24292
|
+
function daemonServiceEnv() {
|
|
24293
|
+
const env = { NODE9_AUTO_STARTED: "1" };
|
|
24294
|
+
const allow = process.env[HOST_ALLOW_ENV]?.trim();
|
|
24295
|
+
if (allow && /^[A-Za-z0-9.*,_ -]+$/.test(allow)) env[HOST_ALLOW_ENV] = allow;
|
|
24296
|
+
return env;
|
|
24297
|
+
}
|
|
24327
24298
|
function launchdPlist(binaryPath) {
|
|
24328
24299
|
const logDir = import_path44.default.join(import_os40.default.homedir(), ".node9");
|
|
24329
24300
|
const nodePath = xmlEscape(process.execPath);
|
|
24330
24301
|
const scriptPath = xmlEscape(binaryPath);
|
|
24331
24302
|
const outLog = xmlEscape(import_path44.default.join(logDir, "daemon.log"));
|
|
24332
24303
|
const errLog = xmlEscape(import_path44.default.join(logDir, "daemon-error.log"));
|
|
24304
|
+
const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
24305
|
+
<string>${xmlEscape(v)}</string>`).join("\n");
|
|
24333
24306
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
24334
24307
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
24335
24308
|
<plist version="1.0">
|
|
@@ -24354,8 +24327,7 @@ function launchdPlist(binaryPath) {
|
|
|
24354
24327
|
<string>${errLog}</string>
|
|
24355
24328
|
<key>EnvironmentVariables</key>
|
|
24356
24329
|
<dict>
|
|
24357
|
-
|
|
24358
|
-
<string>1</string>
|
|
24330
|
+
${envEntries}
|
|
24359
24331
|
</dict>
|
|
24360
24332
|
</dict>
|
|
24361
24333
|
</plist>
|
|
@@ -24384,6 +24356,7 @@ function isLaunchdInstalled() {
|
|
|
24384
24356
|
return import_fs45.default.existsSync(LAUNCHD_PLIST);
|
|
24385
24357
|
}
|
|
24386
24358
|
function systemdUnit(binaryPath) {
|
|
24359
|
+
const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
24387
24360
|
return `[Unit]
|
|
24388
24361
|
Description=node9 approval daemon
|
|
24389
24362
|
After=network.target
|
|
@@ -24393,7 +24366,7 @@ Type=simple
|
|
|
24393
24366
|
ExecStart=${process.execPath} ${binaryPath} daemon
|
|
24394
24367
|
Restart=on-failure
|
|
24395
24368
|
RestartSec=10s
|
|
24396
|
-
|
|
24369
|
+
${envLines}
|
|
24397
24370
|
|
|
24398
24371
|
[Install]
|
|
24399
24372
|
WantedBy=default.target
|
|
@@ -24449,7 +24422,10 @@ function windowsLauncherVbs(nodePath, scriptPath) {
|
|
|
24449
24422
|
"' Auto-generated by node9 - starts the approval daemon with no console window.",
|
|
24450
24423
|
"' Recreated by `node9 daemon install`; removed by `node9 daemon uninstall`.",
|
|
24451
24424
|
'Set sh = CreateObject("Wscript.Shell")',
|
|
24452
|
-
|
|
24425
|
+
// Values are vetted by daemonServiceEnv: no double quote can reach here.
|
|
24426
|
+
...Object.entries(daemonServiceEnv()).map(
|
|
24427
|
+
([k, v]) => `sh.Environment("PROCESS")("${k}") = "${v}"`
|
|
24428
|
+
),
|
|
24453
24429
|
`sh.Run """${nodePath}"" ""${scriptPath}"" daemon", 0, False`,
|
|
24454
24430
|
""
|
|
24455
24431
|
].join("\r\n");
|
|
@@ -24660,6 +24636,7 @@ var init_service = __esm({
|
|
|
24660
24636
|
import_path44 = __toESM(require("path"));
|
|
24661
24637
|
import_os40 = __toESM(require("os"));
|
|
24662
24638
|
import_child_process3 = require("child_process");
|
|
24639
|
+
init_api_url();
|
|
24663
24640
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
24664
24641
|
LAUNCHD_PLIST = import_path44.default.join(import_os40.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
24665
24642
|
SYSTEMD_UNIT_DIR = import_path44.default.join(import_os40.default.homedir(), ".config", "systemd", "user");
|
|
@@ -52323,12 +52300,18 @@ var os44 = __toESM(require("os"));
|
|
|
52323
52300
|
var import_chalk11 = __toESM(require("chalk"));
|
|
52324
52301
|
|
|
52325
52302
|
// src/auth/cloud-endpoints.ts
|
|
52303
|
+
init_api_url();
|
|
52326
52304
|
var PROD_BASE = "https://api.node9.ai/api/v1";
|
|
52327
52305
|
function resolveCloudEndpoint(pathSuffix, override) {
|
|
52306
|
+
const raw = override || process.env.NODE9_API_URL;
|
|
52307
|
+
if (!raw) return PROD_BASE + pathSuffix;
|
|
52308
|
+
if (!validateApiUrl(raw)) {
|
|
52309
|
+
throw new Error(
|
|
52310
|
+
`${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>.`
|
|
52311
|
+
);
|
|
52312
|
+
}
|
|
52328
52313
|
if (override) return override;
|
|
52329
|
-
|
|
52330
|
-
if (base) return base.replace(/\/intercept\/?$/, "") + pathSuffix;
|
|
52331
|
-
return PROD_BASE + pathSuffix;
|
|
52314
|
+
return raw.replace(/\/intercept\/?$/, "") + pathSuffix;
|
|
52332
52315
|
}
|
|
52333
52316
|
|
|
52334
52317
|
// src/auth/device-login.ts
|
|
@@ -52489,14 +52472,27 @@ var os45 = __toESM(require("os"));
|
|
|
52489
52472
|
var path49 = __toESM(require("path"));
|
|
52490
52473
|
var import_chalk12 = __toESM(require("chalk"));
|
|
52491
52474
|
init_safe_text();
|
|
52475
|
+
init_api_url();
|
|
52492
52476
|
async function revokeSelf(creds) {
|
|
52493
|
-
|
|
52477
|
+
let rejected = null;
|
|
52478
|
+
const base = safeApiUrl(creds.apiUrl, (raw) => {
|
|
52479
|
+
rejected = String(raw).slice(0, 200);
|
|
52480
|
+
}).replace(/\/$/, "");
|
|
52481
|
+
const url = base + "/machines/self/disconnect";
|
|
52494
52482
|
try {
|
|
52495
52483
|
const r = await postJson2(url, {}, creds.apiKey);
|
|
52496
52484
|
return { outcome: "revoked", name: r.name };
|
|
52497
52485
|
} catch (e) {
|
|
52498
52486
|
const msg = e instanceof Error ? e.message : String(e);
|
|
52499
|
-
if (/HTTP 401/.test(msg))
|
|
52487
|
+
if (/HTTP 401/.test(msg)) {
|
|
52488
|
+
if (rejected) {
|
|
52489
|
+
return {
|
|
52490
|
+
outcome: "unreachable",
|
|
52491
|
+
detail: `stored apiUrl ${rejected} is not an allowed host (set ${HOST_ALLOW_ENV} for self-hosted); the revoke went to ${base} and was refused`
|
|
52492
|
+
};
|
|
52493
|
+
}
|
|
52494
|
+
return { outcome: "already" };
|
|
52495
|
+
}
|
|
52500
52496
|
return { outcome: "unreachable", detail: msg };
|
|
52501
52497
|
}
|
|
52502
52498
|
}
|
|
@@ -52516,10 +52512,7 @@ function registerLogoutCommand(program2) {
|
|
|
52516
52512
|
console.log(import_chalk12.default.gray("Not logged in \u2014 nothing to disconnect."));
|
|
52517
52513
|
return;
|
|
52518
52514
|
}
|
|
52519
|
-
const res = await revokeSelf({
|
|
52520
|
-
apiKey: entry.apiKey,
|
|
52521
|
-
apiUrl: entry.apiUrl || "https://api.node9.ai/api/v1/intercept"
|
|
52522
|
-
});
|
|
52515
|
+
const res = await revokeSelf({ apiKey: entry.apiKey, apiUrl: entry.apiUrl });
|
|
52523
52516
|
if (res.outcome === "revoked") {
|
|
52524
52517
|
console.log(
|
|
52525
52518
|
import_chalk12.default.green(
|
|
@@ -62645,10 +62638,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
62645
62638
|
const prof = JSON.parse(credRaw)[process.env.NODE9_PROFILE || "default"];
|
|
62646
62639
|
if (prof?.apiKey) {
|
|
62647
62640
|
console.log(import_chalk45.default.bold("Disconnecting from the cloud..."));
|
|
62648
|
-
const r = await revokeSelf({
|
|
62649
|
-
apiKey: prof.apiKey,
|
|
62650
|
-
apiUrl: prof.apiUrl || "https://api.node9.ai/api/v1/intercept"
|
|
62651
|
-
});
|
|
62641
|
+
const r = await revokeSelf({ apiKey: prof.apiKey, apiUrl: prof.apiUrl });
|
|
62652
62642
|
if (r.outcome === "revoked") {
|
|
62653
62643
|
console.log(import_chalk45.default.green(" \u2705 Machine disconnected \u2014 its key is revoked"));
|
|
62654
62644
|
} else if (r.outcome === "already") {
|