@node9/proxy 2.43.0 → 2.44.0
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 +1228 -1100
- package/dist/cli.mjs +1192 -1064
- package/dist/dashboard.mjs +656 -156
- package/dist/index.js +185 -105
- package/dist/index.mjs +185 -105
- package/dist/scan-ink.mjs +5 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -5504,7 +5504,10 @@ function classify(inv) {
|
|
|
5504
5504
|
out.push({ ...f, shown: inv.shown, reason: `${f.reason}: ${inv.shown}` });
|
|
5505
5505
|
};
|
|
5506
5506
|
if (hooksBypass(inv)) found(HOOKS_BYPASS);
|
|
5507
|
-
|
|
5507
|
+
const staged = stagesEverything(inv);
|
|
5508
|
+
if (staged) {
|
|
5509
|
+
found(staged.ifWorkTreeTop === void 0 ? STAGE_ALL : { ...STAGE_ALL, ...staged });
|
|
5510
|
+
}
|
|
5508
5511
|
if (inv.sub === null) {
|
|
5509
5512
|
found(UNREADABLE);
|
|
5510
5513
|
return out;
|
|
@@ -5565,12 +5568,35 @@ function appliedValue(env, name) {
|
|
|
5565
5568
|
const word = f?.Stmts?.[0]?.Cmd?.Args?.[1];
|
|
5566
5569
|
return word ? resolveWordLiteral(word) : null;
|
|
5567
5570
|
}
|
|
5571
|
+
function lexicalPath(p) {
|
|
5572
|
+
if (/^[\\/~$]/.test(p) || /^[A-Za-z]:/.test(p)) return null;
|
|
5573
|
+
const parts = [];
|
|
5574
|
+
let climbed = false;
|
|
5575
|
+
for (const part of p.split(/[\\/]+/)) {
|
|
5576
|
+
if (part === "" || part === ".") continue;
|
|
5577
|
+
if (part === "..") {
|
|
5578
|
+
if (parts.length === 0) climbed = true;
|
|
5579
|
+
else parts.pop();
|
|
5580
|
+
} else parts.push(part);
|
|
5581
|
+
}
|
|
5582
|
+
return { parts, climbed };
|
|
5583
|
+
}
|
|
5584
|
+
function hooksPathBypasses(value) {
|
|
5585
|
+
if (value === null || value === void 0) return true;
|
|
5586
|
+
const v = value.trim();
|
|
5587
|
+
if (v === "" || v === "true") return true;
|
|
5588
|
+
const lp = lexicalPath(v);
|
|
5589
|
+
if (lp === null || lp.climbed || lp.parts.length === 0) return true;
|
|
5590
|
+
return lp.parts[0] === ".git" && lp.parts[1] !== "hooks";
|
|
5591
|
+
}
|
|
5568
5592
|
function hooksBypass(inv) {
|
|
5569
|
-
if (inv.config.has("core.hookspath")
|
|
5593
|
+
if (inv.config.has("core.hookspath") && hooksPathBypasses(inv.config.get("core.hookspath")))
|
|
5594
|
+
return true;
|
|
5570
5595
|
for (const [name, value] of inv.env) {
|
|
5571
5596
|
if (name === "GIT_CONFIG_PARAMETERS" && value !== null && /core\.hookspath/i.test(value))
|
|
5572
5597
|
return true;
|
|
5573
|
-
|
|
5598
|
+
const key = /^GIT_CONFIG_KEY_(\d+)$/.exec(name);
|
|
5599
|
+
if (key && value !== null && value.toLowerCase() === "core.hookspath" && hooksPathBypasses(inv.env.get(`GIT_CONFIG_VALUE_${key[1]}`)))
|
|
5574
5600
|
return true;
|
|
5575
5601
|
}
|
|
5576
5602
|
for (const name of HOOK_KILL_SWITCHES) {
|
|
@@ -5612,7 +5638,10 @@ function configWritesHooksPath(args) {
|
|
|
5612
5638
|
}
|
|
5613
5639
|
const key = args[i];
|
|
5614
5640
|
if (key === null || key === void 0 || key.toLowerCase() !== "core.hookspath") return false;
|
|
5615
|
-
|
|
5641
|
+
const unset = args.slice(0, i).some((w) => w === "--unset" || w === "--unset-all") || args[0] === "unset";
|
|
5642
|
+
if (unset) return true;
|
|
5643
|
+
if (i + 1 < args.length) return hooksPathBypasses(args[i + 1]);
|
|
5644
|
+
return write2;
|
|
5616
5645
|
}
|
|
5617
5646
|
function isHookPath(word) {
|
|
5618
5647
|
return /(^|[\\/])(\.git[\\/]hooks|\.husky)([\\/]|$)/.test(word);
|
|
@@ -5647,31 +5676,49 @@ function chmodDisarmsHook(args, shown) {
|
|
|
5647
5676
|
function wholeTree(operand) {
|
|
5648
5677
|
if (/^:\(top\)\.?$/.test(operand) || operand === ":/" || operand === "*") return true;
|
|
5649
5678
|
if (operand.startsWith(":")) return false;
|
|
5679
|
+
const lp = lexicalPath(operand);
|
|
5680
|
+
return lp !== null && lp.parts.length === 0;
|
|
5681
|
+
}
|
|
5682
|
+
function chdirPrefix(inv) {
|
|
5683
|
+
const dirs = inv.chdir ?? [];
|
|
5684
|
+
if (dirs.length === 0) return null;
|
|
5650
5685
|
const parts = [];
|
|
5651
|
-
for (const
|
|
5652
|
-
if (
|
|
5653
|
-
|
|
5654
|
-
|
|
5686
|
+
for (const d of dirs) {
|
|
5687
|
+
if (d === null || d === "") return null;
|
|
5688
|
+
const lp = lexicalPath(d);
|
|
5689
|
+
if (lp === null || lp.climbed) return null;
|
|
5690
|
+
parts.push(...lp.parts);
|
|
5655
5691
|
}
|
|
5656
|
-
return parts.length === 0
|
|
5692
|
+
return parts.length === 0 ? null : parts.join("/");
|
|
5657
5693
|
}
|
|
5658
5694
|
function stagesEverything(inv) {
|
|
5659
5695
|
const sub = inv.sub?.toLowerCase();
|
|
5696
|
+
const prefix = chdirPrefix(inv);
|
|
5697
|
+
const judge = (operands2) => {
|
|
5698
|
+
let conditional = false;
|
|
5699
|
+
for (const operand of operands2) {
|
|
5700
|
+
if (!wholeTree(operand)) continue;
|
|
5701
|
+
if (prefix === null || operand.startsWith(":") || operand.startsWith("/")) return {};
|
|
5702
|
+
if (wholeTree(`${prefix}/${operand === "*" ? "." : operand}`)) return {};
|
|
5703
|
+
conditional = true;
|
|
5704
|
+
}
|
|
5705
|
+
return conditional && prefix !== null ? { ifWorkTreeTop: prefix } : null;
|
|
5706
|
+
};
|
|
5660
5707
|
if (sub === "add") {
|
|
5661
5708
|
const o = parseOptions(inv.args, "", ADD_LONG_VALUE);
|
|
5662
5709
|
if (o.flags.has("-n") || o.flags.has("--dry-run") || o.flags.has("-i") || o.flags.has("--interactive") || o.flags.has("-p") || o.flags.has("--patch") || o.flags.has("-e") || o.flags.has("--edit"))
|
|
5663
|
-
return
|
|
5710
|
+
return null;
|
|
5664
5711
|
const all = o.flags.has("-A") || o.flags.has("--all") || o.flags.has("--no-ignore-removal") || o.flags.has("-u") || o.flags.has("--update");
|
|
5665
|
-
if (all && o.operands.length === 0) return
|
|
5666
|
-
return o.operands
|
|
5712
|
+
if (all && o.operands.length === 0) return {};
|
|
5713
|
+
return judge(o.operands);
|
|
5667
5714
|
}
|
|
5668
5715
|
if (sub === "commit") {
|
|
5669
5716
|
const o = parseOptions(inv.args, "mFCcut", COMMIT_LONG_VALUE);
|
|
5670
|
-
if (o.flags.has("--dry-run")) return
|
|
5671
|
-
if (o.flags.has("-a") || o.flags.has("--all")) return
|
|
5672
|
-
return o.operands
|
|
5717
|
+
if (o.flags.has("--dry-run")) return null;
|
|
5718
|
+
if (o.flags.has("-a") || o.flags.has("--all")) return {};
|
|
5719
|
+
return judge(o.operands);
|
|
5673
5720
|
}
|
|
5674
|
-
return
|
|
5721
|
+
return null;
|
|
5675
5722
|
}
|
|
5676
5723
|
function hookTextRunsNothing(text) {
|
|
5677
5724
|
return text.split(/\r?\n/).every((line) => INERT_LINE_RE.test(line.trim()));
|
|
@@ -7724,7 +7771,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hostHooks =
|
|
|
7724
7771
|
}
|
|
7725
7772
|
async function evaluatePolicyCore(config, toolName, args, context = {}, hooks = {}) {
|
|
7726
7773
|
const { agent, cwd, activeEnvironment } = context;
|
|
7727
|
-
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2, isConfiguredRegistry } = hooks;
|
|
7774
|
+
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2, isConfiguredRegistry, isGitWorkTreeTop: isGitWorkTreeTop2 } = hooks;
|
|
7728
7775
|
const settingsView = catalogSettings(config);
|
|
7729
7776
|
const layers = inspectionLayers(config.policy);
|
|
7730
7777
|
const cv = (id) => checkValue(settingsView, id);
|
|
@@ -7878,6 +7925,8 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
7878
7925
|
const action = cv(id);
|
|
7879
7926
|
const finding = action === "off" ? void 0 : git.findings.find((f) => f.checkId === id);
|
|
7880
7927
|
if (!finding) continue;
|
|
7928
|
+
if (finding.ifWorkTreeTop !== void 0 && isGitWorkTreeTop2 && cwd && !isGitWorkTreeTop2(finding.ifWorkTreeTop, cwd))
|
|
7929
|
+
continue;
|
|
7881
7930
|
const v = applyCheckValue(action, {
|
|
7882
7931
|
decision: "review",
|
|
7883
7932
|
blockedByLabel: `Node9 (AST): ${finding.ruleName}`,
|
|
@@ -17200,6 +17249,7 @@ zoo`.split("\n");
|
|
|
17200
17249
|
/** The words after `git`: global options, then the subcommand. */
|
|
17201
17250
|
visitGit(rest, env, depth, viaXargs, fromAlias, shown) {
|
|
17202
17251
|
const config = /* @__PURE__ */ new Map();
|
|
17252
|
+
const chdir = [];
|
|
17203
17253
|
let i = 0;
|
|
17204
17254
|
while (i < rest.length) {
|
|
17205
17255
|
const w = rest[i];
|
|
@@ -17214,6 +17264,7 @@ zoo`.split("\n");
|
|
|
17214
17264
|
i++;
|
|
17215
17265
|
value = i < rest.length ? rest[i] : null;
|
|
17216
17266
|
}
|
|
17267
|
+
if (name === "-C") chdir.push(viaXargs || (value ?? "").includes("{}") ? null : value);
|
|
17217
17268
|
if (name === "-c" && value !== null) {
|
|
17218
17269
|
const ceq = value.indexOf("=");
|
|
17219
17270
|
const key = (ceq > 0 ? value.slice(0, ceq) : value).toLowerCase();
|
|
@@ -17230,7 +17281,7 @@ zoo`.split("\n");
|
|
|
17230
17281
|
}
|
|
17231
17282
|
const subWord = i < rest.length ? rest[i] : void 0;
|
|
17232
17283
|
const args = rest.slice(i + 1);
|
|
17233
|
-
const inv = { sub: null, args, config, env, shown };
|
|
17284
|
+
const inv = { sub: null, args, config, env, shown, chdir };
|
|
17234
17285
|
if (subWord === void 0) {
|
|
17235
17286
|
if (!viaXargs) return;
|
|
17236
17287
|
this.emit(inv);
|
|
@@ -17244,16 +17295,23 @@ zoo`.split("\n");
|
|
|
17244
17295
|
const fromConfig = config.get(`alias.${subWord.toLowerCase()}`);
|
|
17245
17296
|
const alias = fromConfig !== void 0 ? fromConfig : this.aliases.get(subWord);
|
|
17246
17297
|
if (alias !== void 0 && !fromAlias) {
|
|
17247
|
-
this.expandAlias(subWord, alias, args, env, depth, 0, shown);
|
|
17298
|
+
this.expandAlias(subWord, alias, args, env, depth, 0, shown, chdir);
|
|
17248
17299
|
return;
|
|
17249
17300
|
}
|
|
17250
17301
|
inv.sub = subWord;
|
|
17251
17302
|
this.emit(inv);
|
|
17252
17303
|
this.recordAliasDefinition(inv, env, depth);
|
|
17253
17304
|
}
|
|
17254
|
-
expandAlias(name, value, args, env, depth, level, shown) {
|
|
17305
|
+
expandAlias(name, value, args, env, depth, level, shown, chdir) {
|
|
17255
17306
|
if (value === null) {
|
|
17256
|
-
this.emit({
|
|
17307
|
+
this.emit({
|
|
17308
|
+
sub: null,
|
|
17309
|
+
args,
|
|
17310
|
+
config: /* @__PURE__ */ new Map(),
|
|
17311
|
+
env,
|
|
17312
|
+
shown: `${shown} (alias ${name})`,
|
|
17313
|
+
chdir
|
|
17314
|
+
});
|
|
17257
17315
|
return;
|
|
17258
17316
|
}
|
|
17259
17317
|
if (value.startsWith("!")) {
|
|
@@ -17267,7 +17325,16 @@ zoo`.split("\n");
|
|
|
17267
17325
|
const nested = this.aliases.get(sub);
|
|
17268
17326
|
const expandedShown = `${shown} (alias ${name} = ${value})`;
|
|
17269
17327
|
if (nested !== void 0 && level < MAX_ALIAS_DEPTH) {
|
|
17270
|
-
this.expandAlias(
|
|
17328
|
+
this.expandAlias(
|
|
17329
|
+
sub,
|
|
17330
|
+
nested,
|
|
17331
|
+
[...words.slice(1), ...args],
|
|
17332
|
+
env,
|
|
17333
|
+
depth,
|
|
17334
|
+
level + 1,
|
|
17335
|
+
shown,
|
|
17336
|
+
chdir
|
|
17337
|
+
);
|
|
17271
17338
|
return;
|
|
17272
17339
|
}
|
|
17273
17340
|
this.emit({
|
|
@@ -17275,7 +17342,8 @@ zoo`.split("\n");
|
|
|
17275
17342
|
args: [...words.slice(1), ...args],
|
|
17276
17343
|
config: /* @__PURE__ */ new Map(),
|
|
17277
17344
|
env,
|
|
17278
|
-
shown: expandedShown
|
|
17345
|
+
shown: expandedShown,
|
|
17346
|
+
chdir
|
|
17279
17347
|
});
|
|
17280
17348
|
}
|
|
17281
17349
|
/**
|
|
@@ -17309,7 +17377,7 @@ zoo`.split("\n");
|
|
|
17309
17377
|
if (i + 1 >= args.length) return;
|
|
17310
17378
|
const value = args[i + 1];
|
|
17311
17379
|
this.aliases.set(m[1], value);
|
|
17312
|
-
this.expandAlias(m[1], value, [], env, depth, 0, `${inv.shown}
|
|
17380
|
+
this.expandAlias(m[1], value, [], env, depth, 0, `${inv.shown}`, []);
|
|
17313
17381
|
}
|
|
17314
17382
|
};
|
|
17315
17383
|
POSITIONAL2 = "\0positional";
|
|
@@ -18855,7 +18923,7 @@ zoo`.split("\n");
|
|
|
18855
18923
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
18856
18924
|
];
|
|
18857
18925
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
18858
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
18926
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v35";
|
|
18859
18927
|
DEDUPE_PREVIEW_LEN = 120;
|
|
18860
18928
|
ENGINE_VERSION = "1.4.0";
|
|
18861
18929
|
}
|
|
@@ -22932,10 +23000,19 @@ async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
|
|
|
22932
23000
|
// reaches a long-lived in-process authorizer (the gateway) within seconds.
|
|
22933
23001
|
isTrustedHost: config.policy.trustedHostsManaged ? (host2) => matchesTrustedHost(host2, config.policy.trustedHosts) : isTrustedHost,
|
|
22934
23002
|
readAgentSettings,
|
|
22935
|
-
isConfiguredRegistry: (host2, at) => configuredRegistryHosts(at).has(host2)
|
|
23003
|
+
isConfiguredRegistry: (host2, at) => configuredRegistryHosts(at).has(host2),
|
|
23004
|
+
isGitWorkTreeTop
|
|
22936
23005
|
}
|
|
22937
23006
|
);
|
|
22938
23007
|
}
|
|
23008
|
+
function isGitWorkTreeTop(dir2, cwd) {
|
|
23009
|
+
if (!path13.isAbsolute(cwd)) return true;
|
|
23010
|
+
try {
|
|
23011
|
+
return fs13.existsSync(path13.join(path13.resolve(cwd, dir2), ".git"));
|
|
23012
|
+
} catch {
|
|
23013
|
+
return true;
|
|
23014
|
+
}
|
|
23015
|
+
}
|
|
22939
23016
|
function readAgentSettings(absPath) {
|
|
22940
23017
|
const r = readCappedText(
|
|
22941
23018
|
absPath,
|
|
@@ -31952,16 +32029,124 @@ var init_credentials = __esm({
|
|
|
31952
32029
|
}
|
|
31953
32030
|
});
|
|
31954
32031
|
|
|
31955
|
-
// src/daemon/
|
|
32032
|
+
// src/daemon/startup-log.ts
|
|
31956
32033
|
import fs38 from "fs";
|
|
31957
32034
|
import path39 from "path";
|
|
31958
32035
|
import os32 from "os";
|
|
32036
|
+
function capStartupLog(file2) {
|
|
32037
|
+
try {
|
|
32038
|
+
if (fs38.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs38.truncateSync(file2);
|
|
32039
|
+
} catch {
|
|
32040
|
+
}
|
|
32041
|
+
}
|
|
32042
|
+
function openStartupLogFd() {
|
|
32043
|
+
try {
|
|
32044
|
+
const file2 = DAEMON_STARTUP_LOG();
|
|
32045
|
+
const dir2 = path39.dirname(file2);
|
|
32046
|
+
if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
|
|
32047
|
+
capStartupLog(file2);
|
|
32048
|
+
return fs38.openSync(file2, "a");
|
|
32049
|
+
} catch {
|
|
32050
|
+
return void 0;
|
|
32051
|
+
}
|
|
32052
|
+
}
|
|
32053
|
+
function recordStartupState(outcome, kind, detail) {
|
|
32054
|
+
try {
|
|
32055
|
+
if (outcome === "starting") {
|
|
32056
|
+
const prev = readStartupState();
|
|
32057
|
+
if (prev?.outcome === "starting") {
|
|
32058
|
+
const prevAt = new Date(prev.at).getTime();
|
|
32059
|
+
const age = Math.abs(Date.now() - prevAt);
|
|
32060
|
+
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
32061
|
+
}
|
|
32062
|
+
}
|
|
32063
|
+
const file2 = DAEMON_STARTUP_STATE();
|
|
32064
|
+
const dir2 = path39.dirname(file2);
|
|
32065
|
+
if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
|
|
32066
|
+
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
32067
|
+
if (kind) state.kind = kind;
|
|
32068
|
+
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
32069
|
+
const tmp = `${file2}.${process.pid}.tmp`;
|
|
32070
|
+
try {
|
|
32071
|
+
fs38.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
32072
|
+
fs38.renameSync(tmp, file2);
|
|
32073
|
+
} catch (err2) {
|
|
32074
|
+
try {
|
|
32075
|
+
fs38.unlinkSync(tmp);
|
|
32076
|
+
} catch {
|
|
32077
|
+
}
|
|
32078
|
+
throw err2;
|
|
32079
|
+
}
|
|
32080
|
+
} catch {
|
|
32081
|
+
}
|
|
32082
|
+
}
|
|
32083
|
+
function readStartupState() {
|
|
32084
|
+
try {
|
|
32085
|
+
const raw = fs38.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
32086
|
+
const s = JSON.parse(raw);
|
|
32087
|
+
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
32088
|
+
return s;
|
|
32089
|
+
} catch {
|
|
32090
|
+
return null;
|
|
32091
|
+
}
|
|
32092
|
+
}
|
|
32093
|
+
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
32094
|
+
const s = readStartupState();
|
|
32095
|
+
if (!s) return null;
|
|
32096
|
+
const at = new Date(s.at);
|
|
32097
|
+
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
32098
|
+
switch (s.outcome) {
|
|
32099
|
+
case "ok":
|
|
32100
|
+
case "ok-elsewhere":
|
|
32101
|
+
return null;
|
|
32102
|
+
case "starting":
|
|
32103
|
+
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
32104
|
+
return {
|
|
32105
|
+
kind: "did-not-start",
|
|
32106
|
+
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
32107
|
+
at,
|
|
32108
|
+
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
32109
|
+
label: "start attempts failing since"
|
|
32110
|
+
};
|
|
32111
|
+
case "failed":
|
|
32112
|
+
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
32113
|
+
default:
|
|
32114
|
+
return null;
|
|
32115
|
+
}
|
|
32116
|
+
}
|
|
32117
|
+
function logDaemonStartup(kind, detail) {
|
|
32118
|
+
try {
|
|
32119
|
+
const file2 = DAEMON_STARTUP_LOG();
|
|
32120
|
+
const dir2 = path39.dirname(file2);
|
|
32121
|
+
if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
|
|
32122
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
|
|
32123
|
+
`;
|
|
32124
|
+
fs38.appendFileSync(file2, line, "utf-8");
|
|
32125
|
+
} catch {
|
|
32126
|
+
}
|
|
32127
|
+
}
|
|
32128
|
+
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
32129
|
+
var init_startup_log = __esm({
|
|
32130
|
+
"src/daemon/startup-log.ts"() {
|
|
32131
|
+
"use strict";
|
|
32132
|
+
DAEMON_STARTUP_LOG = () => path39.join(os32.homedir(), ".node9", "daemon-startup.log");
|
|
32133
|
+
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
32134
|
+
DAEMON_STARTUP_STATE = () => path39.join(os32.homedir(), ".node9", "daemon-startup-state.json");
|
|
32135
|
+
MAX_DETAIL = 200;
|
|
32136
|
+
STARTING_GRACE_MS = 90 * 1e3;
|
|
32137
|
+
}
|
|
32138
|
+
});
|
|
32139
|
+
|
|
32140
|
+
// src/daemon/service.ts
|
|
32141
|
+
import fs39 from "fs";
|
|
32142
|
+
import path40 from "path";
|
|
32143
|
+
import os33 from "os";
|
|
31959
32144
|
import { spawnSync as spawnSync2, execFileSync } from "child_process";
|
|
31960
32145
|
function resolveNode9Binary() {
|
|
31961
32146
|
try {
|
|
31962
32147
|
const script = process.argv[1];
|
|
31963
|
-
if (typeof script === "string" &&
|
|
31964
|
-
return
|
|
32148
|
+
if (typeof script === "string" && path40.isAbsolute(script) && fs39.existsSync(script)) {
|
|
32149
|
+
return fs39.realpathSync(script);
|
|
31965
32150
|
}
|
|
31966
32151
|
} catch {
|
|
31967
32152
|
}
|
|
@@ -31985,11 +32170,11 @@ function daemonServiceEnv() {
|
|
|
31985
32170
|
return env;
|
|
31986
32171
|
}
|
|
31987
32172
|
function launchdPlist(binaryPath) {
|
|
31988
|
-
const logDir =
|
|
32173
|
+
const logDir = path40.join(os33.homedir(), ".node9");
|
|
31989
32174
|
const nodePath = xmlEscape(process.execPath);
|
|
31990
32175
|
const scriptPath = xmlEscape(binaryPath);
|
|
31991
|
-
const outLog = xmlEscape(
|
|
31992
|
-
const errLog = xmlEscape(
|
|
32176
|
+
const outLog = xmlEscape(path40.join(logDir, "daemon.log"));
|
|
32177
|
+
const errLog = xmlEscape(path40.join(logDir, "daemon-error.log"));
|
|
31993
32178
|
const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
31994
32179
|
<string>${xmlEscape(v)}</string>`).join("\n");
|
|
31995
32180
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -32023,9 +32208,9 @@ ${envEntries}
|
|
|
32023
32208
|
`;
|
|
32024
32209
|
}
|
|
32025
32210
|
function installLaunchd(binaryPath) {
|
|
32026
|
-
const dir2 =
|
|
32027
|
-
if (!
|
|
32028
|
-
|
|
32211
|
+
const dir2 = path40.dirname(LAUNCHD_PLIST);
|
|
32212
|
+
if (!fs39.existsSync(dir2)) fs39.mkdirSync(dir2, { recursive: true });
|
|
32213
|
+
fs39.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
32029
32214
|
spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
32030
32215
|
const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
32031
32216
|
encoding: "utf8",
|
|
@@ -32036,13 +32221,13 @@ function installLaunchd(binaryPath) {
|
|
|
32036
32221
|
}
|
|
32037
32222
|
}
|
|
32038
32223
|
function uninstallLaunchd() {
|
|
32039
|
-
if (
|
|
32224
|
+
if (fs39.existsSync(LAUNCHD_PLIST)) {
|
|
32040
32225
|
spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
32041
|
-
|
|
32226
|
+
fs39.unlinkSync(LAUNCHD_PLIST);
|
|
32042
32227
|
}
|
|
32043
32228
|
}
|
|
32044
32229
|
function isLaunchdInstalled() {
|
|
32045
|
-
return
|
|
32230
|
+
return fs39.existsSync(LAUNCHD_PLIST);
|
|
32046
32231
|
}
|
|
32047
32232
|
function systemdUnit(binaryPath) {
|
|
32048
32233
|
const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
@@ -32062,12 +32247,12 @@ WantedBy=default.target
|
|
|
32062
32247
|
`;
|
|
32063
32248
|
}
|
|
32064
32249
|
function installSystemd(binaryPath) {
|
|
32065
|
-
if (!
|
|
32066
|
-
|
|
32250
|
+
if (!fs39.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
32251
|
+
fs39.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
32067
32252
|
}
|
|
32068
|
-
|
|
32253
|
+
fs39.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
32069
32254
|
try {
|
|
32070
|
-
execFileSync("loginctl", ["enable-linger",
|
|
32255
|
+
execFileSync("loginctl", ["enable-linger", os33.userInfo().username], { timeout: 3e3 });
|
|
32071
32256
|
} catch {
|
|
32072
32257
|
}
|
|
32073
32258
|
const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
|
|
@@ -32087,21 +32272,21 @@ function installSystemd(binaryPath) {
|
|
|
32087
32272
|
}
|
|
32088
32273
|
}
|
|
32089
32274
|
function uninstallSystemd() {
|
|
32090
|
-
if (
|
|
32275
|
+
if (fs39.existsSync(SYSTEMD_UNIT)) {
|
|
32091
32276
|
spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
32092
32277
|
encoding: "utf8",
|
|
32093
32278
|
timeout: 5e3
|
|
32094
32279
|
});
|
|
32095
32280
|
spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
32096
|
-
|
|
32281
|
+
fs39.unlinkSync(SYSTEMD_UNIT);
|
|
32097
32282
|
}
|
|
32098
32283
|
}
|
|
32099
32284
|
function isSystemdInstalled() {
|
|
32100
|
-
return
|
|
32285
|
+
return fs39.existsSync(SYSTEMD_UNIT);
|
|
32101
32286
|
}
|
|
32102
|
-
function windowsStartupDir(homeDir2 =
|
|
32103
|
-
const appData = process.env.APPDATA ||
|
|
32104
|
-
return
|
|
32287
|
+
function windowsStartupDir(homeDir2 = os33.homedir()) {
|
|
32288
|
+
const appData = process.env.APPDATA || path40.join(homeDir2, "AppData", "Roaming");
|
|
32289
|
+
return path40.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
|
|
32105
32290
|
}
|
|
32106
32291
|
function windowsLauncherVbs(nodePath, scriptPath) {
|
|
32107
32292
|
for (const p of [nodePath, scriptPath]) {
|
|
@@ -32121,23 +32306,23 @@ function windowsLauncherVbs(nodePath, scriptPath) {
|
|
|
32121
32306
|
}
|
|
32122
32307
|
function installWindowsStartup(binaryPath) {
|
|
32123
32308
|
const target = WIN_LAUNCHER();
|
|
32124
|
-
const dir2 =
|
|
32125
|
-
if (!
|
|
32126
|
-
|
|
32309
|
+
const dir2 = path40.dirname(target);
|
|
32310
|
+
if (!fs39.existsSync(dir2)) fs39.mkdirSync(dir2, { recursive: true });
|
|
32311
|
+
fs39.writeFileSync(target, windowsLauncherVbs(process.execPath, binaryPath), "utf-8");
|
|
32127
32312
|
spawnSync2("wscript.exe", ["//B", target], { encoding: "utf8", timeout: 1e4 });
|
|
32128
32313
|
}
|
|
32129
32314
|
function uninstallWindowsStartup() {
|
|
32130
32315
|
const target = WIN_LAUNCHER();
|
|
32131
|
-
if (
|
|
32316
|
+
if (fs39.existsSync(target)) fs39.unlinkSync(target);
|
|
32132
32317
|
}
|
|
32133
32318
|
function isWindowsStartupInstalled() {
|
|
32134
|
-
return
|
|
32319
|
+
return fs39.existsSync(WIN_LAUNCHER());
|
|
32135
32320
|
}
|
|
32136
32321
|
function stopRunningDaemon() {
|
|
32137
|
-
const pidFile =
|
|
32138
|
-
if (!
|
|
32322
|
+
const pidFile = path40.join(os33.homedir(), ".node9", "daemon.pid");
|
|
32323
|
+
if (!fs39.existsSync(pidFile)) return;
|
|
32139
32324
|
try {
|
|
32140
|
-
const data = JSON.parse(
|
|
32325
|
+
const data = JSON.parse(fs39.readFileSync(pidFile, "utf-8"));
|
|
32141
32326
|
const pid = data.pid;
|
|
32142
32327
|
const MAX_PID2 = 4194304;
|
|
32143
32328
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -32157,7 +32342,7 @@ function stopRunningDaemon() {
|
|
|
32157
32342
|
}
|
|
32158
32343
|
}
|
|
32159
32344
|
try {
|
|
32160
|
-
|
|
32345
|
+
fs39.unlinkSync(pidFile);
|
|
32161
32346
|
} catch {
|
|
32162
32347
|
}
|
|
32163
32348
|
} catch {
|
|
@@ -32250,7 +32435,7 @@ function enableDaemonServiceQuiet() {
|
|
|
32250
32435
|
if (process.platform === "linux") {
|
|
32251
32436
|
const r = spawnSync2("systemctl", ["--user", "enable", "node9-daemon"], {
|
|
32252
32437
|
encoding: "utf8",
|
|
32253
|
-
timeout:
|
|
32438
|
+
timeout: 1e4
|
|
32254
32439
|
});
|
|
32255
32440
|
return r.status === 0;
|
|
32256
32441
|
}
|
|
@@ -32268,19 +32453,35 @@ function ensureAutostartHealthy(autoStartDaemon) {
|
|
|
32268
32453
|
enabled: isDaemonServiceEnabled(),
|
|
32269
32454
|
autoStartDaemon
|
|
32270
32455
|
});
|
|
32271
|
-
if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "
|
|
32456
|
+
if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "failed";
|
|
32272
32457
|
return decision === "ok" ? "ok" : decision === "unsupported" ? "unsupported" : "skipped";
|
|
32273
32458
|
}
|
|
32274
|
-
function
|
|
32459
|
+
function logAutostartHeal(result) {
|
|
32460
|
+
if (result !== "repaired" && result !== "failed") return;
|
|
32461
|
+
try {
|
|
32462
|
+
const file2 = DAEMON_STARTUP_LOG();
|
|
32463
|
+
fs39.mkdirSync(path40.dirname(file2), { recursive: true });
|
|
32464
|
+
capStartupLog(file2);
|
|
32465
|
+
fs39.appendFileSync(file2, `[${(/* @__PURE__ */ new Date()).toISOString()}] [autostart-heal] ${result}
|
|
32466
|
+
`);
|
|
32467
|
+
} catch {
|
|
32468
|
+
}
|
|
32469
|
+
}
|
|
32470
|
+
function scheduleAutostartSelfHeal(autoStartDaemon, heal = ensureAutostartHealthy) {
|
|
32275
32471
|
if (process.env.NODE9_TESTING === "1") return;
|
|
32472
|
+
let failures = 0;
|
|
32276
32473
|
const run = () => {
|
|
32474
|
+
let result = "skipped";
|
|
32277
32475
|
try {
|
|
32278
|
-
|
|
32476
|
+
result = heal(autoStartDaemon());
|
|
32279
32477
|
} catch {
|
|
32280
32478
|
}
|
|
32479
|
+
logAutostartHeal(result);
|
|
32480
|
+
failures = result === "failed" ? failures + 1 : 0;
|
|
32481
|
+
const next = failures > 0 && failures <= AUTOSTART_HEAL_RETRY_MS.length ? AUTOSTART_HEAL_RETRY_MS[failures - 1] : AUTOSTART_HEAL_INTERVAL_MS;
|
|
32482
|
+
setTimeout(run, next).unref();
|
|
32281
32483
|
};
|
|
32282
32484
|
setTimeout(run, 6e4).unref();
|
|
32283
|
-
setInterval(run, AUTOSTART_HEAL_INTERVAL_MS).unref();
|
|
32284
32485
|
}
|
|
32285
32486
|
function restartDaemonService() {
|
|
32286
32487
|
try {
|
|
@@ -32355,39 +32556,41 @@ function autostartState() {
|
|
|
32355
32556
|
if (!isDaemonServiceInstalled()) return "absent";
|
|
32356
32557
|
return isDaemonServiceEnabled() ? "enabled" : "disabled";
|
|
32357
32558
|
}
|
|
32358
|
-
var LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT, STARTUP_FILE, WIN_LAUNCHER, AUTOSTART_HEAL_INTERVAL_MS;
|
|
32559
|
+
var LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT, STARTUP_FILE, WIN_LAUNCHER, AUTOSTART_HEAL_INTERVAL_MS, AUTOSTART_HEAL_RETRY_MS;
|
|
32359
32560
|
var init_service = __esm({
|
|
32360
32561
|
"src/daemon/service.ts"() {
|
|
32361
32562
|
"use strict";
|
|
32563
|
+
init_startup_log();
|
|
32362
32564
|
init_api_url();
|
|
32363
32565
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
32364
|
-
LAUNCHD_PLIST =
|
|
32365
|
-
SYSTEMD_UNIT_DIR =
|
|
32366
|
-
SYSTEMD_UNIT =
|
|
32566
|
+
LAUNCHD_PLIST = path40.join(os33.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
32567
|
+
SYSTEMD_UNIT_DIR = path40.join(os33.homedir(), ".config", "systemd", "user");
|
|
32568
|
+
SYSTEMD_UNIT = path40.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
32367
32569
|
STARTUP_FILE = "node9-daemon.vbs";
|
|
32368
|
-
WIN_LAUNCHER = () =>
|
|
32570
|
+
WIN_LAUNCHER = () => path40.join(windowsStartupDir(), STARTUP_FILE);
|
|
32369
32571
|
AUTOSTART_HEAL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
32572
|
+
AUTOSTART_HEAL_RETRY_MS = [10 * 60 * 1e3, 60 * 60 * 1e3];
|
|
32370
32573
|
}
|
|
32371
32574
|
});
|
|
32372
32575
|
|
|
32373
32576
|
// src/apps/detect.ts
|
|
32374
|
-
import
|
|
32375
|
-
import
|
|
32376
|
-
import
|
|
32577
|
+
import fs40 from "fs";
|
|
32578
|
+
import os34 from "os";
|
|
32579
|
+
import path41 from "path";
|
|
32377
32580
|
function readTail(file2, bytes) {
|
|
32378
32581
|
let fd;
|
|
32379
32582
|
try {
|
|
32380
|
-
fd =
|
|
32381
|
-
const size =
|
|
32583
|
+
fd = fs40.openSync(file2, "r");
|
|
32584
|
+
const size = fs40.fstatSync(fd).size;
|
|
32382
32585
|
const start = Math.max(0, size - bytes);
|
|
32383
32586
|
const buf = Buffer.alloc(size - start);
|
|
32384
|
-
|
|
32587
|
+
fs40.readSync(fd, buf, 0, buf.length, start);
|
|
32385
32588
|
const text = buf.toString("utf8");
|
|
32386
32589
|
return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
|
|
32387
32590
|
} catch {
|
|
32388
32591
|
return "";
|
|
32389
32592
|
} finally {
|
|
32390
|
-
if (fd !== void 0)
|
|
32593
|
+
if (fd !== void 0) fs40.closeSync(fd);
|
|
32391
32594
|
}
|
|
32392
32595
|
}
|
|
32393
32596
|
function programsOf(command) {
|
|
@@ -32401,7 +32604,7 @@ function programsOf(command) {
|
|
|
32401
32604
|
i++;
|
|
32402
32605
|
continue;
|
|
32403
32606
|
}
|
|
32404
|
-
const base2 =
|
|
32607
|
+
const base2 = path41.basename(w.replace(/^["']|["']$/g, ""));
|
|
32405
32608
|
if (WRAPPERS.has(base2)) {
|
|
32406
32609
|
i++;
|
|
32407
32610
|
continue;
|
|
@@ -32436,7 +32639,7 @@ function observeAudit(auditFile) {
|
|
|
32436
32639
|
for (const p of programsOf(row.argsPreview)) commands.add(p);
|
|
32437
32640
|
}
|
|
32438
32641
|
const dir2 = typeof row.workingDir === "string" ? row.workingDir : "";
|
|
32439
|
-
if (dir2 &&
|
|
32642
|
+
if (dir2 && path41.isAbsolute(dir2) && !seenDirs.has(dir2) && dirs.length < MAX_PROJECTS) {
|
|
32440
32643
|
seenDirs.add(dir2);
|
|
32441
32644
|
dirs.push(dir2);
|
|
32442
32645
|
}
|
|
@@ -32446,21 +32649,21 @@ function observeAudit(auditFile) {
|
|
|
32446
32649
|
function readSmall(file2) {
|
|
32447
32650
|
let fd;
|
|
32448
32651
|
try {
|
|
32449
|
-
fd =
|
|
32450
|
-
const st =
|
|
32652
|
+
fd = fs40.openSync(file2, READ_FLAGS);
|
|
32653
|
+
const st = fs40.fstatSync(fd);
|
|
32451
32654
|
if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
|
|
32452
32655
|
const buf = Buffer.alloc(st.size);
|
|
32453
|
-
const n =
|
|
32656
|
+
const n = fs40.readSync(fd, buf, 0, st.size, 0);
|
|
32454
32657
|
return buf.toString("utf8", 0, n);
|
|
32455
32658
|
} catch {
|
|
32456
32659
|
return null;
|
|
32457
32660
|
} finally {
|
|
32458
|
-
if (fd !== void 0)
|
|
32661
|
+
if (fd !== void 0) fs40.closeSync(fd);
|
|
32459
32662
|
}
|
|
32460
32663
|
}
|
|
32461
32664
|
function projectPackages(dir2) {
|
|
32462
32665
|
const out = /* @__PURE__ */ new Set();
|
|
32463
|
-
const pkg = readSmall(
|
|
32666
|
+
const pkg = readSmall(path41.join(dir2, "package.json"));
|
|
32464
32667
|
if (pkg) {
|
|
32465
32668
|
try {
|
|
32466
32669
|
const j = JSON.parse(pkg);
|
|
@@ -32477,7 +32680,7 @@ function projectPackages(dir2) {
|
|
|
32477
32680
|
} catch {
|
|
32478
32681
|
}
|
|
32479
32682
|
}
|
|
32480
|
-
const req = readSmall(
|
|
32683
|
+
const req = readSmall(path41.join(dir2, "requirements.txt"));
|
|
32481
32684
|
if (req) {
|
|
32482
32685
|
for (const line of req.split("\n")) {
|
|
32483
32686
|
const m = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line);
|
|
@@ -32485,7 +32688,7 @@ function projectPackages(dir2) {
|
|
|
32485
32688
|
out.add(m[1].toLowerCase());
|
|
32486
32689
|
}
|
|
32487
32690
|
}
|
|
32488
|
-
const py = readSmall(
|
|
32691
|
+
const py = readSmall(path41.join(dir2, "pyproject.toml"));
|
|
32489
32692
|
if (py) {
|
|
32490
32693
|
for (const m of py.matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:[<>=!~;[\]]|["'])/g))
|
|
32491
32694
|
out.add(m[1].toLowerCase());
|
|
@@ -32505,7 +32708,7 @@ function projectPackages(dir2) {
|
|
|
32505
32708
|
function projectEnvNames(dir2) {
|
|
32506
32709
|
const out = /* @__PURE__ */ new Set();
|
|
32507
32710
|
for (const f of [".env", ".env.local", ".env.development"]) {
|
|
32508
|
-
const text = readSmall(
|
|
32711
|
+
const text = readSmall(path41.join(dir2, f));
|
|
32509
32712
|
if (!text) continue;
|
|
32510
32713
|
for (const line of text.split("\n")) {
|
|
32511
32714
|
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
|
|
@@ -32541,14 +32744,14 @@ function matchApps(obs, projectFacts, catalog = APP_CATALOG) {
|
|
|
32541
32744
|
}
|
|
32542
32745
|
return found;
|
|
32543
32746
|
}
|
|
32544
|
-
function detectApps(home =
|
|
32545
|
-
const obs = observeAudit(
|
|
32747
|
+
function detectApps(home = os34.homedir()) {
|
|
32748
|
+
const obs = observeAudit(path41.join(home, ".node9", "audit.log"));
|
|
32546
32749
|
for (const cfg of Object.values(readMcpToolsConfig())) {
|
|
32547
32750
|
if (cfg?.name) obs.servers.add(cfg.name.toLowerCase());
|
|
32548
32751
|
}
|
|
32549
32752
|
const facts = obs.projects.filter((dir2) => {
|
|
32550
32753
|
try {
|
|
32551
|
-
return
|
|
32754
|
+
return fs40.statSync(dir2).isDirectory();
|
|
32552
32755
|
} catch {
|
|
32553
32756
|
return false;
|
|
32554
32757
|
}
|
|
@@ -32583,7 +32786,7 @@ var init_detect = __esm({
|
|
|
32583
32786
|
"pnpx",
|
|
32584
32787
|
"bunx"
|
|
32585
32788
|
]);
|
|
32586
|
-
READ_FLAGS = typeof
|
|
32789
|
+
READ_FLAGS = typeof fs40.constants.O_NONBLOCK === "number" ? fs40.constants.O_RDONLY | fs40.constants.O_NONBLOCK : fs40.constants.O_RDONLY;
|
|
32587
32790
|
}
|
|
32588
32791
|
});
|
|
32589
32792
|
|
|
@@ -32643,9 +32846,9 @@ var init_daemon2 = __esm({
|
|
|
32643
32846
|
});
|
|
32644
32847
|
|
|
32645
32848
|
// src/config/migrate.ts
|
|
32646
|
-
import
|
|
32647
|
-
import
|
|
32648
|
-
import
|
|
32849
|
+
import fs41 from "fs";
|
|
32850
|
+
import path42 from "path";
|
|
32851
|
+
import os35 from "os";
|
|
32649
32852
|
function backupPath(filePath) {
|
|
32650
32853
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
32651
32854
|
return `${filePath}.bak-${stamp}`;
|
|
@@ -32655,7 +32858,7 @@ function migrateConfigFile(options = {}) {
|
|
|
32655
32858
|
if (options.dryRun) {
|
|
32656
32859
|
let raw;
|
|
32657
32860
|
try {
|
|
32658
|
-
raw = JSON.parse(
|
|
32861
|
+
raw = JSON.parse(fs41.readFileSync(filePath, "utf8"));
|
|
32659
32862
|
} catch (err2) {
|
|
32660
32863
|
if (err2.code === "ENOENT") return { status: "no-file" };
|
|
32661
32864
|
return { status: "failed", error: `cannot read ${filePath}: ${err2.message}` };
|
|
@@ -32678,14 +32881,14 @@ function migrateConfigFile(options = {}) {
|
|
|
32678
32881
|
}
|
|
32679
32882
|
}
|
|
32680
32883
|
function undoMigration(filePath = globalConfigPath()) {
|
|
32681
|
-
const dir2 =
|
|
32682
|
-
const base2 =
|
|
32683
|
-
const backups =
|
|
32884
|
+
const dir2 = path42.dirname(filePath);
|
|
32885
|
+
const base2 = path42.basename(filePath);
|
|
32886
|
+
const backups = fs41.readdirSync(dir2).filter((f) => f.startsWith(`${base2}.bak-`)).sort();
|
|
32684
32887
|
const newest = backups.at(-1);
|
|
32685
32888
|
if (!newest) return null;
|
|
32686
|
-
const from =
|
|
32687
|
-
atomicWriteSync(filePath,
|
|
32688
|
-
|
|
32889
|
+
const from = path42.join(dir2, newest);
|
|
32890
|
+
atomicWriteSync(filePath, fs41.readFileSync(from, "utf8"), { mode: 384 });
|
|
32891
|
+
fs41.unlinkSync(from);
|
|
32689
32892
|
return { restored: from };
|
|
32690
32893
|
}
|
|
32691
32894
|
function autoMigrateLocalConfig() {
|
|
@@ -32703,8 +32906,8 @@ function autoMigrateLocalConfig() {
|
|
|
32703
32906
|
);
|
|
32704
32907
|
} else if (outcome.status === "failed") {
|
|
32705
32908
|
try {
|
|
32706
|
-
|
|
32707
|
-
|
|
32909
|
+
fs41.appendFileSync(
|
|
32910
|
+
path42.join(os35.homedir(), ".node9", "hook-debug.log"),
|
|
32708
32911
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] config migrate failed: ${outcome.error}
|
|
32709
32912
|
`
|
|
32710
32913
|
);
|
|
@@ -32945,18 +33148,18 @@ var init_forecast = __esm({
|
|
|
32945
33148
|
});
|
|
32946
33149
|
|
|
32947
33150
|
// src/version.ts
|
|
32948
|
-
import
|
|
32949
|
-
import
|
|
33151
|
+
import fs42 from "fs";
|
|
33152
|
+
import path43 from "path";
|
|
32950
33153
|
function readVersion() {
|
|
32951
33154
|
let dir2 = __dirname;
|
|
32952
33155
|
for (let i = 0; i < 5; i++) {
|
|
32953
|
-
const p =
|
|
33156
|
+
const p = path43.join(dir2, "package.json");
|
|
32954
33157
|
try {
|
|
32955
|
-
const pkg = JSON.parse(
|
|
33158
|
+
const pkg = JSON.parse(fs42.readFileSync(p, "utf-8"));
|
|
32956
33159
|
if (pkg.name === "@node9/proxy" && typeof pkg.version === "string") return pkg.version;
|
|
32957
33160
|
} catch {
|
|
32958
33161
|
}
|
|
32959
|
-
const parent =
|
|
33162
|
+
const parent = path43.dirname(dir2);
|
|
32960
33163
|
if (parent === dir2) break;
|
|
32961
33164
|
dir2 = parent;
|
|
32962
33165
|
}
|
|
@@ -32971,15 +33174,15 @@ var init_version = __esm({
|
|
|
32971
33174
|
});
|
|
32972
33175
|
|
|
32973
33176
|
// src/cli/commands/scan-cache.ts
|
|
32974
|
-
import
|
|
32975
|
-
import
|
|
32976
|
-
import
|
|
33177
|
+
import fs43 from "fs";
|
|
33178
|
+
import os36 from "os";
|
|
33179
|
+
import path44 from "path";
|
|
32977
33180
|
import { createHash as createHash6 } from "crypto";
|
|
32978
33181
|
function flushScanCaches() {
|
|
32979
33182
|
for (const c of activeCaches) c.flush();
|
|
32980
33183
|
}
|
|
32981
33184
|
function scanCacheRoot() {
|
|
32982
|
-
return
|
|
33185
|
+
return path44.join(os36.homedir(), ".node9", "scan-cache");
|
|
32983
33186
|
}
|
|
32984
33187
|
function configHashOf(config) {
|
|
32985
33188
|
const h = createHash6("sha256");
|
|
@@ -32987,14 +33190,14 @@ function configHashOf(config) {
|
|
|
32987
33190
|
h.update(JSON.stringify(config.policySource));
|
|
32988
33191
|
h.update(JSON.stringify(config.policy));
|
|
32989
33192
|
for (const name of SIDECAR_FILES) {
|
|
32990
|
-
const st = fileStamp(
|
|
33193
|
+
const st = fileStamp(path44.join(os36.homedir(), ".node9", name));
|
|
32991
33194
|
h.update(`|${name}:${st ? `${st.size}:${st.mtimeMs}` : "absent"}`);
|
|
32992
33195
|
}
|
|
32993
33196
|
return h.digest("hex").slice(0, 16);
|
|
32994
33197
|
}
|
|
32995
33198
|
function fileStamp(filePath) {
|
|
32996
33199
|
try {
|
|
32997
|
-
const st =
|
|
33200
|
+
const st = fs43.statSync(filePath);
|
|
32998
33201
|
return { size: st.size, mtimeMs: st.mtimeMs };
|
|
32999
33202
|
} catch {
|
|
33000
33203
|
return null;
|
|
@@ -33018,8 +33221,8 @@ var init_scan_cache = __esm({
|
|
|
33018
33221
|
"trusted-hosts.json",
|
|
33019
33222
|
"trusted-recipients.json",
|
|
33020
33223
|
"rules-cache.json",
|
|
33021
|
-
|
|
33022
|
-
|
|
33224
|
+
path44.join("osv", "npm", "meta.json"),
|
|
33225
|
+
path44.join("osv", "PyPI", "meta.json")
|
|
33023
33226
|
];
|
|
33024
33227
|
ScanCache = class {
|
|
33025
33228
|
root;
|
|
@@ -33048,7 +33251,7 @@ var init_scan_cache = __esm({
|
|
|
33048
33251
|
if (!this.enabled) return fresh;
|
|
33049
33252
|
try {
|
|
33050
33253
|
const raw = JSON.parse(
|
|
33051
|
-
|
|
33254
|
+
fs43.readFileSync(path44.join(this.root, "index.json"), "utf8")
|
|
33052
33255
|
);
|
|
33053
33256
|
if (raw.cacheVersion !== SCAN_CACHE_VERSION || raw.engineVersion !== CANONICAL_EXTRACTOR_VERSION || raw.proxyVersion !== PROXY_VERSION || raw.configHash !== configHash || typeof raw.files !== "object") {
|
|
33054
33257
|
this.dropAll();
|
|
@@ -33061,7 +33264,7 @@ var init_scan_cache = __esm({
|
|
|
33061
33264
|
}
|
|
33062
33265
|
dropAll() {
|
|
33063
33266
|
try {
|
|
33064
|
-
|
|
33267
|
+
fs43.rmSync(this.root, { recursive: true, force: true });
|
|
33065
33268
|
} catch {
|
|
33066
33269
|
}
|
|
33067
33270
|
}
|
|
@@ -33085,7 +33288,7 @@ var init_scan_cache = __esm({
|
|
|
33085
33288
|
}
|
|
33086
33289
|
}
|
|
33087
33290
|
try {
|
|
33088
|
-
const stored = JSON.parse(
|
|
33291
|
+
const stored = JSON.parse(fs43.readFileSync(path44.join(this.root, e.result), "utf8"));
|
|
33089
33292
|
if ((stored.judgedFrom ?? null) !== (e.judgedFrom ?? null) || !stored.result) {
|
|
33090
33293
|
this.misses++;
|
|
33091
33294
|
return null;
|
|
@@ -33100,14 +33303,14 @@ var init_scan_cache = __esm({
|
|
|
33100
33303
|
put(filePath, stamp, agent, result, hasher, judgeFrom = null) {
|
|
33101
33304
|
if (!this.enabled) return;
|
|
33102
33305
|
const name = createHash6("sha256").update(`${agent}|${filePath}`).digest("hex").slice(0, 40);
|
|
33103
|
-
const rel =
|
|
33306
|
+
const rel = path44.join("files", name.slice(0, 2), `${name}.json`);
|
|
33104
33307
|
const configHashes = {};
|
|
33105
33308
|
if (hasher)
|
|
33106
33309
|
for (const cwd of result.configCwds ?? []) configHashes[cwd] = hasher.configHashFor(cwd);
|
|
33107
33310
|
try {
|
|
33108
|
-
const abs =
|
|
33109
|
-
|
|
33110
|
-
|
|
33311
|
+
const abs = path44.join(this.root, rel);
|
|
33312
|
+
fs43.mkdirSync(path44.dirname(abs), { recursive: true, mode: 448 });
|
|
33313
|
+
fs43.writeFileSync(
|
|
33111
33314
|
abs,
|
|
33112
33315
|
JSON.stringify({ judgedFrom: judgeFrom ? judgeFrom.toISOString() : null, result }),
|
|
33113
33316
|
{ mode: 384 }
|
|
@@ -33130,9 +33333,9 @@ var init_scan_cache = __esm({
|
|
|
33130
33333
|
/** Drop the entries of transcripts that no longer exist, and their files. */
|
|
33131
33334
|
prune() {
|
|
33132
33335
|
for (const [key, e] of Object.entries(this.index.files)) {
|
|
33133
|
-
if (
|
|
33336
|
+
if (fs43.existsSync(key)) continue;
|
|
33134
33337
|
try {
|
|
33135
|
-
|
|
33338
|
+
fs43.rmSync(path44.join(this.root, e.result), { force: true });
|
|
33136
33339
|
} catch {
|
|
33137
33340
|
}
|
|
33138
33341
|
delete this.index.files[key];
|
|
@@ -33153,10 +33356,10 @@ var init_scan_cache = __esm({
|
|
|
33153
33356
|
this.lastFlush = Date.now();
|
|
33154
33357
|
if (!this.dirty) return;
|
|
33155
33358
|
try {
|
|
33156
|
-
|
|
33157
|
-
const tmp =
|
|
33158
|
-
|
|
33159
|
-
|
|
33359
|
+
fs43.mkdirSync(this.root, { recursive: true, mode: 448 });
|
|
33360
|
+
const tmp = path44.join(this.root, `index.${process.pid}.tmp`);
|
|
33361
|
+
fs43.writeFileSync(tmp, JSON.stringify(this.index), { mode: 384 });
|
|
33362
|
+
fs43.renameSync(tmp, path44.join(this.root, "index.json"));
|
|
33160
33363
|
this.dirty = false;
|
|
33161
33364
|
} catch {
|
|
33162
33365
|
}
|
|
@@ -33166,9 +33369,9 @@ var init_scan_cache = __esm({
|
|
|
33166
33369
|
});
|
|
33167
33370
|
|
|
33168
33371
|
// src/cli/commands/scan-judge.ts
|
|
33169
|
-
import
|
|
33170
|
-
import
|
|
33171
|
-
import
|
|
33372
|
+
import fs44 from "fs";
|
|
33373
|
+
import os37 from "os";
|
|
33374
|
+
import path45 from "path";
|
|
33172
33375
|
function findingDedupKey(f) {
|
|
33173
33376
|
return `${f.source.rule.name ?? "<unnamed>"}|${preview(f.input)}|${f.project}|${f.repo ?? ""}`;
|
|
33174
33377
|
}
|
|
@@ -33267,24 +33470,24 @@ var init_scan_judge = __esm({
|
|
|
33267
33470
|
* file, so worktrees count), home-relative; undefined when none. Cached per
|
|
33268
33471
|
* directory: history repeats the same few cwds thousands of times. */
|
|
33269
33472
|
repoOf(cwd) {
|
|
33270
|
-
if (!cwd || !
|
|
33473
|
+
if (!cwd || !path45.isAbsolute(cwd)) return void 0;
|
|
33271
33474
|
if (this.repos.has(cwd)) return this.repos.get(cwd);
|
|
33272
33475
|
let dir2 = cwd;
|
|
33273
33476
|
let found;
|
|
33274
33477
|
for (; ; ) {
|
|
33275
33478
|
try {
|
|
33276
|
-
if (
|
|
33479
|
+
if (fs44.existsSync(path45.join(dir2, ".git"))) {
|
|
33277
33480
|
found = dir2;
|
|
33278
33481
|
break;
|
|
33279
33482
|
}
|
|
33280
33483
|
} catch {
|
|
33281
33484
|
break;
|
|
33282
33485
|
}
|
|
33283
|
-
const parent =
|
|
33486
|
+
const parent = path45.dirname(dir2);
|
|
33284
33487
|
if (parent === dir2) break;
|
|
33285
33488
|
dir2 = parent;
|
|
33286
33489
|
}
|
|
33287
|
-
const home =
|
|
33490
|
+
const home = os37.homedir();
|
|
33288
33491
|
const label3 = found && found.startsWith(home) ? "~" + found.slice(home.length) : found;
|
|
33289
33492
|
this.repos.set(cwd, label3);
|
|
33290
33493
|
return label3;
|
|
@@ -33429,15 +33632,15 @@ var init_scan_judge = __esm({
|
|
|
33429
33632
|
});
|
|
33430
33633
|
|
|
33431
33634
|
// src/pricing/litellm.ts
|
|
33432
|
-
import
|
|
33433
|
-
import
|
|
33434
|
-
import
|
|
33635
|
+
import fs45 from "fs";
|
|
33636
|
+
import path46 from "path";
|
|
33637
|
+
import os38 from "os";
|
|
33435
33638
|
function normalizeModel(raw) {
|
|
33436
33639
|
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
33437
33640
|
}
|
|
33438
33641
|
function readCache(opts) {
|
|
33439
33642
|
try {
|
|
33440
|
-
const raw = JSON.parse(
|
|
33643
|
+
const raw = JSON.parse(fs45.readFileSync(CACHE_FILE(), "utf-8"));
|
|
33441
33644
|
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
33442
33645
|
return null;
|
|
33443
33646
|
}
|
|
@@ -33452,18 +33655,18 @@ function readCache(opts) {
|
|
|
33452
33655
|
function writeCache(prices) {
|
|
33453
33656
|
try {
|
|
33454
33657
|
const target = CACHE_FILE();
|
|
33455
|
-
const dir2 =
|
|
33456
|
-
if (!
|
|
33658
|
+
const dir2 = path46.dirname(target);
|
|
33659
|
+
if (!fs45.existsSync(dir2)) fs45.mkdirSync(dir2, { recursive: true });
|
|
33457
33660
|
const tmp = target + ".tmp";
|
|
33458
33661
|
const body = {
|
|
33459
33662
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33460
33663
|
prices
|
|
33461
33664
|
};
|
|
33462
|
-
|
|
33463
|
-
|
|
33665
|
+
fs45.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
33666
|
+
fs45.renameSync(tmp, target);
|
|
33464
33667
|
} catch (err2) {
|
|
33465
33668
|
try {
|
|
33466
|
-
|
|
33669
|
+
fs45.appendFileSync(
|
|
33467
33670
|
HOOK_DEBUG_LOG,
|
|
33468
33671
|
`[pricing] cache write failed: ${err2.message}
|
|
33469
33672
|
`
|
|
@@ -33621,7 +33824,7 @@ var init_litellm = __esm({
|
|
|
33621
33824
|
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
33622
33825
|
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
33623
33826
|
};
|
|
33624
|
-
CACHE_FILE = () =>
|
|
33827
|
+
CACHE_FILE = () => path46.join(os38.homedir(), ".node9", "model-pricing.json");
|
|
33625
33828
|
TTL_MS2 = 24 * 60 * 60 * 1e3;
|
|
33626
33829
|
memCache = null;
|
|
33627
33830
|
memCacheAt = 0;
|
|
@@ -33631,11 +33834,11 @@ var init_litellm = __esm({
|
|
|
33631
33834
|
});
|
|
33632
33835
|
|
|
33633
33836
|
// src/cost-codex.ts
|
|
33634
|
-
import
|
|
33635
|
-
import
|
|
33636
|
-
import
|
|
33837
|
+
import fs46 from "fs";
|
|
33838
|
+
import os39 from "os";
|
|
33839
|
+
import path47 from "path";
|
|
33637
33840
|
function codexSessionsDir() {
|
|
33638
|
-
return
|
|
33841
|
+
return path47.join(process.env.CODEX_HOME?.trim() || path47.join(os39.homedir(), ".codex"), "sessions");
|
|
33639
33842
|
}
|
|
33640
33843
|
function codexPriceFor(model) {
|
|
33641
33844
|
return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
|
|
@@ -33667,14 +33870,14 @@ function codexSessionCost(model, tokens, request2) {
|
|
|
33667
33870
|
function statAndFirstLine(file2) {
|
|
33668
33871
|
const CAP = 4 * 1024 * 1024;
|
|
33669
33872
|
const CHUNK = 64 * 1024;
|
|
33670
|
-
const fd =
|
|
33873
|
+
const fd = fs46.openSync(file2, "r");
|
|
33671
33874
|
try {
|
|
33672
|
-
const stat =
|
|
33875
|
+
const stat = fs46.fstatSync(fd);
|
|
33673
33876
|
const limit = Math.min(stat.size, CAP);
|
|
33674
33877
|
const parts = [];
|
|
33675
33878
|
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
33676
33879
|
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
33677
|
-
const read3 =
|
|
33880
|
+
const read3 = fs46.readSync(fd, buf, 0, buf.length, pos);
|
|
33678
33881
|
if (read3 <= 0) break;
|
|
33679
33882
|
const slice = buf.subarray(0, read3);
|
|
33680
33883
|
const nl = slice.indexOf(10);
|
|
@@ -33683,15 +33886,15 @@ function statAndFirstLine(file2) {
|
|
|
33683
33886
|
}
|
|
33684
33887
|
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
33685
33888
|
} finally {
|
|
33686
|
-
|
|
33889
|
+
fs46.closeSync(fd);
|
|
33687
33890
|
}
|
|
33688
33891
|
}
|
|
33689
33892
|
function listCodexSessionFiles(base2 = codexSessionsDir()) {
|
|
33690
33893
|
const files = [];
|
|
33691
33894
|
const walk2 = (dir2) => {
|
|
33692
33895
|
try {
|
|
33693
|
-
for (const entry of
|
|
33694
|
-
const file2 =
|
|
33896
|
+
for (const entry of fs46.readdirSync(dir2, { withFileTypes: true })) {
|
|
33897
|
+
const file2 = path47.join(dir2, entry.name);
|
|
33695
33898
|
if (entry.isDirectory()) walk2(file2);
|
|
33696
33899
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file2);
|
|
33697
33900
|
}
|
|
@@ -33699,7 +33902,7 @@ function listCodexSessionFiles(base2 = codexSessionsDir()) {
|
|
|
33699
33902
|
}
|
|
33700
33903
|
};
|
|
33701
33904
|
walk2(base2);
|
|
33702
|
-
if (
|
|
33905
|
+
if (path47.basename(base2) === "sessions") walk2(path47.join(path47.dirname(base2), "archived_sessions"));
|
|
33703
33906
|
const sessions = /* @__PURE__ */ new Map();
|
|
33704
33907
|
for (const file2 of files.sort()) {
|
|
33705
33908
|
try {
|
|
@@ -33897,13 +34100,13 @@ var init_cost_codex = __esm({
|
|
|
33897
34100
|
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
33898
34101
|
codexSource = {
|
|
33899
34102
|
id: "codex",
|
|
33900
|
-
available: () =>
|
|
34103
|
+
available: () => fs46.existsSync(codexSessionsDir()) || fs46.existsSync(path47.join(path47.dirname(codexSessionsDir()), "archived_sessions")),
|
|
33901
34104
|
collect(sinceMs) {
|
|
33902
34105
|
const entries = [];
|
|
33903
34106
|
for (const file2 of listCodexSessionFiles()) {
|
|
33904
34107
|
try {
|
|
33905
|
-
if (sinceMs !== void 0 &&
|
|
33906
|
-
entries.push(...parseCodexSession(
|
|
34108
|
+
if (sinceMs !== void 0 && fs46.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34109
|
+
entries.push(...parseCodexSession(fs46.readFileSync(file2, "utf8").split("\n")));
|
|
33907
34110
|
} catch {
|
|
33908
34111
|
}
|
|
33909
34112
|
}
|
|
@@ -33914,11 +34117,11 @@ var init_cost_codex = __esm({
|
|
|
33914
34117
|
});
|
|
33915
34118
|
|
|
33916
34119
|
// src/cost-gemini.ts
|
|
33917
|
-
import
|
|
33918
|
-
import
|
|
33919
|
-
import
|
|
34120
|
+
import fs47 from "fs";
|
|
34121
|
+
import os40 from "os";
|
|
34122
|
+
import path48 from "path";
|
|
33920
34123
|
function geminiTmpDir() {
|
|
33921
|
-
return
|
|
34124
|
+
return path48.join(os40.homedir(), ".gemini", "tmp");
|
|
33922
34125
|
}
|
|
33923
34126
|
function geminiPriceFor(model) {
|
|
33924
34127
|
let tuple = pricingFor(model);
|
|
@@ -33933,14 +34136,14 @@ function geminiPriceFor(model) {
|
|
|
33933
34136
|
}
|
|
33934
34137
|
function safeReaddir(dir2) {
|
|
33935
34138
|
try {
|
|
33936
|
-
return
|
|
34139
|
+
return fs47.readdirSync(dir2);
|
|
33937
34140
|
} catch {
|
|
33938
34141
|
return [];
|
|
33939
34142
|
}
|
|
33940
34143
|
}
|
|
33941
34144
|
function isDir(p) {
|
|
33942
34145
|
try {
|
|
33943
|
-
return
|
|
34146
|
+
return fs47.statSync(p).isDirectory();
|
|
33944
34147
|
} catch {
|
|
33945
34148
|
return false;
|
|
33946
34149
|
}
|
|
@@ -33948,11 +34151,11 @@ function isDir(p) {
|
|
|
33948
34151
|
function listGeminiSessionFiles(base2) {
|
|
33949
34152
|
const out = [];
|
|
33950
34153
|
for (const project of safeReaddir(base2)) {
|
|
33951
|
-
const chats =
|
|
34154
|
+
const chats = path48.join(base2, project, "chats");
|
|
33952
34155
|
if (!isDir(chats)) continue;
|
|
33953
34156
|
for (const f of safeReaddir(chats)) {
|
|
33954
34157
|
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
33955
|
-
out.push({ file:
|
|
34158
|
+
out.push({ file: path48.join(chats, f), project });
|
|
33956
34159
|
}
|
|
33957
34160
|
}
|
|
33958
34161
|
}
|
|
@@ -34019,7 +34222,7 @@ var init_cost_gemini = __esm({
|
|
|
34019
34222
|
id: "gemini",
|
|
34020
34223
|
available() {
|
|
34021
34224
|
try {
|
|
34022
|
-
return
|
|
34225
|
+
return fs47.existsSync(geminiTmpDir());
|
|
34023
34226
|
} catch {
|
|
34024
34227
|
return false;
|
|
34025
34228
|
}
|
|
@@ -34028,13 +34231,13 @@ var init_cost_gemini = __esm({
|
|
|
34028
34231
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34029
34232
|
for (const { file: file2, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
34030
34233
|
try {
|
|
34031
|
-
if (sinceMs !== void 0 &&
|
|
34234
|
+
if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34032
34235
|
} catch {
|
|
34033
34236
|
continue;
|
|
34034
34237
|
}
|
|
34035
34238
|
let content;
|
|
34036
34239
|
try {
|
|
34037
|
-
content =
|
|
34240
|
+
content = fs47.readFileSync(file2, "utf8");
|
|
34038
34241
|
} catch {
|
|
34039
34242
|
continue;
|
|
34040
34243
|
}
|
|
@@ -34059,15 +34262,15 @@ var init_cost_gemini = __esm({
|
|
|
34059
34262
|
});
|
|
34060
34263
|
|
|
34061
34264
|
// src/cost-copilot.ts
|
|
34062
|
-
import
|
|
34063
|
-
import
|
|
34064
|
-
import
|
|
34265
|
+
import fs48 from "fs";
|
|
34266
|
+
import os41 from "os";
|
|
34267
|
+
import path49 from "path";
|
|
34065
34268
|
function copilotSessionsDir() {
|
|
34066
|
-
return
|
|
34269
|
+
return path49.join(os41.homedir(), ".copilot", "session-state");
|
|
34067
34270
|
}
|
|
34068
34271
|
function safeReaddir2(dir2) {
|
|
34069
34272
|
try {
|
|
34070
|
-
return
|
|
34273
|
+
return fs48.readdirSync(dir2);
|
|
34071
34274
|
} catch {
|
|
34072
34275
|
return [];
|
|
34073
34276
|
}
|
|
@@ -34143,7 +34346,7 @@ var init_cost_copilot = __esm({
|
|
|
34143
34346
|
id: "copilot",
|
|
34144
34347
|
available() {
|
|
34145
34348
|
try {
|
|
34146
|
-
return
|
|
34349
|
+
return fs48.existsSync(copilotSessionsDir());
|
|
34147
34350
|
} catch {
|
|
34148
34351
|
return false;
|
|
34149
34352
|
}
|
|
@@ -34152,15 +34355,15 @@ var init_cost_copilot = __esm({
|
|
|
34152
34355
|
const base2 = copilotSessionsDir();
|
|
34153
34356
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34154
34357
|
for (const sid of safeReaddir2(base2)) {
|
|
34155
|
-
const file2 =
|
|
34358
|
+
const file2 = path49.join(base2, sid, "events.jsonl");
|
|
34156
34359
|
try {
|
|
34157
|
-
if (sinceMs !== void 0 &&
|
|
34360
|
+
if (sinceMs !== void 0 && fs48.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34158
34361
|
} catch {
|
|
34159
34362
|
continue;
|
|
34160
34363
|
}
|
|
34161
34364
|
let content;
|
|
34162
34365
|
try {
|
|
34163
|
-
content =
|
|
34366
|
+
content = fs48.readFileSync(file2, "utf8");
|
|
34164
34367
|
} catch {
|
|
34165
34368
|
continue;
|
|
34166
34369
|
}
|
|
@@ -34185,21 +34388,21 @@ var init_cost_copilot = __esm({
|
|
|
34185
34388
|
});
|
|
34186
34389
|
|
|
34187
34390
|
// src/session-files.ts
|
|
34188
|
-
import * as
|
|
34189
|
-
import * as
|
|
34391
|
+
import * as fs49 from "fs";
|
|
34392
|
+
import * as path50 from "path";
|
|
34190
34393
|
function listSessionFiles(dir2, maxDepth = 6) {
|
|
34191
34394
|
const out = [];
|
|
34192
34395
|
const walk2 = (d, rel, depth) => {
|
|
34193
34396
|
if (depth > maxDepth) return;
|
|
34194
34397
|
let entries;
|
|
34195
34398
|
try {
|
|
34196
|
-
entries =
|
|
34399
|
+
entries = fs49.readdirSync(d, { withFileTypes: true });
|
|
34197
34400
|
} catch {
|
|
34198
34401
|
return;
|
|
34199
34402
|
}
|
|
34200
34403
|
for (const e of entries) {
|
|
34201
|
-
const childRel = rel ?
|
|
34202
|
-
if (e.isDirectory()) walk2(
|
|
34404
|
+
const childRel = rel ? path50.join(rel, e.name) : e.name;
|
|
34405
|
+
if (e.isDirectory()) walk2(path50.join(d, e.name), childRel, depth + 1);
|
|
34203
34406
|
else if (e.name.endsWith(".jsonl")) out.push(childRel);
|
|
34204
34407
|
}
|
|
34205
34408
|
};
|
|
@@ -34207,7 +34410,7 @@ function listSessionFiles(dir2, maxDepth = 6) {
|
|
|
34207
34410
|
return out;
|
|
34208
34411
|
}
|
|
34209
34412
|
function sessionIdOf(relPath) {
|
|
34210
|
-
return
|
|
34413
|
+
return path50.basename(relPath).replace(/\.jsonl$/, "");
|
|
34211
34414
|
}
|
|
34212
34415
|
var init_session_files = __esm({
|
|
34213
34416
|
"src/session-files.ts"() {
|
|
@@ -34216,17 +34419,17 @@ var init_session_files = __esm({
|
|
|
34216
34419
|
});
|
|
34217
34420
|
|
|
34218
34421
|
// src/costSync.ts
|
|
34219
|
-
import
|
|
34220
|
-
import
|
|
34221
|
-
import
|
|
34422
|
+
import fs50 from "fs";
|
|
34423
|
+
import path51 from "path";
|
|
34424
|
+
import os42 from "os";
|
|
34222
34425
|
function decodeProjectDirName(dirName) {
|
|
34223
34426
|
return dirName.replace(/-/g, "/");
|
|
34224
34427
|
}
|
|
34225
34428
|
function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
34226
|
-
const runId =
|
|
34429
|
+
const runId = path51.basename(filePath, ".jsonl");
|
|
34227
34430
|
let content;
|
|
34228
34431
|
try {
|
|
34229
|
-
content =
|
|
34432
|
+
content = fs50.readFileSync(filePath, "utf8");
|
|
34230
34433
|
} catch {
|
|
34231
34434
|
return /* @__PURE__ */ new Map();
|
|
34232
34435
|
}
|
|
@@ -34326,7 +34529,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
|
|
|
34326
34529
|
signal: AbortSignal.timeout(15e3)
|
|
34327
34530
|
});
|
|
34328
34531
|
if (!res.ok) {
|
|
34329
|
-
|
|
34532
|
+
fs50.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
34330
34533
|
`);
|
|
34331
34534
|
} else {
|
|
34332
34535
|
let stored;
|
|
@@ -34348,8 +34551,8 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
|
|
|
34348
34551
|
}
|
|
34349
34552
|
function debugLog(line) {
|
|
34350
34553
|
try {
|
|
34351
|
-
|
|
34352
|
-
|
|
34554
|
+
fs50.mkdirSync(path51.dirname(HOOK_DEBUG_LOG), { recursive: true });
|
|
34555
|
+
fs50.appendFileSync(HOOK_DEBUG_LOG, line);
|
|
34353
34556
|
} catch (err2) {
|
|
34354
34557
|
console.error(`[cost-sync] could not write ${HOOK_DEBUG_LOG}: ${safeMessage(err2)}`);
|
|
34355
34558
|
}
|
|
@@ -34362,10 +34565,10 @@ async function syncCost() {
|
|
|
34362
34565
|
if (entries.length === 0) return;
|
|
34363
34566
|
let username = "unknown";
|
|
34364
34567
|
try {
|
|
34365
|
-
username =
|
|
34568
|
+
username = os42.userInfo().username;
|
|
34366
34569
|
} catch {
|
|
34367
34570
|
}
|
|
34368
|
-
const machineId = `${
|
|
34571
|
+
const machineId = `${os42.hostname()}:${username}`;
|
|
34369
34572
|
await postCostBatches(creds.apiUrl, creds.apiKey, machineId, entries);
|
|
34370
34573
|
}
|
|
34371
34574
|
function startCostSync() {
|
|
@@ -34393,22 +34596,22 @@ var init_costSync = __esm({
|
|
|
34393
34596
|
claudeSource = {
|
|
34394
34597
|
id: "claude",
|
|
34395
34598
|
available() {
|
|
34396
|
-
return
|
|
34599
|
+
return fs50.existsSync(path51.join(os42.homedir(), ".claude", "projects"));
|
|
34397
34600
|
},
|
|
34398
34601
|
collect(sinceMs) {
|
|
34399
|
-
const projectsDir =
|
|
34400
|
-
if (!
|
|
34602
|
+
const projectsDir = path51.join(os42.homedir(), ".claude", "projects");
|
|
34603
|
+
if (!fs50.existsSync(projectsDir)) return [];
|
|
34401
34604
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34402
34605
|
let dirs;
|
|
34403
34606
|
try {
|
|
34404
|
-
dirs =
|
|
34607
|
+
dirs = fs50.readdirSync(projectsDir);
|
|
34405
34608
|
} catch {
|
|
34406
34609
|
return [];
|
|
34407
34610
|
}
|
|
34408
34611
|
for (const dir2 of dirs) {
|
|
34409
|
-
const dirPath =
|
|
34612
|
+
const dirPath = path51.join(projectsDir, dir2);
|
|
34410
34613
|
try {
|
|
34411
|
-
if (!
|
|
34614
|
+
if (!fs50.statSync(dirPath).isDirectory()) continue;
|
|
34412
34615
|
} catch {
|
|
34413
34616
|
continue;
|
|
34414
34617
|
}
|
|
@@ -34420,10 +34623,10 @@ var init_costSync = __esm({
|
|
|
34420
34623
|
}
|
|
34421
34624
|
const fallbackWorkingDir = decodeProjectDirName(dir2);
|
|
34422
34625
|
for (const file2 of files) {
|
|
34423
|
-
const filePath =
|
|
34626
|
+
const filePath = path51.join(dirPath, file2);
|
|
34424
34627
|
if (sinceMs !== void 0) {
|
|
34425
34628
|
try {
|
|
34426
|
-
if (
|
|
34629
|
+
if (fs50.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
34427
34630
|
} catch {
|
|
34428
34631
|
continue;
|
|
34429
34632
|
}
|
|
@@ -34659,7 +34862,7 @@ function buildScanSummary(agents) {
|
|
|
34659
34862
|
};
|
|
34660
34863
|
}
|
|
34661
34864
|
function wouldHaveVerb(summary, past = "would have blocked") {
|
|
34662
|
-
return summary.policy?.recordOnly ? `logged only (${summary.policy.mode} mode, nothing enforced)` : past;
|
|
34865
|
+
return summary.policy?.recordOnly ? `logged only (${levelOf(summary.policy.mode)} mode, nothing enforced)` : past;
|
|
34663
34866
|
}
|
|
34664
34867
|
function buildSections(findings) {
|
|
34665
34868
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
@@ -34795,10 +34998,10 @@ var init_scan_summary = __esm({
|
|
|
34795
34998
|
});
|
|
34796
34999
|
|
|
34797
35000
|
// src/credential-files.ts
|
|
34798
|
-
import
|
|
35001
|
+
import path52 from "path";
|
|
34799
35002
|
function homeCredentialFiles(home) {
|
|
34800
35003
|
const f = (parts, description, score, kind = "credential") => ({
|
|
34801
|
-
full:
|
|
35004
|
+
full: path52.join(home, ...parts),
|
|
34802
35005
|
label: "~/" + parts.join("/"),
|
|
34803
35006
|
description,
|
|
34804
35007
|
score,
|
|
@@ -34819,8 +35022,7 @@ function homeCredentialFiles(home) {
|
|
|
34819
35022
|
),
|
|
34820
35023
|
f([".docker", "config.json"], "Docker registry auth tokens", 10),
|
|
34821
35024
|
f([".netrc"], "FTP/HTTP credentials in plain text", 15),
|
|
34822
|
-
f([".npmrc"], "npm auth token: can publish packages as you", 10)
|
|
34823
|
-
f([".node9", "credentials.json"], "Node9 cloud API key", 10)
|
|
35025
|
+
f([".npmrc"], "npm auth token: can publish packages as you", 10)
|
|
34824
35026
|
];
|
|
34825
35027
|
}
|
|
34826
35028
|
var init_credential_files = __esm({
|
|
@@ -34831,9 +35033,9 @@ var init_credential_files = __esm({
|
|
|
34831
35033
|
|
|
34832
35034
|
// src/cli/commands/blast.ts
|
|
34833
35035
|
import chalk3 from "chalk";
|
|
34834
|
-
import
|
|
34835
|
-
import
|
|
34836
|
-
import
|
|
35036
|
+
import fs51 from "fs";
|
|
35037
|
+
import path53 from "path";
|
|
35038
|
+
import os43 from "os";
|
|
34837
35039
|
function buildSensitivePaths(home, cwd) {
|
|
34838
35040
|
const homeFiles = homeCredentialFiles(home).map(({ full, label: label3, description, score }) => ({
|
|
34839
35041
|
full,
|
|
@@ -34844,19 +35046,19 @@ function buildSensitivePaths(home, cwd) {
|
|
|
34844
35046
|
return [
|
|
34845
35047
|
...homeFiles,
|
|
34846
35048
|
{
|
|
34847
|
-
full:
|
|
35049
|
+
full: path53.join(cwd, ".env"),
|
|
34848
35050
|
label: ".env (current folder)",
|
|
34849
35051
|
description: "App secrets: database passwords, API keys",
|
|
34850
35052
|
score: 20
|
|
34851
35053
|
},
|
|
34852
35054
|
{
|
|
34853
|
-
full:
|
|
35055
|
+
full: path53.join(cwd, ".env.local"),
|
|
34854
35056
|
label: ".env.local (current folder)",
|
|
34855
35057
|
description: "Local overrides: often contains real credentials",
|
|
34856
35058
|
score: 15
|
|
34857
35059
|
},
|
|
34858
35060
|
{
|
|
34859
|
-
full:
|
|
35061
|
+
full: path53.join(cwd, ".env.production"),
|
|
34860
35062
|
label: ".env.production (current folder)",
|
|
34861
35063
|
description: "Production secrets",
|
|
34862
35064
|
score: 20
|
|
@@ -34865,7 +35067,7 @@ function buildSensitivePaths(home, cwd) {
|
|
|
34865
35067
|
}
|
|
34866
35068
|
function isReadable(filePath) {
|
|
34867
35069
|
try {
|
|
34868
|
-
|
|
35070
|
+
fs51.accessSync(filePath, fs51.constants.R_OK);
|
|
34869
35071
|
return true;
|
|
34870
35072
|
} catch {
|
|
34871
35073
|
return false;
|
|
@@ -34878,13 +35080,13 @@ function scoreLabel(score) {
|
|
|
34878
35080
|
return chalk3.red.bold(`${score}/100 Critical`);
|
|
34879
35081
|
}
|
|
34880
35082
|
function runBlast() {
|
|
34881
|
-
const home =
|
|
35083
|
+
const home = os43.homedir();
|
|
34882
35084
|
const cwd = process.cwd();
|
|
34883
35085
|
const paths = buildSensitivePaths(home, cwd);
|
|
34884
35086
|
let scoreDeduction = 0;
|
|
34885
35087
|
const reachable = [];
|
|
34886
35088
|
for (const p of paths) {
|
|
34887
|
-
if (
|
|
35089
|
+
if (fs51.existsSync(p.full) && isReadable(p.full)) {
|
|
34888
35090
|
reachable.push(p);
|
|
34889
35091
|
scoreDeduction += p.score;
|
|
34890
35092
|
}
|
|
@@ -34900,11 +35102,18 @@ function runBlast() {
|
|
|
34900
35102
|
}
|
|
34901
35103
|
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
34902
35104
|
}
|
|
35105
|
+
function labelColumnWidth(labels) {
|
|
35106
|
+
return Math.max(38, ...labels.map((l) => l.length + 2));
|
|
35107
|
+
}
|
|
34903
35108
|
function registerBlastCommand(program2) {
|
|
34904
35109
|
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
34905
|
-
const home =
|
|
35110
|
+
const home = os43.homedir();
|
|
34906
35111
|
const cwd = process.cwd();
|
|
34907
35112
|
const { reachable, envFindings, score } = runBlast();
|
|
35113
|
+
const width = labelColumnWidth([
|
|
35114
|
+
...reachable.map((p) => p.label),
|
|
35115
|
+
...envFindings.map((f) => f.key)
|
|
35116
|
+
]);
|
|
34908
35117
|
console.log("");
|
|
34909
35118
|
console.log(
|
|
34910
35119
|
chalk3.bold(" \u{1F52D} Node9 Blast Radius") + chalk3.dim(" \xB7 what an AI agent can reach from here")
|
|
@@ -34915,7 +35124,7 @@ function registerBlastCommand(program2) {
|
|
|
34915
35124
|
console.log(" " + chalk3.red.bold("Sensitive files reachable:"));
|
|
34916
35125
|
for (const p of reachable) {
|
|
34917
35126
|
console.log(
|
|
34918
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(
|
|
35127
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(width)) + chalk3.dim(p.description)
|
|
34919
35128
|
);
|
|
34920
35129
|
}
|
|
34921
35130
|
console.log("");
|
|
@@ -34924,7 +35133,7 @@ function registerBlastCommand(program2) {
|
|
|
34924
35133
|
console.log(" " + chalk3.red.bold("Secrets in active environment:"));
|
|
34925
35134
|
for (const f of envFindings) {
|
|
34926
35135
|
console.log(
|
|
34927
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(
|
|
35136
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(width)) + chalk3.dim(f.patternName)
|
|
34928
35137
|
);
|
|
34929
35138
|
}
|
|
34930
35139
|
console.log("");
|
|
@@ -35039,6 +35248,12 @@ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
|
35039
35248
|
if (days > 90) return "90d+";
|
|
35040
35249
|
return `${days}d`;
|
|
35041
35250
|
}
|
|
35251
|
+
function relativeAgo(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
35252
|
+
const rel = relativeDate(timestamp2, now);
|
|
35253
|
+
if (rel === "?" || rel === "today") return rel;
|
|
35254
|
+
if (rel === "1d") return "yesterday";
|
|
35255
|
+
return `${rel} ago`;
|
|
35256
|
+
}
|
|
35042
35257
|
var PANEL_WIDTH;
|
|
35043
35258
|
var init_scan_derive = __esm({
|
|
35044
35259
|
"src/cli/render/scan-derive.ts"() {
|
|
@@ -35092,17 +35307,17 @@ var init_scan_json = __esm({
|
|
|
35092
35307
|
});
|
|
35093
35308
|
|
|
35094
35309
|
// src/cli/render/scan-history.ts
|
|
35095
|
-
import
|
|
35096
|
-
import
|
|
35097
|
-
import
|
|
35310
|
+
import fs52 from "fs";
|
|
35311
|
+
import path54 from "path";
|
|
35312
|
+
import os44 from "os";
|
|
35098
35313
|
function defaultHistoryPath() {
|
|
35099
|
-
return
|
|
35314
|
+
return path54.join(os44.homedir(), ".node9", "scan-history.json");
|
|
35100
35315
|
}
|
|
35101
35316
|
function readPreviousScan(opts = {}) {
|
|
35102
35317
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
35103
35318
|
try {
|
|
35104
|
-
if (!
|
|
35105
|
-
const raw =
|
|
35319
|
+
if (!fs52.existsSync(filePath)) return null;
|
|
35320
|
+
const raw = fs52.readFileSync(filePath, "utf8");
|
|
35106
35321
|
const parsed = JSON.parse(raw);
|
|
35107
35322
|
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
35108
35323
|
const last = parsed[parsed.length - 1];
|
|
@@ -35116,11 +35331,11 @@ function appendScanHistory(record2, opts = {}) {
|
|
|
35116
35331
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
35117
35332
|
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
35118
35333
|
try {
|
|
35119
|
-
|
|
35334
|
+
fs52.mkdirSync(path54.dirname(filePath), { recursive: true });
|
|
35120
35335
|
let history = [];
|
|
35121
|
-
if (
|
|
35336
|
+
if (fs52.existsSync(filePath)) {
|
|
35122
35337
|
try {
|
|
35123
|
-
const parsed = JSON.parse(
|
|
35338
|
+
const parsed = JSON.parse(fs52.readFileSync(filePath, "utf8"));
|
|
35124
35339
|
if (Array.isArray(parsed)) {
|
|
35125
35340
|
history = parsed.filter(isValidRecord);
|
|
35126
35341
|
}
|
|
@@ -35175,9 +35390,9 @@ __export(scan_watermark_exports, {
|
|
|
35175
35390
|
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
35176
35391
|
tickScanWatcher: () => tickScanWatcher
|
|
35177
35392
|
});
|
|
35178
|
-
import
|
|
35179
|
-
import
|
|
35180
|
-
import
|
|
35393
|
+
import fs53 from "fs";
|
|
35394
|
+
import os45 from "os";
|
|
35395
|
+
import path55 from "path";
|
|
35181
35396
|
import readline from "readline";
|
|
35182
35397
|
function freshWatermark() {
|
|
35183
35398
|
return {
|
|
@@ -35190,7 +35405,7 @@ function freshWatermark() {
|
|
|
35190
35405
|
function loadWatermark() {
|
|
35191
35406
|
let raw;
|
|
35192
35407
|
try {
|
|
35193
|
-
raw =
|
|
35408
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35194
35409
|
} catch {
|
|
35195
35410
|
return { status: "fresh", wm: freshWatermark() };
|
|
35196
35411
|
}
|
|
@@ -35242,28 +35457,28 @@ function loadWatermark() {
|
|
|
35242
35457
|
function saveWatermark(wm) {
|
|
35243
35458
|
if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
|
|
35244
35459
|
const target = WATERMARK_FILE();
|
|
35245
|
-
const dir2 =
|
|
35246
|
-
if (!
|
|
35460
|
+
const dir2 = path55.dirname(target);
|
|
35461
|
+
if (!fs53.existsSync(dir2)) fs53.mkdirSync(dir2, { recursive: true });
|
|
35247
35462
|
const tmp = target + ".tmp";
|
|
35248
|
-
|
|
35249
|
-
|
|
35463
|
+
fs53.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
|
|
35464
|
+
fs53.renameSync(tmp, target);
|
|
35250
35465
|
}
|
|
35251
35466
|
function listJsonlFiles() {
|
|
35252
35467
|
const root = PROJECTS_DIR();
|
|
35253
|
-
if (!
|
|
35468
|
+
if (!fs53.existsSync(root)) return [];
|
|
35254
35469
|
const out = [];
|
|
35255
|
-
for (const entry of
|
|
35470
|
+
for (const entry of fs53.readdirSync(root, { withFileTypes: true })) {
|
|
35256
35471
|
if (!entry.isDirectory()) continue;
|
|
35257
|
-
const projectDir =
|
|
35472
|
+
const projectDir = path55.join(root, entry.name);
|
|
35258
35473
|
let inner;
|
|
35259
35474
|
try {
|
|
35260
|
-
inner =
|
|
35475
|
+
inner = fs53.readdirSync(projectDir, { withFileTypes: true });
|
|
35261
35476
|
} catch {
|
|
35262
35477
|
continue;
|
|
35263
35478
|
}
|
|
35264
35479
|
for (const file2 of inner) {
|
|
35265
35480
|
if (file2.isFile() && file2.name.endsWith(".jsonl")) {
|
|
35266
|
-
out.push(
|
|
35481
|
+
out.push(path55.join(projectDir, file2.name));
|
|
35267
35482
|
}
|
|
35268
35483
|
}
|
|
35269
35484
|
}
|
|
@@ -35271,7 +35486,7 @@ function listJsonlFiles() {
|
|
|
35271
35486
|
}
|
|
35272
35487
|
function fileSize(p) {
|
|
35273
35488
|
try {
|
|
35274
|
-
return
|
|
35489
|
+
return fs53.statSync(p).size;
|
|
35275
35490
|
} catch {
|
|
35276
35491
|
return 0;
|
|
35277
35492
|
}
|
|
@@ -35281,7 +35496,7 @@ async function scanDelta(filePath, fromByte, onLine) {
|
|
|
35281
35496
|
if (size <= fromByte) return fromByte;
|
|
35282
35497
|
const lastNl = findLastNewline(filePath, fromByte, size);
|
|
35283
35498
|
const endsWithNewline = lastNl === size - 1;
|
|
35284
|
-
const stream =
|
|
35499
|
+
const stream = fs53.createReadStream(filePath, {
|
|
35285
35500
|
start: fromByte,
|
|
35286
35501
|
end: size - 1,
|
|
35287
35502
|
highWaterMark: 64 * 1024
|
|
@@ -35310,7 +35525,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35310
35525
|
const CHUNK = 64 * 1024;
|
|
35311
35526
|
let fd;
|
|
35312
35527
|
try {
|
|
35313
|
-
fd =
|
|
35528
|
+
fd = fs53.openSync(filePath, "r");
|
|
35314
35529
|
} catch {
|
|
35315
35530
|
return -1;
|
|
35316
35531
|
}
|
|
@@ -35319,7 +35534,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35319
35534
|
let end = size;
|
|
35320
35535
|
while (end > from) {
|
|
35321
35536
|
const start = Math.max(from, end - CHUNK);
|
|
35322
|
-
const n =
|
|
35537
|
+
const n = fs53.readSync(fd, buf, 0, end - start, start);
|
|
35323
35538
|
const idx = buf.subarray(0, n).lastIndexOf(10);
|
|
35324
35539
|
if (idx !== -1) return start + idx;
|
|
35325
35540
|
end = start;
|
|
@@ -35328,7 +35543,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35328
35543
|
} catch {
|
|
35329
35544
|
return -1;
|
|
35330
35545
|
} finally {
|
|
35331
|
-
|
|
35546
|
+
fs53.closeSync(fd);
|
|
35332
35547
|
}
|
|
35333
35548
|
}
|
|
35334
35549
|
function safeCanaryCtxValues() {
|
|
@@ -35339,7 +35554,7 @@ function safeCanaryCtxValues() {
|
|
|
35339
35554
|
}
|
|
35340
35555
|
}
|
|
35341
35556
|
function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
35342
|
-
const cwd = typeof obj.cwd === "string" &&
|
|
35557
|
+
const cwd = typeof obj.cwd === "string" && path55.isAbsolute(obj.cwd) ? obj.cwd : void 0;
|
|
35343
35558
|
return {
|
|
35344
35559
|
sessionId,
|
|
35345
35560
|
lineIndex,
|
|
@@ -35355,7 +35570,7 @@ function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
|
35355
35570
|
// line-level DLP runs above already
|
|
35356
35571
|
canaryValues: canaryVals,
|
|
35357
35572
|
...cwd && { cwd },
|
|
35358
|
-
homeDirs: [
|
|
35573
|
+
homeDirs: [os45.homedir()]
|
|
35359
35574
|
};
|
|
35360
35575
|
}
|
|
35361
35576
|
function extractFindingsFromLine(line, sessionId, lineIndex, canaryVals = []) {
|
|
@@ -35443,7 +35658,7 @@ async function tickForensicBroadcast(offsets) {
|
|
|
35443
35658
|
continue;
|
|
35444
35659
|
}
|
|
35445
35660
|
if (size <= offset) continue;
|
|
35446
|
-
const sessionId =
|
|
35661
|
+
const sessionId = path55.basename(file2, ".jsonl");
|
|
35447
35662
|
const newOffset = await scanDelta(file2, offset, (obj, lineIndex) => {
|
|
35448
35663
|
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex, canaryVals));
|
|
35449
35664
|
});
|
|
@@ -35499,7 +35714,7 @@ function emptyTick(uploadAs) {
|
|
|
35499
35714
|
function readRawWatermarkPreservingOffsets() {
|
|
35500
35715
|
let raw;
|
|
35501
35716
|
try {
|
|
35502
|
-
raw =
|
|
35717
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35503
35718
|
} catch {
|
|
35504
35719
|
return null;
|
|
35505
35720
|
}
|
|
@@ -35534,13 +35749,13 @@ async function runActualTick(wm) {
|
|
|
35534
35749
|
if (!known) {
|
|
35535
35750
|
let mtimeMs = 0;
|
|
35536
35751
|
try {
|
|
35537
|
-
mtimeMs =
|
|
35752
|
+
mtimeMs = fs53.statSync(filePath).mtime.getTime();
|
|
35538
35753
|
} catch {
|
|
35539
35754
|
continue;
|
|
35540
35755
|
}
|
|
35541
35756
|
if (mtimeMs >= watermarkCreatedAt) {
|
|
35542
35757
|
filesNew++;
|
|
35543
|
-
const sessionId2 =
|
|
35758
|
+
const sessionId2 = path55.basename(filePath, ".jsonl");
|
|
35544
35759
|
const newScannedTo2 = await scanDelta(filePath, 0, (obj, lineIndex) => {
|
|
35545
35760
|
totalToolCalls++;
|
|
35546
35761
|
toolCallsBySession[sessionId2] = (toolCallsBySession[sessionId2] ?? 0) + 1;
|
|
@@ -35558,7 +35773,7 @@ async function runActualTick(wm) {
|
|
|
35558
35773
|
filesSkipped++;
|
|
35559
35774
|
continue;
|
|
35560
35775
|
}
|
|
35561
|
-
const sessionId =
|
|
35776
|
+
const sessionId = path55.basename(filePath, ".jsonl");
|
|
35562
35777
|
const newScannedTo = await scanDelta(filePath, known.scannedTo, (obj, lineIndex) => {
|
|
35563
35778
|
totalToolCalls++;
|
|
35564
35779
|
toolCallsBySession[sessionId] = (toolCallsBySession[sessionId] ?? 0) + 1;
|
|
@@ -35593,8 +35808,8 @@ var init_scan_watermark = __esm({
|
|
|
35593
35808
|
init_registry2();
|
|
35594
35809
|
init_config();
|
|
35595
35810
|
init_dist();
|
|
35596
|
-
PROJECTS_DIR = () =>
|
|
35597
|
-
WATERMARK_FILE = () =>
|
|
35811
|
+
PROJECTS_DIR = () => path55.join(os45.homedir(), ".claude", "projects");
|
|
35812
|
+
WATERMARK_FILE = () => path55.join(os45.homedir(), ".node9", "scan-watermark.json");
|
|
35598
35813
|
MAX_LINE_BYTES = 2 * 1024 * 1024;
|
|
35599
35814
|
WATERMARK_SCHEMA_VERSION = 2;
|
|
35600
35815
|
LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
|
|
@@ -35610,10 +35825,10 @@ __export(scan_upload_history_exports, {
|
|
|
35610
35825
|
parseSinceCutoff: () => parseSinceCutoff,
|
|
35611
35826
|
runUploadHistory: () => runUploadHistory
|
|
35612
35827
|
});
|
|
35613
|
-
import
|
|
35828
|
+
import fs54 from "fs";
|
|
35614
35829
|
import https from "https";
|
|
35615
|
-
import
|
|
35616
|
-
import
|
|
35830
|
+
import os46 from "os";
|
|
35831
|
+
import path56 from "path";
|
|
35617
35832
|
import chalk5 from "chalk";
|
|
35618
35833
|
function emptySignals2() {
|
|
35619
35834
|
return {
|
|
@@ -35648,40 +35863,40 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
|
|
|
35648
35863
|
return now.getTime() - 90 * 864e5;
|
|
35649
35864
|
}
|
|
35650
35865
|
function* iterateJsonlFiles(cutoffMs) {
|
|
35651
|
-
const projectsDir =
|
|
35866
|
+
const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
|
|
35652
35867
|
let dirs;
|
|
35653
35868
|
try {
|
|
35654
|
-
dirs =
|
|
35869
|
+
dirs = fs54.readdirSync(projectsDir);
|
|
35655
35870
|
} catch {
|
|
35656
35871
|
return;
|
|
35657
35872
|
}
|
|
35658
35873
|
for (const dir2 of dirs) {
|
|
35659
|
-
const dirPath =
|
|
35874
|
+
const dirPath = path56.join(projectsDir, dir2);
|
|
35660
35875
|
let stats;
|
|
35661
35876
|
try {
|
|
35662
|
-
stats =
|
|
35877
|
+
stats = fs54.statSync(dirPath);
|
|
35663
35878
|
} catch {
|
|
35664
35879
|
continue;
|
|
35665
35880
|
}
|
|
35666
35881
|
if (!stats.isDirectory()) continue;
|
|
35667
35882
|
let files;
|
|
35668
35883
|
try {
|
|
35669
|
-
files =
|
|
35884
|
+
files = fs54.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
35670
35885
|
} catch {
|
|
35671
35886
|
continue;
|
|
35672
35887
|
}
|
|
35673
35888
|
for (const file2 of files) {
|
|
35674
|
-
const filePath =
|
|
35889
|
+
const filePath = path56.join(dirPath, file2);
|
|
35675
35890
|
let mtime = 0;
|
|
35676
35891
|
try {
|
|
35677
|
-
mtime =
|
|
35892
|
+
mtime = fs54.statSync(filePath).mtimeMs;
|
|
35678
35893
|
} catch {
|
|
35679
35894
|
continue;
|
|
35680
35895
|
}
|
|
35681
35896
|
if (mtime < cutoffMs) continue;
|
|
35682
35897
|
yield {
|
|
35683
35898
|
filePath,
|
|
35684
|
-
sessionId:
|
|
35899
|
+
sessionId: path56.basename(file2, ".jsonl"),
|
|
35685
35900
|
projectDir: dir2
|
|
35686
35901
|
};
|
|
35687
35902
|
}
|
|
@@ -35749,7 +35964,7 @@ async function runUploadHistory(opts) {
|
|
|
35749
35964
|
filesScanned++;
|
|
35750
35965
|
let content;
|
|
35751
35966
|
try {
|
|
35752
|
-
content =
|
|
35967
|
+
content = fs54.readFileSync(filePath, "utf8");
|
|
35753
35968
|
} catch {
|
|
35754
35969
|
continue;
|
|
35755
35970
|
}
|
|
@@ -35823,10 +36038,10 @@ async function runUploadHistory(opts) {
|
|
|
35823
36038
|
const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
|
|
35824
36039
|
let username = "unknown";
|
|
35825
36040
|
try {
|
|
35826
|
-
username =
|
|
36041
|
+
username = os46.userInfo().username;
|
|
35827
36042
|
} catch {
|
|
35828
36043
|
}
|
|
35829
|
-
const machineId = `${
|
|
36044
|
+
const machineId = `${os46.hostname()}:${username}`;
|
|
35830
36045
|
await postJson(costUrl, creds.apiKey, {
|
|
35831
36046
|
machineId,
|
|
35832
36047
|
entries: dailyEntries
|
|
@@ -35900,9 +36115,9 @@ var init_scan_upload_history = __esm({
|
|
|
35900
36115
|
|
|
35901
36116
|
// src/cli/commands/scan.ts
|
|
35902
36117
|
import chalk6 from "chalk";
|
|
35903
|
-
import
|
|
35904
|
-
import
|
|
35905
|
-
import
|
|
36118
|
+
import fs55 from "fs";
|
|
36119
|
+
import path57 from "path";
|
|
36120
|
+
import os47 from "os";
|
|
35906
36121
|
import stringWidth2 from "string-width";
|
|
35907
36122
|
function dayKey(ts) {
|
|
35908
36123
|
if (!ts) return "unknown";
|
|
@@ -36045,7 +36260,7 @@ function emptyScanDedup() {
|
|
|
36045
36260
|
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
|
|
36046
36261
|
}
|
|
36047
36262
|
function displayHome(p) {
|
|
36048
|
-
const home =
|
|
36263
|
+
const home = os47.homedir();
|
|
36049
36264
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
36050
36265
|
}
|
|
36051
36266
|
function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
|
|
@@ -36184,7 +36399,7 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
36184
36399
|
return findings.sort((a, b) => b.count - a.count);
|
|
36185
36400
|
}
|
|
36186
36401
|
function geminiChatFiles(chatsDir) {
|
|
36187
|
-
const files =
|
|
36402
|
+
const files = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
36188
36403
|
const seen = /* @__PURE__ */ new Set();
|
|
36189
36404
|
return files.filter((f) => {
|
|
36190
36405
|
const id = f.replace(/\.jsonl?$/, "");
|
|
@@ -36196,14 +36411,14 @@ function geminiChatFiles(chatsDir) {
|
|
|
36196
36411
|
function countScanFiles(startDate = null) {
|
|
36197
36412
|
let total = 0;
|
|
36198
36413
|
const counts = (p) => fileMayHoldWindow(fileStamp(p), startDate);
|
|
36199
|
-
const claudeDir =
|
|
36200
|
-
if (
|
|
36414
|
+
const claudeDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36415
|
+
if (fs55.existsSync(claudeDir)) {
|
|
36201
36416
|
try {
|
|
36202
|
-
for (const proj of
|
|
36203
|
-
const p =
|
|
36417
|
+
for (const proj of fs55.readdirSync(claudeDir)) {
|
|
36418
|
+
const p = path57.join(claudeDir, proj);
|
|
36204
36419
|
try {
|
|
36205
|
-
if (!
|
|
36206
|
-
total += listSessionFiles(p).filter((f) => counts(
|
|
36420
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36421
|
+
total += listSessionFiles(p).filter((f) => counts(path57.join(p, f))).length;
|
|
36207
36422
|
} catch {
|
|
36208
36423
|
continue;
|
|
36209
36424
|
}
|
|
@@ -36211,18 +36426,18 @@ function countScanFiles(startDate = null) {
|
|
|
36211
36426
|
} catch {
|
|
36212
36427
|
}
|
|
36213
36428
|
}
|
|
36214
|
-
const geminiDir =
|
|
36215
|
-
if (
|
|
36429
|
+
const geminiDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36430
|
+
if (fs55.existsSync(geminiDir)) {
|
|
36216
36431
|
try {
|
|
36217
|
-
for (const slug2 of
|
|
36218
|
-
const p =
|
|
36432
|
+
for (const slug2 of fs55.readdirSync(geminiDir)) {
|
|
36433
|
+
const p = path57.join(geminiDir, slug2);
|
|
36219
36434
|
try {
|
|
36220
|
-
if (!
|
|
36221
|
-
const chatsDir =
|
|
36222
|
-
if (
|
|
36435
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36436
|
+
const chatsDir = path57.join(p, "chats");
|
|
36437
|
+
if (fs55.existsSync(chatsDir)) {
|
|
36223
36438
|
try {
|
|
36224
36439
|
total += geminiChatFiles(chatsDir).filter(
|
|
36225
|
-
(f) => counts(
|
|
36440
|
+
(f) => counts(path57.join(chatsDir, f))
|
|
36226
36441
|
).length;
|
|
36227
36442
|
} catch {
|
|
36228
36443
|
}
|
|
@@ -36235,13 +36450,13 @@ function countScanFiles(startDate = null) {
|
|
|
36235
36450
|
}
|
|
36236
36451
|
}
|
|
36237
36452
|
for (const surface of ["antigravity-cli", "antigravity-ide"]) {
|
|
36238
|
-
const brainDir =
|
|
36239
|
-
if (!
|
|
36453
|
+
const brainDir = path57.join(os47.homedir(), ".gemini", surface, "brain");
|
|
36454
|
+
if (!fs55.existsSync(brainDir)) continue;
|
|
36240
36455
|
try {
|
|
36241
|
-
for (const conv of
|
|
36242
|
-
const convPath =
|
|
36456
|
+
for (const conv of fs55.readdirSync(brainDir)) {
|
|
36457
|
+
const convPath = path57.join(brainDir, conv);
|
|
36243
36458
|
try {
|
|
36244
|
-
if (!
|
|
36459
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36245
36460
|
const transcript = antigravityTranscriptPath(convPath);
|
|
36246
36461
|
if (transcript && counts(transcript)) total += 1;
|
|
36247
36462
|
} catch {
|
|
@@ -36251,12 +36466,12 @@ function countScanFiles(startDate = null) {
|
|
|
36251
36466
|
} catch {
|
|
36252
36467
|
}
|
|
36253
36468
|
}
|
|
36254
|
-
const copilotDir =
|
|
36255
|
-
if (
|
|
36469
|
+
const copilotDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
36470
|
+
if (fs55.existsSync(copilotDir)) {
|
|
36256
36471
|
try {
|
|
36257
|
-
for (const sid of
|
|
36258
|
-
const events =
|
|
36259
|
-
if (
|
|
36472
|
+
for (const sid of fs55.readdirSync(copilotDir)) {
|
|
36473
|
+
const events = path57.join(copilotDir, sid, "events.jsonl");
|
|
36474
|
+
if (fs55.existsSync(events) && counts(events)) total += 1;
|
|
36260
36475
|
}
|
|
36261
36476
|
} catch {
|
|
36262
36477
|
}
|
|
@@ -36280,7 +36495,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36280
36495
|
const session = { sessionId, costUSD: 0, toolCalls: 0 };
|
|
36281
36496
|
let raw;
|
|
36282
36497
|
try {
|
|
36283
|
-
raw =
|
|
36498
|
+
raw = fs55.readFileSync(path57.join(projPath, file2), "utf-8");
|
|
36284
36499
|
} catch {
|
|
36285
36500
|
return false;
|
|
36286
36501
|
}
|
|
@@ -36349,7 +36564,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36349
36564
|
if (block.type !== "tool_result") continue;
|
|
36350
36565
|
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
36351
36566
|
if (filePath) {
|
|
36352
|
-
const ext =
|
|
36567
|
+
const ext = path57.extname(filePath).toLowerCase();
|
|
36353
36568
|
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
36354
36569
|
}
|
|
36355
36570
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
@@ -36469,9 +36684,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36469
36684
|
return true;
|
|
36470
36685
|
}
|
|
36471
36686
|
async function processClaudeProject(proj, projectsDir, judge, cache7, startDate, result, dedup, canaryVals, onProgress, onLine) {
|
|
36472
|
-
const projPath =
|
|
36687
|
+
const projPath = path57.join(projectsDir, proj);
|
|
36473
36688
|
try {
|
|
36474
|
-
if (!
|
|
36689
|
+
if (!fs55.statSync(projPath).isDirectory()) return;
|
|
36475
36690
|
} catch {
|
|
36476
36691
|
return;
|
|
36477
36692
|
}
|
|
@@ -36483,7 +36698,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36483
36698
|
return;
|
|
36484
36699
|
}
|
|
36485
36700
|
for (const file2 of files) {
|
|
36486
|
-
const filePath =
|
|
36701
|
+
const filePath = path57.join(projPath, file2);
|
|
36487
36702
|
const stamp = fileStamp(filePath);
|
|
36488
36703
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36489
36704
|
result.filesScanned++;
|
|
@@ -36514,7 +36729,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36514
36729
|
}
|
|
36515
36730
|
function claudeProjectLabel(dirName) {
|
|
36516
36731
|
const name = decodeURIComponent(dirName);
|
|
36517
|
-
const homeDashed =
|
|
36732
|
+
const homeDashed = os47.homedir().replace(/\//g, "-");
|
|
36518
36733
|
const label3 = name.startsWith(homeDashed) ? "~" + decodeProjectDirName(name.slice(homeDashed.length)) : decodeProjectDirName(name);
|
|
36519
36734
|
return stripTerminalEscapes(label3).slice(0, 40);
|
|
36520
36735
|
}
|
|
@@ -36625,12 +36840,12 @@ function mergeFileScan(result, file2, dedup, startDate = null) {
|
|
|
36625
36840
|
}
|
|
36626
36841
|
}
|
|
36627
36842
|
async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36628
|
-
const projectsDir =
|
|
36843
|
+
const projectsDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36629
36844
|
const result = emptyClaudeScan();
|
|
36630
|
-
if (!
|
|
36845
|
+
if (!fs55.existsSync(projectsDir)) return result;
|
|
36631
36846
|
let projDirs;
|
|
36632
36847
|
try {
|
|
36633
|
-
projDirs =
|
|
36848
|
+
projDirs = fs55.readdirSync(projectsDir);
|
|
36634
36849
|
} catch {
|
|
36635
36850
|
return result;
|
|
36636
36851
|
}
|
|
@@ -36658,7 +36873,7 @@ async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36658
36873
|
}
|
|
36659
36874
|
async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36660
36875
|
const canaryVals = safeCanaryScanValues();
|
|
36661
|
-
const tmpDir =
|
|
36876
|
+
const tmpDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36662
36877
|
const result = {
|
|
36663
36878
|
filesScanned: 0,
|
|
36664
36879
|
sessions: 0,
|
|
@@ -36675,31 +36890,31 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36675
36890
|
perSession: []
|
|
36676
36891
|
};
|
|
36677
36892
|
const dedup = emptyScanDedup();
|
|
36678
|
-
if (!
|
|
36893
|
+
if (!fs55.existsSync(tmpDir)) return result;
|
|
36679
36894
|
let slugDirs;
|
|
36680
36895
|
try {
|
|
36681
|
-
slugDirs =
|
|
36896
|
+
slugDirs = fs55.readdirSync(tmpDir);
|
|
36682
36897
|
} catch {
|
|
36683
36898
|
return result;
|
|
36684
36899
|
}
|
|
36685
36900
|
const judge = new HistoryJudge();
|
|
36686
36901
|
const cache7 = openScanCache(judge, opts);
|
|
36687
36902
|
for (const slug2 of slugDirs) {
|
|
36688
|
-
const slugPath =
|
|
36903
|
+
const slugPath = path57.join(tmpDir, slug2);
|
|
36689
36904
|
try {
|
|
36690
|
-
if (!
|
|
36905
|
+
if (!fs55.statSync(slugPath).isDirectory()) continue;
|
|
36691
36906
|
} catch {
|
|
36692
36907
|
continue;
|
|
36693
36908
|
}
|
|
36694
36909
|
let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
|
|
36695
36910
|
try {
|
|
36696
36911
|
projLabel = stripTerminalEscapes(
|
|
36697
|
-
|
|
36698
|
-
).replace(
|
|
36912
|
+
fs55.readFileSync(path57.join(slugPath, ".project_root"), "utf-8").trim()
|
|
36913
|
+
).replace(os47.homedir(), "~").slice(0, 40);
|
|
36699
36914
|
} catch {
|
|
36700
36915
|
}
|
|
36701
|
-
const chatsDir =
|
|
36702
|
-
if (!
|
|
36916
|
+
const chatsDir = path57.join(slugPath, "chats");
|
|
36917
|
+
if (!fs55.existsSync(chatsDir)) continue;
|
|
36703
36918
|
let chatFiles;
|
|
36704
36919
|
try {
|
|
36705
36920
|
chatFiles = geminiChatFiles(chatsDir);
|
|
@@ -36708,7 +36923,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36708
36923
|
}
|
|
36709
36924
|
for (const chatFile of chatFiles) {
|
|
36710
36925
|
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
36711
|
-
const filePath =
|
|
36926
|
+
const filePath = path57.join(chatsDir, chatFile);
|
|
36712
36927
|
const stamp = fileStamp(filePath);
|
|
36713
36928
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36714
36929
|
result.filesScanned++;
|
|
@@ -36722,7 +36937,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36722
36937
|
const fileDedup = emptyScanDedup();
|
|
36723
36938
|
let raw;
|
|
36724
36939
|
try {
|
|
36725
|
-
raw =
|
|
36940
|
+
raw = fs55.readFileSync(path57.join(chatsDir, chatFile), "utf-8");
|
|
36726
36941
|
} catch {
|
|
36727
36942
|
continue;
|
|
36728
36943
|
}
|
|
@@ -36859,13 +37074,13 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36859
37074
|
return result;
|
|
36860
37075
|
}
|
|
36861
37076
|
function antigravityBrainDirs() {
|
|
36862
|
-
return ["antigravity-cli", "antigravity-ide"].map((surface) =>
|
|
37077
|
+
return ["antigravity-cli", "antigravity-ide"].map((surface) => path57.join(os47.homedir(), ".gemini", surface, "brain")).filter((p) => fs55.existsSync(p));
|
|
36863
37078
|
}
|
|
36864
37079
|
function antigravityTranscriptPath(convPath) {
|
|
36865
|
-
const logsDir =
|
|
37080
|
+
const logsDir = path57.join(convPath, ".system_generated", "logs");
|
|
36866
37081
|
for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
|
|
36867
|
-
const p =
|
|
36868
|
-
if (
|
|
37082
|
+
const p = path57.join(logsDir, name);
|
|
37083
|
+
if (fs55.existsSync(p)) return p;
|
|
36869
37084
|
}
|
|
36870
37085
|
return null;
|
|
36871
37086
|
}
|
|
@@ -36895,14 +37110,14 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36895
37110
|
for (const brainDir of brainDirs) {
|
|
36896
37111
|
let convDirs;
|
|
36897
37112
|
try {
|
|
36898
|
-
convDirs =
|
|
37113
|
+
convDirs = fs55.readdirSync(brainDir);
|
|
36899
37114
|
} catch {
|
|
36900
37115
|
continue;
|
|
36901
37116
|
}
|
|
36902
37117
|
for (const conv of convDirs) {
|
|
36903
|
-
const convPath =
|
|
37118
|
+
const convPath = path57.join(brainDir, conv);
|
|
36904
37119
|
try {
|
|
36905
|
-
if (!
|
|
37120
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36906
37121
|
} catch {
|
|
36907
37122
|
continue;
|
|
36908
37123
|
}
|
|
@@ -36921,7 +37136,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36921
37136
|
const fileDedup = emptyScanDedup();
|
|
36922
37137
|
let raw;
|
|
36923
37138
|
try {
|
|
36924
|
-
raw =
|
|
37139
|
+
raw = fs55.readFileSync(transcriptFile, "utf-8");
|
|
36925
37140
|
} catch {
|
|
36926
37141
|
continue;
|
|
36927
37142
|
}
|
|
@@ -36996,7 +37211,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36996
37211
|
day.bashCalls++;
|
|
36997
37212
|
const cwd = String(input.cwd ?? "");
|
|
36998
37213
|
if (cwd && projLabel === conv.slice(0, 8)) {
|
|
36999
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37214
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37000
37215
|
}
|
|
37001
37216
|
}
|
|
37002
37217
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
@@ -37022,7 +37237,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37022
37237
|
timestamp: timestamp2,
|
|
37023
37238
|
sessionId,
|
|
37024
37239
|
project: projLabel,
|
|
37025
|
-
cwd: typeof input.cwd === "string" &&
|
|
37240
|
+
cwd: typeof input.cwd === "string" && path57.isAbsolute(input.cwd) ? input.cwd : void 0
|
|
37026
37241
|
},
|
|
37027
37242
|
fileResult,
|
|
37028
37243
|
fileDedup
|
|
@@ -37045,7 +37260,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37045
37260
|
}
|
|
37046
37261
|
async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
37047
37262
|
const canaryVals = safeCanaryScanValues();
|
|
37048
|
-
const sessionDir =
|
|
37263
|
+
const sessionDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
37049
37264
|
const result = {
|
|
37050
37265
|
filesScanned: 0,
|
|
37051
37266
|
sessions: 0,
|
|
@@ -37063,18 +37278,18 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37063
37278
|
perSession: []
|
|
37064
37279
|
};
|
|
37065
37280
|
const dedup = emptyScanDedup();
|
|
37066
|
-
if (!
|
|
37281
|
+
if (!fs55.existsSync(sessionDir)) return result;
|
|
37067
37282
|
let sessionIds;
|
|
37068
37283
|
try {
|
|
37069
|
-
sessionIds =
|
|
37284
|
+
sessionIds = fs55.readdirSync(sessionDir);
|
|
37070
37285
|
} catch {
|
|
37071
37286
|
return result;
|
|
37072
37287
|
}
|
|
37073
37288
|
const judge = new HistoryJudge();
|
|
37074
37289
|
const cache7 = openScanCache(judge, opts);
|
|
37075
37290
|
for (const sessionId of sessionIds) {
|
|
37076
|
-
const eventsPath =
|
|
37077
|
-
if (!
|
|
37291
|
+
const eventsPath = path57.join(sessionDir, sessionId, "events.jsonl");
|
|
37292
|
+
if (!fs55.existsSync(eventsPath)) continue;
|
|
37078
37293
|
const stamp = fileStamp(eventsPath);
|
|
37079
37294
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
37080
37295
|
result.filesScanned++;
|
|
@@ -37088,7 +37303,7 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37088
37303
|
const fileDedup = emptyScanDedup();
|
|
37089
37304
|
let raw;
|
|
37090
37305
|
try {
|
|
37091
|
-
raw =
|
|
37306
|
+
raw = fs55.readFileSync(eventsPath, "utf-8");
|
|
37092
37307
|
} catch {
|
|
37093
37308
|
continue;
|
|
37094
37309
|
}
|
|
@@ -37113,8 +37328,8 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37113
37328
|
if (ev.type === "session.start") {
|
|
37114
37329
|
const cwd = ev.data?.context?.cwd;
|
|
37115
37330
|
if (typeof cwd === "string" && cwd) {
|
|
37116
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37117
|
-
if (
|
|
37331
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37332
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37118
37333
|
}
|
|
37119
37334
|
continue;
|
|
37120
37335
|
}
|
|
@@ -37247,7 +37462,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37247
37462
|
const fileDedup = emptyScanDedup();
|
|
37248
37463
|
let lines;
|
|
37249
37464
|
try {
|
|
37250
|
-
lines =
|
|
37465
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37251
37466
|
} catch {
|
|
37252
37467
|
continue;
|
|
37253
37468
|
}
|
|
@@ -37270,8 +37485,8 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37270
37485
|
sessionId = String(payload["id"] ?? filePath);
|
|
37271
37486
|
startTime = String(payload["timestamp"] ?? "");
|
|
37272
37487
|
const cwd = String(payload["cwd"] ?? "");
|
|
37273
|
-
projLabel = stripTerminalEscapes(cwd.replace(
|
|
37274
|
-
if (
|
|
37488
|
+
projLabel = stripTerminalEscapes(cwd.replace(os47.homedir(), "~")).slice(0, 40);
|
|
37489
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37275
37490
|
continue;
|
|
37276
37491
|
}
|
|
37277
37492
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
@@ -37388,17 +37603,17 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37388
37603
|
return result;
|
|
37389
37604
|
}
|
|
37390
37605
|
function scanShellConfig() {
|
|
37391
|
-
const home =
|
|
37606
|
+
const home = os47.homedir();
|
|
37392
37607
|
const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
|
|
37393
|
-
(f) =>
|
|
37608
|
+
(f) => path57.join(home, f)
|
|
37394
37609
|
);
|
|
37395
37610
|
const findings = [];
|
|
37396
37611
|
const seen = /* @__PURE__ */ new Set();
|
|
37397
37612
|
for (const filePath of configFiles) {
|
|
37398
|
-
if (!
|
|
37613
|
+
if (!fs55.existsSync(filePath)) continue;
|
|
37399
37614
|
let lines;
|
|
37400
37615
|
try {
|
|
37401
|
-
lines =
|
|
37616
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37402
37617
|
} catch {
|
|
37403
37618
|
continue;
|
|
37404
37619
|
}
|
|
@@ -37728,25 +37943,25 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
37728
37943
|
const topLines = [];
|
|
37729
37944
|
if (scan.canaryFindings.length > 0) {
|
|
37730
37945
|
const latest = scan.canaryFindings[0];
|
|
37731
|
-
const rel =
|
|
37946
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37732
37947
|
const n = scan.canaryFindings.length;
|
|
37733
37948
|
topLines.push(
|
|
37734
37949
|
mkLine(
|
|
37735
37950
|
["\u{1FAA4} ", chalk6.red],
|
|
37736
37951
|
[`${n} decoy credential${n !== 1 ? "s" : ""} tripped `, chalk6.bold],
|
|
37737
|
-
[`(latest: ${rel}
|
|
37952
|
+
[`(latest: ${rel}, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
|
|
37738
37953
|
)
|
|
37739
37954
|
);
|
|
37740
37955
|
}
|
|
37741
37956
|
if (scan.dlpFindings.length > 0) {
|
|
37742
37957
|
const latest = scan.dlpFindings[0];
|
|
37743
|
-
const rel =
|
|
37958
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37744
37959
|
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
37745
37960
|
topLines.push(
|
|
37746
37961
|
mkLine(
|
|
37747
37962
|
["\u{1F6A8} ", chalk6.red],
|
|
37748
37963
|
[`${scan.dlpFindings.length} ${noun} in tool input `, chalk6.bold],
|
|
37749
|
-
[`(latest: ${rel}
|
|
37964
|
+
[`(latest: ${rel}, ${latest.patternName})`, chalk6.dim]
|
|
37750
37965
|
)
|
|
37751
37966
|
);
|
|
37752
37967
|
}
|
|
@@ -38315,7 +38530,7 @@ function registerScanCommand(program2) {
|
|
|
38315
38530
|
if (!drillDown) {
|
|
38316
38531
|
const useInk2 = !options.classic;
|
|
38317
38532
|
if (useInk2) {
|
|
38318
|
-
const scanInkPath =
|
|
38533
|
+
const scanInkPath = path57.join(__dirname, "scan-ink.mjs");
|
|
38319
38534
|
const dynamicImport = new Function("id", "return import(id)");
|
|
38320
38535
|
const mod = await dynamicImport(`file://${scanInkPath}`);
|
|
38321
38536
|
const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
|
|
@@ -38672,12 +38887,12 @@ var init_scan = __esm({
|
|
|
38672
38887
|
});
|
|
38673
38888
|
|
|
38674
38889
|
// src/daemon/build-id.ts
|
|
38675
|
-
import
|
|
38676
|
-
import
|
|
38890
|
+
import fs56 from "fs";
|
|
38891
|
+
import path58 from "path";
|
|
38677
38892
|
function readOwnVersion() {
|
|
38678
38893
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
38679
38894
|
try {
|
|
38680
|
-
const raw =
|
|
38895
|
+
const raw = fs56.readFileSync(path58.join(__dirname, rel), "utf-8");
|
|
38681
38896
|
const v = JSON.parse(raw).version;
|
|
38682
38897
|
if (typeof v === "string" && v.length > 0) return v;
|
|
38683
38898
|
} catch {
|
|
@@ -38688,7 +38903,7 @@ function readOwnVersion() {
|
|
|
38688
38903
|
function computeBuildId(entry = process.argv[1] ?? "") {
|
|
38689
38904
|
let mtimeMs = 0;
|
|
38690
38905
|
try {
|
|
38691
|
-
if (entry) mtimeMs =
|
|
38906
|
+
if (entry) mtimeMs = fs56.statSync(entry).mtimeMs;
|
|
38692
38907
|
} catch {
|
|
38693
38908
|
}
|
|
38694
38909
|
return { version: readOwnVersion(), mtimeMs };
|
|
@@ -38829,8 +39044,8 @@ var init_suggestion_tracker = __esm({
|
|
|
38829
39044
|
});
|
|
38830
39045
|
|
|
38831
39046
|
// src/daemon/taint-store.ts
|
|
38832
|
-
import
|
|
38833
|
-
import
|
|
39047
|
+
import fs57 from "fs";
|
|
39048
|
+
import path59 from "path";
|
|
38834
39049
|
var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS2, SessionTaintStore;
|
|
38835
39050
|
var init_taint_store = __esm({
|
|
38836
39051
|
"src/daemon/taint-store.ts"() {
|
|
@@ -38900,9 +39115,9 @@ var init_taint_store = __esm({
|
|
|
38900
39115
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
38901
39116
|
_resolve(filePath) {
|
|
38902
39117
|
try {
|
|
38903
|
-
return
|
|
39118
|
+
return fs57.realpathSync.native(path59.resolve(filePath));
|
|
38904
39119
|
} catch {
|
|
38905
|
-
return
|
|
39120
|
+
return path59.resolve(filePath);
|
|
38906
39121
|
}
|
|
38907
39122
|
}
|
|
38908
39123
|
};
|
|
@@ -38958,7 +39173,7 @@ var init_taint_store = __esm({
|
|
|
38958
39173
|
});
|
|
38959
39174
|
|
|
38960
39175
|
// src/daemon/written-store.ts
|
|
38961
|
-
import
|
|
39176
|
+
import path60 from "path";
|
|
38962
39177
|
var SESSION_WRITE_TTL_MS, MAX_WRITTEN, MAX_PATH, SessionWriteStore;
|
|
38963
39178
|
var init_written_store = __esm({
|
|
38964
39179
|
"src/daemon/written-store.ts"() {
|
|
@@ -38970,7 +39185,7 @@ var init_written_store = __esm({
|
|
|
38970
39185
|
records = /* @__PURE__ */ new Map();
|
|
38971
39186
|
key(p) {
|
|
38972
39187
|
if (typeof p !== "string" || !p || p.length > MAX_PATH) return null;
|
|
38973
|
-
return
|
|
39188
|
+
return path60.resolve(p);
|
|
38974
39189
|
}
|
|
38975
39190
|
/** Record that these absolute paths were (or are about to be) written. */
|
|
38976
39191
|
record(paths, ttlMs = SESSION_WRITE_TTL_MS) {
|
|
@@ -39301,9 +39516,9 @@ var init_cards = __esm({
|
|
|
39301
39516
|
|
|
39302
39517
|
// src/daemon/state.ts
|
|
39303
39518
|
import net2 from "net";
|
|
39304
|
-
import
|
|
39305
|
-
import
|
|
39306
|
-
import
|
|
39519
|
+
import fs58 from "fs";
|
|
39520
|
+
import path61 from "path";
|
|
39521
|
+
import os48 from "os";
|
|
39307
39522
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
39308
39523
|
function onCardDecided(card) {
|
|
39309
39524
|
const r = card.record;
|
|
@@ -39329,8 +39544,8 @@ function onCardDecided(card) {
|
|
|
39329
39544
|
}
|
|
39330
39545
|
function loadInsightCounts() {
|
|
39331
39546
|
try {
|
|
39332
|
-
if (!
|
|
39333
|
-
const data = JSON.parse(
|
|
39547
|
+
if (!fs58.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
39548
|
+
const data = JSON.parse(fs58.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
39334
39549
|
for (const [tool, count] of Object.entries(data)) {
|
|
39335
39550
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
39336
39551
|
}
|
|
@@ -39398,16 +39613,16 @@ function appendAuditLog(data) {
|
|
|
39398
39613
|
...data.channel && { channel: data.channel },
|
|
39399
39614
|
...data.cardId && { cardId: data.cardId }
|
|
39400
39615
|
};
|
|
39401
|
-
const dir2 =
|
|
39402
|
-
if (!
|
|
39403
|
-
|
|
39616
|
+
const dir2 = path61.dirname(AUDIT_LOG_FILE);
|
|
39617
|
+
if (!fs58.existsSync(dir2)) fs58.mkdirSync(dir2, { recursive: true });
|
|
39618
|
+
fs58.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
39404
39619
|
} catch {
|
|
39405
39620
|
}
|
|
39406
39621
|
}
|
|
39407
39622
|
function getAuditHistory(limit = 20) {
|
|
39408
39623
|
try {
|
|
39409
|
-
if (!
|
|
39410
|
-
const lines =
|
|
39624
|
+
if (!fs58.existsSync(AUDIT_LOG_FILE)) return [];
|
|
39625
|
+
const lines = fs58.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
39411
39626
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
39412
39627
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
39413
39628
|
} catch {
|
|
@@ -39416,7 +39631,7 @@ function getAuditHistory(limit = 20) {
|
|
|
39416
39631
|
}
|
|
39417
39632
|
function getOrgName() {
|
|
39418
39633
|
try {
|
|
39419
|
-
if (
|
|
39634
|
+
if (fs58.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
39420
39635
|
} catch {
|
|
39421
39636
|
}
|
|
39422
39637
|
return null;
|
|
@@ -39428,8 +39643,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39428
39643
|
try {
|
|
39429
39644
|
let trust = { entries: [] };
|
|
39430
39645
|
try {
|
|
39431
|
-
if (
|
|
39432
|
-
trust = JSON.parse(
|
|
39646
|
+
if (fs58.existsSync(TRUST_FILE2))
|
|
39647
|
+
trust = JSON.parse(fs58.readFileSync(TRUST_FILE2, "utf-8"));
|
|
39433
39648
|
} catch {
|
|
39434
39649
|
}
|
|
39435
39650
|
trust.entries = trust.entries.filter(
|
|
@@ -39446,8 +39661,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39446
39661
|
}
|
|
39447
39662
|
function readPersistentDecisions() {
|
|
39448
39663
|
try {
|
|
39449
|
-
if (
|
|
39450
|
-
return JSON.parse(
|
|
39664
|
+
if (fs58.existsSync(DECISIONS_FILE)) {
|
|
39665
|
+
return JSON.parse(fs58.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
39451
39666
|
}
|
|
39452
39667
|
} catch {
|
|
39453
39668
|
}
|
|
@@ -39475,7 +39690,7 @@ function estimateToolCost(tool, args) {
|
|
|
39475
39690
|
const filePath = a.file_path ?? a.path;
|
|
39476
39691
|
if (filePath) {
|
|
39477
39692
|
try {
|
|
39478
|
-
const bytes =
|
|
39693
|
+
const bytes = fs58.statSync(filePath).size;
|
|
39479
39694
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
39480
39695
|
} catch {
|
|
39481
39696
|
}
|
|
@@ -39543,7 +39758,7 @@ function abandonPending() {
|
|
|
39543
39758
|
cards.abandonLocalOnly();
|
|
39544
39759
|
if (autoStarted && cards.open().length === 0) {
|
|
39545
39760
|
try {
|
|
39546
|
-
|
|
39761
|
+
fs58.unlinkSync(DAEMON_PID_FILE);
|
|
39547
39762
|
} catch {
|
|
39548
39763
|
}
|
|
39549
39764
|
setTimeout(() => {
|
|
@@ -39554,8 +39769,8 @@ function abandonPending() {
|
|
|
39554
39769
|
}
|
|
39555
39770
|
function logActivitySocket(msg) {
|
|
39556
39771
|
try {
|
|
39557
|
-
|
|
39558
|
-
|
|
39772
|
+
fs58.appendFileSync(
|
|
39773
|
+
path61.join(homeDir, ".node9", "hook-debug.log"),
|
|
39559
39774
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
|
|
39560
39775
|
`
|
|
39561
39776
|
);
|
|
@@ -39577,13 +39792,13 @@ function shouldRebind(now = Date.now()) {
|
|
|
39577
39792
|
function startActivitySocket() {
|
|
39578
39793
|
bindActivitySocket();
|
|
39579
39794
|
activityHealthInterval = setInterval(() => {
|
|
39580
|
-
if (!
|
|
39795
|
+
if (!fs58.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
|
|
39581
39796
|
}, ACTIVITY_HEALTH_PROBE_MS);
|
|
39582
39797
|
activityHealthInterval.unref();
|
|
39583
39798
|
process.on("exit", () => {
|
|
39584
39799
|
if (activityHealthInterval) clearInterval(activityHealthInterval);
|
|
39585
39800
|
try {
|
|
39586
|
-
|
|
39801
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39587
39802
|
} catch {
|
|
39588
39803
|
}
|
|
39589
39804
|
});
|
|
@@ -39611,7 +39826,7 @@ function attemptRebind(reason) {
|
|
|
39611
39826
|
}
|
|
39612
39827
|
function bindActivitySocket() {
|
|
39613
39828
|
try {
|
|
39614
|
-
|
|
39829
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39615
39830
|
} catch {
|
|
39616
39831
|
}
|
|
39617
39832
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -39720,14 +39935,14 @@ var init_state3 = __esm({
|
|
|
39720
39935
|
init_session_history();
|
|
39721
39936
|
init_atomic_write();
|
|
39722
39937
|
init_cards();
|
|
39723
|
-
homeDir =
|
|
39724
|
-
DAEMON_PID_FILE =
|
|
39725
|
-
DECISIONS_FILE =
|
|
39726
|
-
AUDIT_LOG_FILE =
|
|
39727
|
-
TRUST_FILE2 =
|
|
39728
|
-
GLOBAL_CONFIG_FILE =
|
|
39729
|
-
CREDENTIALS_FILE =
|
|
39730
|
-
INSIGHT_COUNTS_FILE =
|
|
39938
|
+
homeDir = os48.homedir();
|
|
39939
|
+
DAEMON_PID_FILE = path61.join(homeDir, ".node9", "daemon.pid");
|
|
39940
|
+
DECISIONS_FILE = path61.join(homeDir, ".node9", "decisions.json");
|
|
39941
|
+
AUDIT_LOG_FILE = path61.join(homeDir, ".node9", "audit.log");
|
|
39942
|
+
TRUST_FILE2 = path61.join(homeDir, ".node9", "trust.json");
|
|
39943
|
+
GLOBAL_CONFIG_FILE = path61.join(homeDir, ".node9", "config.json");
|
|
39944
|
+
CREDENTIALS_FILE = path61.join(homeDir, ".node9", "credentials.json");
|
|
39945
|
+
INSIGHT_COUNTS_FILE = path61.join(homeDir, ".node9", "insight-counts.json");
|
|
39731
39946
|
sseClients = /* @__PURE__ */ new Set();
|
|
39732
39947
|
cards = new CardStore({
|
|
39733
39948
|
onDecided: onCardDecided,
|
|
@@ -39750,7 +39965,7 @@ var init_state3 = __esm({
|
|
|
39750
39965
|
"2h": 2 * 60 * 6e4
|
|
39751
39966
|
};
|
|
39752
39967
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
39753
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
39968
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path61.join(os48.tmpdir(), "node9-activity.sock");
|
|
39754
39969
|
ACTIVITY_RING_SIZE = 100;
|
|
39755
39970
|
activityRing = [];
|
|
39756
39971
|
LARGE_RESPONSE_RING_SIZE = 20;
|
|
@@ -39789,13 +40004,13 @@ var init_state3 = __esm({
|
|
|
39789
40004
|
});
|
|
39790
40005
|
|
|
39791
40006
|
// src/posture/secrets.ts
|
|
39792
|
-
import
|
|
39793
|
-
import
|
|
39794
|
-
import
|
|
40007
|
+
import fs59 from "fs";
|
|
40008
|
+
import path62 from "path";
|
|
40009
|
+
import os49 from "os";
|
|
39795
40010
|
function displayPath(p, home) {
|
|
39796
40011
|
if (p === home) return "~";
|
|
39797
|
-
const prefix = home.endsWith(
|
|
39798
|
-
if (p.startsWith(prefix)) return "~" +
|
|
40012
|
+
const prefix = home.endsWith(path62.sep) ? home : home + path62.sep;
|
|
40013
|
+
if (p.startsWith(prefix)) return "~" + path62.sep + p.slice(prefix.length);
|
|
39799
40014
|
return p;
|
|
39800
40015
|
}
|
|
39801
40016
|
function safeRead(file2) {
|
|
@@ -39806,8 +40021,8 @@ function safeRead(file2) {
|
|
|
39806
40021
|
function candidateFiles(home, cwd) {
|
|
39807
40022
|
const files = /* @__PURE__ */ new Set();
|
|
39808
40023
|
try {
|
|
39809
|
-
for (const name of
|
|
39810
|
-
if (name === ".env" || name.startsWith(".env.")) files.add(
|
|
40024
|
+
for (const name of fs59.readdirSync(cwd)) {
|
|
40025
|
+
if (name === ".env" || name.startsWith(".env.")) files.add(path62.join(cwd, name));
|
|
39811
40026
|
}
|
|
39812
40027
|
} catch {
|
|
39813
40028
|
}
|
|
@@ -39815,7 +40030,7 @@ function candidateFiles(home, cwd) {
|
|
|
39815
40030
|
if (spec.hookFile) files.add(spec.hookFile(home));
|
|
39816
40031
|
if (spec.mcpFile) files.add(spec.mcpFile(home));
|
|
39817
40032
|
}
|
|
39818
|
-
files.add(
|
|
40033
|
+
files.add(path62.join(home, ".env"));
|
|
39819
40034
|
return [...files];
|
|
39820
40035
|
}
|
|
39821
40036
|
function credentialMaterial(home) {
|
|
@@ -39829,7 +40044,7 @@ function plantedDecoyPaths() {
|
|
|
39829
40044
|
}
|
|
39830
40045
|
}
|
|
39831
40046
|
function checkSecrets(ctx) {
|
|
39832
|
-
const home = ctx.home ||
|
|
40047
|
+
const home = ctx.home || os49.homedir();
|
|
39833
40048
|
const findings = [];
|
|
39834
40049
|
const planted = plantedDecoyPaths();
|
|
39835
40050
|
const plaintext = [];
|
|
@@ -39865,7 +40080,7 @@ function checkSecrets(ctx) {
|
|
|
39865
40080
|
for (const file2 of credentialMaterial(home)) {
|
|
39866
40081
|
if (planted.has(file2)) continue;
|
|
39867
40082
|
try {
|
|
39868
|
-
if (
|
|
40083
|
+
if (fs59.statSync(file2).isFile()) {
|
|
39869
40084
|
creds.push(displayPath(file2, home));
|
|
39870
40085
|
credPaths.push(file2);
|
|
39871
40086
|
}
|
|
@@ -40035,10 +40250,10 @@ var init_templates = __esm({
|
|
|
40035
40250
|
});
|
|
40036
40251
|
|
|
40037
40252
|
// src/posture/egress.ts
|
|
40038
|
-
import
|
|
40253
|
+
import fs60 from "fs";
|
|
40039
40254
|
function sandboxEgressWallActive() {
|
|
40040
40255
|
try {
|
|
40041
|
-
return
|
|
40256
|
+
return fs60.existsSync(ALLOWED_DOMAINS_PATH);
|
|
40042
40257
|
} catch {
|
|
40043
40258
|
return false;
|
|
40044
40259
|
}
|
|
@@ -40050,7 +40265,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40050
40265
|
severity: "high",
|
|
40051
40266
|
title: "Egress is locked, but node9 is not enforcing it",
|
|
40052
40267
|
what: "Egress is set to block, but node9 is not applying the policy.",
|
|
40053
|
-
why: "node9 isn't wired in (or is in
|
|
40268
|
+
why: "node9 isn't wired in (or is in audit mode), so the lock has no effect.",
|
|
40054
40269
|
who: "The lock protects nothing until node9 is enforcing in-path.",
|
|
40055
40270
|
owner: "node9",
|
|
40056
40271
|
detail: [],
|
|
@@ -40067,7 +40282,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40067
40282
|
severity: "medium",
|
|
40068
40283
|
title: "Egress is in review, but node9 is not enforcing it",
|
|
40069
40284
|
what: "Egress is set to review (approval-gate), but node9 is not applying the policy.",
|
|
40070
|
-
why: "node9 isn't wired in (or is in
|
|
40285
|
+
why: "node9 isn't wired in (or is in audit mode), so the gate has no effect.",
|
|
40071
40286
|
who: "Nothing gates outbound until node9 is enforcing in-path.",
|
|
40072
40287
|
owner: "node9",
|
|
40073
40288
|
detail: [],
|
|
@@ -40208,14 +40423,14 @@ var init_gate = __esm({
|
|
|
40208
40423
|
});
|
|
40209
40424
|
|
|
40210
40425
|
// src/posture/supply-chain.ts
|
|
40211
|
-
import
|
|
40212
|
-
import
|
|
40426
|
+
import os50 from "os";
|
|
40427
|
+
import path63 from "path";
|
|
40213
40428
|
import { parse as parseToml6 } from "smol-toml";
|
|
40214
40429
|
function isNode9Managed(command, args = []) {
|
|
40215
40430
|
if (!command) return false;
|
|
40216
|
-
if (
|
|
40217
|
-
if (PACKAGE_RUNNERS.has(
|
|
40218
|
-
return args.some((a) => a === "node9" ||
|
|
40431
|
+
if (path63.basename(command).toLowerCase() === "node9") return true;
|
|
40432
|
+
if (PACKAGE_RUNNERS.has(path63.basename(command).toLowerCase())) {
|
|
40433
|
+
return args.some((a) => a === "node9" || path63.basename(a).toLowerCase() === "node9");
|
|
40219
40434
|
}
|
|
40220
40435
|
return false;
|
|
40221
40436
|
}
|
|
@@ -40237,7 +40452,7 @@ function readServers(file2, format, agent) {
|
|
|
40237
40452
|
}
|
|
40238
40453
|
}
|
|
40239
40454
|
function checkSupplyChain(ctx) {
|
|
40240
|
-
const home = ctx.home ||
|
|
40455
|
+
const home = ctx.home || os50.homedir();
|
|
40241
40456
|
const servers = [];
|
|
40242
40457
|
for (const spec of AGENT_SPECS) {
|
|
40243
40458
|
if (!spec.mcpFile) continue;
|
|
@@ -40336,11 +40551,11 @@ var init_privilege = __esm({
|
|
|
40336
40551
|
});
|
|
40337
40552
|
|
|
40338
40553
|
// src/posture/containment.ts
|
|
40339
|
-
import
|
|
40554
|
+
import fs61 from "fs";
|
|
40340
40555
|
function inContainer() {
|
|
40341
|
-
if (
|
|
40556
|
+
if (fs61.existsSync("/.dockerenv") || fs61.existsSync("/run/.containerenv")) return true;
|
|
40342
40557
|
try {
|
|
40343
|
-
const cgroup =
|
|
40558
|
+
const cgroup = fs61.readFileSync("/proc/1/cgroup", "utf8");
|
|
40344
40559
|
if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
|
|
40345
40560
|
} catch {
|
|
40346
40561
|
}
|
|
@@ -40386,7 +40601,7 @@ var init_containment = __esm({
|
|
|
40386
40601
|
});
|
|
40387
40602
|
|
|
40388
40603
|
// src/posture/inbound.ts
|
|
40389
|
-
import
|
|
40604
|
+
import fs62 from "fs";
|
|
40390
40605
|
function buildNetworkFix(labels) {
|
|
40391
40606
|
const shielded = [
|
|
40392
40607
|
...new Map(
|
|
@@ -40444,7 +40659,7 @@ function collectListeners() {
|
|
|
40444
40659
|
const byPort = /* @__PURE__ */ new Map();
|
|
40445
40660
|
for (const file2 of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
40446
40661
|
try {
|
|
40447
|
-
for (const l of parseListeners(
|
|
40662
|
+
for (const l of parseListeners(fs62.readFileSync(file2, "utf8"))) {
|
|
40448
40663
|
if (!byPort.has(l.port)) byPort.set(l.port, l);
|
|
40449
40664
|
}
|
|
40450
40665
|
} catch {
|
|
@@ -40456,11 +40671,11 @@ function readProc(pid) {
|
|
|
40456
40671
|
let comm = "unknown";
|
|
40457
40672
|
let cmdline = "";
|
|
40458
40673
|
try {
|
|
40459
|
-
comm =
|
|
40674
|
+
comm = fs62.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
|
|
40460
40675
|
} catch {
|
|
40461
40676
|
}
|
|
40462
40677
|
try {
|
|
40463
|
-
cmdline =
|
|
40678
|
+
cmdline = fs62.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
|
|
40464
40679
|
} catch {
|
|
40465
40680
|
}
|
|
40466
40681
|
return { comm, cmdline };
|
|
@@ -40470,21 +40685,21 @@ function resolveProcesses(inodes) {
|
|
|
40470
40685
|
if (inodes.size === 0) return map;
|
|
40471
40686
|
let pids;
|
|
40472
40687
|
try {
|
|
40473
|
-
pids =
|
|
40688
|
+
pids = fs62.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
40474
40689
|
} catch {
|
|
40475
40690
|
return map;
|
|
40476
40691
|
}
|
|
40477
40692
|
for (const pid of pids) {
|
|
40478
40693
|
let fds;
|
|
40479
40694
|
try {
|
|
40480
|
-
fds =
|
|
40695
|
+
fds = fs62.readdirSync(`/proc/${pid}/fd`);
|
|
40481
40696
|
} catch {
|
|
40482
40697
|
continue;
|
|
40483
40698
|
}
|
|
40484
40699
|
for (const fd of fds) {
|
|
40485
40700
|
let link;
|
|
40486
40701
|
try {
|
|
40487
|
-
link =
|
|
40702
|
+
link = fs62.readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
40488
40703
|
} catch {
|
|
40489
40704
|
continue;
|
|
40490
40705
|
}
|
|
@@ -40589,9 +40804,9 @@ var init_inbound = __esm({
|
|
|
40589
40804
|
});
|
|
40590
40805
|
|
|
40591
40806
|
// src/posture/coverage.ts
|
|
40592
|
-
import
|
|
40807
|
+
import os51 from "os";
|
|
40593
40808
|
function checkCoverage(ctx) {
|
|
40594
|
-
const home = ctx.home ||
|
|
40809
|
+
const home = ctx.home || os51.homedir();
|
|
40595
40810
|
const findings = [];
|
|
40596
40811
|
const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
|
|
40597
40812
|
if (protectedAgents.length === 0) {
|
|
@@ -40609,13 +40824,14 @@ function checkCoverage(ctx) {
|
|
|
40609
40824
|
return findings;
|
|
40610
40825
|
}
|
|
40611
40826
|
const mode = getConfig(ctx.cwd).settings.mode;
|
|
40612
|
-
if (mode
|
|
40827
|
+
if (isRecordOnly(mode)) {
|
|
40828
|
+
const level = levelOf(mode);
|
|
40613
40829
|
findings.push({
|
|
40614
40830
|
category: "Coverage",
|
|
40615
40831
|
severity: "high",
|
|
40616
|
-
title: `node9 is in ${
|
|
40832
|
+
title: `node9 is in ${level} mode \u2014 watching, not blocking`,
|
|
40617
40833
|
what: "node9 is watching but not actually blocking anything.",
|
|
40618
|
-
why: `It's in ${
|
|
40834
|
+
why: `It's in ${level} mode, which logs risky actions but lets them through.`,
|
|
40619
40835
|
who: "The guardrails above are observed, not enforced.",
|
|
40620
40836
|
detail: [],
|
|
40621
40837
|
owner: "node9",
|
|
@@ -40628,12 +40844,13 @@ var init_coverage = __esm({
|
|
|
40628
40844
|
"src/posture/coverage.ts"() {
|
|
40629
40845
|
"use strict";
|
|
40630
40846
|
init_config();
|
|
40847
|
+
init_dist();
|
|
40631
40848
|
init_agent_wiring();
|
|
40632
40849
|
}
|
|
40633
40850
|
});
|
|
40634
40851
|
|
|
40635
40852
|
// src/posture/governance.ts
|
|
40636
|
-
import
|
|
40853
|
+
import path64 from "path";
|
|
40637
40854
|
function checkData(ctx) {
|
|
40638
40855
|
const dlp = getConfig(ctx.cwd).policy.dlp;
|
|
40639
40856
|
if (!dlp?.enabled) {
|
|
@@ -40777,7 +40994,7 @@ var init_governance = __esm({
|
|
|
40777
40994
|
"src/posture/governance.ts"() {
|
|
40778
40995
|
"use strict";
|
|
40779
40996
|
init_config();
|
|
40780
|
-
jailProbePath = (home) =>
|
|
40997
|
+
jailProbePath = (home) => path64.join(home, ".aws", "credentials");
|
|
40781
40998
|
}
|
|
40782
40999
|
});
|
|
40783
41000
|
|
|
@@ -40931,7 +41148,7 @@ async function annotateCoverage(findings, ctx) {
|
|
|
40931
41148
|
const worst = verdicts.some((v) => v === "review") ? "review" : "block";
|
|
40932
41149
|
f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
|
|
40933
41150
|
if (isRecordOnly(mode) && f.fix?.includes("shield enable project-jail")) {
|
|
40934
|
-
f.fix = `node9 already recognises reads of these files, but it is in ${mode
|
|
41151
|
+
f.fix = `node9 already recognises reads of these files, but it is in ${levelOf(mode)} mode, which logs them and lets them through. Fix it now: set mode to \`standard\` (or \`strict\`) to stop them.`;
|
|
40935
41152
|
}
|
|
40936
41153
|
}
|
|
40937
41154
|
continue;
|
|
@@ -40956,7 +41173,7 @@ var init_enforcement = __esm({
|
|
|
40956
41173
|
});
|
|
40957
41174
|
|
|
40958
41175
|
// src/posture/index.ts
|
|
40959
|
-
import
|
|
41176
|
+
import os52 from "os";
|
|
40960
41177
|
function dropEnforcementRedundant(findings) {
|
|
40961
41178
|
const coveragePresent = findings.some((f) => f.category === "Coverage");
|
|
40962
41179
|
if (!coveragePresent) return findings;
|
|
@@ -40982,7 +41199,7 @@ async function runChecks(checks, ctx) {
|
|
|
40982
41199
|
}
|
|
40983
41200
|
async function runPosture(opts = {}) {
|
|
40984
41201
|
const ctx = {
|
|
40985
|
-
home: opts.home ??
|
|
41202
|
+
home: opts.home ?? os52.homedir(),
|
|
40986
41203
|
cwd: opts.cwd ?? process.cwd(),
|
|
40987
41204
|
agent: opts.agent
|
|
40988
41205
|
};
|
|
@@ -41359,20 +41576,20 @@ var init_ship2 = __esm({
|
|
|
41359
41576
|
|
|
41360
41577
|
// src/machine-id.ts
|
|
41361
41578
|
import * as crypto7 from "crypto";
|
|
41362
|
-
import * as
|
|
41363
|
-
import * as
|
|
41364
|
-
import * as
|
|
41365
|
-
function getMachineId(homeDir2 =
|
|
41366
|
-
const file2 =
|
|
41579
|
+
import * as fs63 from "fs";
|
|
41580
|
+
import * as os53 from "os";
|
|
41581
|
+
import * as path65 from "path";
|
|
41582
|
+
function getMachineId(homeDir2 = os53.homedir()) {
|
|
41583
|
+
const file2 = path65.join(homeDir2, ".node9", "machine-id");
|
|
41367
41584
|
try {
|
|
41368
|
-
const existing =
|
|
41585
|
+
const existing = fs63.readFileSync(file2, "utf-8").trim();
|
|
41369
41586
|
if (UUID_RE.test(existing)) return existing;
|
|
41370
41587
|
} catch {
|
|
41371
41588
|
}
|
|
41372
41589
|
const id = crypto7.randomUUID();
|
|
41373
41590
|
try {
|
|
41374
|
-
|
|
41375
|
-
|
|
41591
|
+
fs63.mkdirSync(path65.dirname(file2), { recursive: true });
|
|
41592
|
+
fs63.writeFileSync(file2, id + "\n", { mode: 384 });
|
|
41376
41593
|
} catch {
|
|
41377
41594
|
}
|
|
41378
41595
|
return id;
|
|
@@ -41412,10 +41629,10 @@ var init_sync_trigger = __esm({
|
|
|
41412
41629
|
});
|
|
41413
41630
|
|
|
41414
41631
|
// src/daemon/sync.ts
|
|
41415
|
-
import
|
|
41632
|
+
import fs64 from "fs";
|
|
41416
41633
|
import https4 from "https";
|
|
41417
|
-
import
|
|
41418
|
-
import
|
|
41634
|
+
import os54 from "os";
|
|
41635
|
+
import path66 from "path";
|
|
41419
41636
|
function emptySignals3() {
|
|
41420
41637
|
return {
|
|
41421
41638
|
dlpFindings: 0,
|
|
@@ -41463,7 +41680,7 @@ function readCredentials() {
|
|
|
41463
41680
|
}
|
|
41464
41681
|
function readCachedEtag() {
|
|
41465
41682
|
try {
|
|
41466
|
-
const raw = JSON.parse(
|
|
41683
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41467
41684
|
return typeof raw.etag === "string" ? raw.etag : void 0;
|
|
41468
41685
|
} catch {
|
|
41469
41686
|
return void 0;
|
|
@@ -41471,7 +41688,7 @@ function readCachedEtag() {
|
|
|
41471
41688
|
}
|
|
41472
41689
|
function readCachedSyncIntervalHours() {
|
|
41473
41690
|
try {
|
|
41474
|
-
const raw = JSON.parse(
|
|
41691
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41475
41692
|
return typeof raw.syncIntervalHours === "number" ? raw.syncIntervalHours : void 0;
|
|
41476
41693
|
} catch {
|
|
41477
41694
|
return void 0;
|
|
@@ -41488,7 +41705,7 @@ function effectiveSyncIntervalMs() {
|
|
|
41488
41705
|
}
|
|
41489
41706
|
function readSyncHealth() {
|
|
41490
41707
|
try {
|
|
41491
|
-
const raw = JSON.parse(
|
|
41708
|
+
const raw = JSON.parse(fs64.readFileSync(syncHealthFile(), "utf-8"));
|
|
41492
41709
|
return {
|
|
41493
41710
|
lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
|
|
41494
41711
|
lastChangedAt: typeof raw.lastChangedAt === "string" ? raw.lastChangedAt : void 0,
|
|
@@ -41503,17 +41720,17 @@ function readSyncHealth() {
|
|
|
41503
41720
|
function writeSyncHealth(h) {
|
|
41504
41721
|
try {
|
|
41505
41722
|
const file2 = syncHealthFile();
|
|
41506
|
-
const dir2 =
|
|
41507
|
-
if (!
|
|
41723
|
+
const dir2 = path66.dirname(file2);
|
|
41724
|
+
if (!fs64.existsSync(dir2)) fs64.mkdirSync(dir2, { recursive: true });
|
|
41508
41725
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
41509
|
-
|
|
41510
|
-
|
|
41726
|
+
fs64.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
|
|
41727
|
+
fs64.renameSync(tmp, file2);
|
|
41511
41728
|
} catch {
|
|
41512
41729
|
}
|
|
41513
41730
|
}
|
|
41514
41731
|
function readCacheFetchedAt() {
|
|
41515
41732
|
try {
|
|
41516
|
-
const raw = JSON.parse(
|
|
41733
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41517
41734
|
return typeof raw.fetchedAt === "string" ? raw.fetchedAt : void 0;
|
|
41518
41735
|
} catch {
|
|
41519
41736
|
return void 0;
|
|
@@ -41550,13 +41767,13 @@ function safeNode9Version() {
|
|
|
41550
41767
|
let dir2 = __dirname;
|
|
41551
41768
|
for (let up = 0; up < 5; up++) {
|
|
41552
41769
|
try {
|
|
41553
|
-
const pkg = JSON.parse(
|
|
41770
|
+
const pkg = JSON.parse(fs64.readFileSync(path66.join(dir2, "package.json"), "utf-8"));
|
|
41554
41771
|
if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
|
|
41555
41772
|
return pkg.version;
|
|
41556
41773
|
}
|
|
41557
41774
|
} catch {
|
|
41558
41775
|
}
|
|
41559
|
-
const parent =
|
|
41776
|
+
const parent = path66.dirname(dir2);
|
|
41560
41777
|
if (parent === dir2) break;
|
|
41561
41778
|
dir2 = parent;
|
|
41562
41779
|
}
|
|
@@ -41809,13 +42026,13 @@ function extractManagedConfig(body) {
|
|
|
41809
42026
|
}
|
|
41810
42027
|
function sweepStaleTmp(target) {
|
|
41811
42028
|
try {
|
|
41812
|
-
const dir2 =
|
|
41813
|
-
const prefix = `${
|
|
41814
|
-
for (const name of
|
|
42029
|
+
const dir2 = path66.dirname(target);
|
|
42030
|
+
const prefix = `${path66.basename(target)}.`;
|
|
42031
|
+
for (const name of fs64.readdirSync(dir2)) {
|
|
41815
42032
|
if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
|
|
41816
|
-
const full =
|
|
42033
|
+
const full = path66.join(dir2, name);
|
|
41817
42034
|
try {
|
|
41818
|
-
if (Date.now() -
|
|
42035
|
+
if (Date.now() - fs64.statSync(full).mtimeMs > 5 * 60 * 1e3) fs64.unlinkSync(full);
|
|
41819
42036
|
} catch {
|
|
41820
42037
|
}
|
|
41821
42038
|
}
|
|
@@ -41918,7 +42135,7 @@ async function pushBlastSnapshot(creds) {
|
|
|
41918
42135
|
}
|
|
41919
42136
|
async function pushPostureSnapshot(creds) {
|
|
41920
42137
|
try {
|
|
41921
|
-
const home =
|
|
42138
|
+
const home = os54.homedir();
|
|
41922
42139
|
const result = await runPosture({ home, cwd: home });
|
|
41923
42140
|
await shipPosture(result, creds);
|
|
41924
42141
|
} catch {
|
|
@@ -42092,7 +42309,7 @@ async function runCloudSync() {
|
|
|
42092
42309
|
}
|
|
42093
42310
|
function getCloudSyncStatus() {
|
|
42094
42311
|
try {
|
|
42095
|
-
const raw = JSON.parse(
|
|
42312
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42096
42313
|
if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
|
|
42097
42314
|
return {
|
|
42098
42315
|
cached: true,
|
|
@@ -42109,7 +42326,7 @@ function getCloudSyncStatus() {
|
|
|
42109
42326
|
}
|
|
42110
42327
|
function getCloudRules() {
|
|
42111
42328
|
try {
|
|
42112
|
-
const raw = JSON.parse(
|
|
42329
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42113
42330
|
return Array.isArray(raw.rules) ? raw.rules : null;
|
|
42114
42331
|
} catch {
|
|
42115
42332
|
return null;
|
|
@@ -42179,12 +42396,12 @@ var init_sync = __esm({
|
|
|
42179
42396
|
loop: "loops",
|
|
42180
42397
|
"long-output-redacted": "longOutputRedactions"
|
|
42181
42398
|
};
|
|
42182
|
-
rulesCacheFile = () =>
|
|
42183
|
-
rulesCacheBackupFile = () =>
|
|
42399
|
+
rulesCacheFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.json");
|
|
42400
|
+
rulesCacheBackupFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.last-good.json");
|
|
42184
42401
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
42185
42402
|
MIN_INTERVAL_SECONDS = 15;
|
|
42186
42403
|
MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
42187
|
-
syncHealthFile = () =>
|
|
42404
|
+
syncHealthFile = () => path66.join(os54.homedir(), ".node9", "sync-health.json");
|
|
42188
42405
|
STALE_MIN_MS = 3 * 60 * 60 * 1e3;
|
|
42189
42406
|
STALE_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
42190
42407
|
STALE_FACTOR = 3;
|
|
@@ -42209,9 +42426,9 @@ __export(audit_shipper_exports, {
|
|
|
42209
42426
|
startAuditShipper: () => startAuditShipper,
|
|
42210
42427
|
writeWatermark: () => writeWatermark
|
|
42211
42428
|
});
|
|
42212
|
-
import
|
|
42213
|
-
import
|
|
42214
|
-
import
|
|
42429
|
+
import fs65 from "fs";
|
|
42430
|
+
import path67 from "path";
|
|
42431
|
+
import os55 from "os";
|
|
42215
42432
|
import crypto8 from "crypto";
|
|
42216
42433
|
function exposureOf(v) {
|
|
42217
42434
|
if (!v || typeof v !== "object") return null;
|
|
@@ -42221,21 +42438,21 @@ function exposureOf(v) {
|
|
|
42221
42438
|
return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
|
|
42222
42439
|
}
|
|
42223
42440
|
function fileSignature(filePath) {
|
|
42224
|
-
const fd =
|
|
42441
|
+
const fd = fs65.openSync(filePath, "r");
|
|
42225
42442
|
try {
|
|
42226
42443
|
const buf = Buffer.alloc(512);
|
|
42227
|
-
const read3 =
|
|
42444
|
+
const read3 = fs65.readSync(fd, buf, 0, 512, 0);
|
|
42228
42445
|
const slice = buf.subarray(0, read3);
|
|
42229
42446
|
const nl = slice.indexOf(10);
|
|
42230
42447
|
const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
|
|
42231
42448
|
return crypto8.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
|
|
42232
42449
|
} finally {
|
|
42233
|
-
|
|
42450
|
+
fs65.closeSync(fd);
|
|
42234
42451
|
}
|
|
42235
42452
|
}
|
|
42236
42453
|
function readWatermark(watermarkPath) {
|
|
42237
42454
|
try {
|
|
42238
|
-
const raw = JSON.parse(
|
|
42455
|
+
const raw = JSON.parse(fs65.readFileSync(watermarkPath, "utf-8"));
|
|
42239
42456
|
if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
|
|
42240
42457
|
return raw;
|
|
42241
42458
|
} catch {
|
|
@@ -42244,8 +42461,8 @@ function readWatermark(watermarkPath) {
|
|
|
42244
42461
|
}
|
|
42245
42462
|
function writeWatermark(watermarkPath, wm) {
|
|
42246
42463
|
const tmp = `${watermarkPath}.tmp`;
|
|
42247
|
-
|
|
42248
|
-
|
|
42464
|
+
fs65.writeFileSync(tmp, JSON.stringify(wm));
|
|
42465
|
+
fs65.renameSync(tmp, watermarkPath);
|
|
42249
42466
|
}
|
|
42250
42467
|
function buildWireRows(chunk2) {
|
|
42251
42468
|
const lastNl = chunk2.lastIndexOf(10);
|
|
@@ -42332,11 +42549,11 @@ async function shipOnce(deps = {}) {
|
|
|
42332
42549
|
if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
|
|
42333
42550
|
const endpoint = buildBatchEndpoint(creds.apiUrl);
|
|
42334
42551
|
if (!endpoint) return { status: "no-creds", shipped: 0 };
|
|
42335
|
-
if (!
|
|
42552
|
+
if (!fs65.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
|
|
42336
42553
|
let shipped = 0;
|
|
42337
42554
|
try {
|
|
42338
42555
|
for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
|
|
42339
|
-
const size =
|
|
42556
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42340
42557
|
if (size === 0) break;
|
|
42341
42558
|
const sig = fileSignature(auditLogPath);
|
|
42342
42559
|
const wm = readWatermark(watermarkPath);
|
|
@@ -42344,12 +42561,12 @@ async function shipOnce(deps = {}) {
|
|
|
42344
42561
|
if (offset >= size) break;
|
|
42345
42562
|
const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
|
|
42346
42563
|
const buf = Buffer.alloc(toRead);
|
|
42347
|
-
const fd =
|
|
42564
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42348
42565
|
let read3;
|
|
42349
42566
|
try {
|
|
42350
|
-
read3 =
|
|
42567
|
+
read3 = fs65.readSync(fd, buf, 0, toRead, offset);
|
|
42351
42568
|
} finally {
|
|
42352
|
-
|
|
42569
|
+
fs65.closeSync(fd);
|
|
42353
42570
|
}
|
|
42354
42571
|
const { rows, consumed } = buildWireRows(buf.subarray(0, read3));
|
|
42355
42572
|
if (consumed === 0) break;
|
|
@@ -42396,8 +42613,8 @@ async function shipOnce(deps = {}) {
|
|
|
42396
42613
|
}
|
|
42397
42614
|
function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
|
|
42398
42615
|
try {
|
|
42399
|
-
if (!
|
|
42400
|
-
const size =
|
|
42616
|
+
if (!fs65.existsSync(auditLogPath)) return 0;
|
|
42617
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42401
42618
|
const wm = readWatermark(watermarkPath);
|
|
42402
42619
|
if (!wm) return size;
|
|
42403
42620
|
if (wm.fileSig !== fileSignature(auditLogPath)) return size;
|
|
@@ -42422,20 +42639,20 @@ function startAuditShipper() {
|
|
|
42422
42639
|
}
|
|
42423
42640
|
function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK, maxBytes = MAX_CHUNK_BYTES) {
|
|
42424
42641
|
try {
|
|
42425
|
-
const fd =
|
|
42642
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42426
42643
|
let chunk2;
|
|
42427
42644
|
let truncated;
|
|
42428
42645
|
try {
|
|
42429
|
-
const size =
|
|
42646
|
+
const size = fs65.fstatSync(fd).size;
|
|
42430
42647
|
const wm = readWatermark(watermarkPath);
|
|
42431
42648
|
const offset = wm && wm.fileSig === fileSignature(auditLogPath) && wm.offset <= size ? wm.offset : 0;
|
|
42432
42649
|
const length = Math.min(size - offset, maxBytes);
|
|
42433
42650
|
const buffer = Buffer.alloc(length);
|
|
42434
|
-
const read3 =
|
|
42651
|
+
const read3 = fs65.readSync(fd, buffer, 0, length, offset);
|
|
42435
42652
|
chunk2 = buffer.subarray(0, read3);
|
|
42436
42653
|
truncated = size - offset > length;
|
|
42437
42654
|
} finally {
|
|
42438
|
-
|
|
42655
|
+
fs65.closeSync(fd);
|
|
42439
42656
|
}
|
|
42440
42657
|
const { rows } = buildWireRows(chunk2);
|
|
42441
42658
|
const oldest = rows.reduce((old, row) => {
|
|
@@ -42472,7 +42689,7 @@ var init_audit_shipper = __esm({
|
|
|
42472
42689
|
"policy",
|
|
42473
42690
|
"abandoned"
|
|
42474
42691
|
]);
|
|
42475
|
-
AUDIT_SHIP_WATERMARK =
|
|
42692
|
+
AUDIT_SHIP_WATERMARK = path67.join(os55.homedir(), ".node9", "audit-ship.json");
|
|
42476
42693
|
DEFAULT_INTERVAL_MS = 2e4;
|
|
42477
42694
|
MAX_BATCH = 500;
|
|
42478
42695
|
MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
@@ -42554,12 +42771,12 @@ var init_decision = __esm({
|
|
|
42554
42771
|
});
|
|
42555
42772
|
|
|
42556
42773
|
// src/daemon/dlp-scanner.ts
|
|
42557
|
-
import
|
|
42558
|
-
import
|
|
42559
|
-
import
|
|
42774
|
+
import fs66 from "fs";
|
|
42775
|
+
import path68 from "path";
|
|
42776
|
+
import os56 from "os";
|
|
42560
42777
|
function loadIndex() {
|
|
42561
42778
|
try {
|
|
42562
|
-
const raw = JSON.parse(
|
|
42779
|
+
const raw = JSON.parse(fs66.readFileSync(INDEX_FILE, "utf-8"));
|
|
42563
42780
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
42564
42781
|
const r = raw;
|
|
42565
42782
|
if (r.offsets && typeof r.offsets === "object") return r.offsets;
|
|
@@ -42571,13 +42788,13 @@ function loadIndex() {
|
|
|
42571
42788
|
}
|
|
42572
42789
|
function saveIndex(index) {
|
|
42573
42790
|
try {
|
|
42574
|
-
|
|
42791
|
+
fs66.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
42575
42792
|
} catch {
|
|
42576
42793
|
}
|
|
42577
42794
|
}
|
|
42578
42795
|
function appendAuditEntry(entry) {
|
|
42579
42796
|
try {
|
|
42580
|
-
|
|
42797
|
+
fs66.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
42581
42798
|
} catch {
|
|
42582
42799
|
}
|
|
42583
42800
|
}
|
|
@@ -42591,52 +42808,52 @@ function runDlpScan() {
|
|
|
42591
42808
|
canaryVals = [];
|
|
42592
42809
|
}
|
|
42593
42810
|
const canaryFindings = [];
|
|
42594
|
-
if (!
|
|
42811
|
+
if (!fs66.existsSync(PROJECTS_DIR2)) return;
|
|
42595
42812
|
const index = loadIndex();
|
|
42596
42813
|
const seenThisPass = /* @__PURE__ */ new Set();
|
|
42597
42814
|
const newFindings = [];
|
|
42598
42815
|
let updated = false;
|
|
42599
42816
|
let projDirs;
|
|
42600
42817
|
try {
|
|
42601
|
-
projDirs =
|
|
42818
|
+
projDirs = fs66.readdirSync(PROJECTS_DIR2);
|
|
42602
42819
|
} catch {
|
|
42603
42820
|
return;
|
|
42604
42821
|
}
|
|
42605
42822
|
for (const proj of projDirs) {
|
|
42606
|
-
const projPath =
|
|
42823
|
+
const projPath = path68.join(PROJECTS_DIR2, proj);
|
|
42607
42824
|
try {
|
|
42608
|
-
if (!
|
|
42609
|
-
const real =
|
|
42610
|
-
if (!real.startsWith(PROJECTS_DIR2 +
|
|
42825
|
+
if (!fs66.lstatSync(projPath).isDirectory()) continue;
|
|
42826
|
+
const real = fs66.realpathSync(projPath);
|
|
42827
|
+
if (!real.startsWith(PROJECTS_DIR2 + path68.sep) && real !== PROJECTS_DIR2) continue;
|
|
42611
42828
|
} catch {
|
|
42612
42829
|
continue;
|
|
42613
42830
|
}
|
|
42614
42831
|
let files;
|
|
42615
42832
|
try {
|
|
42616
|
-
files =
|
|
42833
|
+
files = fs66.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
42617
42834
|
} catch {
|
|
42618
42835
|
continue;
|
|
42619
42836
|
}
|
|
42620
42837
|
for (const file2 of files) {
|
|
42621
|
-
const filePath =
|
|
42838
|
+
const filePath = path68.join(projPath, file2);
|
|
42622
42839
|
const lastOffset = index[filePath] ?? 0;
|
|
42623
42840
|
let size;
|
|
42624
42841
|
try {
|
|
42625
|
-
size =
|
|
42842
|
+
size = fs66.statSync(filePath).size;
|
|
42626
42843
|
} catch {
|
|
42627
42844
|
continue;
|
|
42628
42845
|
}
|
|
42629
42846
|
if (size <= lastOffset) continue;
|
|
42630
42847
|
let fd;
|
|
42631
42848
|
try {
|
|
42632
|
-
fd =
|
|
42849
|
+
fd = fs66.openSync(filePath, "r");
|
|
42633
42850
|
} catch {
|
|
42634
42851
|
continue;
|
|
42635
42852
|
}
|
|
42636
42853
|
try {
|
|
42637
42854
|
const chunkSize = size - lastOffset;
|
|
42638
42855
|
const buf = Buffer.alloc(chunkSize);
|
|
42639
|
-
|
|
42856
|
+
fs66.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
42640
42857
|
const chunk2 = buf.toString("utf-8");
|
|
42641
42858
|
for (const line of chunk2.split("\n")) {
|
|
42642
42859
|
if (!line.trim()) continue;
|
|
@@ -42657,7 +42874,7 @@ function runDlpScan() {
|
|
|
42657
42874
|
const match = scanText(text);
|
|
42658
42875
|
const canary = canaryVals.length > 0 ? matchCanary(text, canaryVals) : null;
|
|
42659
42876
|
if (!match && !canary) continue;
|
|
42660
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
42877
|
+
const projLabel = decodeURIComponent(proj).replace(os56.homedir(), "~").slice(0, 40);
|
|
42661
42878
|
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
42662
42879
|
if (canary) {
|
|
42663
42880
|
const rec = canaryById.get(canary.id);
|
|
@@ -42707,7 +42924,7 @@ function runDlpScan() {
|
|
|
42707
42924
|
updated = true;
|
|
42708
42925
|
} finally {
|
|
42709
42926
|
try {
|
|
42710
|
-
|
|
42927
|
+
fs66.closeSync(fd);
|
|
42711
42928
|
} catch {
|
|
42712
42929
|
}
|
|
42713
42930
|
}
|
|
@@ -42766,15 +42983,15 @@ var init_dlp_scanner = __esm({
|
|
|
42766
42983
|
init_registry2();
|
|
42767
42984
|
init_native();
|
|
42768
42985
|
init_state3();
|
|
42769
|
-
INDEX_FILE =
|
|
42770
|
-
PROJECTS_DIR2 =
|
|
42986
|
+
INDEX_FILE = path68.join(os56.homedir(), ".node9", "dlp-index.json");
|
|
42987
|
+
PROJECTS_DIR2 = path68.join(os56.homedir(), ".claude", "projects");
|
|
42771
42988
|
}
|
|
42772
42989
|
});
|
|
42773
42990
|
|
|
42774
42991
|
// src/daemon/mcp-reconciler.ts
|
|
42775
|
-
import
|
|
42776
|
-
import
|
|
42777
|
-
import
|
|
42992
|
+
import fs67 from "fs";
|
|
42993
|
+
import path69 from "path";
|
|
42994
|
+
import os57 from "os";
|
|
42778
42995
|
import crypto9 from "crypto";
|
|
42779
42996
|
function idKey(e) {
|
|
42780
42997
|
const cmdHash = crypto9.createHash("sha256").update(`${e.command} ${e.args.join(" ")}`).digest("hex").slice(0, 16);
|
|
@@ -42782,7 +42999,7 @@ function idKey(e) {
|
|
|
42782
42999
|
}
|
|
42783
43000
|
function loadBaseline() {
|
|
42784
43001
|
try {
|
|
42785
|
-
const raw = JSON.parse(
|
|
43002
|
+
const raw = JSON.parse(fs67.readFileSync(BASELINE_FILE2, "utf-8"));
|
|
42786
43003
|
return new Set(Array.isArray(raw) ? raw : []);
|
|
42787
43004
|
} catch {
|
|
42788
43005
|
return /* @__PURE__ */ new Set();
|
|
@@ -42790,7 +43007,7 @@ function loadBaseline() {
|
|
|
42790
43007
|
}
|
|
42791
43008
|
function saveBaseline(keys) {
|
|
42792
43009
|
try {
|
|
42793
|
-
|
|
43010
|
+
fs67.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
|
|
42794
43011
|
mode: 384
|
|
42795
43012
|
});
|
|
42796
43013
|
} catch {
|
|
@@ -42978,7 +43195,7 @@ var init_mcp_reconciler = __esm({
|
|
|
42978
43195
|
init_cloud();
|
|
42979
43196
|
init_audit();
|
|
42980
43197
|
init_mcp_pin();
|
|
42981
|
-
BASELINE_FILE2 =
|
|
43198
|
+
BASELINE_FILE2 = path69.join(os57.homedir(), ".node9", "mcp-baseline.json");
|
|
42982
43199
|
BASELINE_CAP = 500;
|
|
42983
43200
|
DEFAULT_INTERVAL_MIN = 60;
|
|
42984
43201
|
DEFAULT_STALE_DAYS = 7;
|
|
@@ -43052,114 +43269,6 @@ var init_hook_heal = __esm({
|
|
|
43052
43269
|
}
|
|
43053
43270
|
});
|
|
43054
43271
|
|
|
43055
|
-
// src/daemon/startup-log.ts
|
|
43056
|
-
import fs67 from "fs";
|
|
43057
|
-
import path69 from "path";
|
|
43058
|
-
import os57 from "os";
|
|
43059
|
-
function capStartupLog(file2) {
|
|
43060
|
-
try {
|
|
43061
|
-
if (fs67.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs67.truncateSync(file2);
|
|
43062
|
-
} catch {
|
|
43063
|
-
}
|
|
43064
|
-
}
|
|
43065
|
-
function openStartupLogFd() {
|
|
43066
|
-
try {
|
|
43067
|
-
const file2 = DAEMON_STARTUP_LOG();
|
|
43068
|
-
const dir2 = path69.dirname(file2);
|
|
43069
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43070
|
-
capStartupLog(file2);
|
|
43071
|
-
return fs67.openSync(file2, "a");
|
|
43072
|
-
} catch {
|
|
43073
|
-
return void 0;
|
|
43074
|
-
}
|
|
43075
|
-
}
|
|
43076
|
-
function recordStartupState(outcome, kind, detail) {
|
|
43077
|
-
try {
|
|
43078
|
-
if (outcome === "starting") {
|
|
43079
|
-
const prev = readStartupState();
|
|
43080
|
-
if (prev?.outcome === "starting") {
|
|
43081
|
-
const prevAt = new Date(prev.at).getTime();
|
|
43082
|
-
const age = Math.abs(Date.now() - prevAt);
|
|
43083
|
-
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
43084
|
-
}
|
|
43085
|
-
}
|
|
43086
|
-
const file2 = DAEMON_STARTUP_STATE();
|
|
43087
|
-
const dir2 = path69.dirname(file2);
|
|
43088
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43089
|
-
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
43090
|
-
if (kind) state.kind = kind;
|
|
43091
|
-
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
43092
|
-
const tmp = `${file2}.${process.pid}.tmp`;
|
|
43093
|
-
try {
|
|
43094
|
-
fs67.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
43095
|
-
fs67.renameSync(tmp, file2);
|
|
43096
|
-
} catch (err2) {
|
|
43097
|
-
try {
|
|
43098
|
-
fs67.unlinkSync(tmp);
|
|
43099
|
-
} catch {
|
|
43100
|
-
}
|
|
43101
|
-
throw err2;
|
|
43102
|
-
}
|
|
43103
|
-
} catch {
|
|
43104
|
-
}
|
|
43105
|
-
}
|
|
43106
|
-
function readStartupState() {
|
|
43107
|
-
try {
|
|
43108
|
-
const raw = fs67.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
43109
|
-
const s = JSON.parse(raw);
|
|
43110
|
-
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
43111
|
-
return s;
|
|
43112
|
-
} catch {
|
|
43113
|
-
return null;
|
|
43114
|
-
}
|
|
43115
|
-
}
|
|
43116
|
-
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
43117
|
-
const s = readStartupState();
|
|
43118
|
-
if (!s) return null;
|
|
43119
|
-
const at = new Date(s.at);
|
|
43120
|
-
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
43121
|
-
switch (s.outcome) {
|
|
43122
|
-
case "ok":
|
|
43123
|
-
case "ok-elsewhere":
|
|
43124
|
-
return null;
|
|
43125
|
-
case "starting":
|
|
43126
|
-
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
43127
|
-
return {
|
|
43128
|
-
kind: "did-not-start",
|
|
43129
|
-
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
43130
|
-
at,
|
|
43131
|
-
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
43132
|
-
label: "start attempts failing since"
|
|
43133
|
-
};
|
|
43134
|
-
case "failed":
|
|
43135
|
-
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
43136
|
-
default:
|
|
43137
|
-
return null;
|
|
43138
|
-
}
|
|
43139
|
-
}
|
|
43140
|
-
function logDaemonStartup(kind, detail) {
|
|
43141
|
-
try {
|
|
43142
|
-
const file2 = DAEMON_STARTUP_LOG();
|
|
43143
|
-
const dir2 = path69.dirname(file2);
|
|
43144
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43145
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
|
|
43146
|
-
`;
|
|
43147
|
-
fs67.appendFileSync(file2, line, "utf-8");
|
|
43148
|
-
} catch {
|
|
43149
|
-
}
|
|
43150
|
-
}
|
|
43151
|
-
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
43152
|
-
var init_startup_log = __esm({
|
|
43153
|
-
"src/daemon/startup-log.ts"() {
|
|
43154
|
-
"use strict";
|
|
43155
|
-
DAEMON_STARTUP_LOG = () => path69.join(os57.homedir(), ".node9", "daemon-startup.log");
|
|
43156
|
-
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
43157
|
-
DAEMON_STARTUP_STATE = () => path69.join(os57.homedir(), ".node9", "daemon-startup-state.json");
|
|
43158
|
-
MAX_DETAIL = 200;
|
|
43159
|
-
STARTING_GRACE_MS = 90 * 1e3;
|
|
43160
|
-
}
|
|
43161
|
-
});
|
|
43162
|
-
|
|
43163
43272
|
// src/supply-chain/zip.ts
|
|
43164
43273
|
import fs68 from "fs";
|
|
43165
43274
|
import zlib from "zlib";
|
|
@@ -77491,7 +77600,7 @@ function buildReportJson(input) {
|
|
|
77491
77600
|
var BLOCK_REASON_LABELS = {
|
|
77492
77601
|
timeout: "Approval timeout",
|
|
77493
77602
|
"smart-rule-block": "Smart rule",
|
|
77494
|
-
"observe-mode-dlp-would-block": "DLP (
|
|
77603
|
+
"observe-mode-dlp-would-block": "DLP (audit)",
|
|
77495
77604
|
"persistent-deny": "Persistent deny",
|
|
77496
77605
|
"local-decision": "User denied",
|
|
77497
77606
|
"dlp-block": "DLP block",
|
|
@@ -77531,10 +77640,10 @@ function fmtCost2(usd) {
|
|
|
77531
77640
|
return "$" + usd.toFixed(2);
|
|
77532
77641
|
}
|
|
77533
77642
|
function registerReportCommand(program2) {
|
|
77534
|
-
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "
|
|
77643
|
+
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | 90d | month", "30d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
|
|
77535
77644
|
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
77536
77645
|
options.period
|
|
77537
|
-
) ? options.period : "
|
|
77646
|
+
) ? options.period : "30d";
|
|
77538
77647
|
const excludeTests = options.tests === false;
|
|
77539
77648
|
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
77540
77649
|
excludeTests
|
|
@@ -77732,7 +77841,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
77732
77841
|
);
|
|
77733
77842
|
summaryRow(
|
|
77734
77843
|
observeDlp > 0 ? chalk20.blue("\u{1F441}") : chalk20.dim("\u{1F441}"),
|
|
77735
|
-
"DLP (
|
|
77844
|
+
"DLP (audit)",
|
|
77736
77845
|
observeDlp,
|
|
77737
77846
|
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
77738
77847
|
observeDlp > 0 ? (s) => chalk20.blue(s) : (s) => chalk20.dim(s)
|
|
@@ -79240,6 +79349,235 @@ import fs92 from "fs";
|
|
|
79240
79349
|
import os78 from "os";
|
|
79241
79350
|
import path91 from "path";
|
|
79242
79351
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
79352
|
+
|
|
79353
|
+
// src/cli/commands/egress.ts
|
|
79354
|
+
init_config();
|
|
79355
|
+
init_keyed_guard();
|
|
79356
|
+
init_dist();
|
|
79357
|
+
init_egress_config();
|
|
79358
|
+
init_egress_config();
|
|
79359
|
+
import chalk27 from "chalk";
|
|
79360
|
+
function guard(fn) {
|
|
79361
|
+
try {
|
|
79362
|
+
fn();
|
|
79363
|
+
return true;
|
|
79364
|
+
} catch (err2) {
|
|
79365
|
+
console.error(chalk27.red(`
|
|
79366
|
+
\u2717 ${err2.message}
|
|
79367
|
+
`));
|
|
79368
|
+
process.exitCode = 1;
|
|
79369
|
+
return false;
|
|
79370
|
+
}
|
|
79371
|
+
}
|
|
79372
|
+
function mutate(action, change) {
|
|
79373
|
+
if (!cliGuardPolicyWrite(action)) return false;
|
|
79374
|
+
return guard(() => setEgress(change));
|
|
79375
|
+
}
|
|
79376
|
+
function addHost(list2, host2) {
|
|
79377
|
+
if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
|
|
79378
|
+
return guard(() => addEgressHost(list2, host2));
|
|
79379
|
+
}
|
|
79380
|
+
function exempt2(address) {
|
|
79381
|
+
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
79382
|
+
return guard(() => addSsrfExemption(address));
|
|
79383
|
+
}
|
|
79384
|
+
var INTERNAL_FIELDS = {
|
|
79385
|
+
allowed: { ssrfStrict: false, allowPrivate: true },
|
|
79386
|
+
listed: { ssrfStrict: false, allowPrivate: false },
|
|
79387
|
+
blocked: { ssrfStrict: true, allowPrivate: false }
|
|
79388
|
+
};
|
|
79389
|
+
var INTERNAL_SAID = {
|
|
79390
|
+
allowed: "reachable without listing them",
|
|
79391
|
+
listed: "reachable only if they are on your allowlist",
|
|
79392
|
+
blocked: "blocked"
|
|
79393
|
+
};
|
|
79394
|
+
function describeInternalState(state) {
|
|
79395
|
+
return INTERNAL_SAID[state];
|
|
79396
|
+
}
|
|
79397
|
+
function readInternalState(e) {
|
|
79398
|
+
if (e.ssrfStrict === true) return "blocked";
|
|
79399
|
+
return e.allowPrivate === false ? "listed" : "allowed";
|
|
79400
|
+
}
|
|
79401
|
+
function setInternal(value) {
|
|
79402
|
+
const state = value.trim().toLowerCase();
|
|
79403
|
+
if (!(state in INTERNAL_FIELDS)) {
|
|
79404
|
+
console.error(
|
|
79405
|
+
chalk27.red(`
|
|
79406
|
+
\u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk27.gray(
|
|
79407
|
+
"\n allowed loopback, 10/172.16/192.168 and CGNAT are reachable (default)\n listed they are reachable only if you allowlist them\n blocked they are blocked at the floor\n"
|
|
79408
|
+
)
|
|
79409
|
+
);
|
|
79410
|
+
process.exitCode = 1;
|
|
79411
|
+
return;
|
|
79412
|
+
}
|
|
79413
|
+
if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
|
|
79414
|
+
_resetConfigCache();
|
|
79415
|
+
const effective = readInternalState(getConfig().policy.egress);
|
|
79416
|
+
if (effective !== state) {
|
|
79417
|
+
console.log(
|
|
79418
|
+
chalk27.yellow(
|
|
79419
|
+
`
|
|
79420
|
+
\u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
|
|
79421
|
+
Change it in the dashboard, Enforcement \u2192 Network.
|
|
79422
|
+
`
|
|
79423
|
+
)
|
|
79424
|
+
);
|
|
79425
|
+
return;
|
|
79426
|
+
}
|
|
79427
|
+
const line = `
|
|
79428
|
+
\u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
|
|
79429
|
+
`;
|
|
79430
|
+
console.log(state === "allowed" ? chalk27.yellow(line) : chalk27.green(line));
|
|
79431
|
+
}
|
|
79432
|
+
function showFloor(e, ssrfStrictSource) {
|
|
79433
|
+
console.log(
|
|
79434
|
+
chalk27.gray("\n Protected addresses") + chalk27.gray(" \u2014 in shell commands and declared URLs")
|
|
79435
|
+
);
|
|
79436
|
+
console.log(
|
|
79437
|
+
chalk27.gray(
|
|
79438
|
+
" always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
|
|
79439
|
+
)
|
|
79440
|
+
);
|
|
79441
|
+
const state = readInternalState(e);
|
|
79442
|
+
const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
|
|
79443
|
+
const STATE_LABEL = {
|
|
79444
|
+
allowed: "allowed",
|
|
79445
|
+
listed: "allowlist only",
|
|
79446
|
+
blocked: "blocked"
|
|
79447
|
+
};
|
|
79448
|
+
console.log(
|
|
79449
|
+
` Internal addresses: ${state === "blocked" ? chalk27.green(STATE_LABEL[state]) : chalk27.yellow(STATE_LABEL[state])}` + chalk27.gray(` loopback, 10/172.16/192.168 and CGNAT are ${INTERNAL_SAID[state]}`)
|
|
79450
|
+
);
|
|
79451
|
+
console.log(chalk27.gray(` set by: ${by}`));
|
|
79452
|
+
const exemptions = e.ssrfAllow ?? [];
|
|
79453
|
+
console.log(chalk27.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
|
|
79454
|
+
console.log(
|
|
79455
|
+
chalk27.gray(
|
|
79456
|
+
" Covered: shell commands, and tools that declare a URL (WebFetch, an MCP\n fetch tool, browser navigate).\n Not covered: an interpreter one-liner (node -e, python3 -c) hides its\n destination inside a program and does not reach this gate.\n CGNAT (100.64/10) is NOT in the always-blocked set: it is reachable\n until Internal addresses is on, and an exemption can release it."
|
|
79457
|
+
)
|
|
79458
|
+
);
|
|
79459
|
+
}
|
|
79460
|
+
function showStatus() {
|
|
79461
|
+
const cfg = getConfig();
|
|
79462
|
+
const e = cfg.policy.egress;
|
|
79463
|
+
const state = !e.enabled ? chalk27.red("OFF \u2014 your agent can reach any host, except the protected ones below") : e.mode === "block" ? chalk27.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk27.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
|
|
79464
|
+
console.log(chalk27.cyan.bold("\n\u{1F310} Egress control"));
|
|
79465
|
+
console.log(" State: " + state);
|
|
79466
|
+
if (cfg.policySource === "workspace") {
|
|
79467
|
+
console.log(
|
|
79468
|
+
chalk27.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
|
|
79469
|
+
);
|
|
79470
|
+
}
|
|
79471
|
+
console.log(
|
|
79472
|
+
chalk27.gray(
|
|
79473
|
+
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
79474
|
+
)
|
|
79475
|
+
);
|
|
79476
|
+
showFloor(e, cfg.ssrfStrictSource);
|
|
79477
|
+
if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
|
|
79478
|
+
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
79479
|
+
if (!e.enabled) {
|
|
79480
|
+
console.log(chalk27.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
79481
|
+
console.log(chalk27.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
79482
|
+
}
|
|
79483
|
+
console.log("");
|
|
79484
|
+
}
|
|
79485
|
+
function registerEgressCommand(program2) {
|
|
79486
|
+
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
79487
|
+
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
79488
|
+
if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
|
|
79489
|
+
console.log(chalk27.green("\n\u2713 Egress is now watched (review mode)."));
|
|
79490
|
+
console.log(
|
|
79491
|
+
chalk27.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
79492
|
+
);
|
|
79493
|
+
console.log(
|
|
79494
|
+
chalk27.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
79495
|
+
);
|
|
79496
|
+
});
|
|
79497
|
+
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
79498
|
+
if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
|
|
79499
|
+
console.log(chalk27.green("\n\u2713 Egress is now locked (block mode)."));
|
|
79500
|
+
console.log(chalk27.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
79501
|
+
console.log(chalk27.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
79502
|
+
});
|
|
79503
|
+
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
|
|
79504
|
+
if (!addHost("allow", host2)) return;
|
|
79505
|
+
console.log(chalk27.green(`
|
|
79506
|
+
\u2713 Allowed egress to ${host2}.
|
|
79507
|
+
`));
|
|
79508
|
+
});
|
|
79509
|
+
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
|
|
79510
|
+
if (!addHost("deny", host2)) return;
|
|
79511
|
+
console.log(chalk27.green(`
|
|
79512
|
+
\u2713 Denied egress to ${host2}.
|
|
79513
|
+
`));
|
|
79514
|
+
});
|
|
79515
|
+
egress.command("off").description("Turn egress control off").action(() => {
|
|
79516
|
+
if (!mutate("egress off", { enabled: false })) return;
|
|
79517
|
+
console.log(
|
|
79518
|
+
chalk27.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
79519
|
+
);
|
|
79520
|
+
});
|
|
79521
|
+
egress.command("internal <allowed|listed|blocked>").description(
|
|
79522
|
+
"How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
|
|
79523
|
+
).action((value) => setInternal(value));
|
|
79524
|
+
egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
|
|
79525
|
+
const v = value.trim().toLowerCase();
|
|
79526
|
+
if (v !== "on" && v !== "off") {
|
|
79527
|
+
console.error(chalk27.red(`
|
|
79528
|
+
\u2717 Expected "on" or "off", got "${value}".
|
|
79529
|
+
`));
|
|
79530
|
+
process.exitCode = 1;
|
|
79531
|
+
return;
|
|
79532
|
+
}
|
|
79533
|
+
const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
|
|
79534
|
+
console.log(chalk27.gray(`
|
|
79535
|
+
(\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
|
|
79536
|
+
setInternal(state);
|
|
79537
|
+
});
|
|
79538
|
+
egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
|
|
79539
|
+
const a = normalizeEgressHost(address);
|
|
79540
|
+
const slash = a.indexOf("/");
|
|
79541
|
+
const base2 = slash === -1 ? a : a.slice(0, slash);
|
|
79542
|
+
const prefixText = slash === -1 ? null : a.slice(slash + 1);
|
|
79543
|
+
if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
|
|
79544
|
+
console.error(
|
|
79545
|
+
chalk27.red(`
|
|
79546
|
+
\u2717 "${address}" is not an address or a range.`) + chalk27.gray(
|
|
79547
|
+
"\n Exemptions are matched as an address (10.0.0.5) or a CIDR\n range (100.64.0.0/10), never a name.\n"
|
|
79548
|
+
)
|
|
79549
|
+
);
|
|
79550
|
+
process.exitCode = 1;
|
|
79551
|
+
return;
|
|
79552
|
+
}
|
|
79553
|
+
const m = classifySsrf(base2);
|
|
79554
|
+
if (m && !m.overridable) {
|
|
79555
|
+
console.error(
|
|
79556
|
+
chalk27.red(`
|
|
79557
|
+
\u2717 ${a} cannot be exempted.`) + chalk27.gray(
|
|
79558
|
+
`
|
|
79559
|
+
${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
|
|
79560
|
+
`
|
|
79561
|
+
)
|
|
79562
|
+
);
|
|
79563
|
+
process.exitCode = 1;
|
|
79564
|
+
return;
|
|
79565
|
+
}
|
|
79566
|
+
if (!exempt2(a)) return;
|
|
79567
|
+
console.log(chalk27.green(`
|
|
79568
|
+
\u2713 ${a} is exempt from the floor.`));
|
|
79569
|
+
if (!m)
|
|
79570
|
+
console.log(
|
|
79571
|
+
chalk27.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
|
|
79572
|
+
);
|
|
79573
|
+
else console.log("");
|
|
79574
|
+
});
|
|
79575
|
+
egress.command("status").description("Show the current egress state").action(showStatus);
|
|
79576
|
+
egress.action(showStatus);
|
|
79577
|
+
}
|
|
79578
|
+
|
|
79579
|
+
// src/mcp-server/index.ts
|
|
79580
|
+
init_agent_wiring();
|
|
79243
79581
|
function ok(id, result) {
|
|
79244
79582
|
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
|
|
79245
79583
|
}
|
|
@@ -79373,7 +79711,7 @@ var TOOLS = [
|
|
|
79373
79711
|
},
|
|
79374
79712
|
{
|
|
79375
79713
|
name: "node9_scan",
|
|
79376
|
-
description: "Scan all
|
|
79714
|
+
description: "Scan the history of all supported coding agents (Claude Code, Gemini, Codex, Copilot, Antigravity) and report what node9 would have blocked or flagged. Shows blocked operations, reviewed commands, credential leaks, and agent spend. Use this to audit past activity and find security gaps before they become incidents.",
|
|
79377
79715
|
inputSchema: {
|
|
79378
79716
|
type: "object",
|
|
79379
79717
|
properties: {
|
|
@@ -79393,8 +79731,8 @@ var TOOLS = [
|
|
|
79393
79731
|
properties: {
|
|
79394
79732
|
period: {
|
|
79395
79733
|
type: "string",
|
|
79396
|
-
enum: ["today", "7d", "30d", "month"],
|
|
79397
|
-
description: "Time period for the report (default:
|
|
79734
|
+
enum: ["today", "7d", "30d", "90d", "month"],
|
|
79735
|
+
description: "Time period for the report (default: 30d)."
|
|
79398
79736
|
},
|
|
79399
79737
|
no_tests: {
|
|
79400
79738
|
type: "boolean",
|
|
@@ -79528,7 +79866,7 @@ function handleStatus() {
|
|
|
79528
79866
|
const wsGoverned = config.policySource === "workspace";
|
|
79529
79867
|
const activeShields = wsGoverned ? config.policy.appliedShields ?? [] : readActiveShields();
|
|
79530
79868
|
const lines = [];
|
|
79531
|
-
lines.push(`Mode: ${settings.mode}`);
|
|
79869
|
+
lines.push(`Mode: ${levelOf(settings.mode)}`);
|
|
79532
79870
|
if (wsGoverned) {
|
|
79533
79871
|
lines.push(
|
|
79534
79872
|
"Policy source: workspace config (app.node9.ai) \u2014 local config files are ignored for policy"
|
|
@@ -79544,6 +79882,12 @@ function handleStatus() {
|
|
|
79544
79882
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
79545
79883
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
79546
79884
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
79885
|
+
lines.push("Agent hooks:");
|
|
79886
|
+
for (const a of getAgentWiring()) {
|
|
79887
|
+
const hooked = a.hooks.some((h) => h.wired);
|
|
79888
|
+
const state = !a.installed ? "not installed" : hooked ? "wired" : a.isProtected ? "protected through MCP" : "not wired";
|
|
79889
|
+
lines.push(` ${a.label}: ${state}`);
|
|
79890
|
+
}
|
|
79547
79891
|
const projectConfig = path91.join(process.cwd(), "node9.config.json");
|
|
79548
79892
|
const globalConfig = path91.join(os78.homedir(), ".node9", "config.json");
|
|
79549
79893
|
const presentLabel = wsGoverned ? "present \u2014 ignored (workspace config governs)" : "present";
|
|
@@ -79636,6 +79980,11 @@ function handleShieldDisable(args) {
|
|
|
79636
79980
|
if (disabledApp) recordUserDecision(disabledApp.slug, "off");
|
|
79637
79981
|
return `Shield "${name}" disabled.`;
|
|
79638
79982
|
}
|
|
79983
|
+
function internalAddressLine(e) {
|
|
79984
|
+
const configured = readInternalState(e);
|
|
79985
|
+
const state = !e.enabled && configured === "listed" ? "allowed" : configured;
|
|
79986
|
+
return `Internal addresses: ${state} \u2014 loopback and the private ranges are ${describeInternalState(state)}.`;
|
|
79987
|
+
}
|
|
79639
79988
|
function handleEgressStatus() {
|
|
79640
79989
|
const cfgEgress = getConfig();
|
|
79641
79990
|
const e = cfgEgress.policy.egress;
|
|
@@ -79657,7 +80006,7 @@ function handleEgressStatus() {
|
|
|
79657
80006
|
// actually covers is measured in egress.integration.test.ts.
|
|
79658
80007
|
"Protected addresses: cloud metadata, link-local and multicast are blocked before any of the above is consulted, in shell commands AND in tools that declare a URL (WebFetch, an MCP fetch tool, browser navigate). No setting releases them, though `node9 pause` suspends all enforcement.",
|
|
79659
80008
|
"NOT covered by that: an interpreter one-liner (node -e, python3 -c) carries its destination inside a program and does not reach this gate. CGNAT (100.64/10) is NOT in the always-blocked set \u2014 it is reachable until the strict tier is on, and an exemption can release it.",
|
|
79660
|
-
|
|
80009
|
+
internalAddressLine(e),
|
|
79661
80010
|
`Floor exemptions: ${e.ssrfAllow?.length ? e.ssrfAllow.join(", ") : "(none)"}`
|
|
79662
80011
|
];
|
|
79663
80012
|
return lines.join("\n");
|
|
@@ -79980,7 +80329,7 @@ function registerMcpServerCommand(program2) {
|
|
|
79980
80329
|
init_trusted_hosts();
|
|
79981
80330
|
init_keyed_guard();
|
|
79982
80331
|
init_trusted_recipients();
|
|
79983
|
-
import
|
|
80332
|
+
import chalk28 from "chalk";
|
|
79984
80333
|
function isValidHost(host2) {
|
|
79985
80334
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host2);
|
|
79986
80335
|
}
|
|
@@ -79991,17 +80340,17 @@ function registerTrustCommand(program2) {
|
|
|
79991
80340
|
const normalized = normalizeHost2(host2.trim());
|
|
79992
80341
|
if (!isValidHost(normalized)) {
|
|
79993
80342
|
console.error(
|
|
79994
|
-
|
|
80343
|
+
chalk28.red(`
|
|
79995
80344
|
\u274C Invalid host: "${host2}"
|
|
79996
|
-
`) +
|
|
80345
|
+
`) + chalk28.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
79997
80346
|
);
|
|
79998
80347
|
process.exit(1);
|
|
79999
80348
|
}
|
|
80000
80349
|
addTrustedHost(normalized);
|
|
80001
|
-
console.log(
|
|
80350
|
+
console.log(chalk28.green(`
|
|
80002
80351
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
80003
80352
|
console.log(
|
|
80004
|
-
|
|
80353
|
+
chalk28.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
80005
80354
|
);
|
|
80006
80355
|
});
|
|
80007
80356
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host2) => {
|
|
@@ -80009,27 +80358,27 @@ function registerTrustCommand(program2) {
|
|
|
80009
80358
|
const normalized = normalizeHost2(host2.trim());
|
|
80010
80359
|
const removed = removeTrustedHost(normalized);
|
|
80011
80360
|
if (!removed) {
|
|
80012
|
-
console.error(
|
|
80361
|
+
console.error(chalk28.yellow(`
|
|
80013
80362
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
80014
80363
|
`));
|
|
80015
80364
|
process.exit(1);
|
|
80016
80365
|
}
|
|
80017
|
-
console.log(
|
|
80366
|
+
console.log(chalk28.green(`
|
|
80018
80367
|
\u2705 ${normalized} removed from trusted hosts.
|
|
80019
80368
|
`));
|
|
80020
80369
|
});
|
|
80021
80370
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
80022
80371
|
const hosts = readTrustedHosts();
|
|
80023
80372
|
if (hosts.length === 0) {
|
|
80024
|
-
console.log(
|
|
80025
|
-
console.log(` Add one: ${
|
|
80373
|
+
console.log(chalk28.gray("\n No trusted hosts configured.\n"));
|
|
80374
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust add api.mycompany.com")}
|
|
80026
80375
|
`);
|
|
80027
80376
|
return;
|
|
80028
80377
|
}
|
|
80029
|
-
console.log(
|
|
80378
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
80030
80379
|
for (const entry of hosts) {
|
|
80031
80380
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
80032
|
-
console.log(` ${
|
|
80381
|
+
console.log(` ${chalk28.cyan(entry.host.padEnd(40))} ${chalk28.gray(`added ${date}`)}`);
|
|
80033
80382
|
}
|
|
80034
80383
|
console.log("");
|
|
80035
80384
|
});
|
|
@@ -80041,41 +80390,41 @@ function registerTrustCommand(program2) {
|
|
|
80041
80390
|
const stored = addTrustedRecipient(value, "user");
|
|
80042
80391
|
if (!stored) {
|
|
80043
80392
|
console.error(
|
|
80044
|
-
|
|
80393
|
+
chalk28.red(`
|
|
80045
80394
|
\u274C Not a destination: "${value}"
|
|
80046
|
-
`) +
|
|
80395
|
+
`) + chalk28.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
|
|
80047
80396
|
);
|
|
80048
80397
|
process.exit(1);
|
|
80049
80398
|
}
|
|
80050
|
-
console.log(
|
|
80399
|
+
console.log(chalk28.green(`
|
|
80051
80400
|
\u2705 ${stored} added to trusted recipients.
|
|
80052
80401
|
`));
|
|
80053
80402
|
});
|
|
80054
80403
|
rec.command("remove <value>").description("Remove a trusted recipient").action((value) => {
|
|
80055
80404
|
if (!cliGuardPolicyWrite(`trust recipient remove ${value}`)) return;
|
|
80056
80405
|
if (!removeTrustedRecipient(value)) {
|
|
80057
|
-
console.error(
|
|
80406
|
+
console.error(chalk28.yellow(`
|
|
80058
80407
|
\u26A0\uFE0F "${value}" is not in the trusted recipients list.
|
|
80059
80408
|
`));
|
|
80060
80409
|
process.exit(1);
|
|
80061
80410
|
}
|
|
80062
|
-
console.log(
|
|
80411
|
+
console.log(chalk28.green(`
|
|
80063
80412
|
\u2705 ${value} removed from trusted recipients.
|
|
80064
80413
|
`));
|
|
80065
80414
|
});
|
|
80066
80415
|
rec.command("list").description("Show all trusted recipients").action(() => {
|
|
80067
80416
|
const entries = readTrustedRecipients();
|
|
80068
80417
|
if (entries.length === 0) {
|
|
80069
|
-
console.log(
|
|
80070
|
-
console.log(` Add one: ${
|
|
80418
|
+
console.log(chalk28.gray("\n No trusted recipients.\n"));
|
|
80419
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust recipient add partner@example.com")}
|
|
80071
80420
|
`);
|
|
80072
80421
|
return;
|
|
80073
80422
|
}
|
|
80074
|
-
console.log(
|
|
80423
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Recipients\n"));
|
|
80075
80424
|
for (const e of entries) {
|
|
80076
80425
|
const date = new Date(e.addedAt).toLocaleDateString();
|
|
80077
80426
|
console.log(
|
|
80078
|
-
` ${
|
|
80427
|
+
` ${chalk28.cyan(e.value.padEnd(44))} ${chalk28.gray(`added ${date} (${e.addedBy})`)}`
|
|
80079
80428
|
);
|
|
80080
80429
|
}
|
|
80081
80430
|
console.log("");
|
|
@@ -80084,13 +80433,13 @@ function registerTrustCommand(program2) {
|
|
|
80084
80433
|
|
|
80085
80434
|
// src/cli/commands/mcp-pin.ts
|
|
80086
80435
|
init_mcp_pin();
|
|
80087
|
-
import
|
|
80436
|
+
import chalk30 from "chalk";
|
|
80088
80437
|
import fs93 from "fs";
|
|
80089
80438
|
|
|
80090
80439
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
80091
80440
|
init_mcp_wrap();
|
|
80092
80441
|
init_mcp_status();
|
|
80093
|
-
import
|
|
80442
|
+
import chalk29 from "chalk";
|
|
80094
80443
|
var STATE_WORD = {
|
|
80095
80444
|
gatewayed: "governed",
|
|
80096
80445
|
"node9-self": "node9",
|
|
@@ -80099,10 +80448,10 @@ var STATE_WORD = {
|
|
|
80099
80448
|
};
|
|
80100
80449
|
function stateChip(state) {
|
|
80101
80450
|
const w = STATE_WORD[state].padEnd(12);
|
|
80102
|
-
if (state === "gatewayed") return
|
|
80103
|
-
if (state === "node9-self") return
|
|
80104
|
-
if (state === "remote") return
|
|
80105
|
-
return
|
|
80451
|
+
if (state === "gatewayed") return chalk29.green(w);
|
|
80452
|
+
if (state === "node9-self") return chalk29.gray(w);
|
|
80453
|
+
if (state === "remote") return chalk29.gray(w);
|
|
80454
|
+
return chalk29.yellow(w);
|
|
80106
80455
|
}
|
|
80107
80456
|
function relativeAge(ms, now) {
|
|
80108
80457
|
const s = Math.max(0, Math.round((now - ms) / 1e3));
|
|
@@ -80116,15 +80465,15 @@ function relativeAge(ms, now) {
|
|
|
80116
80465
|
function connectionChip(e, now) {
|
|
80117
80466
|
switch (e.connection) {
|
|
80118
80467
|
case "connected":
|
|
80119
|
-
return
|
|
80468
|
+
return chalk29.green(
|
|
80120
80469
|
`\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
|
|
80121
80470
|
);
|
|
80122
80471
|
case "stale":
|
|
80123
|
-
return
|
|
80472
|
+
return chalk29.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
|
|
80124
80473
|
case "pending-launch":
|
|
80125
|
-
return
|
|
80474
|
+
return chalk29.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
|
|
80126
80475
|
case "unlaunchable":
|
|
80127
|
-
return
|
|
80476
|
+
return chalk29.red(
|
|
80128
80477
|
`\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
|
|
80129
80478
|
);
|
|
80130
80479
|
default:
|
|
@@ -80149,7 +80498,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80149
80498
|
const now = Date.now();
|
|
80150
80499
|
const rows = resolveMcpStatus(void 0, process.env, now);
|
|
80151
80500
|
if (rows.length === 0) {
|
|
80152
|
-
console.error(
|
|
80501
|
+
console.error(chalk29.gray("No MCP servers found in any agent config."));
|
|
80153
80502
|
return;
|
|
80154
80503
|
}
|
|
80155
80504
|
for (const e of rows) {
|
|
@@ -80163,20 +80512,20 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80163
80512
|
const pending = rows.filter((e) => e.connection === "pending-launch").length;
|
|
80164
80513
|
if (ungoverned > 0) {
|
|
80165
80514
|
console.error(
|
|
80166
|
-
|
|
80515
|
+
chalk29.yellow(
|
|
80167
80516
|
`
|
|
80168
|
-
${ungoverned} ungoverned \u2014 run ` +
|
|
80517
|
+
${ungoverned} ungoverned \u2014 run ` + chalk29.bold("node9 mcp gateway --all") + " to govern."
|
|
80169
80518
|
)
|
|
80170
80519
|
);
|
|
80171
80520
|
}
|
|
80172
80521
|
if (pending > 0) {
|
|
80173
80522
|
console.error(
|
|
80174
|
-
|
|
80523
|
+
chalk29.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
80175
80524
|
);
|
|
80176
80525
|
}
|
|
80177
80526
|
if (unlaunchable > 0) {
|
|
80178
80527
|
console.error(
|
|
80179
|
-
|
|
80528
|
+
chalk29.red(
|
|
80180
80529
|
`${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
|
|
80181
80530
|
)
|
|
80182
80531
|
);
|
|
@@ -80195,27 +80544,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80195
80544
|
} else if (name) {
|
|
80196
80545
|
const matches2 = resolve(name, opts.agent);
|
|
80197
80546
|
if (matches2.length === 0) {
|
|
80198
|
-
console.error(
|
|
80547
|
+
console.error(chalk29.red(`No MCP server named "${name}" found.`));
|
|
80199
80548
|
process.exitCode = 1;
|
|
80200
80549
|
return;
|
|
80201
80550
|
}
|
|
80202
80551
|
if (matches2.length > 1) {
|
|
80203
80552
|
console.error(
|
|
80204
|
-
|
|
80553
|
+
chalk29.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
|
|
80205
80554
|
);
|
|
80206
80555
|
process.exitCode = 1;
|
|
80207
80556
|
return;
|
|
80208
80557
|
}
|
|
80209
80558
|
targets = matches2;
|
|
80210
80559
|
} else {
|
|
80211
|
-
console.error(
|
|
80560
|
+
console.error(chalk29.red("Pass a server name or --all. See `node9 mcp status`."));
|
|
80212
80561
|
process.exitCode = 1;
|
|
80213
80562
|
return;
|
|
80214
80563
|
}
|
|
80215
80564
|
const doable = targets.filter((e) => e.state === targetState);
|
|
80216
80565
|
if (doable.length === 0) {
|
|
80217
80566
|
console.error(
|
|
80218
|
-
|
|
80567
|
+
chalk29.gray(
|
|
80219
80568
|
opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
|
|
80220
80569
|
)
|
|
80221
80570
|
);
|
|
@@ -80229,20 +80578,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80229
80578
|
else wrapEntry(e);
|
|
80230
80579
|
agents.add(e.agentLabel);
|
|
80231
80580
|
const verb = opts.rewrap ? "refreshed" : "governed";
|
|
80232
|
-
console.error(
|
|
80581
|
+
console.error(chalk29.green(`\u2713 ${verb} ${e.name}`) + chalk29.gray(` (${e.agentLabel})`));
|
|
80233
80582
|
} catch (err2) {
|
|
80234
80583
|
failed++;
|
|
80235
80584
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80236
80585
|
console.error(
|
|
80237
|
-
|
|
80586
|
+
chalk29.red(`\u2717 failed ${e.name}`) + chalk29.gray(` (${e.agentLabel}): ${msg}`)
|
|
80238
80587
|
);
|
|
80239
80588
|
}
|
|
80240
80589
|
}
|
|
80241
80590
|
if (failed > 0) process.exitCode = 1;
|
|
80242
80591
|
if (agents.size > 0) {
|
|
80243
80592
|
console.error(
|
|
80244
|
-
|
|
80245
|
-
Restart ${[...agents].join(", ")}`) +
|
|
80593
|
+
chalk29.bold(`
|
|
80594
|
+
Restart ${[...agents].join(", ")}`) + chalk29.gray(" to activate. Undo any server with ") + chalk29.cyan("node9 mcp ungateway <name>") + chalk29.gray(".")
|
|
80246
80595
|
);
|
|
80247
80596
|
}
|
|
80248
80597
|
}
|
|
@@ -80250,19 +80599,19 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80250
80599
|
mcp.command("ungateway <name>").description("Restore an MCP server to its original (un-route it from node9)").option("--agent <id>", "disambiguate a name in more than one agent").action((name, opts) => {
|
|
80251
80600
|
const matches2 = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
|
|
80252
80601
|
if (matches2.length === 0) {
|
|
80253
|
-
console.error(
|
|
80602
|
+
console.error(chalk29.red(`No governed MCP server named "${name}" found.`));
|
|
80254
80603
|
process.exitCode = 1;
|
|
80255
80604
|
return;
|
|
80256
80605
|
}
|
|
80257
80606
|
if (matches2.length > 1) {
|
|
80258
|
-
console.error(
|
|
80607
|
+
console.error(chalk29.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
|
|
80259
80608
|
process.exitCode = 1;
|
|
80260
80609
|
return;
|
|
80261
80610
|
}
|
|
80262
80611
|
const e = matches2[0];
|
|
80263
80612
|
const orig = fromGateway(e.raw);
|
|
80264
80613
|
if (!orig) {
|
|
80265
|
-
console.error(
|
|
80614
|
+
console.error(chalk29.red(`Couldn't parse the original command for "${name}".`));
|
|
80266
80615
|
process.exitCode = 1;
|
|
80267
80616
|
return;
|
|
80268
80617
|
}
|
|
@@ -80270,12 +80619,12 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80270
80619
|
writeMcpEntry(e.mcpFile, e.format, e.name, orig);
|
|
80271
80620
|
} catch (err2) {
|
|
80272
80621
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80273
|
-
console.error(
|
|
80622
|
+
console.error(chalk29.red(`Failed to restore "${name}": ${msg}`));
|
|
80274
80623
|
process.exitCode = 1;
|
|
80275
80624
|
return;
|
|
80276
80625
|
}
|
|
80277
80626
|
console.error(
|
|
80278
|
-
|
|
80627
|
+
chalk29.green(`\u2713 restored ${e.name}`) + chalk29.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
|
|
80279
80628
|
);
|
|
80280
80629
|
});
|
|
80281
80630
|
}
|
|
@@ -80301,14 +80650,14 @@ function registerMcpPinCommand(program2) {
|
|
|
80301
80650
|
}
|
|
80302
80651
|
}
|
|
80303
80652
|
if (repoCorrupt) {
|
|
80304
|
-
console.error(
|
|
80653
|
+
console.error(chalk30.red(`
|
|
80305
80654
|
\u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
|
|
80306
80655
|
process.exit(1);
|
|
80307
80656
|
}
|
|
80308
80657
|
if (!homeResult.ok && homeResult.reason === "corrupt") {
|
|
80309
|
-
console.error(
|
|
80658
|
+
console.error(chalk30.red(`
|
|
80310
80659
|
\u274C Home pin file is corrupt: ${homeResult.detail}`));
|
|
80311
|
-
console.error(
|
|
80660
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80312
80661
|
process.exit(1);
|
|
80313
80662
|
}
|
|
80314
80663
|
const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
|
|
@@ -80320,25 +80669,25 @@ function registerMcpPinCommand(program2) {
|
|
|
80320
80669
|
merged.set(key, { entry, source: "repo" });
|
|
80321
80670
|
}
|
|
80322
80671
|
if (merged.size === 0) {
|
|
80323
|
-
console.log(
|
|
80672
|
+
console.log(chalk30.gray("\nNo MCP servers are pinned yet."));
|
|
80324
80673
|
console.log(
|
|
80325
|
-
|
|
80674
|
+
chalk30.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
80326
80675
|
);
|
|
80327
80676
|
return;
|
|
80328
80677
|
}
|
|
80329
|
-
console.log(
|
|
80678
|
+
console.log(chalk30.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
80330
80679
|
const showSource = found.source === "repo";
|
|
80331
80680
|
for (const [key, { entry, source }] of merged) {
|
|
80332
|
-
const tag = showSource ? ` ${
|
|
80333
|
-
console.log(` ${
|
|
80334
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
80335
|
-
console.log(` Hash: ${
|
|
80336
|
-
console.log(` Pinned: ${
|
|
80681
|
+
const tag = showSource ? ` ${chalk30.yellow(`[${source}]`)}` : "";
|
|
80682
|
+
console.log(` ${chalk30.cyan(key)}${tag} ${chalk30.gray(entry.label)}`);
|
|
80683
|
+
console.log(` Tools (${entry.toolCount}): ${chalk30.white(entry.toolNames.join(", "))}`);
|
|
80684
|
+
console.log(` Hash: ${chalk30.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
80685
|
+
console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}`);
|
|
80337
80686
|
console.log("");
|
|
80338
80687
|
}
|
|
80339
80688
|
if (showSource) {
|
|
80340
|
-
console.log(
|
|
80341
|
-
console.log(
|
|
80689
|
+
console.log(chalk30.gray(` [repo] entries come from ${found.path}`));
|
|
80690
|
+
console.log(chalk30.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
|
|
80342
80691
|
}
|
|
80343
80692
|
});
|
|
80344
80693
|
pinSubCmd.command("promote <serverKey>").description(
|
|
@@ -80348,22 +80697,22 @@ function registerMcpPinCommand(program2) {
|
|
|
80348
80697
|
const { repoPath, created } = promotePin(serverKey, process.cwd());
|
|
80349
80698
|
if (created) {
|
|
80350
80699
|
console.log(
|
|
80351
|
-
|
|
80700
|
+
chalk30.green(
|
|
80352
80701
|
`
|
|
80353
|
-
\u2705 Created ${repoPath} with the promoted pin for ${
|
|
80702
|
+
\u2705 Created ${repoPath} with the promoted pin for ${chalk30.cyan(serverKey)}.`
|
|
80354
80703
|
)
|
|
80355
80704
|
);
|
|
80356
80705
|
} else {
|
|
80357
|
-
console.log(
|
|
80358
|
-
\u2705 Promoted ${
|
|
80706
|
+
console.log(chalk30.green(`
|
|
80707
|
+
\u2705 Promoted ${chalk30.cyan(serverKey)} into ${repoPath}.`));
|
|
80359
80708
|
}
|
|
80360
|
-
console.log(
|
|
80361
|
-
console.log(
|
|
80362
|
-
console.log(
|
|
80709
|
+
console.log(chalk30.gray(" Review the change and commit it:"));
|
|
80710
|
+
console.log(chalk30.cyan(` git add ${repoPath}`));
|
|
80711
|
+
console.log(chalk30.cyan(` git commit -m "pin ${serverKey} (node9)"`));
|
|
80363
80712
|
console.log("");
|
|
80364
80713
|
} catch (err2) {
|
|
80365
80714
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80366
|
-
console.error(
|
|
80715
|
+
console.error(chalk30.red(`
|
|
80367
80716
|
\u274C ${msg}
|
|
80368
80717
|
`));
|
|
80369
80718
|
process.exit(1);
|
|
@@ -80376,36 +80725,36 @@ function registerMcpPinCommand(program2) {
|
|
|
80376
80725
|
try {
|
|
80377
80726
|
pins = readMcpPins();
|
|
80378
80727
|
} catch {
|
|
80379
|
-
console.error(
|
|
80380
|
-
console.error(
|
|
80728
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80729
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80381
80730
|
process.exit(1);
|
|
80382
80731
|
}
|
|
80383
80732
|
if (!pins.servers[serverKey]) {
|
|
80384
|
-
console.error(
|
|
80733
|
+
console.error(chalk30.red(`
|
|
80385
80734
|
\u274C No pin found for server key "${serverKey}"
|
|
80386
80735
|
`));
|
|
80387
|
-
console.error(`Run ${
|
|
80736
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80388
80737
|
`);
|
|
80389
80738
|
process.exit(1);
|
|
80390
80739
|
}
|
|
80391
80740
|
const label3 = pins.servers[serverKey].label;
|
|
80392
80741
|
removePin(serverKey);
|
|
80393
|
-
console.log(
|
|
80394
|
-
\u{1F513} Pin removed for ${
|
|
80395
|
-
console.log(
|
|
80396
|
-
console.log(
|
|
80742
|
+
console.log(chalk30.green(`
|
|
80743
|
+
\u{1F513} Pin removed for ${chalk30.cyan(serverKey)}`));
|
|
80744
|
+
console.log(chalk30.gray(` Server: ${label3}`));
|
|
80745
|
+
console.log(chalk30.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
80397
80746
|
});
|
|
80398
80747
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
80399
80748
|
const result = readMcpPinsSafe();
|
|
80400
80749
|
if (!result.ok && result.reason === "missing") {
|
|
80401
|
-
console.log(
|
|
80750
|
+
console.log(chalk30.gray("\nNo pins to clear.\n"));
|
|
80402
80751
|
return;
|
|
80403
80752
|
}
|
|
80404
80753
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
80405
80754
|
clearAllPins();
|
|
80406
|
-
console.log(
|
|
80755
|
+
console.log(chalk30.green(`
|
|
80407
80756
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
80408
|
-
console.log(
|
|
80757
|
+
console.log(chalk30.gray(" Next connection to each server will re-pin.\n"));
|
|
80409
80758
|
});
|
|
80410
80759
|
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
80411
80760
|
if (opts.stale) {
|
|
@@ -80414,7 +80763,7 @@ function registerMcpPinCommand(program2) {
|
|
|
80414
80763
|
}
|
|
80415
80764
|
if (!serverKey) {
|
|
80416
80765
|
console.error(
|
|
80417
|
-
|
|
80766
|
+
chalk30.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
80418
80767
|
);
|
|
80419
80768
|
process.exit(1);
|
|
80420
80769
|
}
|
|
@@ -80422,15 +80771,15 @@ function registerMcpPinCommand(program2) {
|
|
|
80422
80771
|
try {
|
|
80423
80772
|
pins = readMcpPins();
|
|
80424
80773
|
} catch {
|
|
80425
|
-
console.error(
|
|
80426
|
-
console.error(
|
|
80774
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80775
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80427
80776
|
process.exit(1);
|
|
80428
80777
|
}
|
|
80429
80778
|
if (!pins.servers[serverKey]) {
|
|
80430
|
-
console.error(
|
|
80779
|
+
console.error(chalk30.red(`
|
|
80431
80780
|
\u274C No pin found for server key "${serverKey}"
|
|
80432
80781
|
`));
|
|
80433
|
-
console.error(`Run ${
|
|
80782
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80434
80783
|
`);
|
|
80435
80784
|
process.exit(1);
|
|
80436
80785
|
}
|
|
@@ -80438,10 +80787,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80438
80787
|
const liveKeys = inventoryServerKeys(inv);
|
|
80439
80788
|
if (liveKeys.has(serverKey)) {
|
|
80440
80789
|
const agent = "an agent config";
|
|
80441
|
-
console.error(
|
|
80790
|
+
console.error(chalk30.red(`
|
|
80442
80791
|
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
80443
80792
|
console.error(
|
|
80444
|
-
|
|
80793
|
+
chalk30.yellow(
|
|
80445
80794
|
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
80446
80795
|
`
|
|
80447
80796
|
)
|
|
@@ -80450,10 +80799,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80450
80799
|
}
|
|
80451
80800
|
const label3 = pins.servers[serverKey].label;
|
|
80452
80801
|
removePin(serverKey);
|
|
80453
|
-
console.log(
|
|
80454
|
-
\u2713 Forgot server ${
|
|
80455
|
-
console.log(
|
|
80456
|
-
console.log(
|
|
80802
|
+
console.log(chalk30.green(`
|
|
80803
|
+
\u2713 Forgot server ${chalk30.cyan(serverKey)}`));
|
|
80804
|
+
console.log(chalk30.gray(` Was: ${label3}`));
|
|
80805
|
+
console.log(chalk30.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
80457
80806
|
});
|
|
80458
80807
|
}
|
|
80459
80808
|
function forgetAllStale() {
|
|
@@ -80461,16 +80810,16 @@ function forgetAllStale() {
|
|
|
80461
80810
|
try {
|
|
80462
80811
|
pins = readMcpPins();
|
|
80463
80812
|
} catch {
|
|
80464
|
-
console.error(
|
|
80465
|
-
console.error(
|
|
80813
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80814
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80466
80815
|
process.exit(1);
|
|
80467
80816
|
}
|
|
80468
80817
|
const inv = inventoryMcp();
|
|
80469
80818
|
const liveKeys = inventoryServerKeys(inv);
|
|
80470
80819
|
if (liveKeys.size === 0) {
|
|
80471
|
-
console.error(
|
|
80820
|
+
console.error(chalk30.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
80472
80821
|
console.error(
|
|
80473
|
-
|
|
80822
|
+
chalk30.yellow(
|
|
80474
80823
|
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
80475
80824
|
)
|
|
80476
80825
|
);
|
|
@@ -80478,118 +80827,118 @@ function forgetAllStale() {
|
|
|
80478
80827
|
}
|
|
80479
80828
|
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
80480
80829
|
if (stale.length === 0) {
|
|
80481
|
-
console.log(
|
|
80830
|
+
console.log(chalk30.gray("\nNo stale servers to remove.\n"));
|
|
80482
80831
|
return;
|
|
80483
80832
|
}
|
|
80484
80833
|
for (const [sk, pin] of stale) {
|
|
80485
80834
|
delete pins.servers[sk];
|
|
80486
|
-
console.log(
|
|
80835
|
+
console.log(chalk30.green(` \u2713 ${chalk30.cyan(sk)} ${chalk30.gray(pin.label)}`));
|
|
80487
80836
|
}
|
|
80488
80837
|
writeMcpPins(pins);
|
|
80489
|
-
console.log(
|
|
80838
|
+
console.log(chalk30.green(`
|
|
80490
80839
|
Removed ${stale.length} stale server(s).
|
|
80491
80840
|
`));
|
|
80492
80841
|
}
|
|
80493
80842
|
|
|
80494
80843
|
// src/cli/commands/sync.ts
|
|
80495
80844
|
init_sync();
|
|
80496
|
-
import
|
|
80845
|
+
import chalk31 from "chalk";
|
|
80497
80846
|
function registerSyncCommand(program2) {
|
|
80498
80847
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
80499
80848
|
policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
|
|
80500
|
-
process.stdout.write(
|
|
80849
|
+
process.stdout.write(chalk31.cyan("Pushing policy to the dashboard\u2026"));
|
|
80501
80850
|
const result = await runPolicyPush();
|
|
80502
80851
|
process.stdout.write("\n");
|
|
80503
80852
|
if (!result.ok) {
|
|
80504
|
-
console.error(
|
|
80853
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80505
80854
|
process.exit(1);
|
|
80506
80855
|
}
|
|
80507
|
-
console.log(
|
|
80508
|
-
console.log(
|
|
80856
|
+
console.log(chalk31.green("\u2713 Policy mirrored to the dashboard"));
|
|
80857
|
+
console.log(chalk31.gray(" See it under Security Policy \u2192 Machines"));
|
|
80509
80858
|
});
|
|
80510
80859
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
80511
|
-
process.stdout.write(
|
|
80860
|
+
process.stdout.write(chalk31.cyan("Syncing cloud policy rules\u2026"));
|
|
80512
80861
|
const result = await runCloudSync();
|
|
80513
80862
|
process.stdout.write("\n");
|
|
80514
80863
|
if (!result.ok) {
|
|
80515
|
-
console.error(
|
|
80864
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80516
80865
|
process.exit(1);
|
|
80517
80866
|
}
|
|
80518
80867
|
if (result.unchanged) {
|
|
80519
80868
|
console.log(
|
|
80520
|
-
|
|
80869
|
+
chalk31.green(
|
|
80521
80870
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
80522
80871
|
)
|
|
80523
80872
|
);
|
|
80524
|
-
console.log(
|
|
80525
|
-
console.log(
|
|
80873
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80874
|
+
console.log(chalk31.gray(` Server returned 304 (no changes since last sync)`));
|
|
80526
80875
|
} else {
|
|
80527
80876
|
console.log(
|
|
80528
|
-
|
|
80877
|
+
chalk31.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
80529
80878
|
);
|
|
80530
|
-
console.log(
|
|
80531
|
-
console.log(
|
|
80879
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80880
|
+
console.log(chalk31.gray(` File: ~/.node9/rules-cache.json`));
|
|
80532
80881
|
}
|
|
80533
80882
|
});
|
|
80534
80883
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
80535
80884
|
const status = getCloudSyncStatus();
|
|
80536
80885
|
if (!status.cached) {
|
|
80537
|
-
console.log(
|
|
80886
|
+
console.log(chalk31.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
80538
80887
|
return;
|
|
80539
80888
|
}
|
|
80540
80889
|
const rules = getCloudRules() ?? [];
|
|
80541
80890
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
80542
80891
|
console.log(
|
|
80543
|
-
|
|
80544
|
-
Cloud policy rules`) +
|
|
80892
|
+
chalk31.bold(`
|
|
80893
|
+
Cloud policy rules`) + chalk31.gray(
|
|
80545
80894
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
80546
80895
|
`
|
|
80547
80896
|
)
|
|
80548
80897
|
);
|
|
80549
80898
|
if (rules.length === 0) {
|
|
80550
|
-
console.log(
|
|
80899
|
+
console.log(chalk31.gray(" No rules defined in cloud policy.\n"));
|
|
80551
80900
|
return;
|
|
80552
80901
|
}
|
|
80553
80902
|
for (const rule of rules) {
|
|
80554
80903
|
const r = rule;
|
|
80555
|
-
const verdictColor = r.verdict === "block" ?
|
|
80904
|
+
const verdictColor = r.verdict === "block" ? chalk31.red : r.verdict === "allow" ? chalk31.green : chalk31.yellow;
|
|
80556
80905
|
console.log(
|
|
80557
80906
|
` ${verdictColor(
|
|
80558
80907
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
80559
|
-
)} ${
|
|
80908
|
+
)} ${chalk31.white(String(r.name ?? "(unnamed)"))}`
|
|
80560
80909
|
);
|
|
80561
|
-
if (r.reason) console.log(
|
|
80910
|
+
if (r.reason) console.log(chalk31.gray(` ${String(r.reason)}`));
|
|
80562
80911
|
}
|
|
80563
80912
|
console.log("");
|
|
80564
80913
|
});
|
|
80565
80914
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
80566
80915
|
const s = getCloudSyncStatus();
|
|
80567
80916
|
if (!s.cached) {
|
|
80568
|
-
console.log(
|
|
80917
|
+
console.log(chalk31.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
80569
80918
|
return;
|
|
80570
80919
|
}
|
|
80571
80920
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
80572
80921
|
console.log(`
|
|
80573
|
-
Rules : ${
|
|
80922
|
+
Rules : ${chalk31.green(String(s.rules))} cloud rules loaded`);
|
|
80574
80923
|
console.log(
|
|
80575
|
-
` Synced : ${
|
|
80924
|
+
` Synced : ${chalk31.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
80576
80925
|
);
|
|
80577
80926
|
if (s.workspaceId) {
|
|
80578
|
-
console.log(` Workspace: ${
|
|
80927
|
+
console.log(` Workspace: ${chalk31.gray(s.workspaceId)}`);
|
|
80579
80928
|
}
|
|
80580
80929
|
if (s.panicMode) {
|
|
80581
80930
|
console.log(
|
|
80582
|
-
` ${
|
|
80931
|
+
` ${chalk31.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk31.dim("(every review-verdict becomes block)")
|
|
80583
80932
|
);
|
|
80584
80933
|
}
|
|
80585
80934
|
if (s.shadowMode) {
|
|
80586
80935
|
console.log(
|
|
80587
|
-
` ${
|
|
80936
|
+
` ${chalk31.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk31.dim("(blocks become would-block log entries)")
|
|
80588
80937
|
);
|
|
80589
80938
|
}
|
|
80590
80939
|
if (s.syncIntervalHours) {
|
|
80591
80940
|
console.log(
|
|
80592
|
-
|
|
80941
|
+
chalk31.gray(
|
|
80593
80942
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
80594
80943
|
)
|
|
80595
80944
|
);
|
|
@@ -80600,7 +80949,7 @@ function registerSyncCommand(program2) {
|
|
|
80600
80949
|
|
|
80601
80950
|
// src/cli/commands/agents.ts
|
|
80602
80951
|
init_setup();
|
|
80603
|
-
import
|
|
80952
|
+
import chalk32 from "chalk";
|
|
80604
80953
|
var SETUP_FN = {
|
|
80605
80954
|
claude: setupClaude,
|
|
80606
80955
|
gemini: setupGemini,
|
|
@@ -80643,30 +80992,30 @@ function registerAgentsCommand(program2) {
|
|
|
80643
80992
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
80644
80993
|
console.log(" " + "\u2500".repeat(44));
|
|
80645
80994
|
for (const s of statuses) {
|
|
80646
|
-
const installed = s.installed ?
|
|
80647
|
-
const wired = !s.installed ?
|
|
80648
|
-
const mode = s.mode ?
|
|
80649
|
-
const hint = s.installed && !s.wired ?
|
|
80995
|
+
const installed = s.installed ? chalk32.green("\u2713") : chalk32.gray("\u2717");
|
|
80996
|
+
const wired = !s.installed ? chalk32.gray("\u2014") : s.wired ? chalk32.green("\u2713") : chalk32.yellow("\u2717");
|
|
80997
|
+
const mode = s.mode ? chalk32.gray(s.mode) : chalk32.gray("\u2014");
|
|
80998
|
+
const hint = s.installed && !s.wired ? chalk32.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
80650
80999
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
80651
81000
|
}
|
|
80652
81001
|
console.log("");
|
|
80653
81002
|
if (!anyInstalled) {
|
|
80654
81003
|
console.log(
|
|
80655
|
-
|
|
81004
|
+
chalk32.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk32.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
|
|
80656
81005
|
);
|
|
80657
81006
|
return;
|
|
80658
81007
|
}
|
|
80659
81008
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
80660
81009
|
if (unwired.length > 0) {
|
|
80661
81010
|
console.log(
|
|
80662
|
-
|
|
81011
|
+
chalk32.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk32.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
80663
81012
|
);
|
|
80664
81013
|
}
|
|
80665
81014
|
if (statuses.some((s) => s.name === "gemini" && s.installed)) {
|
|
80666
81015
|
console.log(
|
|
80667
|
-
|
|
81016
|
+
chalk32.yellow(
|
|
80668
81017
|
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
|
|
80669
|
-
) +
|
|
81018
|
+
) + chalk32.gray(" Migrate to Antigravity: ") + chalk32.white("node9 agents add antigravity") + "\n"
|
|
80670
81019
|
);
|
|
80671
81020
|
}
|
|
80672
81021
|
});
|
|
@@ -80674,7 +81023,7 @@ function registerAgentsCommand(program2) {
|
|
|
80674
81023
|
const name = resolveAgentName(agent);
|
|
80675
81024
|
const fn = SETUP_FN[name];
|
|
80676
81025
|
if (!fn) {
|
|
80677
|
-
console.error(
|
|
81026
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80678
81027
|
process.exit(1);
|
|
80679
81028
|
}
|
|
80680
81029
|
await fn();
|
|
@@ -80683,14 +81032,14 @@ function registerAgentsCommand(program2) {
|
|
|
80683
81032
|
const name = resolveAgentName(agent);
|
|
80684
81033
|
const fn = TEARDOWN_FN[name];
|
|
80685
81034
|
if (!fn) {
|
|
80686
|
-
console.error(
|
|
81035
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80687
81036
|
process.exit(1);
|
|
80688
81037
|
}
|
|
80689
|
-
console.log(
|
|
81038
|
+
console.log(chalk32.cyan(`
|
|
80690
81039
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
80691
81040
|
`));
|
|
80692
81041
|
fn();
|
|
80693
|
-
console.log(
|
|
81042
|
+
console.log(chalk32.gray("\n Restart the agent for changes to take effect."));
|
|
80694
81043
|
});
|
|
80695
81044
|
}
|
|
80696
81045
|
|
|
@@ -80699,21 +81048,21 @@ init_scan();
|
|
|
80699
81048
|
|
|
80700
81049
|
// src/cli/commands/posture.ts
|
|
80701
81050
|
init_posture();
|
|
80702
|
-
import
|
|
81051
|
+
import chalk34 from "chalk";
|
|
80703
81052
|
|
|
80704
81053
|
// src/posture/render.ts
|
|
80705
81054
|
init_score();
|
|
80706
|
-
import
|
|
81055
|
+
import chalk33 from "chalk";
|
|
80707
81056
|
var ICON = {
|
|
80708
|
-
critical:
|
|
80709
|
-
high:
|
|
80710
|
-
medium:
|
|
80711
|
-
advisory:
|
|
81057
|
+
critical: chalk33.red("\u274C"),
|
|
81058
|
+
high: chalk33.red("\u274C"),
|
|
81059
|
+
medium: chalk33.yellow("\u26A0\uFE0F "),
|
|
81060
|
+
advisory: chalk33.gray("\u26A0\uFE0F ")
|
|
80712
81061
|
};
|
|
80713
81062
|
var TIER_LABEL = {
|
|
80714
|
-
good:
|
|
80715
|
-
"at-risk":
|
|
80716
|
-
critical:
|
|
81063
|
+
good: chalk33.green("Good"),
|
|
81064
|
+
"at-risk": chalk33.yellow("At risk"),
|
|
81065
|
+
critical: chalk33.red("Critical")
|
|
80717
81066
|
};
|
|
80718
81067
|
function wrap(text, width) {
|
|
80719
81068
|
const out = [];
|
|
@@ -80731,7 +81080,7 @@ function wrap(text, width) {
|
|
|
80731
81080
|
}
|
|
80732
81081
|
var LABEL_WIDTH = 14;
|
|
80733
81082
|
function label2(category) {
|
|
80734
|
-
return
|
|
81083
|
+
return chalk33.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
80735
81084
|
}
|
|
80736
81085
|
function guardedWhat(f) {
|
|
80737
81086
|
if (f.detail.length === 0) return "this";
|
|
@@ -80741,31 +81090,31 @@ function guardedWhat(f) {
|
|
|
80741
81090
|
}
|
|
80742
81091
|
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
80743
81092
|
const lines = [];
|
|
80744
|
-
const wt = showWeight && f.scoreWeight ?
|
|
81093
|
+
const wt = showWeight && f.scoreWeight ? chalk33.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
80745
81094
|
lines.push(` ${ICON[f.severity]} ${label2(displayLabel)}${wt}${f.title}`);
|
|
80746
81095
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
80747
81096
|
const width = 80 - indent.length;
|
|
80748
81097
|
for (const s of [f.what, f.why, f.who]) {
|
|
80749
|
-
if (s) for (const l of wrap(s, width)) lines.push(indent +
|
|
81098
|
+
if (s) for (const l of wrap(s, width)) lines.push(indent + chalk33.gray(l));
|
|
80750
81099
|
}
|
|
80751
|
-
for (const d of f.detail) lines.push(indent +
|
|
81100
|
+
for (const d of f.detail) lines.push(indent + chalk33.gray(d));
|
|
80752
81101
|
if (f.fix) {
|
|
80753
81102
|
let first = true;
|
|
80754
81103
|
for (const seg of f.fix.split("\n")) {
|
|
80755
81104
|
for (const l of wrap(seg, width - 2)) {
|
|
80756
|
-
lines.push(indent +
|
|
81105
|
+
lines.push(indent + chalk33.cyan(first ? "\u2192 " + l : " " + l));
|
|
80757
81106
|
first = false;
|
|
80758
81107
|
}
|
|
80759
81108
|
}
|
|
80760
81109
|
}
|
|
80761
81110
|
const tradeoff = [
|
|
80762
|
-
[f.gain, "gain: ",
|
|
80763
|
-
[f.cost, "cost: ",
|
|
81111
|
+
[f.gain, "gain: ", chalk33.green],
|
|
81112
|
+
[f.cost, "cost: ", chalk33.yellow]
|
|
80764
81113
|
];
|
|
80765
81114
|
for (const [text, lbl, color2] of tradeoff) {
|
|
80766
81115
|
if (!text) continue;
|
|
80767
81116
|
wrap(text, width - 6).forEach((l, i) => {
|
|
80768
|
-
lines.push(indent + (i === 0 ? color2(lbl) : " ") +
|
|
81117
|
+
lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk33.gray(l));
|
|
80769
81118
|
});
|
|
80770
81119
|
}
|
|
80771
81120
|
return lines;
|
|
@@ -80775,7 +81124,7 @@ function renderPosture(result) {
|
|
|
80775
81124
|
const tier = TIER_LABEL[result.tier];
|
|
80776
81125
|
lines.push("");
|
|
80777
81126
|
lines.push(
|
|
80778
|
-
|
|
81127
|
+
chalk33.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk33.gray(` \u2014 ${result.agent}`) + ` ${chalk33.bold(`Score: ${result.score}/100`)} (${tier})`
|
|
80779
81128
|
);
|
|
80780
81129
|
const headroom = openHeadroom(result.findings);
|
|
80781
81130
|
if (headroom > 0) {
|
|
@@ -80783,15 +81132,15 @@ function renderPosture(result) {
|
|
|
80783
81132
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
80784
81133
|
).length;
|
|
80785
81134
|
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
80786
|
-
for (const l of wrap(note, 76)) lines.push(" " +
|
|
81135
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk33.gray(l));
|
|
80787
81136
|
}
|
|
80788
81137
|
lines.push("");
|
|
80789
81138
|
if (result.headline) {
|
|
80790
81139
|
const indent = " ";
|
|
80791
|
-
lines.push(` ${
|
|
80792
|
-
for (const l of wrap(result.headline.risk, 74)) lines.push(indent +
|
|
81140
|
+
lines.push(` ${chalk33.red.bold("\u{1F525} Biggest risk")}`);
|
|
81141
|
+
for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk33.white(l));
|
|
80793
81142
|
const action = wrap(`Do this first: ${result.headline.action}`, 72);
|
|
80794
|
-
action.forEach((l, i) => lines.push(indent +
|
|
81143
|
+
action.forEach((l, i) => lines.push(indent + chalk33.cyan(i === 0 ? "\u2192 " + l : " " + l)));
|
|
80795
81144
|
lines.push("");
|
|
80796
81145
|
}
|
|
80797
81146
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
@@ -80801,13 +81150,13 @@ function renderPosture(result) {
|
|
|
80801
81150
|
);
|
|
80802
81151
|
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
80803
81152
|
if (covered.length > 0) {
|
|
80804
|
-
lines.push(" " +
|
|
81153
|
+
lines.push(" " + chalk33.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
80805
81154
|
for (const f of covered) {
|
|
80806
81155
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
80807
81156
|
const via = f.coverage?.via ?? "node9";
|
|
80808
81157
|
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
80809
81158
|
lines.push(
|
|
80810
|
-
` ${
|
|
81159
|
+
` ${chalk33.green("\u2705")} ${label2(lbl)}${chalk33.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
80811
81160
|
);
|
|
80812
81161
|
}
|
|
80813
81162
|
lines.push("");
|
|
@@ -80816,24 +81165,24 @@ function renderPosture(result) {
|
|
|
80816
81165
|
const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
|
|
80817
81166
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
80818
81167
|
if (node9Open.length > 0) {
|
|
80819
|
-
lines.push(" " +
|
|
81168
|
+
lines.push(" " + chalk33.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
80820
81169
|
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80821
81170
|
}
|
|
80822
81171
|
if (reduceOpen.length > 0) {
|
|
80823
81172
|
if (node9Open.length > 0) lines.push("");
|
|
80824
|
-
lines.push(" " +
|
|
81173
|
+
lines.push(" " + chalk33.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
80825
81174
|
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80826
81175
|
}
|
|
80827
81176
|
if (osOpen.length > 0) {
|
|
80828
81177
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
80829
|
-
lines.push(" " +
|
|
81178
|
+
lines.push(" " + chalk33.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
80830
81179
|
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
80831
81180
|
}
|
|
80832
81181
|
for (const cat of result.passedCategories) {
|
|
80833
|
-
lines.push(` ${
|
|
81182
|
+
lines.push(` ${chalk33.green("\u2705")} ${label2(cat)}${chalk33.gray("no issues found")}`);
|
|
80834
81183
|
}
|
|
80835
81184
|
for (const cat of result.erroredCategories) {
|
|
80836
|
-
lines.push(` ${
|
|
81185
|
+
lines.push(` ${chalk33.gray("\u2022")} ${label2(cat)}${chalk33.gray("could not be checked")}`);
|
|
80837
81186
|
}
|
|
80838
81187
|
lines.push("");
|
|
80839
81188
|
const crit = open.filter((f) => f.severity === "critical").length;
|
|
@@ -80841,16 +81190,16 @@ function renderPosture(result) {
|
|
|
80841
81190
|
const med = open.filter((f) => f.severity === "medium").length;
|
|
80842
81191
|
const adv = open.filter((f) => f.severity === "advisory").length;
|
|
80843
81192
|
const parts = [];
|
|
80844
|
-
if (crit) parts.push(
|
|
80845
|
-
if (high) parts.push(
|
|
80846
|
-
if (med) parts.push(
|
|
80847
|
-
if (adv) parts.push(
|
|
80848
|
-
const summary = parts.length ? parts.join(" \xB7 ") :
|
|
81193
|
+
if (crit) parts.push(chalk33.red(`${crit} critical`));
|
|
81194
|
+
if (high) parts.push(chalk33.red(`${high} high`));
|
|
81195
|
+
if (med) parts.push(chalk33.yellow(`${med} medium`));
|
|
81196
|
+
if (adv) parts.push(chalk33.gray(`${adv} advisory`));
|
|
81197
|
+
const summary = parts.length ? parts.join(" \xB7 ") : chalk33.green("no findings");
|
|
80849
81198
|
lines.push(` ${summary}`);
|
|
80850
81199
|
lines.push("");
|
|
80851
|
-
lines.push(" " +
|
|
81200
|
+
lines.push(" " + chalk33.bold("Track this across your fleet & keep it green:"));
|
|
80852
81201
|
lines.push(
|
|
80853
|
-
" " +
|
|
81202
|
+
" " + chalk33.dim("\u2192 ") + chalk33.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
|
|
80854
81203
|
);
|
|
80855
81204
|
lines.push("");
|
|
80856
81205
|
return lines.join("\n");
|
|
@@ -80870,11 +81219,11 @@ function registerPostureCommand(program2) {
|
|
|
80870
81219
|
if (opts.ship) {
|
|
80871
81220
|
const creds = readCredentials();
|
|
80872
81221
|
if (!creds) {
|
|
80873
|
-
console.error(
|
|
81222
|
+
console.error(chalk34.gray(" Run `node9 login` to ship this to your dashboard."));
|
|
80874
81223
|
} else {
|
|
80875
81224
|
const ok2 = await shipPosture(result, creds);
|
|
80876
81225
|
console.error(
|
|
80877
|
-
ok2 ?
|
|
81226
|
+
ok2 ? chalk34.gray(" \u2713 Shipped to your node9 dashboard.") : chalk34.gray(" Could not reach the dashboard \u2014 saved locally only.")
|
|
80878
81227
|
);
|
|
80879
81228
|
}
|
|
80880
81229
|
}
|
|
@@ -80883,7 +81232,7 @@ function registerPostureCommand(program2) {
|
|
|
80883
81232
|
}
|
|
80884
81233
|
|
|
80885
81234
|
// src/cli/commands/scan-repo.ts
|
|
80886
|
-
import
|
|
81235
|
+
import chalk36 from "chalk";
|
|
80887
81236
|
|
|
80888
81237
|
// src/ci-check/fetch.ts
|
|
80889
81238
|
var import_undici = __toESM(require_undici());
|
|
@@ -86395,7 +86744,7 @@ async function scanRepo(input, onProgress) {
|
|
|
86395
86744
|
}
|
|
86396
86745
|
|
|
86397
86746
|
// src/ci-check/render.ts
|
|
86398
|
-
import
|
|
86747
|
+
import chalk35 from "chalk";
|
|
86399
86748
|
var listed = (fs109) => fs109.filter((f) => f.tier !== "review");
|
|
86400
86749
|
function excerpt(f) {
|
|
86401
86750
|
const m = /`([^`]+)`/.exec(f.signals[0] ?? "");
|
|
@@ -86440,23 +86789,23 @@ function reviewTerminal(findings) {
|
|
|
86440
86789
|
const L = [];
|
|
86441
86790
|
if (items) {
|
|
86442
86791
|
L.push(
|
|
86443
|
-
|
|
86792
|
+
chalk35.bold(`\u{1F50D} ${items} ${items === 1 ? "item" : "items"} worth a look (not counted)`) + chalk35.gray(": risky-looking text in a context that is usually harmless. Please confirm.")
|
|
86444
86793
|
);
|
|
86445
86794
|
const files = [...byFile.keys()];
|
|
86446
86795
|
for (const file2 of files.slice(0, REVIEW_FILES)) {
|
|
86447
86796
|
const fs109 = byFile.get(file2);
|
|
86448
|
-
L.push(
|
|
86797
|
+
L.push(chalk35.gray(` ${file2}`));
|
|
86449
86798
|
for (const f of fs109.slice(0, REVIEW_LINES_PER_FILE))
|
|
86450
86799
|
L.push(` \u2022 ${f.line ? `line ${f.line}: ` : ""}${excerpt(f)}`);
|
|
86451
86800
|
if (fs109.length > REVIEW_LINES_PER_FILE)
|
|
86452
|
-
L.push(
|
|
86801
|
+
L.push(chalk35.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
|
|
86453
86802
|
}
|
|
86454
86803
|
const more = files.length - REVIEW_FILES;
|
|
86455
|
-
if (more > 0) L.push(
|
|
86804
|
+
if (more > 0) L.push(chalk35.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
|
|
86456
86805
|
}
|
|
86457
86806
|
if (grants)
|
|
86458
86807
|
L.push(
|
|
86459
|
-
|
|
86808
|
+
chalk35.gray(
|
|
86460
86809
|
` ${grants} skill${grants === 1 ? " is" : "s are"} granted broad tools (Bash, Write or Edit); usually what the skill needs.`
|
|
86461
86810
|
)
|
|
86462
86811
|
);
|
|
@@ -86470,45 +86819,45 @@ var ICON2 = {
|
|
|
86470
86819
|
advisory: "\u{1F7E2}"
|
|
86471
86820
|
};
|
|
86472
86821
|
var COLOR = {
|
|
86473
|
-
critical:
|
|
86474
|
-
high:
|
|
86475
|
-
medium:
|
|
86476
|
-
advisory:
|
|
86822
|
+
critical: chalk35.red.bold,
|
|
86823
|
+
high: chalk35.red,
|
|
86824
|
+
medium: chalk35.yellow,
|
|
86825
|
+
advisory: chalk35.gray
|
|
86477
86826
|
};
|
|
86478
86827
|
var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
|
|
86479
86828
|
function renderCta(res) {
|
|
86480
86829
|
const L = [];
|
|
86481
|
-
L.push(
|
|
86830
|
+
L.push(chalk35.dim(" " + "\u2500".repeat(63)));
|
|
86482
86831
|
if (res.worst === "critical" || res.worst === "high") {
|
|
86483
86832
|
const n = res.findings.filter(
|
|
86484
86833
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86485
86834
|
).length;
|
|
86486
86835
|
L.push(
|
|
86487
|
-
" " +
|
|
86836
|
+
" " + chalk35.red.bold(
|
|
86488
86837
|
`\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix, then stop the next at the PR.`
|
|
86489
86838
|
)
|
|
86490
86839
|
);
|
|
86491
86840
|
L.push("");
|
|
86492
|
-
L.push(" " +
|
|
86841
|
+
L.push(" " + chalk35.bold("Catch this class of issue on every PR, automatically:"));
|
|
86493
86842
|
} else if (res.worst) {
|
|
86494
|
-
L.push(" " +
|
|
86843
|
+
L.push(" " + chalk35.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
|
|
86495
86844
|
L.push("");
|
|
86496
|
-
L.push(" " +
|
|
86845
|
+
L.push(" " + chalk35.bold("Check every PR for agent-CI risk:"));
|
|
86497
86846
|
} else if (res.incomplete) {
|
|
86498
|
-
L.push(" " +
|
|
86847
|
+
L.push(" " + chalk35.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
|
|
86499
86848
|
L.push("");
|
|
86500
|
-
L.push(" " +
|
|
86849
|
+
L.push(" " + chalk35.bold("Get a complete check on every PR (CI reads the tree directly):"));
|
|
86501
86850
|
} else {
|
|
86502
86851
|
L.push(
|
|
86503
|
-
" " +
|
|
86852
|
+
" " + chalk35.green(
|
|
86504
86853
|
groupReview(res.findings).byFile.size ? "\u2705 No alerts. Check the items worth a look above." : "\u2705 Agent CI is well-configured: 0 unmitigated issues."
|
|
86505
86854
|
)
|
|
86506
86855
|
);
|
|
86507
86856
|
L.push("");
|
|
86508
|
-
L.push(" " +
|
|
86857
|
+
L.push(" " + chalk35.bold("Keep it green as you add agent workflows. Check every PR:"));
|
|
86509
86858
|
}
|
|
86510
|
-
L.push(" " +
|
|
86511
|
-
L.push(" " +
|
|
86859
|
+
L.push(" " + chalk35.dim("\u2192 ") + chalk35.cyan.underline(ACTION_URL));
|
|
86860
|
+
L.push(" " + chalk35.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
|
|
86512
86861
|
return L;
|
|
86513
86862
|
}
|
|
86514
86863
|
function ownedHint(source) {
|
|
@@ -86548,21 +86897,21 @@ function renderScan(res, diff) {
|
|
|
86548
86897
|
const L = [];
|
|
86549
86898
|
const shown = listed(res.findings);
|
|
86550
86899
|
const n = shown.length;
|
|
86551
|
-
const head = res.worst === "critical" || res.worst === "high" ?
|
|
86552
|
-
L.push(`\u{1F6E1}\uFE0F ${
|
|
86900
|
+
const head = res.worst === "critical" || res.worst === "high" ? chalk35.red.bold("\u26A0\uFE0F agent-security risk found") : res.worst ? chalk35.yellow("agent-security notes") : res.incomplete ? chalk35.yellow.bold("\u26A0\uFE0F INCOMPLETE: could not read all files") : groupReview(res.findings).byFile.size ? chalk35.green("\u2705 agent-security: no alerts (items worth a look below)") : chalk35.green("\u2705 agent-security: clean");
|
|
86901
|
+
L.push(`\u{1F6E1}\uFE0F ${chalk35.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
|
|
86553
86902
|
L.push(
|
|
86554
|
-
|
|
86903
|
+
chalk35.gray(
|
|
86555
86904
|
` inspected ${res.inspected.length} config file(s), ${n} finding(s)` + (res.suppressedCount ? ` \xB7 ${res.suppressedCount} suppressed by .node9-ignore.json` : "")
|
|
86556
86905
|
)
|
|
86557
86906
|
);
|
|
86558
86907
|
if (diff) {
|
|
86559
86908
|
const introduced = listed(diff.added).length + diff.escalated.length;
|
|
86560
86909
|
L.push(
|
|
86561
|
-
diff.base !== "ok" ?
|
|
86910
|
+
diff.base !== "ok" ? chalk35.yellow.bold(
|
|
86562
86911
|
` \u26A0\uFE0F base ${diff.base === "did-not-run" ? "could not be read" : "scan was incomplete"}, so it cannot say what is new; showing everything`
|
|
86563
|
-
) : introduced > 0 ?
|
|
86912
|
+
) : introduced > 0 ? chalk35.red.bold(
|
|
86564
86913
|
` \u26A0\uFE0F this change introduced ${introduced} finding(s)` + (diff.escalated.length ? ` (${diff.escalated.length} by widening an existing one)` : "")
|
|
86565
|
-
) :
|
|
86914
|
+
) : chalk35.green(
|
|
86566
86915
|
` \u2705 this change introduced nothing` + (diff.unchanged.length ? ` (${diff.unchanged.length} pre-existing)` : "") + (diff.removed.length ? `, and fixed ${diff.removed.length}` : "")
|
|
86567
86916
|
)
|
|
86568
86917
|
);
|
|
@@ -86570,7 +86919,7 @@ function renderScan(res, diff) {
|
|
|
86570
86919
|
if (res.incomplete) {
|
|
86571
86920
|
const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
|
|
86572
86921
|
L.push(
|
|
86573
|
-
|
|
86922
|
+
chalk35.yellow.bold(
|
|
86574
86923
|
rateLimited ? " \u26A0\uFE0F Rate-limited: some files were unread. NOT a clean bill of health; set GITHUB_TOKEN (or run `gh auth login`) and re-run." : " \u26A0\uFE0F A network error left some files unread. NOT a clean bill of health; re-run."
|
|
86575
86924
|
)
|
|
86576
86925
|
);
|
|
@@ -86579,36 +86928,36 @@ function renderScan(res, diff) {
|
|
|
86579
86928
|
for (const f of shown) {
|
|
86580
86929
|
const ex = f.explain;
|
|
86581
86930
|
L.push(
|
|
86582
|
-
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${
|
|
86931
|
+
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk35.bold(ex ? ex.headline : f.title)}`
|
|
86583
86932
|
);
|
|
86584
|
-
L.push(
|
|
86933
|
+
L.push(chalk35.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
|
|
86585
86934
|
if (ex) {
|
|
86586
86935
|
L.push(` ${ex.happens}`);
|
|
86587
86936
|
for (const s of ex.saw) L.push(` \u2022 ${s}`);
|
|
86588
|
-
ex.fix.forEach((x, i) => L.push(
|
|
86937
|
+
ex.fix.forEach((x, i) => L.push(chalk35.cyan(` ${i + 1}. ${x}`)));
|
|
86589
86938
|
} else {
|
|
86590
86939
|
for (const s of f.signals) L.push(` \u2022 ${s}`);
|
|
86591
86940
|
if (f.mitigations?.length)
|
|
86592
|
-
L.push(
|
|
86593
|
-
L.push(
|
|
86941
|
+
L.push(chalk35.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
|
|
86942
|
+
L.push(chalk35.cyan(` \u2192 ${f.fix}`));
|
|
86594
86943
|
}
|
|
86595
86944
|
L.push("");
|
|
86596
86945
|
}
|
|
86597
86946
|
if (n === 0 && !res.incomplete) {
|
|
86598
86947
|
L.push(
|
|
86599
|
-
|
|
86948
|
+
chalk35.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
|
|
86600
86949
|
);
|
|
86601
86950
|
L.push("");
|
|
86602
86951
|
}
|
|
86603
86952
|
L.push(...reviewTerminal(res.findings));
|
|
86604
|
-
for (const note of res.notes) L.push(
|
|
86953
|
+
for (const note of res.notes) L.push(chalk35.gray(` note: ${note}`));
|
|
86605
86954
|
const hasHigh = shown.some(
|
|
86606
86955
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86607
86956
|
);
|
|
86608
86957
|
if (hasHigh && !ownedHint(res.source)) {
|
|
86609
86958
|
L.push("");
|
|
86610
86959
|
L.push(
|
|
86611
|
-
|
|
86960
|
+
chalk35.yellow(
|
|
86612
86961
|
" \u26A0\uFE0F This looks like a live issue on a repo you may not own. Disclose it privately\n to the maintainers; do not publish it. (node9 never weaponizes findings.)"
|
|
86613
86962
|
)
|
|
86614
86963
|
);
|
|
@@ -86802,7 +87151,7 @@ function makeProgress(target, quiet) {
|
|
|
86802
87151
|
let frame = 0;
|
|
86803
87152
|
let last = `scanning ${target}\u2026`;
|
|
86804
87153
|
const timer = setInterval(() => {
|
|
86805
|
-
process.stderr.write(`\r${
|
|
87154
|
+
process.stderr.write(`\r${chalk36.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
|
|
86806
87155
|
}, 80);
|
|
86807
87156
|
return {
|
|
86808
87157
|
onProgress: (p) => {
|
|
@@ -86865,236 +87214,13 @@ function registerScanRepoCommand(program2) {
|
|
|
86865
87214
|
if (!telemetryDisabled()) {
|
|
86866
87215
|
await sendScanRepoPing(buildScanRepoPing(res, target));
|
|
86867
87216
|
if (!opts.json && !opts.markdown && process.stderr.isTTY) {
|
|
86868
|
-
process.stderr.write(
|
|
87217
|
+
process.stderr.write(chalk36.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
|
|
86869
87218
|
}
|
|
86870
87219
|
}
|
|
86871
87220
|
}
|
|
86872
87221
|
);
|
|
86873
87222
|
}
|
|
86874
87223
|
|
|
86875
|
-
// src/cli/commands/egress.ts
|
|
86876
|
-
init_config();
|
|
86877
|
-
init_keyed_guard();
|
|
86878
|
-
init_dist();
|
|
86879
|
-
init_egress_config();
|
|
86880
|
-
init_egress_config();
|
|
86881
|
-
import chalk36 from "chalk";
|
|
86882
|
-
function guard(fn) {
|
|
86883
|
-
try {
|
|
86884
|
-
fn();
|
|
86885
|
-
return true;
|
|
86886
|
-
} catch (err2) {
|
|
86887
|
-
console.error(chalk36.red(`
|
|
86888
|
-
\u2717 ${err2.message}
|
|
86889
|
-
`));
|
|
86890
|
-
process.exitCode = 1;
|
|
86891
|
-
return false;
|
|
86892
|
-
}
|
|
86893
|
-
}
|
|
86894
|
-
function mutate(action, change) {
|
|
86895
|
-
if (!cliGuardPolicyWrite(action)) return false;
|
|
86896
|
-
return guard(() => setEgress(change));
|
|
86897
|
-
}
|
|
86898
|
-
function addHost(list2, host2) {
|
|
86899
|
-
if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
|
|
86900
|
-
return guard(() => addEgressHost(list2, host2));
|
|
86901
|
-
}
|
|
86902
|
-
function exempt2(address) {
|
|
86903
|
-
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
86904
|
-
return guard(() => addSsrfExemption(address));
|
|
86905
|
-
}
|
|
86906
|
-
var INTERNAL_FIELDS = {
|
|
86907
|
-
allowed: { ssrfStrict: false, allowPrivate: true },
|
|
86908
|
-
listed: { ssrfStrict: false, allowPrivate: false },
|
|
86909
|
-
blocked: { ssrfStrict: true, allowPrivate: false }
|
|
86910
|
-
};
|
|
86911
|
-
var INTERNAL_SAID = {
|
|
86912
|
-
allowed: "reachable without listing them",
|
|
86913
|
-
listed: "reachable only if they are on your allowlist",
|
|
86914
|
-
blocked: "blocked"
|
|
86915
|
-
};
|
|
86916
|
-
function readInternalState(e) {
|
|
86917
|
-
if (e.ssrfStrict === true) return "blocked";
|
|
86918
|
-
return e.allowPrivate === false ? "listed" : "allowed";
|
|
86919
|
-
}
|
|
86920
|
-
function setInternal(value) {
|
|
86921
|
-
const state = value.trim().toLowerCase();
|
|
86922
|
-
if (!(state in INTERNAL_FIELDS)) {
|
|
86923
|
-
console.error(
|
|
86924
|
-
chalk36.red(`
|
|
86925
|
-
\u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk36.gray(
|
|
86926
|
-
"\n allowed loopback, 10/172.16/192.168 and CGNAT are reachable (default)\n listed they are reachable only if you allowlist them\n blocked they are blocked at the floor\n"
|
|
86927
|
-
)
|
|
86928
|
-
);
|
|
86929
|
-
process.exitCode = 1;
|
|
86930
|
-
return;
|
|
86931
|
-
}
|
|
86932
|
-
if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
|
|
86933
|
-
_resetConfigCache();
|
|
86934
|
-
const effective = readInternalState(getConfig().policy.egress);
|
|
86935
|
-
if (effective !== state) {
|
|
86936
|
-
console.log(
|
|
86937
|
-
chalk36.yellow(
|
|
86938
|
-
`
|
|
86939
|
-
\u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
|
|
86940
|
-
Change it in the dashboard, Enforcement \u2192 Network.
|
|
86941
|
-
`
|
|
86942
|
-
)
|
|
86943
|
-
);
|
|
86944
|
-
return;
|
|
86945
|
-
}
|
|
86946
|
-
const line = `
|
|
86947
|
-
\u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
|
|
86948
|
-
`;
|
|
86949
|
-
console.log(state === "allowed" ? chalk36.yellow(line) : chalk36.green(line));
|
|
86950
|
-
}
|
|
86951
|
-
function showFloor(e, ssrfStrictSource) {
|
|
86952
|
-
console.log(
|
|
86953
|
-
chalk36.gray("\n Protected addresses") + chalk36.gray(" \u2014 in shell commands and declared URLs")
|
|
86954
|
-
);
|
|
86955
|
-
console.log(
|
|
86956
|
-
chalk36.gray(
|
|
86957
|
-
" always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
|
|
86958
|
-
)
|
|
86959
|
-
);
|
|
86960
|
-
const state = readInternalState(e);
|
|
86961
|
-
const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
|
|
86962
|
-
const STATE_LABEL = {
|
|
86963
|
-
allowed: "allowed",
|
|
86964
|
-
listed: "allowlist only",
|
|
86965
|
-
blocked: "blocked"
|
|
86966
|
-
};
|
|
86967
|
-
console.log(
|
|
86968
|
-
` Internal addresses: ${state === "blocked" ? chalk36.green(STATE_LABEL[state]) : chalk36.yellow(STATE_LABEL[state])}` + chalk36.gray(` loopback, 10/172.16/192.168 and CGNAT are ${INTERNAL_SAID[state]}`)
|
|
86969
|
-
);
|
|
86970
|
-
console.log(chalk36.gray(` set by: ${by}`));
|
|
86971
|
-
const exemptions = e.ssrfAllow ?? [];
|
|
86972
|
-
console.log(chalk36.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
|
|
86973
|
-
console.log(
|
|
86974
|
-
chalk36.gray(
|
|
86975
|
-
" Covered: shell commands, and tools that declare a URL (WebFetch, an MCP\n fetch tool, browser navigate).\n Not covered: an interpreter one-liner (node -e, python3 -c) hides its\n destination inside a program and does not reach this gate.\n CGNAT (100.64/10) is NOT in the always-blocked set: it is reachable\n until Internal addresses is on, and an exemption can release it."
|
|
86976
|
-
)
|
|
86977
|
-
);
|
|
86978
|
-
}
|
|
86979
|
-
function showStatus() {
|
|
86980
|
-
const cfg = getConfig();
|
|
86981
|
-
const e = cfg.policy.egress;
|
|
86982
|
-
const state = !e.enabled ? chalk36.red("OFF \u2014 your agent can reach any host, except the protected ones below") : e.mode === "block" ? chalk36.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk36.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
|
|
86983
|
-
console.log(chalk36.cyan.bold("\n\u{1F310} Egress control"));
|
|
86984
|
-
console.log(" State: " + state);
|
|
86985
|
-
if (cfg.policySource === "workspace") {
|
|
86986
|
-
console.log(
|
|
86987
|
-
chalk36.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
|
|
86988
|
-
);
|
|
86989
|
-
}
|
|
86990
|
-
console.log(
|
|
86991
|
-
chalk36.gray(
|
|
86992
|
-
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
86993
|
-
)
|
|
86994
|
-
);
|
|
86995
|
-
showFloor(e, cfg.ssrfStrictSource);
|
|
86996
|
-
if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
|
|
86997
|
-
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
86998
|
-
if (!e.enabled) {
|
|
86999
|
-
console.log(chalk36.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
87000
|
-
console.log(chalk36.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
87001
|
-
}
|
|
87002
|
-
console.log("");
|
|
87003
|
-
}
|
|
87004
|
-
function registerEgressCommand(program2) {
|
|
87005
|
-
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
87006
|
-
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
87007
|
-
if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
|
|
87008
|
-
console.log(chalk36.green("\n\u2713 Egress is now watched (review mode)."));
|
|
87009
|
-
console.log(
|
|
87010
|
-
chalk36.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
87011
|
-
);
|
|
87012
|
-
console.log(
|
|
87013
|
-
chalk36.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
87014
|
-
);
|
|
87015
|
-
});
|
|
87016
|
-
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
87017
|
-
if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
|
|
87018
|
-
console.log(chalk36.green("\n\u2713 Egress is now locked (block mode)."));
|
|
87019
|
-
console.log(chalk36.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
87020
|
-
console.log(chalk36.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
87021
|
-
});
|
|
87022
|
-
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
|
|
87023
|
-
if (!addHost("allow", host2)) return;
|
|
87024
|
-
console.log(chalk36.green(`
|
|
87025
|
-
\u2713 Allowed egress to ${host2}.
|
|
87026
|
-
`));
|
|
87027
|
-
});
|
|
87028
|
-
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
|
|
87029
|
-
if (!addHost("deny", host2)) return;
|
|
87030
|
-
console.log(chalk36.green(`
|
|
87031
|
-
\u2713 Denied egress to ${host2}.
|
|
87032
|
-
`));
|
|
87033
|
-
});
|
|
87034
|
-
egress.command("off").description("Turn egress control off").action(() => {
|
|
87035
|
-
if (!mutate("egress off", { enabled: false })) return;
|
|
87036
|
-
console.log(
|
|
87037
|
-
chalk36.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
87038
|
-
);
|
|
87039
|
-
});
|
|
87040
|
-
egress.command("internal <allowed|listed|blocked>").description(
|
|
87041
|
-
"How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
|
|
87042
|
-
).action((value) => setInternal(value));
|
|
87043
|
-
egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
|
|
87044
|
-
const v = value.trim().toLowerCase();
|
|
87045
|
-
if (v !== "on" && v !== "off") {
|
|
87046
|
-
console.error(chalk36.red(`
|
|
87047
|
-
\u2717 Expected "on" or "off", got "${value}".
|
|
87048
|
-
`));
|
|
87049
|
-
process.exitCode = 1;
|
|
87050
|
-
return;
|
|
87051
|
-
}
|
|
87052
|
-
const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
|
|
87053
|
-
console.log(chalk36.gray(`
|
|
87054
|
-
(\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
|
|
87055
|
-
setInternal(state);
|
|
87056
|
-
});
|
|
87057
|
-
egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
|
|
87058
|
-
const a = normalizeEgressHost(address);
|
|
87059
|
-
const slash = a.indexOf("/");
|
|
87060
|
-
const base2 = slash === -1 ? a : a.slice(0, slash);
|
|
87061
|
-
const prefixText = slash === -1 ? null : a.slice(slash + 1);
|
|
87062
|
-
if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
|
|
87063
|
-
console.error(
|
|
87064
|
-
chalk36.red(`
|
|
87065
|
-
\u2717 "${address}" is not an address or a range.`) + chalk36.gray(
|
|
87066
|
-
"\n Exemptions are matched as an address (10.0.0.5) or a CIDR\n range (100.64.0.0/10), never a name.\n"
|
|
87067
|
-
)
|
|
87068
|
-
);
|
|
87069
|
-
process.exitCode = 1;
|
|
87070
|
-
return;
|
|
87071
|
-
}
|
|
87072
|
-
const m = classifySsrf(base2);
|
|
87073
|
-
if (m && !m.overridable) {
|
|
87074
|
-
console.error(
|
|
87075
|
-
chalk36.red(`
|
|
87076
|
-
\u2717 ${a} cannot be exempted.`) + chalk36.gray(
|
|
87077
|
-
`
|
|
87078
|
-
${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
|
|
87079
|
-
`
|
|
87080
|
-
)
|
|
87081
|
-
);
|
|
87082
|
-
process.exitCode = 1;
|
|
87083
|
-
return;
|
|
87084
|
-
}
|
|
87085
|
-
if (!exempt2(a)) return;
|
|
87086
|
-
console.log(chalk36.green(`
|
|
87087
|
-
\u2713 ${a} is exempt from the floor.`));
|
|
87088
|
-
if (!m)
|
|
87089
|
-
console.log(
|
|
87090
|
-
chalk36.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
|
|
87091
|
-
);
|
|
87092
|
-
else console.log("");
|
|
87093
|
-
});
|
|
87094
|
-
egress.command("status").description("Show the current egress state").action(showStatus);
|
|
87095
|
-
egress.action(showStatus);
|
|
87096
|
-
}
|
|
87097
|
-
|
|
87098
87224
|
// src/cli/commands/checks.ts
|
|
87099
87225
|
init_dist();
|
|
87100
87226
|
init_config();
|
|
@@ -87135,7 +87261,9 @@ function renderChecks(report) {
|
|
|
87135
87261
|
const lines = [];
|
|
87136
87262
|
const where = report.policySource === "workspace" ? "workspace (app.node9.ai)" : "local (~/.node9/config.json)";
|
|
87137
87263
|
lines.push("");
|
|
87138
|
-
lines.push(
|
|
87264
|
+
lines.push(
|
|
87265
|
+
`${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${levelOf(report.mode)}`
|
|
87266
|
+
);
|
|
87139
87267
|
lines.push("");
|
|
87140
87268
|
const row = (c) => {
|
|
87141
87269
|
const value = (VALUE_COLOR[c.value] ?? chalk37.white)(c.value.padEnd(7));
|