@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 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
- if (h === "localhost" || h === "0.0.0.0") return true;
2110
- if (h.endsWith(".local") || h.endsWith(".internal") || h.endsWith(".localhost")) return true;
2111
- if (/^127\./.test(h)) return true;
2112
- if (/^10\./.test(h)) return true;
2113
- if (/^192\.168\./.test(h)) return true;
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, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, 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;
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 = validateApiUrl2(creds.apiUrl);
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
- if (process.env.NODE9_API_KEY) {
21569
- return {
21570
- apiKey: process.env.NODE9_API_KEY,
21571
- apiUrl: process.env.NODE9_API_URL ?? DEFAULT_API_URL2
21572
- };
21573
- }
21574
- try {
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, DEFAULT_API_URL2, 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;
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 = validateApiUrl2(rawApiUrl);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
- <key>NODE9_AUTO_STARTED</key>
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
- Environment=NODE9_AUTO_STARTED=1
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
- 'sh.Environment("PROCESS")("NODE9_AUTO_STARTED") = "1"',
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
- const base = process.env.NODE9_API_URL;
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
- const url = creds.apiUrl.replace(/\/$/, "") + "/machines/self/disconnect";
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)) return { outcome: "already" };
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") {