@node9/proxy 2.43.1 → 2.45.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 +1172 -1137
- package/dist/cli.mjs +1136 -1101
- package/dist/dashboard.mjs +6391 -4559
- package/dist/scan-ink.mjs +14 -11
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -32029,16 +32029,124 @@ var init_credentials = __esm({
|
|
|
32029
32029
|
}
|
|
32030
32030
|
});
|
|
32031
32031
|
|
|
32032
|
-
// src/daemon/
|
|
32032
|
+
// src/daemon/startup-log.ts
|
|
32033
32033
|
import fs38 from "fs";
|
|
32034
32034
|
import path39 from "path";
|
|
32035
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";
|
|
32036
32144
|
import { spawnSync as spawnSync2, execFileSync } from "child_process";
|
|
32037
32145
|
function resolveNode9Binary() {
|
|
32038
32146
|
try {
|
|
32039
32147
|
const script = process.argv[1];
|
|
32040
|
-
if (typeof script === "string" &&
|
|
32041
|
-
return
|
|
32148
|
+
if (typeof script === "string" && path40.isAbsolute(script) && fs39.existsSync(script)) {
|
|
32149
|
+
return fs39.realpathSync(script);
|
|
32042
32150
|
}
|
|
32043
32151
|
} catch {
|
|
32044
32152
|
}
|
|
@@ -32062,11 +32170,11 @@ function daemonServiceEnv() {
|
|
|
32062
32170
|
return env;
|
|
32063
32171
|
}
|
|
32064
32172
|
function launchdPlist(binaryPath) {
|
|
32065
|
-
const logDir =
|
|
32173
|
+
const logDir = path40.join(os33.homedir(), ".node9");
|
|
32066
32174
|
const nodePath = xmlEscape(process.execPath);
|
|
32067
32175
|
const scriptPath = xmlEscape(binaryPath);
|
|
32068
|
-
const outLog = xmlEscape(
|
|
32069
|
-
const errLog = xmlEscape(
|
|
32176
|
+
const outLog = xmlEscape(path40.join(logDir, "daemon.log"));
|
|
32177
|
+
const errLog = xmlEscape(path40.join(logDir, "daemon-error.log"));
|
|
32070
32178
|
const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
32071
32179
|
<string>${xmlEscape(v)}</string>`).join("\n");
|
|
32072
32180
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -32100,9 +32208,9 @@ ${envEntries}
|
|
|
32100
32208
|
`;
|
|
32101
32209
|
}
|
|
32102
32210
|
function installLaunchd(binaryPath) {
|
|
32103
|
-
const dir2 =
|
|
32104
|
-
if (!
|
|
32105
|
-
|
|
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");
|
|
32106
32214
|
spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
32107
32215
|
const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
32108
32216
|
encoding: "utf8",
|
|
@@ -32113,13 +32221,13 @@ function installLaunchd(binaryPath) {
|
|
|
32113
32221
|
}
|
|
32114
32222
|
}
|
|
32115
32223
|
function uninstallLaunchd() {
|
|
32116
|
-
if (
|
|
32224
|
+
if (fs39.existsSync(LAUNCHD_PLIST)) {
|
|
32117
32225
|
spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
32118
|
-
|
|
32226
|
+
fs39.unlinkSync(LAUNCHD_PLIST);
|
|
32119
32227
|
}
|
|
32120
32228
|
}
|
|
32121
32229
|
function isLaunchdInstalled() {
|
|
32122
|
-
return
|
|
32230
|
+
return fs39.existsSync(LAUNCHD_PLIST);
|
|
32123
32231
|
}
|
|
32124
32232
|
function systemdUnit(binaryPath) {
|
|
32125
32233
|
const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
@@ -32139,12 +32247,12 @@ WantedBy=default.target
|
|
|
32139
32247
|
`;
|
|
32140
32248
|
}
|
|
32141
32249
|
function installSystemd(binaryPath) {
|
|
32142
|
-
if (!
|
|
32143
|
-
|
|
32250
|
+
if (!fs39.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
32251
|
+
fs39.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
32144
32252
|
}
|
|
32145
|
-
|
|
32253
|
+
fs39.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
32146
32254
|
try {
|
|
32147
|
-
execFileSync("loginctl", ["enable-linger",
|
|
32255
|
+
execFileSync("loginctl", ["enable-linger", os33.userInfo().username], { timeout: 3e3 });
|
|
32148
32256
|
} catch {
|
|
32149
32257
|
}
|
|
32150
32258
|
const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
|
|
@@ -32164,21 +32272,21 @@ function installSystemd(binaryPath) {
|
|
|
32164
32272
|
}
|
|
32165
32273
|
}
|
|
32166
32274
|
function uninstallSystemd() {
|
|
32167
|
-
if (
|
|
32275
|
+
if (fs39.existsSync(SYSTEMD_UNIT)) {
|
|
32168
32276
|
spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
32169
32277
|
encoding: "utf8",
|
|
32170
32278
|
timeout: 5e3
|
|
32171
32279
|
});
|
|
32172
32280
|
spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
32173
|
-
|
|
32281
|
+
fs39.unlinkSync(SYSTEMD_UNIT);
|
|
32174
32282
|
}
|
|
32175
32283
|
}
|
|
32176
32284
|
function isSystemdInstalled() {
|
|
32177
|
-
return
|
|
32285
|
+
return fs39.existsSync(SYSTEMD_UNIT);
|
|
32178
32286
|
}
|
|
32179
|
-
function windowsStartupDir(homeDir2 =
|
|
32180
|
-
const appData = process.env.APPDATA ||
|
|
32181
|
-
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");
|
|
32182
32290
|
}
|
|
32183
32291
|
function windowsLauncherVbs(nodePath, scriptPath) {
|
|
32184
32292
|
for (const p of [nodePath, scriptPath]) {
|
|
@@ -32198,23 +32306,23 @@ function windowsLauncherVbs(nodePath, scriptPath) {
|
|
|
32198
32306
|
}
|
|
32199
32307
|
function installWindowsStartup(binaryPath) {
|
|
32200
32308
|
const target = WIN_LAUNCHER();
|
|
32201
|
-
const dir2 =
|
|
32202
|
-
if (!
|
|
32203
|
-
|
|
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");
|
|
32204
32312
|
spawnSync2("wscript.exe", ["//B", target], { encoding: "utf8", timeout: 1e4 });
|
|
32205
32313
|
}
|
|
32206
32314
|
function uninstallWindowsStartup() {
|
|
32207
32315
|
const target = WIN_LAUNCHER();
|
|
32208
|
-
if (
|
|
32316
|
+
if (fs39.existsSync(target)) fs39.unlinkSync(target);
|
|
32209
32317
|
}
|
|
32210
32318
|
function isWindowsStartupInstalled() {
|
|
32211
|
-
return
|
|
32319
|
+
return fs39.existsSync(WIN_LAUNCHER());
|
|
32212
32320
|
}
|
|
32213
32321
|
function stopRunningDaemon() {
|
|
32214
|
-
const pidFile =
|
|
32215
|
-
if (!
|
|
32322
|
+
const pidFile = path40.join(os33.homedir(), ".node9", "daemon.pid");
|
|
32323
|
+
if (!fs39.existsSync(pidFile)) return;
|
|
32216
32324
|
try {
|
|
32217
|
-
const data = JSON.parse(
|
|
32325
|
+
const data = JSON.parse(fs39.readFileSync(pidFile, "utf-8"));
|
|
32218
32326
|
const pid = data.pid;
|
|
32219
32327
|
const MAX_PID2 = 4194304;
|
|
32220
32328
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -32234,7 +32342,7 @@ function stopRunningDaemon() {
|
|
|
32234
32342
|
}
|
|
32235
32343
|
}
|
|
32236
32344
|
try {
|
|
32237
|
-
|
|
32345
|
+
fs39.unlinkSync(pidFile);
|
|
32238
32346
|
} catch {
|
|
32239
32347
|
}
|
|
32240
32348
|
} catch {
|
|
@@ -32327,7 +32435,7 @@ function enableDaemonServiceQuiet() {
|
|
|
32327
32435
|
if (process.platform === "linux") {
|
|
32328
32436
|
const r = spawnSync2("systemctl", ["--user", "enable", "node9-daemon"], {
|
|
32329
32437
|
encoding: "utf8",
|
|
32330
|
-
timeout:
|
|
32438
|
+
timeout: 1e4
|
|
32331
32439
|
});
|
|
32332
32440
|
return r.status === 0;
|
|
32333
32441
|
}
|
|
@@ -32345,19 +32453,35 @@ function ensureAutostartHealthy(autoStartDaemon) {
|
|
|
32345
32453
|
enabled: isDaemonServiceEnabled(),
|
|
32346
32454
|
autoStartDaemon
|
|
32347
32455
|
});
|
|
32348
|
-
if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "
|
|
32456
|
+
if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "failed";
|
|
32349
32457
|
return decision === "ok" ? "ok" : decision === "unsupported" ? "unsupported" : "skipped";
|
|
32350
32458
|
}
|
|
32351
|
-
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) {
|
|
32352
32471
|
if (process.env.NODE9_TESTING === "1") return;
|
|
32472
|
+
let failures = 0;
|
|
32353
32473
|
const run = () => {
|
|
32474
|
+
let result = "skipped";
|
|
32354
32475
|
try {
|
|
32355
|
-
|
|
32476
|
+
result = heal(autoStartDaemon());
|
|
32356
32477
|
} catch {
|
|
32357
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();
|
|
32358
32483
|
};
|
|
32359
32484
|
setTimeout(run, 6e4).unref();
|
|
32360
|
-
setInterval(run, AUTOSTART_HEAL_INTERVAL_MS).unref();
|
|
32361
32485
|
}
|
|
32362
32486
|
function restartDaemonService() {
|
|
32363
32487
|
try {
|
|
@@ -32432,39 +32556,41 @@ function autostartState() {
|
|
|
32432
32556
|
if (!isDaemonServiceInstalled()) return "absent";
|
|
32433
32557
|
return isDaemonServiceEnabled() ? "enabled" : "disabled";
|
|
32434
32558
|
}
|
|
32435
|
-
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;
|
|
32436
32560
|
var init_service = __esm({
|
|
32437
32561
|
"src/daemon/service.ts"() {
|
|
32438
32562
|
"use strict";
|
|
32563
|
+
init_startup_log();
|
|
32439
32564
|
init_api_url();
|
|
32440
32565
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
32441
|
-
LAUNCHD_PLIST =
|
|
32442
|
-
SYSTEMD_UNIT_DIR =
|
|
32443
|
-
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");
|
|
32444
32569
|
STARTUP_FILE = "node9-daemon.vbs";
|
|
32445
|
-
WIN_LAUNCHER = () =>
|
|
32570
|
+
WIN_LAUNCHER = () => path40.join(windowsStartupDir(), STARTUP_FILE);
|
|
32446
32571
|
AUTOSTART_HEAL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
32572
|
+
AUTOSTART_HEAL_RETRY_MS = [10 * 60 * 1e3, 60 * 60 * 1e3];
|
|
32447
32573
|
}
|
|
32448
32574
|
});
|
|
32449
32575
|
|
|
32450
32576
|
// src/apps/detect.ts
|
|
32451
|
-
import
|
|
32452
|
-
import
|
|
32453
|
-
import
|
|
32577
|
+
import fs40 from "fs";
|
|
32578
|
+
import os34 from "os";
|
|
32579
|
+
import path41 from "path";
|
|
32454
32580
|
function readTail(file2, bytes) {
|
|
32455
32581
|
let fd;
|
|
32456
32582
|
try {
|
|
32457
|
-
fd =
|
|
32458
|
-
const size =
|
|
32583
|
+
fd = fs40.openSync(file2, "r");
|
|
32584
|
+
const size = fs40.fstatSync(fd).size;
|
|
32459
32585
|
const start = Math.max(0, size - bytes);
|
|
32460
32586
|
const buf = Buffer.alloc(size - start);
|
|
32461
|
-
|
|
32587
|
+
fs40.readSync(fd, buf, 0, buf.length, start);
|
|
32462
32588
|
const text = buf.toString("utf8");
|
|
32463
32589
|
return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
|
|
32464
32590
|
} catch {
|
|
32465
32591
|
return "";
|
|
32466
32592
|
} finally {
|
|
32467
|
-
if (fd !== void 0)
|
|
32593
|
+
if (fd !== void 0) fs40.closeSync(fd);
|
|
32468
32594
|
}
|
|
32469
32595
|
}
|
|
32470
32596
|
function programsOf(command) {
|
|
@@ -32478,7 +32604,7 @@ function programsOf(command) {
|
|
|
32478
32604
|
i++;
|
|
32479
32605
|
continue;
|
|
32480
32606
|
}
|
|
32481
|
-
const base2 =
|
|
32607
|
+
const base2 = path41.basename(w.replace(/^["']|["']$/g, ""));
|
|
32482
32608
|
if (WRAPPERS.has(base2)) {
|
|
32483
32609
|
i++;
|
|
32484
32610
|
continue;
|
|
@@ -32513,7 +32639,7 @@ function observeAudit(auditFile) {
|
|
|
32513
32639
|
for (const p of programsOf(row.argsPreview)) commands.add(p);
|
|
32514
32640
|
}
|
|
32515
32641
|
const dir2 = typeof row.workingDir === "string" ? row.workingDir : "";
|
|
32516
|
-
if (dir2 &&
|
|
32642
|
+
if (dir2 && path41.isAbsolute(dir2) && !seenDirs.has(dir2) && dirs.length < MAX_PROJECTS) {
|
|
32517
32643
|
seenDirs.add(dir2);
|
|
32518
32644
|
dirs.push(dir2);
|
|
32519
32645
|
}
|
|
@@ -32523,21 +32649,21 @@ function observeAudit(auditFile) {
|
|
|
32523
32649
|
function readSmall(file2) {
|
|
32524
32650
|
let fd;
|
|
32525
32651
|
try {
|
|
32526
|
-
fd =
|
|
32527
|
-
const st =
|
|
32652
|
+
fd = fs40.openSync(file2, READ_FLAGS);
|
|
32653
|
+
const st = fs40.fstatSync(fd);
|
|
32528
32654
|
if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
|
|
32529
32655
|
const buf = Buffer.alloc(st.size);
|
|
32530
|
-
const n =
|
|
32656
|
+
const n = fs40.readSync(fd, buf, 0, st.size, 0);
|
|
32531
32657
|
return buf.toString("utf8", 0, n);
|
|
32532
32658
|
} catch {
|
|
32533
32659
|
return null;
|
|
32534
32660
|
} finally {
|
|
32535
|
-
if (fd !== void 0)
|
|
32661
|
+
if (fd !== void 0) fs40.closeSync(fd);
|
|
32536
32662
|
}
|
|
32537
32663
|
}
|
|
32538
32664
|
function projectPackages(dir2) {
|
|
32539
32665
|
const out = /* @__PURE__ */ new Set();
|
|
32540
|
-
const pkg = readSmall(
|
|
32666
|
+
const pkg = readSmall(path41.join(dir2, "package.json"));
|
|
32541
32667
|
if (pkg) {
|
|
32542
32668
|
try {
|
|
32543
32669
|
const j = JSON.parse(pkg);
|
|
@@ -32554,7 +32680,7 @@ function projectPackages(dir2) {
|
|
|
32554
32680
|
} catch {
|
|
32555
32681
|
}
|
|
32556
32682
|
}
|
|
32557
|
-
const req = readSmall(
|
|
32683
|
+
const req = readSmall(path41.join(dir2, "requirements.txt"));
|
|
32558
32684
|
if (req) {
|
|
32559
32685
|
for (const line of req.split("\n")) {
|
|
32560
32686
|
const m = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line);
|
|
@@ -32562,7 +32688,7 @@ function projectPackages(dir2) {
|
|
|
32562
32688
|
out.add(m[1].toLowerCase());
|
|
32563
32689
|
}
|
|
32564
32690
|
}
|
|
32565
|
-
const py = readSmall(
|
|
32691
|
+
const py = readSmall(path41.join(dir2, "pyproject.toml"));
|
|
32566
32692
|
if (py) {
|
|
32567
32693
|
for (const m of py.matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:[<>=!~;[\]]|["'])/g))
|
|
32568
32694
|
out.add(m[1].toLowerCase());
|
|
@@ -32582,7 +32708,7 @@ function projectPackages(dir2) {
|
|
|
32582
32708
|
function projectEnvNames(dir2) {
|
|
32583
32709
|
const out = /* @__PURE__ */ new Set();
|
|
32584
32710
|
for (const f of [".env", ".env.local", ".env.development"]) {
|
|
32585
|
-
const text = readSmall(
|
|
32711
|
+
const text = readSmall(path41.join(dir2, f));
|
|
32586
32712
|
if (!text) continue;
|
|
32587
32713
|
for (const line of text.split("\n")) {
|
|
32588
32714
|
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
|
|
@@ -32618,14 +32744,14 @@ function matchApps(obs, projectFacts, catalog = APP_CATALOG) {
|
|
|
32618
32744
|
}
|
|
32619
32745
|
return found;
|
|
32620
32746
|
}
|
|
32621
|
-
function detectApps(home =
|
|
32622
|
-
const obs = observeAudit(
|
|
32747
|
+
function detectApps(home = os34.homedir()) {
|
|
32748
|
+
const obs = observeAudit(path41.join(home, ".node9", "audit.log"));
|
|
32623
32749
|
for (const cfg of Object.values(readMcpToolsConfig())) {
|
|
32624
32750
|
if (cfg?.name) obs.servers.add(cfg.name.toLowerCase());
|
|
32625
32751
|
}
|
|
32626
32752
|
const facts = obs.projects.filter((dir2) => {
|
|
32627
32753
|
try {
|
|
32628
|
-
return
|
|
32754
|
+
return fs40.statSync(dir2).isDirectory();
|
|
32629
32755
|
} catch {
|
|
32630
32756
|
return false;
|
|
32631
32757
|
}
|
|
@@ -32660,7 +32786,7 @@ var init_detect = __esm({
|
|
|
32660
32786
|
"pnpx",
|
|
32661
32787
|
"bunx"
|
|
32662
32788
|
]);
|
|
32663
|
-
READ_FLAGS = typeof
|
|
32789
|
+
READ_FLAGS = typeof fs40.constants.O_NONBLOCK === "number" ? fs40.constants.O_RDONLY | fs40.constants.O_NONBLOCK : fs40.constants.O_RDONLY;
|
|
32664
32790
|
}
|
|
32665
32791
|
});
|
|
32666
32792
|
|
|
@@ -32720,9 +32846,9 @@ var init_daemon2 = __esm({
|
|
|
32720
32846
|
});
|
|
32721
32847
|
|
|
32722
32848
|
// src/config/migrate.ts
|
|
32723
|
-
import
|
|
32724
|
-
import
|
|
32725
|
-
import
|
|
32849
|
+
import fs41 from "fs";
|
|
32850
|
+
import path42 from "path";
|
|
32851
|
+
import os35 from "os";
|
|
32726
32852
|
function backupPath(filePath) {
|
|
32727
32853
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
32728
32854
|
return `${filePath}.bak-${stamp}`;
|
|
@@ -32732,7 +32858,7 @@ function migrateConfigFile(options = {}) {
|
|
|
32732
32858
|
if (options.dryRun) {
|
|
32733
32859
|
let raw;
|
|
32734
32860
|
try {
|
|
32735
|
-
raw = JSON.parse(
|
|
32861
|
+
raw = JSON.parse(fs41.readFileSync(filePath, "utf8"));
|
|
32736
32862
|
} catch (err2) {
|
|
32737
32863
|
if (err2.code === "ENOENT") return { status: "no-file" };
|
|
32738
32864
|
return { status: "failed", error: `cannot read ${filePath}: ${err2.message}` };
|
|
@@ -32755,14 +32881,14 @@ function migrateConfigFile(options = {}) {
|
|
|
32755
32881
|
}
|
|
32756
32882
|
}
|
|
32757
32883
|
function undoMigration(filePath = globalConfigPath()) {
|
|
32758
|
-
const dir2 =
|
|
32759
|
-
const base2 =
|
|
32760
|
-
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();
|
|
32761
32887
|
const newest = backups.at(-1);
|
|
32762
32888
|
if (!newest) return null;
|
|
32763
|
-
const from =
|
|
32764
|
-
atomicWriteSync(filePath,
|
|
32765
|
-
|
|
32889
|
+
const from = path42.join(dir2, newest);
|
|
32890
|
+
atomicWriteSync(filePath, fs41.readFileSync(from, "utf8"), { mode: 384 });
|
|
32891
|
+
fs41.unlinkSync(from);
|
|
32766
32892
|
return { restored: from };
|
|
32767
32893
|
}
|
|
32768
32894
|
function autoMigrateLocalConfig() {
|
|
@@ -32780,8 +32906,8 @@ function autoMigrateLocalConfig() {
|
|
|
32780
32906
|
);
|
|
32781
32907
|
} else if (outcome.status === "failed") {
|
|
32782
32908
|
try {
|
|
32783
|
-
|
|
32784
|
-
|
|
32909
|
+
fs41.appendFileSync(
|
|
32910
|
+
path42.join(os35.homedir(), ".node9", "hook-debug.log"),
|
|
32785
32911
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] config migrate failed: ${outcome.error}
|
|
32786
32912
|
`
|
|
32787
32913
|
);
|
|
@@ -33022,18 +33148,18 @@ var init_forecast = __esm({
|
|
|
33022
33148
|
});
|
|
33023
33149
|
|
|
33024
33150
|
// src/version.ts
|
|
33025
|
-
import
|
|
33026
|
-
import
|
|
33151
|
+
import fs42 from "fs";
|
|
33152
|
+
import path43 from "path";
|
|
33027
33153
|
function readVersion() {
|
|
33028
33154
|
let dir2 = __dirname;
|
|
33029
33155
|
for (let i = 0; i < 5; i++) {
|
|
33030
|
-
const p =
|
|
33156
|
+
const p = path43.join(dir2, "package.json");
|
|
33031
33157
|
try {
|
|
33032
|
-
const pkg = JSON.parse(
|
|
33158
|
+
const pkg = JSON.parse(fs42.readFileSync(p, "utf-8"));
|
|
33033
33159
|
if (pkg.name === "@node9/proxy" && typeof pkg.version === "string") return pkg.version;
|
|
33034
33160
|
} catch {
|
|
33035
33161
|
}
|
|
33036
|
-
const parent =
|
|
33162
|
+
const parent = path43.dirname(dir2);
|
|
33037
33163
|
if (parent === dir2) break;
|
|
33038
33164
|
dir2 = parent;
|
|
33039
33165
|
}
|
|
@@ -33048,15 +33174,15 @@ var init_version = __esm({
|
|
|
33048
33174
|
});
|
|
33049
33175
|
|
|
33050
33176
|
// src/cli/commands/scan-cache.ts
|
|
33051
|
-
import
|
|
33052
|
-
import
|
|
33053
|
-
import
|
|
33177
|
+
import fs43 from "fs";
|
|
33178
|
+
import os36 from "os";
|
|
33179
|
+
import path44 from "path";
|
|
33054
33180
|
import { createHash as createHash6 } from "crypto";
|
|
33055
33181
|
function flushScanCaches() {
|
|
33056
33182
|
for (const c of activeCaches) c.flush();
|
|
33057
33183
|
}
|
|
33058
33184
|
function scanCacheRoot() {
|
|
33059
|
-
return
|
|
33185
|
+
return path44.join(os36.homedir(), ".node9", "scan-cache");
|
|
33060
33186
|
}
|
|
33061
33187
|
function configHashOf(config) {
|
|
33062
33188
|
const h = createHash6("sha256");
|
|
@@ -33064,14 +33190,14 @@ function configHashOf(config) {
|
|
|
33064
33190
|
h.update(JSON.stringify(config.policySource));
|
|
33065
33191
|
h.update(JSON.stringify(config.policy));
|
|
33066
33192
|
for (const name of SIDECAR_FILES) {
|
|
33067
|
-
const st = fileStamp(
|
|
33193
|
+
const st = fileStamp(path44.join(os36.homedir(), ".node9", name));
|
|
33068
33194
|
h.update(`|${name}:${st ? `${st.size}:${st.mtimeMs}` : "absent"}`);
|
|
33069
33195
|
}
|
|
33070
33196
|
return h.digest("hex").slice(0, 16);
|
|
33071
33197
|
}
|
|
33072
33198
|
function fileStamp(filePath) {
|
|
33073
33199
|
try {
|
|
33074
|
-
const st =
|
|
33200
|
+
const st = fs43.statSync(filePath);
|
|
33075
33201
|
return { size: st.size, mtimeMs: st.mtimeMs };
|
|
33076
33202
|
} catch {
|
|
33077
33203
|
return null;
|
|
@@ -33095,8 +33221,8 @@ var init_scan_cache = __esm({
|
|
|
33095
33221
|
"trusted-hosts.json",
|
|
33096
33222
|
"trusted-recipients.json",
|
|
33097
33223
|
"rules-cache.json",
|
|
33098
|
-
|
|
33099
|
-
|
|
33224
|
+
path44.join("osv", "npm", "meta.json"),
|
|
33225
|
+
path44.join("osv", "PyPI", "meta.json")
|
|
33100
33226
|
];
|
|
33101
33227
|
ScanCache = class {
|
|
33102
33228
|
root;
|
|
@@ -33125,7 +33251,7 @@ var init_scan_cache = __esm({
|
|
|
33125
33251
|
if (!this.enabled) return fresh;
|
|
33126
33252
|
try {
|
|
33127
33253
|
const raw = JSON.parse(
|
|
33128
|
-
|
|
33254
|
+
fs43.readFileSync(path44.join(this.root, "index.json"), "utf8")
|
|
33129
33255
|
);
|
|
33130
33256
|
if (raw.cacheVersion !== SCAN_CACHE_VERSION || raw.engineVersion !== CANONICAL_EXTRACTOR_VERSION || raw.proxyVersion !== PROXY_VERSION || raw.configHash !== configHash || typeof raw.files !== "object") {
|
|
33131
33257
|
this.dropAll();
|
|
@@ -33138,7 +33264,7 @@ var init_scan_cache = __esm({
|
|
|
33138
33264
|
}
|
|
33139
33265
|
dropAll() {
|
|
33140
33266
|
try {
|
|
33141
|
-
|
|
33267
|
+
fs43.rmSync(this.root, { recursive: true, force: true });
|
|
33142
33268
|
} catch {
|
|
33143
33269
|
}
|
|
33144
33270
|
}
|
|
@@ -33162,7 +33288,7 @@ var init_scan_cache = __esm({
|
|
|
33162
33288
|
}
|
|
33163
33289
|
}
|
|
33164
33290
|
try {
|
|
33165
|
-
const stored = JSON.parse(
|
|
33291
|
+
const stored = JSON.parse(fs43.readFileSync(path44.join(this.root, e.result), "utf8"));
|
|
33166
33292
|
if ((stored.judgedFrom ?? null) !== (e.judgedFrom ?? null) || !stored.result) {
|
|
33167
33293
|
this.misses++;
|
|
33168
33294
|
return null;
|
|
@@ -33177,14 +33303,14 @@ var init_scan_cache = __esm({
|
|
|
33177
33303
|
put(filePath, stamp, agent, result, hasher, judgeFrom = null) {
|
|
33178
33304
|
if (!this.enabled) return;
|
|
33179
33305
|
const name = createHash6("sha256").update(`${agent}|${filePath}`).digest("hex").slice(0, 40);
|
|
33180
|
-
const rel =
|
|
33306
|
+
const rel = path44.join("files", name.slice(0, 2), `${name}.json`);
|
|
33181
33307
|
const configHashes = {};
|
|
33182
33308
|
if (hasher)
|
|
33183
33309
|
for (const cwd of result.configCwds ?? []) configHashes[cwd] = hasher.configHashFor(cwd);
|
|
33184
33310
|
try {
|
|
33185
|
-
const abs =
|
|
33186
|
-
|
|
33187
|
-
|
|
33311
|
+
const abs = path44.join(this.root, rel);
|
|
33312
|
+
fs43.mkdirSync(path44.dirname(abs), { recursive: true, mode: 448 });
|
|
33313
|
+
fs43.writeFileSync(
|
|
33188
33314
|
abs,
|
|
33189
33315
|
JSON.stringify({ judgedFrom: judgeFrom ? judgeFrom.toISOString() : null, result }),
|
|
33190
33316
|
{ mode: 384 }
|
|
@@ -33207,9 +33333,9 @@ var init_scan_cache = __esm({
|
|
|
33207
33333
|
/** Drop the entries of transcripts that no longer exist, and their files. */
|
|
33208
33334
|
prune() {
|
|
33209
33335
|
for (const [key, e] of Object.entries(this.index.files)) {
|
|
33210
|
-
if (
|
|
33336
|
+
if (fs43.existsSync(key)) continue;
|
|
33211
33337
|
try {
|
|
33212
|
-
|
|
33338
|
+
fs43.rmSync(path44.join(this.root, e.result), { force: true });
|
|
33213
33339
|
} catch {
|
|
33214
33340
|
}
|
|
33215
33341
|
delete this.index.files[key];
|
|
@@ -33230,10 +33356,10 @@ var init_scan_cache = __esm({
|
|
|
33230
33356
|
this.lastFlush = Date.now();
|
|
33231
33357
|
if (!this.dirty) return;
|
|
33232
33358
|
try {
|
|
33233
|
-
|
|
33234
|
-
const tmp =
|
|
33235
|
-
|
|
33236
|
-
|
|
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"));
|
|
33237
33363
|
this.dirty = false;
|
|
33238
33364
|
} catch {
|
|
33239
33365
|
}
|
|
@@ -33243,9 +33369,9 @@ var init_scan_cache = __esm({
|
|
|
33243
33369
|
});
|
|
33244
33370
|
|
|
33245
33371
|
// src/cli/commands/scan-judge.ts
|
|
33246
|
-
import
|
|
33247
|
-
import
|
|
33248
|
-
import
|
|
33372
|
+
import fs44 from "fs";
|
|
33373
|
+
import os37 from "os";
|
|
33374
|
+
import path45 from "path";
|
|
33249
33375
|
function findingDedupKey(f) {
|
|
33250
33376
|
return `${f.source.rule.name ?? "<unnamed>"}|${preview(f.input)}|${f.project}|${f.repo ?? ""}`;
|
|
33251
33377
|
}
|
|
@@ -33344,24 +33470,24 @@ var init_scan_judge = __esm({
|
|
|
33344
33470
|
* file, so worktrees count), home-relative; undefined when none. Cached per
|
|
33345
33471
|
* directory: history repeats the same few cwds thousands of times. */
|
|
33346
33472
|
repoOf(cwd) {
|
|
33347
|
-
if (!cwd || !
|
|
33473
|
+
if (!cwd || !path45.isAbsolute(cwd)) return void 0;
|
|
33348
33474
|
if (this.repos.has(cwd)) return this.repos.get(cwd);
|
|
33349
33475
|
let dir2 = cwd;
|
|
33350
33476
|
let found;
|
|
33351
33477
|
for (; ; ) {
|
|
33352
33478
|
try {
|
|
33353
|
-
if (
|
|
33479
|
+
if (fs44.existsSync(path45.join(dir2, ".git"))) {
|
|
33354
33480
|
found = dir2;
|
|
33355
33481
|
break;
|
|
33356
33482
|
}
|
|
33357
33483
|
} catch {
|
|
33358
33484
|
break;
|
|
33359
33485
|
}
|
|
33360
|
-
const parent =
|
|
33486
|
+
const parent = path45.dirname(dir2);
|
|
33361
33487
|
if (parent === dir2) break;
|
|
33362
33488
|
dir2 = parent;
|
|
33363
33489
|
}
|
|
33364
|
-
const home =
|
|
33490
|
+
const home = os37.homedir();
|
|
33365
33491
|
const label3 = found && found.startsWith(home) ? "~" + found.slice(home.length) : found;
|
|
33366
33492
|
this.repos.set(cwd, label3);
|
|
33367
33493
|
return label3;
|
|
@@ -33506,15 +33632,15 @@ var init_scan_judge = __esm({
|
|
|
33506
33632
|
});
|
|
33507
33633
|
|
|
33508
33634
|
// src/pricing/litellm.ts
|
|
33509
|
-
import
|
|
33510
|
-
import
|
|
33511
|
-
import
|
|
33635
|
+
import fs45 from "fs";
|
|
33636
|
+
import path46 from "path";
|
|
33637
|
+
import os38 from "os";
|
|
33512
33638
|
function normalizeModel(raw) {
|
|
33513
33639
|
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
33514
33640
|
}
|
|
33515
33641
|
function readCache(opts) {
|
|
33516
33642
|
try {
|
|
33517
|
-
const raw = JSON.parse(
|
|
33643
|
+
const raw = JSON.parse(fs45.readFileSync(CACHE_FILE(), "utf-8"));
|
|
33518
33644
|
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
33519
33645
|
return null;
|
|
33520
33646
|
}
|
|
@@ -33529,18 +33655,18 @@ function readCache(opts) {
|
|
|
33529
33655
|
function writeCache(prices) {
|
|
33530
33656
|
try {
|
|
33531
33657
|
const target = CACHE_FILE();
|
|
33532
|
-
const dir2 =
|
|
33533
|
-
if (!
|
|
33658
|
+
const dir2 = path46.dirname(target);
|
|
33659
|
+
if (!fs45.existsSync(dir2)) fs45.mkdirSync(dir2, { recursive: true });
|
|
33534
33660
|
const tmp = target + ".tmp";
|
|
33535
33661
|
const body = {
|
|
33536
33662
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33537
33663
|
prices
|
|
33538
33664
|
};
|
|
33539
|
-
|
|
33540
|
-
|
|
33665
|
+
fs45.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
33666
|
+
fs45.renameSync(tmp, target);
|
|
33541
33667
|
} catch (err2) {
|
|
33542
33668
|
try {
|
|
33543
|
-
|
|
33669
|
+
fs45.appendFileSync(
|
|
33544
33670
|
HOOK_DEBUG_LOG,
|
|
33545
33671
|
`[pricing] cache write failed: ${err2.message}
|
|
33546
33672
|
`
|
|
@@ -33698,7 +33824,7 @@ var init_litellm = __esm({
|
|
|
33698
33824
|
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
33699
33825
|
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
33700
33826
|
};
|
|
33701
|
-
CACHE_FILE = () =>
|
|
33827
|
+
CACHE_FILE = () => path46.join(os38.homedir(), ".node9", "model-pricing.json");
|
|
33702
33828
|
TTL_MS2 = 24 * 60 * 60 * 1e3;
|
|
33703
33829
|
memCache = null;
|
|
33704
33830
|
memCacheAt = 0;
|
|
@@ -33708,11 +33834,11 @@ var init_litellm = __esm({
|
|
|
33708
33834
|
});
|
|
33709
33835
|
|
|
33710
33836
|
// src/cost-codex.ts
|
|
33711
|
-
import
|
|
33712
|
-
import
|
|
33713
|
-
import
|
|
33837
|
+
import fs46 from "fs";
|
|
33838
|
+
import os39 from "os";
|
|
33839
|
+
import path47 from "path";
|
|
33714
33840
|
function codexSessionsDir() {
|
|
33715
|
-
return
|
|
33841
|
+
return path47.join(process.env.CODEX_HOME?.trim() || path47.join(os39.homedir(), ".codex"), "sessions");
|
|
33716
33842
|
}
|
|
33717
33843
|
function codexPriceFor(model) {
|
|
33718
33844
|
return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
|
|
@@ -33744,14 +33870,14 @@ function codexSessionCost(model, tokens, request2) {
|
|
|
33744
33870
|
function statAndFirstLine(file2) {
|
|
33745
33871
|
const CAP = 4 * 1024 * 1024;
|
|
33746
33872
|
const CHUNK = 64 * 1024;
|
|
33747
|
-
const fd =
|
|
33873
|
+
const fd = fs46.openSync(file2, "r");
|
|
33748
33874
|
try {
|
|
33749
|
-
const stat =
|
|
33875
|
+
const stat = fs46.fstatSync(fd);
|
|
33750
33876
|
const limit = Math.min(stat.size, CAP);
|
|
33751
33877
|
const parts = [];
|
|
33752
33878
|
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
33753
33879
|
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
33754
|
-
const read3 =
|
|
33880
|
+
const read3 = fs46.readSync(fd, buf, 0, buf.length, pos);
|
|
33755
33881
|
if (read3 <= 0) break;
|
|
33756
33882
|
const slice = buf.subarray(0, read3);
|
|
33757
33883
|
const nl = slice.indexOf(10);
|
|
@@ -33760,15 +33886,15 @@ function statAndFirstLine(file2) {
|
|
|
33760
33886
|
}
|
|
33761
33887
|
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
33762
33888
|
} finally {
|
|
33763
|
-
|
|
33889
|
+
fs46.closeSync(fd);
|
|
33764
33890
|
}
|
|
33765
33891
|
}
|
|
33766
33892
|
function listCodexSessionFiles(base2 = codexSessionsDir()) {
|
|
33767
33893
|
const files = [];
|
|
33768
33894
|
const walk2 = (dir2) => {
|
|
33769
33895
|
try {
|
|
33770
|
-
for (const entry of
|
|
33771
|
-
const file2 =
|
|
33896
|
+
for (const entry of fs46.readdirSync(dir2, { withFileTypes: true })) {
|
|
33897
|
+
const file2 = path47.join(dir2, entry.name);
|
|
33772
33898
|
if (entry.isDirectory()) walk2(file2);
|
|
33773
33899
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file2);
|
|
33774
33900
|
}
|
|
@@ -33776,7 +33902,7 @@ function listCodexSessionFiles(base2 = codexSessionsDir()) {
|
|
|
33776
33902
|
}
|
|
33777
33903
|
};
|
|
33778
33904
|
walk2(base2);
|
|
33779
|
-
if (
|
|
33905
|
+
if (path47.basename(base2) === "sessions") walk2(path47.join(path47.dirname(base2), "archived_sessions"));
|
|
33780
33906
|
const sessions = /* @__PURE__ */ new Map();
|
|
33781
33907
|
for (const file2 of files.sort()) {
|
|
33782
33908
|
try {
|
|
@@ -33974,13 +34100,13 @@ var init_cost_codex = __esm({
|
|
|
33974
34100
|
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
33975
34101
|
codexSource = {
|
|
33976
34102
|
id: "codex",
|
|
33977
|
-
available: () =>
|
|
34103
|
+
available: () => fs46.existsSync(codexSessionsDir()) || fs46.existsSync(path47.join(path47.dirname(codexSessionsDir()), "archived_sessions")),
|
|
33978
34104
|
collect(sinceMs) {
|
|
33979
34105
|
const entries = [];
|
|
33980
34106
|
for (const file2 of listCodexSessionFiles()) {
|
|
33981
34107
|
try {
|
|
33982
|
-
if (sinceMs !== void 0 &&
|
|
33983
|
-
entries.push(...parseCodexSession(
|
|
34108
|
+
if (sinceMs !== void 0 && fs46.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34109
|
+
entries.push(...parseCodexSession(fs46.readFileSync(file2, "utf8").split("\n")));
|
|
33984
34110
|
} catch {
|
|
33985
34111
|
}
|
|
33986
34112
|
}
|
|
@@ -33991,11 +34117,11 @@ var init_cost_codex = __esm({
|
|
|
33991
34117
|
});
|
|
33992
34118
|
|
|
33993
34119
|
// src/cost-gemini.ts
|
|
33994
|
-
import
|
|
33995
|
-
import
|
|
33996
|
-
import
|
|
34120
|
+
import fs47 from "fs";
|
|
34121
|
+
import os40 from "os";
|
|
34122
|
+
import path48 from "path";
|
|
33997
34123
|
function geminiTmpDir() {
|
|
33998
|
-
return
|
|
34124
|
+
return path48.join(os40.homedir(), ".gemini", "tmp");
|
|
33999
34125
|
}
|
|
34000
34126
|
function geminiPriceFor(model) {
|
|
34001
34127
|
let tuple = pricingFor(model);
|
|
@@ -34010,14 +34136,14 @@ function geminiPriceFor(model) {
|
|
|
34010
34136
|
}
|
|
34011
34137
|
function safeReaddir(dir2) {
|
|
34012
34138
|
try {
|
|
34013
|
-
return
|
|
34139
|
+
return fs47.readdirSync(dir2);
|
|
34014
34140
|
} catch {
|
|
34015
34141
|
return [];
|
|
34016
34142
|
}
|
|
34017
34143
|
}
|
|
34018
34144
|
function isDir(p) {
|
|
34019
34145
|
try {
|
|
34020
|
-
return
|
|
34146
|
+
return fs47.statSync(p).isDirectory();
|
|
34021
34147
|
} catch {
|
|
34022
34148
|
return false;
|
|
34023
34149
|
}
|
|
@@ -34025,11 +34151,11 @@ function isDir(p) {
|
|
|
34025
34151
|
function listGeminiSessionFiles(base2) {
|
|
34026
34152
|
const out = [];
|
|
34027
34153
|
for (const project of safeReaddir(base2)) {
|
|
34028
|
-
const chats =
|
|
34154
|
+
const chats = path48.join(base2, project, "chats");
|
|
34029
34155
|
if (!isDir(chats)) continue;
|
|
34030
34156
|
for (const f of safeReaddir(chats)) {
|
|
34031
34157
|
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
34032
|
-
out.push({ file:
|
|
34158
|
+
out.push({ file: path48.join(chats, f), project });
|
|
34033
34159
|
}
|
|
34034
34160
|
}
|
|
34035
34161
|
}
|
|
@@ -34096,7 +34222,7 @@ var init_cost_gemini = __esm({
|
|
|
34096
34222
|
id: "gemini",
|
|
34097
34223
|
available() {
|
|
34098
34224
|
try {
|
|
34099
|
-
return
|
|
34225
|
+
return fs47.existsSync(geminiTmpDir());
|
|
34100
34226
|
} catch {
|
|
34101
34227
|
return false;
|
|
34102
34228
|
}
|
|
@@ -34105,13 +34231,13 @@ var init_cost_gemini = __esm({
|
|
|
34105
34231
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34106
34232
|
for (const { file: file2, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
34107
34233
|
try {
|
|
34108
|
-
if (sinceMs !== void 0 &&
|
|
34234
|
+
if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34109
34235
|
} catch {
|
|
34110
34236
|
continue;
|
|
34111
34237
|
}
|
|
34112
34238
|
let content;
|
|
34113
34239
|
try {
|
|
34114
|
-
content =
|
|
34240
|
+
content = fs47.readFileSync(file2, "utf8");
|
|
34115
34241
|
} catch {
|
|
34116
34242
|
continue;
|
|
34117
34243
|
}
|
|
@@ -34136,15 +34262,15 @@ var init_cost_gemini = __esm({
|
|
|
34136
34262
|
});
|
|
34137
34263
|
|
|
34138
34264
|
// src/cost-copilot.ts
|
|
34139
|
-
import
|
|
34140
|
-
import
|
|
34141
|
-
import
|
|
34265
|
+
import fs48 from "fs";
|
|
34266
|
+
import os41 from "os";
|
|
34267
|
+
import path49 from "path";
|
|
34142
34268
|
function copilotSessionsDir() {
|
|
34143
|
-
return
|
|
34269
|
+
return path49.join(os41.homedir(), ".copilot", "session-state");
|
|
34144
34270
|
}
|
|
34145
34271
|
function safeReaddir2(dir2) {
|
|
34146
34272
|
try {
|
|
34147
|
-
return
|
|
34273
|
+
return fs48.readdirSync(dir2);
|
|
34148
34274
|
} catch {
|
|
34149
34275
|
return [];
|
|
34150
34276
|
}
|
|
@@ -34220,7 +34346,7 @@ var init_cost_copilot = __esm({
|
|
|
34220
34346
|
id: "copilot",
|
|
34221
34347
|
available() {
|
|
34222
34348
|
try {
|
|
34223
|
-
return
|
|
34349
|
+
return fs48.existsSync(copilotSessionsDir());
|
|
34224
34350
|
} catch {
|
|
34225
34351
|
return false;
|
|
34226
34352
|
}
|
|
@@ -34229,15 +34355,15 @@ var init_cost_copilot = __esm({
|
|
|
34229
34355
|
const base2 = copilotSessionsDir();
|
|
34230
34356
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34231
34357
|
for (const sid of safeReaddir2(base2)) {
|
|
34232
|
-
const file2 =
|
|
34358
|
+
const file2 = path49.join(base2, sid, "events.jsonl");
|
|
34233
34359
|
try {
|
|
34234
|
-
if (sinceMs !== void 0 &&
|
|
34360
|
+
if (sinceMs !== void 0 && fs48.statSync(file2).mtimeMs < sinceMs) continue;
|
|
34235
34361
|
} catch {
|
|
34236
34362
|
continue;
|
|
34237
34363
|
}
|
|
34238
34364
|
let content;
|
|
34239
34365
|
try {
|
|
34240
|
-
content =
|
|
34366
|
+
content = fs48.readFileSync(file2, "utf8");
|
|
34241
34367
|
} catch {
|
|
34242
34368
|
continue;
|
|
34243
34369
|
}
|
|
@@ -34262,21 +34388,21 @@ var init_cost_copilot = __esm({
|
|
|
34262
34388
|
});
|
|
34263
34389
|
|
|
34264
34390
|
// src/session-files.ts
|
|
34265
|
-
import * as
|
|
34266
|
-
import * as
|
|
34391
|
+
import * as fs49 from "fs";
|
|
34392
|
+
import * as path50 from "path";
|
|
34267
34393
|
function listSessionFiles(dir2, maxDepth = 6) {
|
|
34268
34394
|
const out = [];
|
|
34269
34395
|
const walk2 = (d, rel, depth) => {
|
|
34270
34396
|
if (depth > maxDepth) return;
|
|
34271
34397
|
let entries;
|
|
34272
34398
|
try {
|
|
34273
|
-
entries =
|
|
34399
|
+
entries = fs49.readdirSync(d, { withFileTypes: true });
|
|
34274
34400
|
} catch {
|
|
34275
34401
|
return;
|
|
34276
34402
|
}
|
|
34277
34403
|
for (const e of entries) {
|
|
34278
|
-
const childRel = rel ?
|
|
34279
|
-
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);
|
|
34280
34406
|
else if (e.name.endsWith(".jsonl")) out.push(childRel);
|
|
34281
34407
|
}
|
|
34282
34408
|
};
|
|
@@ -34284,7 +34410,7 @@ function listSessionFiles(dir2, maxDepth = 6) {
|
|
|
34284
34410
|
return out;
|
|
34285
34411
|
}
|
|
34286
34412
|
function sessionIdOf(relPath) {
|
|
34287
|
-
return
|
|
34413
|
+
return path50.basename(relPath).replace(/\.jsonl$/, "");
|
|
34288
34414
|
}
|
|
34289
34415
|
var init_session_files = __esm({
|
|
34290
34416
|
"src/session-files.ts"() {
|
|
@@ -34293,17 +34419,17 @@ var init_session_files = __esm({
|
|
|
34293
34419
|
});
|
|
34294
34420
|
|
|
34295
34421
|
// src/costSync.ts
|
|
34296
|
-
import
|
|
34297
|
-
import
|
|
34298
|
-
import
|
|
34422
|
+
import fs50 from "fs";
|
|
34423
|
+
import path51 from "path";
|
|
34424
|
+
import os42 from "os";
|
|
34299
34425
|
function decodeProjectDirName(dirName) {
|
|
34300
34426
|
return dirName.replace(/-/g, "/");
|
|
34301
34427
|
}
|
|
34302
34428
|
function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
34303
|
-
const runId =
|
|
34429
|
+
const runId = path51.basename(filePath, ".jsonl");
|
|
34304
34430
|
let content;
|
|
34305
34431
|
try {
|
|
34306
|
-
content =
|
|
34432
|
+
content = fs50.readFileSync(filePath, "utf8");
|
|
34307
34433
|
} catch {
|
|
34308
34434
|
return /* @__PURE__ */ new Map();
|
|
34309
34435
|
}
|
|
@@ -34403,7 +34529,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
|
|
|
34403
34529
|
signal: AbortSignal.timeout(15e3)
|
|
34404
34530
|
});
|
|
34405
34531
|
if (!res.ok) {
|
|
34406
|
-
|
|
34532
|
+
fs50.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
34407
34533
|
`);
|
|
34408
34534
|
} else {
|
|
34409
34535
|
let stored;
|
|
@@ -34425,8 +34551,8 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
|
|
|
34425
34551
|
}
|
|
34426
34552
|
function debugLog(line) {
|
|
34427
34553
|
try {
|
|
34428
|
-
|
|
34429
|
-
|
|
34554
|
+
fs50.mkdirSync(path51.dirname(HOOK_DEBUG_LOG), { recursive: true });
|
|
34555
|
+
fs50.appendFileSync(HOOK_DEBUG_LOG, line);
|
|
34430
34556
|
} catch (err2) {
|
|
34431
34557
|
console.error(`[cost-sync] could not write ${HOOK_DEBUG_LOG}: ${safeMessage(err2)}`);
|
|
34432
34558
|
}
|
|
@@ -34439,10 +34565,10 @@ async function syncCost() {
|
|
|
34439
34565
|
if (entries.length === 0) return;
|
|
34440
34566
|
let username = "unknown";
|
|
34441
34567
|
try {
|
|
34442
|
-
username =
|
|
34568
|
+
username = os42.userInfo().username;
|
|
34443
34569
|
} catch {
|
|
34444
34570
|
}
|
|
34445
|
-
const machineId = `${
|
|
34571
|
+
const machineId = `${os42.hostname()}:${username}`;
|
|
34446
34572
|
await postCostBatches(creds.apiUrl, creds.apiKey, machineId, entries);
|
|
34447
34573
|
}
|
|
34448
34574
|
function startCostSync() {
|
|
@@ -34470,22 +34596,22 @@ var init_costSync = __esm({
|
|
|
34470
34596
|
claudeSource = {
|
|
34471
34597
|
id: "claude",
|
|
34472
34598
|
available() {
|
|
34473
|
-
return
|
|
34599
|
+
return fs50.existsSync(path51.join(os42.homedir(), ".claude", "projects"));
|
|
34474
34600
|
},
|
|
34475
34601
|
collect(sinceMs) {
|
|
34476
|
-
const projectsDir =
|
|
34477
|
-
if (!
|
|
34602
|
+
const projectsDir = path51.join(os42.homedir(), ".claude", "projects");
|
|
34603
|
+
if (!fs50.existsSync(projectsDir)) return [];
|
|
34478
34604
|
const combined2 = /* @__PURE__ */ new Map();
|
|
34479
34605
|
let dirs;
|
|
34480
34606
|
try {
|
|
34481
|
-
dirs =
|
|
34607
|
+
dirs = fs50.readdirSync(projectsDir);
|
|
34482
34608
|
} catch {
|
|
34483
34609
|
return [];
|
|
34484
34610
|
}
|
|
34485
34611
|
for (const dir2 of dirs) {
|
|
34486
|
-
const dirPath =
|
|
34612
|
+
const dirPath = path51.join(projectsDir, dir2);
|
|
34487
34613
|
try {
|
|
34488
|
-
if (!
|
|
34614
|
+
if (!fs50.statSync(dirPath).isDirectory()) continue;
|
|
34489
34615
|
} catch {
|
|
34490
34616
|
continue;
|
|
34491
34617
|
}
|
|
@@ -34497,10 +34623,10 @@ var init_costSync = __esm({
|
|
|
34497
34623
|
}
|
|
34498
34624
|
const fallbackWorkingDir = decodeProjectDirName(dir2);
|
|
34499
34625
|
for (const file2 of files) {
|
|
34500
|
-
const filePath =
|
|
34626
|
+
const filePath = path51.join(dirPath, file2);
|
|
34501
34627
|
if (sinceMs !== void 0) {
|
|
34502
34628
|
try {
|
|
34503
|
-
if (
|
|
34629
|
+
if (fs50.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
34504
34630
|
} catch {
|
|
34505
34631
|
continue;
|
|
34506
34632
|
}
|
|
@@ -34736,7 +34862,7 @@ function buildScanSummary(agents) {
|
|
|
34736
34862
|
};
|
|
34737
34863
|
}
|
|
34738
34864
|
function wouldHaveVerb(summary, past = "would have blocked") {
|
|
34739
|
-
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;
|
|
34740
34866
|
}
|
|
34741
34867
|
function buildSections(findings) {
|
|
34742
34868
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
@@ -34872,10 +34998,10 @@ var init_scan_summary = __esm({
|
|
|
34872
34998
|
});
|
|
34873
34999
|
|
|
34874
35000
|
// src/credential-files.ts
|
|
34875
|
-
import
|
|
35001
|
+
import path52 from "path";
|
|
34876
35002
|
function homeCredentialFiles(home) {
|
|
34877
35003
|
const f = (parts, description, score, kind = "credential") => ({
|
|
34878
|
-
full:
|
|
35004
|
+
full: path52.join(home, ...parts),
|
|
34879
35005
|
label: "~/" + parts.join("/"),
|
|
34880
35006
|
description,
|
|
34881
35007
|
score,
|
|
@@ -34896,8 +35022,7 @@ function homeCredentialFiles(home) {
|
|
|
34896
35022
|
),
|
|
34897
35023
|
f([".docker", "config.json"], "Docker registry auth tokens", 10),
|
|
34898
35024
|
f([".netrc"], "FTP/HTTP credentials in plain text", 15),
|
|
34899
|
-
f([".npmrc"], "npm auth token: can publish packages as you", 10)
|
|
34900
|
-
f([".node9", "credentials.json"], "Node9 cloud API key", 10)
|
|
35025
|
+
f([".npmrc"], "npm auth token: can publish packages as you", 10)
|
|
34901
35026
|
];
|
|
34902
35027
|
}
|
|
34903
35028
|
var init_credential_files = __esm({
|
|
@@ -34908,9 +35033,9 @@ var init_credential_files = __esm({
|
|
|
34908
35033
|
|
|
34909
35034
|
// src/cli/commands/blast.ts
|
|
34910
35035
|
import chalk3 from "chalk";
|
|
34911
|
-
import
|
|
34912
|
-
import
|
|
34913
|
-
import
|
|
35036
|
+
import fs51 from "fs";
|
|
35037
|
+
import path53 from "path";
|
|
35038
|
+
import os43 from "os";
|
|
34914
35039
|
function buildSensitivePaths(home, cwd) {
|
|
34915
35040
|
const homeFiles = homeCredentialFiles(home).map(({ full, label: label3, description, score }) => ({
|
|
34916
35041
|
full,
|
|
@@ -34921,19 +35046,19 @@ function buildSensitivePaths(home, cwd) {
|
|
|
34921
35046
|
return [
|
|
34922
35047
|
...homeFiles,
|
|
34923
35048
|
{
|
|
34924
|
-
full:
|
|
35049
|
+
full: path53.join(cwd, ".env"),
|
|
34925
35050
|
label: ".env (current folder)",
|
|
34926
35051
|
description: "App secrets: database passwords, API keys",
|
|
34927
35052
|
score: 20
|
|
34928
35053
|
},
|
|
34929
35054
|
{
|
|
34930
|
-
full:
|
|
35055
|
+
full: path53.join(cwd, ".env.local"),
|
|
34931
35056
|
label: ".env.local (current folder)",
|
|
34932
35057
|
description: "Local overrides: often contains real credentials",
|
|
34933
35058
|
score: 15
|
|
34934
35059
|
},
|
|
34935
35060
|
{
|
|
34936
|
-
full:
|
|
35061
|
+
full: path53.join(cwd, ".env.production"),
|
|
34937
35062
|
label: ".env.production (current folder)",
|
|
34938
35063
|
description: "Production secrets",
|
|
34939
35064
|
score: 20
|
|
@@ -34942,26 +35067,20 @@ function buildSensitivePaths(home, cwd) {
|
|
|
34942
35067
|
}
|
|
34943
35068
|
function isReadable(filePath) {
|
|
34944
35069
|
try {
|
|
34945
|
-
|
|
35070
|
+
fs51.accessSync(filePath, fs51.constants.R_OK);
|
|
34946
35071
|
return true;
|
|
34947
35072
|
} catch {
|
|
34948
35073
|
return false;
|
|
34949
35074
|
}
|
|
34950
35075
|
}
|
|
34951
|
-
function scoreLabel(score) {
|
|
34952
|
-
if (score >= 80) return chalk3.green(`${score}/100 Good`);
|
|
34953
|
-
if (score >= 50) return chalk3.yellow(`${score}/100 Moderate risk`);
|
|
34954
|
-
if (score >= 25) return chalk3.red(`${score}/100 High risk`);
|
|
34955
|
-
return chalk3.red.bold(`${score}/100 Critical`);
|
|
34956
|
-
}
|
|
34957
35076
|
function runBlast() {
|
|
34958
|
-
const home =
|
|
35077
|
+
const home = os43.homedir();
|
|
34959
35078
|
const cwd = process.cwd();
|
|
34960
35079
|
const paths = buildSensitivePaths(home, cwd);
|
|
34961
35080
|
let scoreDeduction = 0;
|
|
34962
35081
|
const reachable = [];
|
|
34963
35082
|
for (const p of paths) {
|
|
34964
|
-
if (
|
|
35083
|
+
if (fs51.existsSync(p.full) && isReadable(p.full)) {
|
|
34965
35084
|
reachable.push(p);
|
|
34966
35085
|
scoreDeduction += p.score;
|
|
34967
35086
|
}
|
|
@@ -34977,11 +35096,18 @@ function runBlast() {
|
|
|
34977
35096
|
}
|
|
34978
35097
|
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
34979
35098
|
}
|
|
35099
|
+
function labelColumnWidth(labels) {
|
|
35100
|
+
return Math.max(38, ...labels.map((l) => l.length + 2));
|
|
35101
|
+
}
|
|
34980
35102
|
function registerBlastCommand(program2) {
|
|
34981
35103
|
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
34982
|
-
const home =
|
|
35104
|
+
const home = os43.homedir();
|
|
34983
35105
|
const cwd = process.cwd();
|
|
34984
|
-
const { reachable, envFindings
|
|
35106
|
+
const { reachable, envFindings } = runBlast();
|
|
35107
|
+
const width = labelColumnWidth([
|
|
35108
|
+
...reachable.map((p) => p.label),
|
|
35109
|
+
...envFindings.map((f) => f.key)
|
|
35110
|
+
]);
|
|
34985
35111
|
console.log("");
|
|
34986
35112
|
console.log(
|
|
34987
35113
|
chalk3.bold(" \u{1F52D} Node9 Blast Radius") + chalk3.dim(" \xB7 what an AI agent can reach from here")
|
|
@@ -34992,7 +35118,7 @@ function registerBlastCommand(program2) {
|
|
|
34992
35118
|
console.log(" " + chalk3.red.bold("Sensitive files reachable:"));
|
|
34993
35119
|
for (const p of reachable) {
|
|
34994
35120
|
console.log(
|
|
34995
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(
|
|
35121
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(width)) + chalk3.dim(p.description)
|
|
34996
35122
|
);
|
|
34997
35123
|
}
|
|
34998
35124
|
console.log("");
|
|
@@ -35001,7 +35127,7 @@ function registerBlastCommand(program2) {
|
|
|
35001
35127
|
console.log(" " + chalk3.red.bold("Secrets in active environment:"));
|
|
35002
35128
|
for (const f of envFindings) {
|
|
35003
35129
|
console.log(
|
|
35004
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(
|
|
35130
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(width)) + chalk3.dim(f.patternName)
|
|
35005
35131
|
);
|
|
35006
35132
|
}
|
|
35007
35133
|
console.log("");
|
|
@@ -35009,21 +35135,24 @@ function registerBlastCommand(program2) {
|
|
|
35009
35135
|
console.log(" " + chalk3.dim("\u2500".repeat(70)));
|
|
35010
35136
|
if (reachable.length === 0 && envFindings.length === 0) {
|
|
35011
35137
|
console.log(" " + chalk3.green("\u2705 No sensitive files or environment secrets found."));
|
|
35012
|
-
console.log(" Security Score: " + scoreLabel(score));
|
|
35013
35138
|
} else {
|
|
35014
35139
|
console.log(
|
|
35015
|
-
|
|
35016
|
-
`
|
|
35017
|
-
)
|
|
35140
|
+
chalk3.bold(
|
|
35141
|
+
` ${reachable.length} file${reachable.length !== 1 ? "s" : ""} and ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""}`
|
|
35142
|
+
) + chalk3.dim(" every AI agent you start can read.")
|
|
35018
35143
|
);
|
|
35019
35144
|
console.log("");
|
|
35020
35145
|
console.log(
|
|
35021
35146
|
chalk3.dim(
|
|
35022
|
-
"
|
|
35147
|
+
" Run `node9 shield enable project-jail` to restrict agent file access.\n Run `node9 mask` to redact secrets from existing session history."
|
|
35023
35148
|
)
|
|
35024
35149
|
);
|
|
35025
35150
|
}
|
|
35026
35151
|
console.log("");
|
|
35152
|
+
console.log(
|
|
35153
|
+
chalk3.dim(" Machine score: ") + chalk3.cyan("node9 posture") + chalk3.dim(" (this list is its Secrets section)")
|
|
35154
|
+
);
|
|
35155
|
+
console.log("");
|
|
35027
35156
|
});
|
|
35028
35157
|
}
|
|
35029
35158
|
var init_blast = __esm({
|
|
@@ -35116,6 +35245,12 @@ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
|
35116
35245
|
if (days > 90) return "90d+";
|
|
35117
35246
|
return `${days}d`;
|
|
35118
35247
|
}
|
|
35248
|
+
function relativeAgo(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
35249
|
+
const rel = relativeDate(timestamp2, now);
|
|
35250
|
+
if (rel === "?" || rel === "today") return rel;
|
|
35251
|
+
if (rel === "1d") return "yesterday";
|
|
35252
|
+
return `${rel} ago`;
|
|
35253
|
+
}
|
|
35119
35254
|
var PANEL_WIDTH;
|
|
35120
35255
|
var init_scan_derive = __esm({
|
|
35121
35256
|
"src/cli/render/scan-derive.ts"() {
|
|
@@ -35125,13 +35260,14 @@ var init_scan_derive = __esm({
|
|
|
35125
35260
|
});
|
|
35126
35261
|
|
|
35127
35262
|
// src/protection.ts
|
|
35128
|
-
|
|
35263
|
+
function isProtectiveShield(name) {
|
|
35264
|
+
return PROTECTIVE_SHIELDS.has(name);
|
|
35265
|
+
}
|
|
35266
|
+
var PROTECTIVE_SHIELDS;
|
|
35129
35267
|
var init_protection = __esm({
|
|
35130
35268
|
"src/protection.ts"() {
|
|
35131
35269
|
"use strict";
|
|
35132
|
-
|
|
35133
|
-
"project-jail": 0.7
|
|
35134
|
-
};
|
|
35270
|
+
PROTECTIVE_SHIELDS = /* @__PURE__ */ new Set(["project-jail"]);
|
|
35135
35271
|
}
|
|
35136
35272
|
});
|
|
35137
35273
|
|
|
@@ -35169,35 +35305,21 @@ var init_scan_json = __esm({
|
|
|
35169
35305
|
});
|
|
35170
35306
|
|
|
35171
35307
|
// src/cli/render/scan-history.ts
|
|
35172
|
-
import
|
|
35173
|
-
import
|
|
35174
|
-
import
|
|
35308
|
+
import fs52 from "fs";
|
|
35309
|
+
import path54 from "path";
|
|
35310
|
+
import os44 from "os";
|
|
35175
35311
|
function defaultHistoryPath() {
|
|
35176
|
-
return
|
|
35177
|
-
}
|
|
35178
|
-
function readPreviousScan(opts = {}) {
|
|
35179
|
-
const filePath = opts.path ?? defaultHistoryPath();
|
|
35180
|
-
try {
|
|
35181
|
-
if (!fs51.existsSync(filePath)) return null;
|
|
35182
|
-
const raw = fs51.readFileSync(filePath, "utf8");
|
|
35183
|
-
const parsed = JSON.parse(raw);
|
|
35184
|
-
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
35185
|
-
const last = parsed[parsed.length - 1];
|
|
35186
|
-
if (!isValidRecord(last)) return null;
|
|
35187
|
-
return last;
|
|
35188
|
-
} catch {
|
|
35189
|
-
return null;
|
|
35190
|
-
}
|
|
35312
|
+
return path54.join(os44.homedir(), ".node9", "scan-history.json");
|
|
35191
35313
|
}
|
|
35192
35314
|
function appendScanHistory(record2, opts = {}) {
|
|
35193
35315
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
35194
35316
|
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
35195
35317
|
try {
|
|
35196
|
-
|
|
35318
|
+
fs52.mkdirSync(path54.dirname(filePath), { recursive: true });
|
|
35197
35319
|
let history = [];
|
|
35198
|
-
if (
|
|
35320
|
+
if (fs52.existsSync(filePath)) {
|
|
35199
35321
|
try {
|
|
35200
|
-
const parsed = JSON.parse(
|
|
35322
|
+
const parsed = JSON.parse(fs52.readFileSync(filePath, "utf8"));
|
|
35201
35323
|
if (Array.isArray(parsed)) {
|
|
35202
35324
|
history = parsed.filter(isValidRecord);
|
|
35203
35325
|
}
|
|
@@ -35216,15 +35338,6 @@ function appendScanHistory(record2, opts = {}) {
|
|
|
35216
35338
|
);
|
|
35217
35339
|
}
|
|
35218
35340
|
}
|
|
35219
|
-
function computeScanDelta(current, previous, now = Date.now()) {
|
|
35220
|
-
if (!previous) return null;
|
|
35221
|
-
const prevMs = Date.parse(previous.timestamp);
|
|
35222
|
-
if (Number.isNaN(prevMs)) return null;
|
|
35223
|
-
const scoreDelta = current.score - previous.score;
|
|
35224
|
-
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
35225
|
-
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
35226
|
-
return { scoreDelta, daysAgo };
|
|
35227
|
-
}
|
|
35228
35341
|
function isValidRecord(x) {
|
|
35229
35342
|
if (typeof x !== "object" || x === null) return false;
|
|
35230
35343
|
const r = x;
|
|
@@ -35252,9 +35365,9 @@ __export(scan_watermark_exports, {
|
|
|
35252
35365
|
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
35253
35366
|
tickScanWatcher: () => tickScanWatcher
|
|
35254
35367
|
});
|
|
35255
|
-
import
|
|
35256
|
-
import
|
|
35257
|
-
import
|
|
35368
|
+
import fs53 from "fs";
|
|
35369
|
+
import os45 from "os";
|
|
35370
|
+
import path55 from "path";
|
|
35258
35371
|
import readline from "readline";
|
|
35259
35372
|
function freshWatermark() {
|
|
35260
35373
|
return {
|
|
@@ -35267,7 +35380,7 @@ function freshWatermark() {
|
|
|
35267
35380
|
function loadWatermark() {
|
|
35268
35381
|
let raw;
|
|
35269
35382
|
try {
|
|
35270
|
-
raw =
|
|
35383
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35271
35384
|
} catch {
|
|
35272
35385
|
return { status: "fresh", wm: freshWatermark() };
|
|
35273
35386
|
}
|
|
@@ -35319,28 +35432,28 @@ function loadWatermark() {
|
|
|
35319
35432
|
function saveWatermark(wm) {
|
|
35320
35433
|
if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
|
|
35321
35434
|
const target = WATERMARK_FILE();
|
|
35322
|
-
const dir2 =
|
|
35323
|
-
if (!
|
|
35435
|
+
const dir2 = path55.dirname(target);
|
|
35436
|
+
if (!fs53.existsSync(dir2)) fs53.mkdirSync(dir2, { recursive: true });
|
|
35324
35437
|
const tmp = target + ".tmp";
|
|
35325
|
-
|
|
35326
|
-
|
|
35438
|
+
fs53.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
|
|
35439
|
+
fs53.renameSync(tmp, target);
|
|
35327
35440
|
}
|
|
35328
35441
|
function listJsonlFiles() {
|
|
35329
35442
|
const root = PROJECTS_DIR();
|
|
35330
|
-
if (!
|
|
35443
|
+
if (!fs53.existsSync(root)) return [];
|
|
35331
35444
|
const out = [];
|
|
35332
|
-
for (const entry of
|
|
35445
|
+
for (const entry of fs53.readdirSync(root, { withFileTypes: true })) {
|
|
35333
35446
|
if (!entry.isDirectory()) continue;
|
|
35334
|
-
const projectDir =
|
|
35447
|
+
const projectDir = path55.join(root, entry.name);
|
|
35335
35448
|
let inner;
|
|
35336
35449
|
try {
|
|
35337
|
-
inner =
|
|
35450
|
+
inner = fs53.readdirSync(projectDir, { withFileTypes: true });
|
|
35338
35451
|
} catch {
|
|
35339
35452
|
continue;
|
|
35340
35453
|
}
|
|
35341
35454
|
for (const file2 of inner) {
|
|
35342
35455
|
if (file2.isFile() && file2.name.endsWith(".jsonl")) {
|
|
35343
|
-
out.push(
|
|
35456
|
+
out.push(path55.join(projectDir, file2.name));
|
|
35344
35457
|
}
|
|
35345
35458
|
}
|
|
35346
35459
|
}
|
|
@@ -35348,7 +35461,7 @@ function listJsonlFiles() {
|
|
|
35348
35461
|
}
|
|
35349
35462
|
function fileSize(p) {
|
|
35350
35463
|
try {
|
|
35351
|
-
return
|
|
35464
|
+
return fs53.statSync(p).size;
|
|
35352
35465
|
} catch {
|
|
35353
35466
|
return 0;
|
|
35354
35467
|
}
|
|
@@ -35358,7 +35471,7 @@ async function scanDelta(filePath, fromByte, onLine) {
|
|
|
35358
35471
|
if (size <= fromByte) return fromByte;
|
|
35359
35472
|
const lastNl = findLastNewline(filePath, fromByte, size);
|
|
35360
35473
|
const endsWithNewline = lastNl === size - 1;
|
|
35361
|
-
const stream =
|
|
35474
|
+
const stream = fs53.createReadStream(filePath, {
|
|
35362
35475
|
start: fromByte,
|
|
35363
35476
|
end: size - 1,
|
|
35364
35477
|
highWaterMark: 64 * 1024
|
|
@@ -35387,7 +35500,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35387
35500
|
const CHUNK = 64 * 1024;
|
|
35388
35501
|
let fd;
|
|
35389
35502
|
try {
|
|
35390
|
-
fd =
|
|
35503
|
+
fd = fs53.openSync(filePath, "r");
|
|
35391
35504
|
} catch {
|
|
35392
35505
|
return -1;
|
|
35393
35506
|
}
|
|
@@ -35396,7 +35509,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35396
35509
|
let end = size;
|
|
35397
35510
|
while (end > from) {
|
|
35398
35511
|
const start = Math.max(from, end - CHUNK);
|
|
35399
|
-
const n =
|
|
35512
|
+
const n = fs53.readSync(fd, buf, 0, end - start, start);
|
|
35400
35513
|
const idx = buf.subarray(0, n).lastIndexOf(10);
|
|
35401
35514
|
if (idx !== -1) return start + idx;
|
|
35402
35515
|
end = start;
|
|
@@ -35405,7 +35518,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35405
35518
|
} catch {
|
|
35406
35519
|
return -1;
|
|
35407
35520
|
} finally {
|
|
35408
|
-
|
|
35521
|
+
fs53.closeSync(fd);
|
|
35409
35522
|
}
|
|
35410
35523
|
}
|
|
35411
35524
|
function safeCanaryCtxValues() {
|
|
@@ -35416,7 +35529,7 @@ function safeCanaryCtxValues() {
|
|
|
35416
35529
|
}
|
|
35417
35530
|
}
|
|
35418
35531
|
function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
35419
|
-
const cwd = typeof obj.cwd === "string" &&
|
|
35532
|
+
const cwd = typeof obj.cwd === "string" && path55.isAbsolute(obj.cwd) ? obj.cwd : void 0;
|
|
35420
35533
|
return {
|
|
35421
35534
|
sessionId,
|
|
35422
35535
|
lineIndex,
|
|
@@ -35432,7 +35545,7 @@ function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
|
35432
35545
|
// line-level DLP runs above already
|
|
35433
35546
|
canaryValues: canaryVals,
|
|
35434
35547
|
...cwd && { cwd },
|
|
35435
|
-
homeDirs: [
|
|
35548
|
+
homeDirs: [os45.homedir()]
|
|
35436
35549
|
};
|
|
35437
35550
|
}
|
|
35438
35551
|
function extractFindingsFromLine(line, sessionId, lineIndex, canaryVals = []) {
|
|
@@ -35520,7 +35633,7 @@ async function tickForensicBroadcast(offsets) {
|
|
|
35520
35633
|
continue;
|
|
35521
35634
|
}
|
|
35522
35635
|
if (size <= offset) continue;
|
|
35523
|
-
const sessionId =
|
|
35636
|
+
const sessionId = path55.basename(file2, ".jsonl");
|
|
35524
35637
|
const newOffset = await scanDelta(file2, offset, (obj, lineIndex) => {
|
|
35525
35638
|
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex, canaryVals));
|
|
35526
35639
|
});
|
|
@@ -35576,7 +35689,7 @@ function emptyTick(uploadAs) {
|
|
|
35576
35689
|
function readRawWatermarkPreservingOffsets() {
|
|
35577
35690
|
let raw;
|
|
35578
35691
|
try {
|
|
35579
|
-
raw =
|
|
35692
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35580
35693
|
} catch {
|
|
35581
35694
|
return null;
|
|
35582
35695
|
}
|
|
@@ -35611,13 +35724,13 @@ async function runActualTick(wm) {
|
|
|
35611
35724
|
if (!known) {
|
|
35612
35725
|
let mtimeMs = 0;
|
|
35613
35726
|
try {
|
|
35614
|
-
mtimeMs =
|
|
35727
|
+
mtimeMs = fs53.statSync(filePath).mtime.getTime();
|
|
35615
35728
|
} catch {
|
|
35616
35729
|
continue;
|
|
35617
35730
|
}
|
|
35618
35731
|
if (mtimeMs >= watermarkCreatedAt) {
|
|
35619
35732
|
filesNew++;
|
|
35620
|
-
const sessionId2 =
|
|
35733
|
+
const sessionId2 = path55.basename(filePath, ".jsonl");
|
|
35621
35734
|
const newScannedTo2 = await scanDelta(filePath, 0, (obj, lineIndex) => {
|
|
35622
35735
|
totalToolCalls++;
|
|
35623
35736
|
toolCallsBySession[sessionId2] = (toolCallsBySession[sessionId2] ?? 0) + 1;
|
|
@@ -35635,7 +35748,7 @@ async function runActualTick(wm) {
|
|
|
35635
35748
|
filesSkipped++;
|
|
35636
35749
|
continue;
|
|
35637
35750
|
}
|
|
35638
|
-
const sessionId =
|
|
35751
|
+
const sessionId = path55.basename(filePath, ".jsonl");
|
|
35639
35752
|
const newScannedTo = await scanDelta(filePath, known.scannedTo, (obj, lineIndex) => {
|
|
35640
35753
|
totalToolCalls++;
|
|
35641
35754
|
toolCallsBySession[sessionId] = (toolCallsBySession[sessionId] ?? 0) + 1;
|
|
@@ -35670,8 +35783,8 @@ var init_scan_watermark = __esm({
|
|
|
35670
35783
|
init_registry2();
|
|
35671
35784
|
init_config();
|
|
35672
35785
|
init_dist();
|
|
35673
|
-
PROJECTS_DIR = () =>
|
|
35674
|
-
WATERMARK_FILE = () =>
|
|
35786
|
+
PROJECTS_DIR = () => path55.join(os45.homedir(), ".claude", "projects");
|
|
35787
|
+
WATERMARK_FILE = () => path55.join(os45.homedir(), ".node9", "scan-watermark.json");
|
|
35675
35788
|
MAX_LINE_BYTES = 2 * 1024 * 1024;
|
|
35676
35789
|
WATERMARK_SCHEMA_VERSION = 2;
|
|
35677
35790
|
LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
|
|
@@ -35687,10 +35800,10 @@ __export(scan_upload_history_exports, {
|
|
|
35687
35800
|
parseSinceCutoff: () => parseSinceCutoff,
|
|
35688
35801
|
runUploadHistory: () => runUploadHistory
|
|
35689
35802
|
});
|
|
35690
|
-
import
|
|
35803
|
+
import fs54 from "fs";
|
|
35691
35804
|
import https from "https";
|
|
35692
|
-
import
|
|
35693
|
-
import
|
|
35805
|
+
import os46 from "os";
|
|
35806
|
+
import path56 from "path";
|
|
35694
35807
|
import chalk5 from "chalk";
|
|
35695
35808
|
function emptySignals2() {
|
|
35696
35809
|
return {
|
|
@@ -35725,40 +35838,40 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
|
|
|
35725
35838
|
return now.getTime() - 90 * 864e5;
|
|
35726
35839
|
}
|
|
35727
35840
|
function* iterateJsonlFiles(cutoffMs) {
|
|
35728
|
-
const projectsDir =
|
|
35841
|
+
const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
|
|
35729
35842
|
let dirs;
|
|
35730
35843
|
try {
|
|
35731
|
-
dirs =
|
|
35844
|
+
dirs = fs54.readdirSync(projectsDir);
|
|
35732
35845
|
} catch {
|
|
35733
35846
|
return;
|
|
35734
35847
|
}
|
|
35735
35848
|
for (const dir2 of dirs) {
|
|
35736
|
-
const dirPath =
|
|
35849
|
+
const dirPath = path56.join(projectsDir, dir2);
|
|
35737
35850
|
let stats;
|
|
35738
35851
|
try {
|
|
35739
|
-
stats =
|
|
35852
|
+
stats = fs54.statSync(dirPath);
|
|
35740
35853
|
} catch {
|
|
35741
35854
|
continue;
|
|
35742
35855
|
}
|
|
35743
35856
|
if (!stats.isDirectory()) continue;
|
|
35744
35857
|
let files;
|
|
35745
35858
|
try {
|
|
35746
|
-
files =
|
|
35859
|
+
files = fs54.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
35747
35860
|
} catch {
|
|
35748
35861
|
continue;
|
|
35749
35862
|
}
|
|
35750
35863
|
for (const file2 of files) {
|
|
35751
|
-
const filePath =
|
|
35864
|
+
const filePath = path56.join(dirPath, file2);
|
|
35752
35865
|
let mtime = 0;
|
|
35753
35866
|
try {
|
|
35754
|
-
mtime =
|
|
35867
|
+
mtime = fs54.statSync(filePath).mtimeMs;
|
|
35755
35868
|
} catch {
|
|
35756
35869
|
continue;
|
|
35757
35870
|
}
|
|
35758
35871
|
if (mtime < cutoffMs) continue;
|
|
35759
35872
|
yield {
|
|
35760
35873
|
filePath,
|
|
35761
|
-
sessionId:
|
|
35874
|
+
sessionId: path56.basename(file2, ".jsonl"),
|
|
35762
35875
|
projectDir: dir2
|
|
35763
35876
|
};
|
|
35764
35877
|
}
|
|
@@ -35826,7 +35939,7 @@ async function runUploadHistory(opts) {
|
|
|
35826
35939
|
filesScanned++;
|
|
35827
35940
|
let content;
|
|
35828
35941
|
try {
|
|
35829
|
-
content =
|
|
35942
|
+
content = fs54.readFileSync(filePath, "utf8");
|
|
35830
35943
|
} catch {
|
|
35831
35944
|
continue;
|
|
35832
35945
|
}
|
|
@@ -35888,7 +36001,7 @@ async function runUploadHistory(opts) {
|
|
|
35888
36001
|
` Parsed ${linesParsed.toLocaleString()} JSONL lines from ${filesScanned} session${filesScanned === 1 ? "" : "s"} (skipped ${linesSkipped} malformed)`
|
|
35889
36002
|
)
|
|
35890
36003
|
);
|
|
35891
|
-
console.log(chalk5.gray(` Findings: ${findings.length}
|
|
36004
|
+
console.log(chalk5.gray(` Findings: ${findings.length}`));
|
|
35892
36005
|
const scanUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/scan/report") : `${creds.apiUrl.replace(/\/$/, "")}/scan/report`;
|
|
35893
36006
|
await postJson(scanUrl, creds.apiKey, {
|
|
35894
36007
|
...summary,
|
|
@@ -35900,10 +36013,10 @@ async function runUploadHistory(opts) {
|
|
|
35900
36013
|
const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
|
|
35901
36014
|
let username = "unknown";
|
|
35902
36015
|
try {
|
|
35903
|
-
username =
|
|
36016
|
+
username = os46.userInfo().username;
|
|
35904
36017
|
} catch {
|
|
35905
36018
|
}
|
|
35906
|
-
const machineId = `${
|
|
36019
|
+
const machineId = `${os46.hostname()}:${username}`;
|
|
35907
36020
|
await postJson(costUrl, creds.apiKey, {
|
|
35908
36021
|
machineId,
|
|
35909
36022
|
entries: dailyEntries
|
|
@@ -35977,9 +36090,9 @@ var init_scan_upload_history = __esm({
|
|
|
35977
36090
|
|
|
35978
36091
|
// src/cli/commands/scan.ts
|
|
35979
36092
|
import chalk6 from "chalk";
|
|
35980
|
-
import
|
|
35981
|
-
import
|
|
35982
|
-
import
|
|
36093
|
+
import fs55 from "fs";
|
|
36094
|
+
import path57 from "path";
|
|
36095
|
+
import os47 from "os";
|
|
35983
36096
|
import stringWidth2 from "string-width";
|
|
35984
36097
|
function dayKey(ts) {
|
|
35985
36098
|
if (!ts) return "unknown";
|
|
@@ -36122,7 +36235,7 @@ function emptyScanDedup() {
|
|
|
36122
36235
|
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
|
|
36123
36236
|
}
|
|
36124
36237
|
function displayHome(p) {
|
|
36125
|
-
const home =
|
|
36238
|
+
const home = os47.homedir();
|
|
36126
36239
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
36127
36240
|
}
|
|
36128
36241
|
function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
|
|
@@ -36261,7 +36374,7 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
36261
36374
|
return findings.sort((a, b) => b.count - a.count);
|
|
36262
36375
|
}
|
|
36263
36376
|
function geminiChatFiles(chatsDir) {
|
|
36264
|
-
const files =
|
|
36377
|
+
const files = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
36265
36378
|
const seen = /* @__PURE__ */ new Set();
|
|
36266
36379
|
return files.filter((f) => {
|
|
36267
36380
|
const id = f.replace(/\.jsonl?$/, "");
|
|
@@ -36273,14 +36386,14 @@ function geminiChatFiles(chatsDir) {
|
|
|
36273
36386
|
function countScanFiles(startDate = null) {
|
|
36274
36387
|
let total = 0;
|
|
36275
36388
|
const counts = (p) => fileMayHoldWindow(fileStamp(p), startDate);
|
|
36276
|
-
const claudeDir =
|
|
36277
|
-
if (
|
|
36389
|
+
const claudeDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36390
|
+
if (fs55.existsSync(claudeDir)) {
|
|
36278
36391
|
try {
|
|
36279
|
-
for (const proj of
|
|
36280
|
-
const p =
|
|
36392
|
+
for (const proj of fs55.readdirSync(claudeDir)) {
|
|
36393
|
+
const p = path57.join(claudeDir, proj);
|
|
36281
36394
|
try {
|
|
36282
|
-
if (!
|
|
36283
|
-
total += listSessionFiles(p).filter((f) => counts(
|
|
36395
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36396
|
+
total += listSessionFiles(p).filter((f) => counts(path57.join(p, f))).length;
|
|
36284
36397
|
} catch {
|
|
36285
36398
|
continue;
|
|
36286
36399
|
}
|
|
@@ -36288,18 +36401,18 @@ function countScanFiles(startDate = null) {
|
|
|
36288
36401
|
} catch {
|
|
36289
36402
|
}
|
|
36290
36403
|
}
|
|
36291
|
-
const geminiDir =
|
|
36292
|
-
if (
|
|
36404
|
+
const geminiDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36405
|
+
if (fs55.existsSync(geminiDir)) {
|
|
36293
36406
|
try {
|
|
36294
|
-
for (const slug2 of
|
|
36295
|
-
const p =
|
|
36407
|
+
for (const slug2 of fs55.readdirSync(geminiDir)) {
|
|
36408
|
+
const p = path57.join(geminiDir, slug2);
|
|
36296
36409
|
try {
|
|
36297
|
-
if (!
|
|
36298
|
-
const chatsDir =
|
|
36299
|
-
if (
|
|
36410
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36411
|
+
const chatsDir = path57.join(p, "chats");
|
|
36412
|
+
if (fs55.existsSync(chatsDir)) {
|
|
36300
36413
|
try {
|
|
36301
36414
|
total += geminiChatFiles(chatsDir).filter(
|
|
36302
|
-
(f) => counts(
|
|
36415
|
+
(f) => counts(path57.join(chatsDir, f))
|
|
36303
36416
|
).length;
|
|
36304
36417
|
} catch {
|
|
36305
36418
|
}
|
|
@@ -36312,13 +36425,13 @@ function countScanFiles(startDate = null) {
|
|
|
36312
36425
|
}
|
|
36313
36426
|
}
|
|
36314
36427
|
for (const surface of ["antigravity-cli", "antigravity-ide"]) {
|
|
36315
|
-
const brainDir =
|
|
36316
|
-
if (!
|
|
36428
|
+
const brainDir = path57.join(os47.homedir(), ".gemini", surface, "brain");
|
|
36429
|
+
if (!fs55.existsSync(brainDir)) continue;
|
|
36317
36430
|
try {
|
|
36318
|
-
for (const conv of
|
|
36319
|
-
const convPath =
|
|
36431
|
+
for (const conv of fs55.readdirSync(brainDir)) {
|
|
36432
|
+
const convPath = path57.join(brainDir, conv);
|
|
36320
36433
|
try {
|
|
36321
|
-
if (!
|
|
36434
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36322
36435
|
const transcript = antigravityTranscriptPath(convPath);
|
|
36323
36436
|
if (transcript && counts(transcript)) total += 1;
|
|
36324
36437
|
} catch {
|
|
@@ -36328,12 +36441,12 @@ function countScanFiles(startDate = null) {
|
|
|
36328
36441
|
} catch {
|
|
36329
36442
|
}
|
|
36330
36443
|
}
|
|
36331
|
-
const copilotDir =
|
|
36332
|
-
if (
|
|
36444
|
+
const copilotDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
36445
|
+
if (fs55.existsSync(copilotDir)) {
|
|
36333
36446
|
try {
|
|
36334
|
-
for (const sid of
|
|
36335
|
-
const events =
|
|
36336
|
-
if (
|
|
36447
|
+
for (const sid of fs55.readdirSync(copilotDir)) {
|
|
36448
|
+
const events = path57.join(copilotDir, sid, "events.jsonl");
|
|
36449
|
+
if (fs55.existsSync(events) && counts(events)) total += 1;
|
|
36337
36450
|
}
|
|
36338
36451
|
} catch {
|
|
36339
36452
|
}
|
|
@@ -36357,7 +36470,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36357
36470
|
const session = { sessionId, costUSD: 0, toolCalls: 0 };
|
|
36358
36471
|
let raw;
|
|
36359
36472
|
try {
|
|
36360
|
-
raw =
|
|
36473
|
+
raw = fs55.readFileSync(path57.join(projPath, file2), "utf-8");
|
|
36361
36474
|
} catch {
|
|
36362
36475
|
return false;
|
|
36363
36476
|
}
|
|
@@ -36426,7 +36539,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36426
36539
|
if (block.type !== "tool_result") continue;
|
|
36427
36540
|
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
36428
36541
|
if (filePath) {
|
|
36429
|
-
const ext =
|
|
36542
|
+
const ext = path57.extname(filePath).toLowerCase();
|
|
36430
36543
|
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
36431
36544
|
}
|
|
36432
36545
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
@@ -36546,9 +36659,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36546
36659
|
return true;
|
|
36547
36660
|
}
|
|
36548
36661
|
async function processClaudeProject(proj, projectsDir, judge, cache7, startDate, result, dedup, canaryVals, onProgress, onLine) {
|
|
36549
|
-
const projPath =
|
|
36662
|
+
const projPath = path57.join(projectsDir, proj);
|
|
36550
36663
|
try {
|
|
36551
|
-
if (!
|
|
36664
|
+
if (!fs55.statSync(projPath).isDirectory()) return;
|
|
36552
36665
|
} catch {
|
|
36553
36666
|
return;
|
|
36554
36667
|
}
|
|
@@ -36560,7 +36673,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36560
36673
|
return;
|
|
36561
36674
|
}
|
|
36562
36675
|
for (const file2 of files) {
|
|
36563
|
-
const filePath =
|
|
36676
|
+
const filePath = path57.join(projPath, file2);
|
|
36564
36677
|
const stamp = fileStamp(filePath);
|
|
36565
36678
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36566
36679
|
result.filesScanned++;
|
|
@@ -36591,7 +36704,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36591
36704
|
}
|
|
36592
36705
|
function claudeProjectLabel(dirName) {
|
|
36593
36706
|
const name = decodeURIComponent(dirName);
|
|
36594
|
-
const homeDashed =
|
|
36707
|
+
const homeDashed = os47.homedir().replace(/\//g, "-");
|
|
36595
36708
|
const label3 = name.startsWith(homeDashed) ? "~" + decodeProjectDirName(name.slice(homeDashed.length)) : decodeProjectDirName(name);
|
|
36596
36709
|
return stripTerminalEscapes(label3).slice(0, 40);
|
|
36597
36710
|
}
|
|
@@ -36702,12 +36815,12 @@ function mergeFileScan(result, file2, dedup, startDate = null) {
|
|
|
36702
36815
|
}
|
|
36703
36816
|
}
|
|
36704
36817
|
async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36705
|
-
const projectsDir =
|
|
36818
|
+
const projectsDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36706
36819
|
const result = emptyClaudeScan();
|
|
36707
|
-
if (!
|
|
36820
|
+
if (!fs55.existsSync(projectsDir)) return result;
|
|
36708
36821
|
let projDirs;
|
|
36709
36822
|
try {
|
|
36710
|
-
projDirs =
|
|
36823
|
+
projDirs = fs55.readdirSync(projectsDir);
|
|
36711
36824
|
} catch {
|
|
36712
36825
|
return result;
|
|
36713
36826
|
}
|
|
@@ -36735,7 +36848,7 @@ async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36735
36848
|
}
|
|
36736
36849
|
async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36737
36850
|
const canaryVals = safeCanaryScanValues();
|
|
36738
|
-
const tmpDir =
|
|
36851
|
+
const tmpDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36739
36852
|
const result = {
|
|
36740
36853
|
filesScanned: 0,
|
|
36741
36854
|
sessions: 0,
|
|
@@ -36752,31 +36865,31 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36752
36865
|
perSession: []
|
|
36753
36866
|
};
|
|
36754
36867
|
const dedup = emptyScanDedup();
|
|
36755
|
-
if (!
|
|
36868
|
+
if (!fs55.existsSync(tmpDir)) return result;
|
|
36756
36869
|
let slugDirs;
|
|
36757
36870
|
try {
|
|
36758
|
-
slugDirs =
|
|
36871
|
+
slugDirs = fs55.readdirSync(tmpDir);
|
|
36759
36872
|
} catch {
|
|
36760
36873
|
return result;
|
|
36761
36874
|
}
|
|
36762
36875
|
const judge = new HistoryJudge();
|
|
36763
36876
|
const cache7 = openScanCache(judge, opts);
|
|
36764
36877
|
for (const slug2 of slugDirs) {
|
|
36765
|
-
const slugPath =
|
|
36878
|
+
const slugPath = path57.join(tmpDir, slug2);
|
|
36766
36879
|
try {
|
|
36767
|
-
if (!
|
|
36880
|
+
if (!fs55.statSync(slugPath).isDirectory()) continue;
|
|
36768
36881
|
} catch {
|
|
36769
36882
|
continue;
|
|
36770
36883
|
}
|
|
36771
36884
|
let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
|
|
36772
36885
|
try {
|
|
36773
36886
|
projLabel = stripTerminalEscapes(
|
|
36774
|
-
|
|
36775
|
-
).replace(
|
|
36887
|
+
fs55.readFileSync(path57.join(slugPath, ".project_root"), "utf-8").trim()
|
|
36888
|
+
).replace(os47.homedir(), "~").slice(0, 40);
|
|
36776
36889
|
} catch {
|
|
36777
36890
|
}
|
|
36778
|
-
const chatsDir =
|
|
36779
|
-
if (!
|
|
36891
|
+
const chatsDir = path57.join(slugPath, "chats");
|
|
36892
|
+
if (!fs55.existsSync(chatsDir)) continue;
|
|
36780
36893
|
let chatFiles;
|
|
36781
36894
|
try {
|
|
36782
36895
|
chatFiles = geminiChatFiles(chatsDir);
|
|
@@ -36785,7 +36898,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36785
36898
|
}
|
|
36786
36899
|
for (const chatFile of chatFiles) {
|
|
36787
36900
|
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
36788
|
-
const filePath =
|
|
36901
|
+
const filePath = path57.join(chatsDir, chatFile);
|
|
36789
36902
|
const stamp = fileStamp(filePath);
|
|
36790
36903
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36791
36904
|
result.filesScanned++;
|
|
@@ -36799,7 +36912,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36799
36912
|
const fileDedup = emptyScanDedup();
|
|
36800
36913
|
let raw;
|
|
36801
36914
|
try {
|
|
36802
|
-
raw =
|
|
36915
|
+
raw = fs55.readFileSync(path57.join(chatsDir, chatFile), "utf-8");
|
|
36803
36916
|
} catch {
|
|
36804
36917
|
continue;
|
|
36805
36918
|
}
|
|
@@ -36936,13 +37049,13 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36936
37049
|
return result;
|
|
36937
37050
|
}
|
|
36938
37051
|
function antigravityBrainDirs() {
|
|
36939
|
-
return ["antigravity-cli", "antigravity-ide"].map((surface) =>
|
|
37052
|
+
return ["antigravity-cli", "antigravity-ide"].map((surface) => path57.join(os47.homedir(), ".gemini", surface, "brain")).filter((p) => fs55.existsSync(p));
|
|
36940
37053
|
}
|
|
36941
37054
|
function antigravityTranscriptPath(convPath) {
|
|
36942
|
-
const logsDir =
|
|
37055
|
+
const logsDir = path57.join(convPath, ".system_generated", "logs");
|
|
36943
37056
|
for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
|
|
36944
|
-
const p =
|
|
36945
|
-
if (
|
|
37057
|
+
const p = path57.join(logsDir, name);
|
|
37058
|
+
if (fs55.existsSync(p)) return p;
|
|
36946
37059
|
}
|
|
36947
37060
|
return null;
|
|
36948
37061
|
}
|
|
@@ -36972,14 +37085,14 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36972
37085
|
for (const brainDir of brainDirs) {
|
|
36973
37086
|
let convDirs;
|
|
36974
37087
|
try {
|
|
36975
|
-
convDirs =
|
|
37088
|
+
convDirs = fs55.readdirSync(brainDir);
|
|
36976
37089
|
} catch {
|
|
36977
37090
|
continue;
|
|
36978
37091
|
}
|
|
36979
37092
|
for (const conv of convDirs) {
|
|
36980
|
-
const convPath =
|
|
37093
|
+
const convPath = path57.join(brainDir, conv);
|
|
36981
37094
|
try {
|
|
36982
|
-
if (!
|
|
37095
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36983
37096
|
} catch {
|
|
36984
37097
|
continue;
|
|
36985
37098
|
}
|
|
@@ -36998,7 +37111,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36998
37111
|
const fileDedup = emptyScanDedup();
|
|
36999
37112
|
let raw;
|
|
37000
37113
|
try {
|
|
37001
|
-
raw =
|
|
37114
|
+
raw = fs55.readFileSync(transcriptFile, "utf-8");
|
|
37002
37115
|
} catch {
|
|
37003
37116
|
continue;
|
|
37004
37117
|
}
|
|
@@ -37073,7 +37186,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37073
37186
|
day.bashCalls++;
|
|
37074
37187
|
const cwd = String(input.cwd ?? "");
|
|
37075
37188
|
if (cwd && projLabel === conv.slice(0, 8)) {
|
|
37076
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37189
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37077
37190
|
}
|
|
37078
37191
|
}
|
|
37079
37192
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
@@ -37099,7 +37212,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37099
37212
|
timestamp: timestamp2,
|
|
37100
37213
|
sessionId,
|
|
37101
37214
|
project: projLabel,
|
|
37102
|
-
cwd: typeof input.cwd === "string" &&
|
|
37215
|
+
cwd: typeof input.cwd === "string" && path57.isAbsolute(input.cwd) ? input.cwd : void 0
|
|
37103
37216
|
},
|
|
37104
37217
|
fileResult,
|
|
37105
37218
|
fileDedup
|
|
@@ -37122,7 +37235,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37122
37235
|
}
|
|
37123
37236
|
async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
37124
37237
|
const canaryVals = safeCanaryScanValues();
|
|
37125
|
-
const sessionDir =
|
|
37238
|
+
const sessionDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
37126
37239
|
const result = {
|
|
37127
37240
|
filesScanned: 0,
|
|
37128
37241
|
sessions: 0,
|
|
@@ -37140,18 +37253,18 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37140
37253
|
perSession: []
|
|
37141
37254
|
};
|
|
37142
37255
|
const dedup = emptyScanDedup();
|
|
37143
|
-
if (!
|
|
37256
|
+
if (!fs55.existsSync(sessionDir)) return result;
|
|
37144
37257
|
let sessionIds;
|
|
37145
37258
|
try {
|
|
37146
|
-
sessionIds =
|
|
37259
|
+
sessionIds = fs55.readdirSync(sessionDir);
|
|
37147
37260
|
} catch {
|
|
37148
37261
|
return result;
|
|
37149
37262
|
}
|
|
37150
37263
|
const judge = new HistoryJudge();
|
|
37151
37264
|
const cache7 = openScanCache(judge, opts);
|
|
37152
37265
|
for (const sessionId of sessionIds) {
|
|
37153
|
-
const eventsPath =
|
|
37154
|
-
if (!
|
|
37266
|
+
const eventsPath = path57.join(sessionDir, sessionId, "events.jsonl");
|
|
37267
|
+
if (!fs55.existsSync(eventsPath)) continue;
|
|
37155
37268
|
const stamp = fileStamp(eventsPath);
|
|
37156
37269
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
37157
37270
|
result.filesScanned++;
|
|
@@ -37165,7 +37278,7 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37165
37278
|
const fileDedup = emptyScanDedup();
|
|
37166
37279
|
let raw;
|
|
37167
37280
|
try {
|
|
37168
|
-
raw =
|
|
37281
|
+
raw = fs55.readFileSync(eventsPath, "utf-8");
|
|
37169
37282
|
} catch {
|
|
37170
37283
|
continue;
|
|
37171
37284
|
}
|
|
@@ -37190,8 +37303,8 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37190
37303
|
if (ev.type === "session.start") {
|
|
37191
37304
|
const cwd = ev.data?.context?.cwd;
|
|
37192
37305
|
if (typeof cwd === "string" && cwd) {
|
|
37193
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37194
|
-
if (
|
|
37306
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37307
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37195
37308
|
}
|
|
37196
37309
|
continue;
|
|
37197
37310
|
}
|
|
@@ -37324,7 +37437,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37324
37437
|
const fileDedup = emptyScanDedup();
|
|
37325
37438
|
let lines;
|
|
37326
37439
|
try {
|
|
37327
|
-
lines =
|
|
37440
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37328
37441
|
} catch {
|
|
37329
37442
|
continue;
|
|
37330
37443
|
}
|
|
@@ -37347,8 +37460,8 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37347
37460
|
sessionId = String(payload["id"] ?? filePath);
|
|
37348
37461
|
startTime = String(payload["timestamp"] ?? "");
|
|
37349
37462
|
const cwd = String(payload["cwd"] ?? "");
|
|
37350
|
-
projLabel = stripTerminalEscapes(cwd.replace(
|
|
37351
|
-
if (
|
|
37463
|
+
projLabel = stripTerminalEscapes(cwd.replace(os47.homedir(), "~")).slice(0, 40);
|
|
37464
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37352
37465
|
continue;
|
|
37353
37466
|
}
|
|
37354
37467
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
@@ -37465,17 +37578,17 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37465
37578
|
return result;
|
|
37466
37579
|
}
|
|
37467
37580
|
function scanShellConfig() {
|
|
37468
|
-
const home =
|
|
37581
|
+
const home = os47.homedir();
|
|
37469
37582
|
const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
|
|
37470
|
-
(f) =>
|
|
37583
|
+
(f) => path57.join(home, f)
|
|
37471
37584
|
);
|
|
37472
37585
|
const findings = [];
|
|
37473
37586
|
const seen = /* @__PURE__ */ new Set();
|
|
37474
37587
|
for (const filePath of configFiles) {
|
|
37475
|
-
if (!
|
|
37588
|
+
if (!fs55.existsSync(filePath)) continue;
|
|
37476
37589
|
let lines;
|
|
37477
37590
|
try {
|
|
37478
|
-
lines =
|
|
37591
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37479
37592
|
} catch {
|
|
37480
37593
|
continue;
|
|
37481
37594
|
}
|
|
@@ -37669,6 +37782,9 @@ function renderCompactScorecard(input) {
|
|
|
37669
37782
|
);
|
|
37670
37783
|
console.log("");
|
|
37671
37784
|
}
|
|
37785
|
+
console.log(
|
|
37786
|
+
chalk6.dim("\u2192 Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
|
|
37787
|
+
);
|
|
37672
37788
|
console.log(
|
|
37673
37789
|
chalk6.dim("\u2192 ") + chalk6.cyan("npx node9-ai scan") + chalk6.dim(" run this on your machine")
|
|
37674
37790
|
);
|
|
@@ -37779,6 +37895,9 @@ function renderNarrativeScorecard(input) {
|
|
|
37779
37895
|
}
|
|
37780
37896
|
console.log("");
|
|
37781
37897
|
}
|
|
37898
|
+
console.log(
|
|
37899
|
+
chalk6.dim("\u2192 Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
|
|
37900
|
+
);
|
|
37782
37901
|
console.log(
|
|
37783
37902
|
chalk6.dim("\u2192 ") + chalk6.cyan("npx node9-ai scan") + chalk6.dim(" run this on your machine")
|
|
37784
37903
|
);
|
|
@@ -37805,25 +37924,25 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
37805
37924
|
const topLines = [];
|
|
37806
37925
|
if (scan.canaryFindings.length > 0) {
|
|
37807
37926
|
const latest = scan.canaryFindings[0];
|
|
37808
|
-
const rel =
|
|
37927
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37809
37928
|
const n = scan.canaryFindings.length;
|
|
37810
37929
|
topLines.push(
|
|
37811
37930
|
mkLine(
|
|
37812
37931
|
["\u{1FAA4} ", chalk6.red],
|
|
37813
37932
|
[`${n} decoy credential${n !== 1 ? "s" : ""} tripped `, chalk6.bold],
|
|
37814
|
-
[`(latest: ${rel}
|
|
37933
|
+
[`(latest: ${rel}, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
|
|
37815
37934
|
)
|
|
37816
37935
|
);
|
|
37817
37936
|
}
|
|
37818
37937
|
if (scan.dlpFindings.length > 0) {
|
|
37819
37938
|
const latest = scan.dlpFindings[0];
|
|
37820
|
-
const rel =
|
|
37939
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37821
37940
|
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
37822
37941
|
topLines.push(
|
|
37823
37942
|
mkLine(
|
|
37824
37943
|
["\u{1F6A8} ", chalk6.red],
|
|
37825
37944
|
[`${scan.dlpFindings.length} ${noun} in tool input `, chalk6.bold],
|
|
37826
|
-
[`(latest: ${rel}
|
|
37945
|
+
[`(latest: ${rel}, ${latest.patternName})`, chalk6.dim]
|
|
37827
37946
|
)
|
|
37828
37947
|
);
|
|
37829
37948
|
}
|
|
@@ -38021,24 +38140,24 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
38021
38140
|
const shieldImpacts = rollupByShield(summary.sections);
|
|
38022
38141
|
const shieldLines = [];
|
|
38023
38142
|
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
38024
|
-
const
|
|
38025
|
-
const
|
|
38026
|
-
if (
|
|
38143
|
+
const aProt = isProtectiveShield(a.shieldName) ? 1 : 0;
|
|
38144
|
+
const bProt = isProtectiveShield(b.shieldName) ? 1 : 0;
|
|
38145
|
+
if (aProt !== bProt) return bProt - aProt;
|
|
38027
38146
|
return b.totalCatches - a.totalCatches;
|
|
38028
38147
|
});
|
|
38029
38148
|
for (const impact of ranked) {
|
|
38030
38149
|
if (impact.totalCatches === 0) continue;
|
|
38031
|
-
const
|
|
38150
|
+
const protective = isProtectiveShield(impact.shieldName);
|
|
38032
38151
|
const isOn = enabledSet.has(impact.shieldName);
|
|
38033
|
-
const icon =
|
|
38152
|
+
const icon = protective ? "\u{1F6E1}\uFE0F " : "\u2610 ";
|
|
38034
38153
|
const wouldCatch = `catches ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
38035
|
-
const suffix = isOn ? "\u2713 enabled" :
|
|
38154
|
+
const suffix = isOn ? "\u2713 enabled" : protective ? "\u2192 blocks these reads in-path" : "";
|
|
38036
38155
|
shieldLines.push(
|
|
38037
38156
|
mkLine(
|
|
38038
|
-
[icon,
|
|
38157
|
+
[icon, protective ? chalk6.cyan : chalk6.dim],
|
|
38039
38158
|
[impact.shieldName.padEnd(14), chalk6.bold],
|
|
38040
38159
|
[wouldCatch.padEnd(22), chalk6.dim],
|
|
38041
|
-
[suffix, isOn ||
|
|
38160
|
+
[suffix, isOn || protective ? chalk6.green.bold : chalk6.dim]
|
|
38042
38161
|
)
|
|
38043
38162
|
);
|
|
38044
38163
|
if (impact.topRuleLabels.length > 0) {
|
|
@@ -38067,10 +38186,10 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
38067
38186
|
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", chalk6.dim]));
|
|
38068
38187
|
}
|
|
38069
38188
|
const topRec = ranked.find(
|
|
38070
|
-
(r) => r.totalCatches > 0 && (
|
|
38189
|
+
(r) => r.totalCatches > 0 && isProtectiveShield(r.shieldName) && !enabledSet.has(r.shieldName)
|
|
38071
38190
|
);
|
|
38072
38191
|
const allCovered = !topRec && ranked.some(
|
|
38073
|
-
(r) => r.totalCatches > 0 && (
|
|
38192
|
+
(r) => r.totalCatches > 0 && isProtectiveShield(r.shieldName) && enabledSet.has(r.shieldName)
|
|
38074
38193
|
);
|
|
38075
38194
|
if (topRec) {
|
|
38076
38195
|
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 widest coverage)`;
|
|
@@ -38275,9 +38394,7 @@ function registerScanCommand(program2) {
|
|
|
38275
38394
|
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
38276
38395
|
return;
|
|
38277
38396
|
}
|
|
38278
|
-
let scanDelta2 = null;
|
|
38279
38397
|
if (!screenshotMode) {
|
|
38280
|
-
const previous = readPreviousScan();
|
|
38281
38398
|
const currentRecord = {
|
|
38282
38399
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38283
38400
|
score: blast.score,
|
|
@@ -38289,7 +38406,6 @@ function registerScanCommand(program2) {
|
|
|
38289
38406
|
totalCalls: scan.totalToolCalls
|
|
38290
38407
|
};
|
|
38291
38408
|
appendScanHistory(currentRecord);
|
|
38292
|
-
scanDelta2 = computeScanDelta(currentRecord, previous);
|
|
38293
38409
|
}
|
|
38294
38410
|
if (options.compact) {
|
|
38295
38411
|
renderCompactScorecard({
|
|
@@ -38328,19 +38444,9 @@ function registerScanCommand(program2) {
|
|
|
38328
38444
|
}
|
|
38329
38445
|
{
|
|
38330
38446
|
const totalRisky = scan.canaryFindings.length + totalFindings + scan.dlpFindings.length;
|
|
38331
|
-
|
|
38332
|
-
const severityDisplay = score.band === "critical" ? chalk6.red.bold(score.label) : score.color(score.label);
|
|
38333
|
-
const trendSuffix = (() => {
|
|
38334
|
-
if (!scanDelta2) return "";
|
|
38335
|
-
const { scoreDelta, daysAgo } = scanDelta2;
|
|
38336
|
-
if (scoreDelta === 0) return "";
|
|
38337
|
-
const arrow = scoreDelta > 0 ? chalk6.green(`\u25B2${scoreDelta}`) : chalk6.red(`\u25BC${Math.abs(scoreDelta)}`);
|
|
38338
|
-
const since = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo} days ago`;
|
|
38339
|
-
return chalk6.dim(" \xB7 ") + arrow + chalk6.dim(` since ${since}`);
|
|
38340
|
-
})();
|
|
38341
|
-
if (!useInkForHero) {
|
|
38447
|
+
if (!useInkForHero && totalRisky > 0) {
|
|
38342
38448
|
console.log(
|
|
38343
|
-
" " +
|
|
38449
|
+
" " + chalk6.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) + chalk6.dim(" in the scanned history")
|
|
38344
38450
|
);
|
|
38345
38451
|
}
|
|
38346
38452
|
const cardParts = [];
|
|
@@ -38392,7 +38498,7 @@ function registerScanCommand(program2) {
|
|
|
38392
38498
|
if (!drillDown) {
|
|
38393
38499
|
const useInk2 = !options.classic;
|
|
38394
38500
|
if (useInk2) {
|
|
38395
|
-
const scanInkPath =
|
|
38501
|
+
const scanInkPath = path57.join(__dirname, "scan-ink.mjs");
|
|
38396
38502
|
const dynamicImport = new Function("id", "return import(id)");
|
|
38397
38503
|
const mod = await dynamicImport(`file://${scanInkPath}`);
|
|
38398
38504
|
const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
|
|
@@ -38422,6 +38528,10 @@ function registerScanCommand(program2) {
|
|
|
38422
38528
|
keyed
|
|
38423
38529
|
});
|
|
38424
38530
|
}
|
|
38531
|
+
console.log(
|
|
38532
|
+
chalk6.dim(" Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
|
|
38533
|
+
);
|
|
38534
|
+
console.log("");
|
|
38425
38535
|
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
38426
38536
|
console.log(" " + chalk6.green(cta));
|
|
38427
38537
|
console.log(
|
|
@@ -38616,6 +38726,10 @@ function registerScanCommand(program2) {
|
|
|
38616
38726
|
);
|
|
38617
38727
|
console.log("");
|
|
38618
38728
|
}
|
|
38729
|
+
console.log(
|
|
38730
|
+
chalk6.dim(" Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
|
|
38731
|
+
);
|
|
38732
|
+
console.log("");
|
|
38619
38733
|
if (isWired) {
|
|
38620
38734
|
console.log(chalk6.green(" \u2705 node9 is active \u2014 your future sessions are protected."));
|
|
38621
38735
|
console.log(
|
|
@@ -38749,12 +38863,12 @@ var init_scan = __esm({
|
|
|
38749
38863
|
});
|
|
38750
38864
|
|
|
38751
38865
|
// src/daemon/build-id.ts
|
|
38752
|
-
import
|
|
38753
|
-
import
|
|
38866
|
+
import fs56 from "fs";
|
|
38867
|
+
import path58 from "path";
|
|
38754
38868
|
function readOwnVersion() {
|
|
38755
38869
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
38756
38870
|
try {
|
|
38757
|
-
const raw =
|
|
38871
|
+
const raw = fs56.readFileSync(path58.join(__dirname, rel), "utf-8");
|
|
38758
38872
|
const v = JSON.parse(raw).version;
|
|
38759
38873
|
if (typeof v === "string" && v.length > 0) return v;
|
|
38760
38874
|
} catch {
|
|
@@ -38765,7 +38879,7 @@ function readOwnVersion() {
|
|
|
38765
38879
|
function computeBuildId(entry = process.argv[1] ?? "") {
|
|
38766
38880
|
let mtimeMs = 0;
|
|
38767
38881
|
try {
|
|
38768
|
-
if (entry) mtimeMs =
|
|
38882
|
+
if (entry) mtimeMs = fs56.statSync(entry).mtimeMs;
|
|
38769
38883
|
} catch {
|
|
38770
38884
|
}
|
|
38771
38885
|
return { version: readOwnVersion(), mtimeMs };
|
|
@@ -38906,8 +39020,8 @@ var init_suggestion_tracker = __esm({
|
|
|
38906
39020
|
});
|
|
38907
39021
|
|
|
38908
39022
|
// src/daemon/taint-store.ts
|
|
38909
|
-
import
|
|
38910
|
-
import
|
|
39023
|
+
import fs57 from "fs";
|
|
39024
|
+
import path59 from "path";
|
|
38911
39025
|
var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS2, SessionTaintStore;
|
|
38912
39026
|
var init_taint_store = __esm({
|
|
38913
39027
|
"src/daemon/taint-store.ts"() {
|
|
@@ -38977,9 +39091,9 @@ var init_taint_store = __esm({
|
|
|
38977
39091
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
38978
39092
|
_resolve(filePath) {
|
|
38979
39093
|
try {
|
|
38980
|
-
return
|
|
39094
|
+
return fs57.realpathSync.native(path59.resolve(filePath));
|
|
38981
39095
|
} catch {
|
|
38982
|
-
return
|
|
39096
|
+
return path59.resolve(filePath);
|
|
38983
39097
|
}
|
|
38984
39098
|
}
|
|
38985
39099
|
};
|
|
@@ -39035,7 +39149,7 @@ var init_taint_store = __esm({
|
|
|
39035
39149
|
});
|
|
39036
39150
|
|
|
39037
39151
|
// src/daemon/written-store.ts
|
|
39038
|
-
import
|
|
39152
|
+
import path60 from "path";
|
|
39039
39153
|
var SESSION_WRITE_TTL_MS, MAX_WRITTEN, MAX_PATH, SessionWriteStore;
|
|
39040
39154
|
var init_written_store = __esm({
|
|
39041
39155
|
"src/daemon/written-store.ts"() {
|
|
@@ -39047,7 +39161,7 @@ var init_written_store = __esm({
|
|
|
39047
39161
|
records = /* @__PURE__ */ new Map();
|
|
39048
39162
|
key(p) {
|
|
39049
39163
|
if (typeof p !== "string" || !p || p.length > MAX_PATH) return null;
|
|
39050
|
-
return
|
|
39164
|
+
return path60.resolve(p);
|
|
39051
39165
|
}
|
|
39052
39166
|
/** Record that these absolute paths were (or are about to be) written. */
|
|
39053
39167
|
record(paths, ttlMs = SESSION_WRITE_TTL_MS) {
|
|
@@ -39378,9 +39492,9 @@ var init_cards = __esm({
|
|
|
39378
39492
|
|
|
39379
39493
|
// src/daemon/state.ts
|
|
39380
39494
|
import net2 from "net";
|
|
39381
|
-
import
|
|
39382
|
-
import
|
|
39383
|
-
import
|
|
39495
|
+
import fs58 from "fs";
|
|
39496
|
+
import path61 from "path";
|
|
39497
|
+
import os48 from "os";
|
|
39384
39498
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
39385
39499
|
function onCardDecided(card) {
|
|
39386
39500
|
const r = card.record;
|
|
@@ -39406,8 +39520,8 @@ function onCardDecided(card) {
|
|
|
39406
39520
|
}
|
|
39407
39521
|
function loadInsightCounts() {
|
|
39408
39522
|
try {
|
|
39409
|
-
if (!
|
|
39410
|
-
const data = JSON.parse(
|
|
39523
|
+
if (!fs58.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
39524
|
+
const data = JSON.parse(fs58.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
39411
39525
|
for (const [tool, count] of Object.entries(data)) {
|
|
39412
39526
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
39413
39527
|
}
|
|
@@ -39475,16 +39589,16 @@ function appendAuditLog(data) {
|
|
|
39475
39589
|
...data.channel && { channel: data.channel },
|
|
39476
39590
|
...data.cardId && { cardId: data.cardId }
|
|
39477
39591
|
};
|
|
39478
|
-
const dir2 =
|
|
39479
|
-
if (!
|
|
39480
|
-
|
|
39592
|
+
const dir2 = path61.dirname(AUDIT_LOG_FILE);
|
|
39593
|
+
if (!fs58.existsSync(dir2)) fs58.mkdirSync(dir2, { recursive: true });
|
|
39594
|
+
fs58.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
39481
39595
|
} catch {
|
|
39482
39596
|
}
|
|
39483
39597
|
}
|
|
39484
39598
|
function getAuditHistory(limit = 20) {
|
|
39485
39599
|
try {
|
|
39486
|
-
if (!
|
|
39487
|
-
const lines =
|
|
39600
|
+
if (!fs58.existsSync(AUDIT_LOG_FILE)) return [];
|
|
39601
|
+
const lines = fs58.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
39488
39602
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
39489
39603
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
39490
39604
|
} catch {
|
|
@@ -39493,7 +39607,7 @@ function getAuditHistory(limit = 20) {
|
|
|
39493
39607
|
}
|
|
39494
39608
|
function getOrgName() {
|
|
39495
39609
|
try {
|
|
39496
|
-
if (
|
|
39610
|
+
if (fs58.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
39497
39611
|
} catch {
|
|
39498
39612
|
}
|
|
39499
39613
|
return null;
|
|
@@ -39505,8 +39619,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39505
39619
|
try {
|
|
39506
39620
|
let trust = { entries: [] };
|
|
39507
39621
|
try {
|
|
39508
|
-
if (
|
|
39509
|
-
trust = JSON.parse(
|
|
39622
|
+
if (fs58.existsSync(TRUST_FILE2))
|
|
39623
|
+
trust = JSON.parse(fs58.readFileSync(TRUST_FILE2, "utf-8"));
|
|
39510
39624
|
} catch {
|
|
39511
39625
|
}
|
|
39512
39626
|
trust.entries = trust.entries.filter(
|
|
@@ -39523,8 +39637,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39523
39637
|
}
|
|
39524
39638
|
function readPersistentDecisions() {
|
|
39525
39639
|
try {
|
|
39526
|
-
if (
|
|
39527
|
-
return JSON.parse(
|
|
39640
|
+
if (fs58.existsSync(DECISIONS_FILE)) {
|
|
39641
|
+
return JSON.parse(fs58.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
39528
39642
|
}
|
|
39529
39643
|
} catch {
|
|
39530
39644
|
}
|
|
@@ -39552,7 +39666,7 @@ function estimateToolCost(tool, args) {
|
|
|
39552
39666
|
const filePath = a.file_path ?? a.path;
|
|
39553
39667
|
if (filePath) {
|
|
39554
39668
|
try {
|
|
39555
|
-
const bytes =
|
|
39669
|
+
const bytes = fs58.statSync(filePath).size;
|
|
39556
39670
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
39557
39671
|
} catch {
|
|
39558
39672
|
}
|
|
@@ -39620,7 +39734,7 @@ function abandonPending() {
|
|
|
39620
39734
|
cards.abandonLocalOnly();
|
|
39621
39735
|
if (autoStarted && cards.open().length === 0) {
|
|
39622
39736
|
try {
|
|
39623
|
-
|
|
39737
|
+
fs58.unlinkSync(DAEMON_PID_FILE);
|
|
39624
39738
|
} catch {
|
|
39625
39739
|
}
|
|
39626
39740
|
setTimeout(() => {
|
|
@@ -39631,8 +39745,8 @@ function abandonPending() {
|
|
|
39631
39745
|
}
|
|
39632
39746
|
function logActivitySocket(msg) {
|
|
39633
39747
|
try {
|
|
39634
|
-
|
|
39635
|
-
|
|
39748
|
+
fs58.appendFileSync(
|
|
39749
|
+
path61.join(homeDir, ".node9", "hook-debug.log"),
|
|
39636
39750
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
|
|
39637
39751
|
`
|
|
39638
39752
|
);
|
|
@@ -39654,13 +39768,13 @@ function shouldRebind(now = Date.now()) {
|
|
|
39654
39768
|
function startActivitySocket() {
|
|
39655
39769
|
bindActivitySocket();
|
|
39656
39770
|
activityHealthInterval = setInterval(() => {
|
|
39657
|
-
if (!
|
|
39771
|
+
if (!fs58.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
|
|
39658
39772
|
}, ACTIVITY_HEALTH_PROBE_MS);
|
|
39659
39773
|
activityHealthInterval.unref();
|
|
39660
39774
|
process.on("exit", () => {
|
|
39661
39775
|
if (activityHealthInterval) clearInterval(activityHealthInterval);
|
|
39662
39776
|
try {
|
|
39663
|
-
|
|
39777
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39664
39778
|
} catch {
|
|
39665
39779
|
}
|
|
39666
39780
|
});
|
|
@@ -39688,7 +39802,7 @@ function attemptRebind(reason) {
|
|
|
39688
39802
|
}
|
|
39689
39803
|
function bindActivitySocket() {
|
|
39690
39804
|
try {
|
|
39691
|
-
|
|
39805
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39692
39806
|
} catch {
|
|
39693
39807
|
}
|
|
39694
39808
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -39797,14 +39911,14 @@ var init_state3 = __esm({
|
|
|
39797
39911
|
init_session_history();
|
|
39798
39912
|
init_atomic_write();
|
|
39799
39913
|
init_cards();
|
|
39800
|
-
homeDir =
|
|
39801
|
-
DAEMON_PID_FILE =
|
|
39802
|
-
DECISIONS_FILE =
|
|
39803
|
-
AUDIT_LOG_FILE =
|
|
39804
|
-
TRUST_FILE2 =
|
|
39805
|
-
GLOBAL_CONFIG_FILE =
|
|
39806
|
-
CREDENTIALS_FILE =
|
|
39807
|
-
INSIGHT_COUNTS_FILE =
|
|
39914
|
+
homeDir = os48.homedir();
|
|
39915
|
+
DAEMON_PID_FILE = path61.join(homeDir, ".node9", "daemon.pid");
|
|
39916
|
+
DECISIONS_FILE = path61.join(homeDir, ".node9", "decisions.json");
|
|
39917
|
+
AUDIT_LOG_FILE = path61.join(homeDir, ".node9", "audit.log");
|
|
39918
|
+
TRUST_FILE2 = path61.join(homeDir, ".node9", "trust.json");
|
|
39919
|
+
GLOBAL_CONFIG_FILE = path61.join(homeDir, ".node9", "config.json");
|
|
39920
|
+
CREDENTIALS_FILE = path61.join(homeDir, ".node9", "credentials.json");
|
|
39921
|
+
INSIGHT_COUNTS_FILE = path61.join(homeDir, ".node9", "insight-counts.json");
|
|
39808
39922
|
sseClients = /* @__PURE__ */ new Set();
|
|
39809
39923
|
cards = new CardStore({
|
|
39810
39924
|
onDecided: onCardDecided,
|
|
@@ -39827,7 +39941,7 @@ var init_state3 = __esm({
|
|
|
39827
39941
|
"2h": 2 * 60 * 6e4
|
|
39828
39942
|
};
|
|
39829
39943
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
39830
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
39944
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path61.join(os48.tmpdir(), "node9-activity.sock");
|
|
39831
39945
|
ACTIVITY_RING_SIZE = 100;
|
|
39832
39946
|
activityRing = [];
|
|
39833
39947
|
LARGE_RESPONSE_RING_SIZE = 20;
|
|
@@ -39866,13 +39980,13 @@ var init_state3 = __esm({
|
|
|
39866
39980
|
});
|
|
39867
39981
|
|
|
39868
39982
|
// src/posture/secrets.ts
|
|
39869
|
-
import
|
|
39870
|
-
import
|
|
39871
|
-
import
|
|
39983
|
+
import fs59 from "fs";
|
|
39984
|
+
import path62 from "path";
|
|
39985
|
+
import os49 from "os";
|
|
39872
39986
|
function displayPath(p, home) {
|
|
39873
39987
|
if (p === home) return "~";
|
|
39874
|
-
const prefix = home.endsWith(
|
|
39875
|
-
if (p.startsWith(prefix)) return "~" +
|
|
39988
|
+
const prefix = home.endsWith(path62.sep) ? home : home + path62.sep;
|
|
39989
|
+
if (p.startsWith(prefix)) return "~" + path62.sep + p.slice(prefix.length);
|
|
39876
39990
|
return p;
|
|
39877
39991
|
}
|
|
39878
39992
|
function safeRead(file2) {
|
|
@@ -39883,8 +39997,8 @@ function safeRead(file2) {
|
|
|
39883
39997
|
function candidateFiles(home, cwd) {
|
|
39884
39998
|
const files = /* @__PURE__ */ new Set();
|
|
39885
39999
|
try {
|
|
39886
|
-
for (const name of
|
|
39887
|
-
if (name === ".env" || name.startsWith(".env.")) files.add(
|
|
40000
|
+
for (const name of fs59.readdirSync(cwd)) {
|
|
40001
|
+
if (name === ".env" || name.startsWith(".env.")) files.add(path62.join(cwd, name));
|
|
39888
40002
|
}
|
|
39889
40003
|
} catch {
|
|
39890
40004
|
}
|
|
@@ -39892,7 +40006,7 @@ function candidateFiles(home, cwd) {
|
|
|
39892
40006
|
if (spec.hookFile) files.add(spec.hookFile(home));
|
|
39893
40007
|
if (spec.mcpFile) files.add(spec.mcpFile(home));
|
|
39894
40008
|
}
|
|
39895
|
-
files.add(
|
|
40009
|
+
files.add(path62.join(home, ".env"));
|
|
39896
40010
|
return [...files];
|
|
39897
40011
|
}
|
|
39898
40012
|
function credentialMaterial(home) {
|
|
@@ -39906,7 +40020,7 @@ function plantedDecoyPaths() {
|
|
|
39906
40020
|
}
|
|
39907
40021
|
}
|
|
39908
40022
|
function checkSecrets(ctx) {
|
|
39909
|
-
const home = ctx.home ||
|
|
40023
|
+
const home = ctx.home || os49.homedir();
|
|
39910
40024
|
const findings = [];
|
|
39911
40025
|
const planted = plantedDecoyPaths();
|
|
39912
40026
|
const plaintext = [];
|
|
@@ -39942,7 +40056,7 @@ function checkSecrets(ctx) {
|
|
|
39942
40056
|
for (const file2 of credentialMaterial(home)) {
|
|
39943
40057
|
if (planted.has(file2)) continue;
|
|
39944
40058
|
try {
|
|
39945
|
-
if (
|
|
40059
|
+
if (fs59.statSync(file2).isFile()) {
|
|
39946
40060
|
creds.push(displayPath(file2, home));
|
|
39947
40061
|
credPaths.push(file2);
|
|
39948
40062
|
}
|
|
@@ -40112,10 +40226,10 @@ var init_templates = __esm({
|
|
|
40112
40226
|
});
|
|
40113
40227
|
|
|
40114
40228
|
// src/posture/egress.ts
|
|
40115
|
-
import
|
|
40229
|
+
import fs60 from "fs";
|
|
40116
40230
|
function sandboxEgressWallActive() {
|
|
40117
40231
|
try {
|
|
40118
|
-
return
|
|
40232
|
+
return fs60.existsSync(ALLOWED_DOMAINS_PATH);
|
|
40119
40233
|
} catch {
|
|
40120
40234
|
return false;
|
|
40121
40235
|
}
|
|
@@ -40127,7 +40241,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40127
40241
|
severity: "high",
|
|
40128
40242
|
title: "Egress is locked, but node9 is not enforcing it",
|
|
40129
40243
|
what: "Egress is set to block, but node9 is not applying the policy.",
|
|
40130
|
-
why: "node9 isn't wired in (or is in
|
|
40244
|
+
why: "node9 isn't wired in (or is in audit mode), so the lock has no effect.",
|
|
40131
40245
|
who: "The lock protects nothing until node9 is enforcing in-path.",
|
|
40132
40246
|
owner: "node9",
|
|
40133
40247
|
detail: [],
|
|
@@ -40144,7 +40258,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40144
40258
|
severity: "medium",
|
|
40145
40259
|
title: "Egress is in review, but node9 is not enforcing it",
|
|
40146
40260
|
what: "Egress is set to review (approval-gate), but node9 is not applying the policy.",
|
|
40147
|
-
why: "node9 isn't wired in (or is in
|
|
40261
|
+
why: "node9 isn't wired in (or is in audit mode), so the gate has no effect.",
|
|
40148
40262
|
who: "Nothing gates outbound until node9 is enforcing in-path.",
|
|
40149
40263
|
owner: "node9",
|
|
40150
40264
|
detail: [],
|
|
@@ -40285,14 +40399,14 @@ var init_gate = __esm({
|
|
|
40285
40399
|
});
|
|
40286
40400
|
|
|
40287
40401
|
// src/posture/supply-chain.ts
|
|
40288
|
-
import
|
|
40289
|
-
import
|
|
40402
|
+
import os50 from "os";
|
|
40403
|
+
import path63 from "path";
|
|
40290
40404
|
import { parse as parseToml6 } from "smol-toml";
|
|
40291
40405
|
function isNode9Managed(command, args = []) {
|
|
40292
40406
|
if (!command) return false;
|
|
40293
|
-
if (
|
|
40294
|
-
if (PACKAGE_RUNNERS.has(
|
|
40295
|
-
return args.some((a) => a === "node9" ||
|
|
40407
|
+
if (path63.basename(command).toLowerCase() === "node9") return true;
|
|
40408
|
+
if (PACKAGE_RUNNERS.has(path63.basename(command).toLowerCase())) {
|
|
40409
|
+
return args.some((a) => a === "node9" || path63.basename(a).toLowerCase() === "node9");
|
|
40296
40410
|
}
|
|
40297
40411
|
return false;
|
|
40298
40412
|
}
|
|
@@ -40314,7 +40428,7 @@ function readServers(file2, format, agent) {
|
|
|
40314
40428
|
}
|
|
40315
40429
|
}
|
|
40316
40430
|
function checkSupplyChain(ctx) {
|
|
40317
|
-
const home = ctx.home ||
|
|
40431
|
+
const home = ctx.home || os50.homedir();
|
|
40318
40432
|
const servers = [];
|
|
40319
40433
|
for (const spec of AGENT_SPECS) {
|
|
40320
40434
|
if (!spec.mcpFile) continue;
|
|
@@ -40413,11 +40527,11 @@ var init_privilege = __esm({
|
|
|
40413
40527
|
});
|
|
40414
40528
|
|
|
40415
40529
|
// src/posture/containment.ts
|
|
40416
|
-
import
|
|
40530
|
+
import fs61 from "fs";
|
|
40417
40531
|
function inContainer() {
|
|
40418
|
-
if (
|
|
40532
|
+
if (fs61.existsSync("/.dockerenv") || fs61.existsSync("/run/.containerenv")) return true;
|
|
40419
40533
|
try {
|
|
40420
|
-
const cgroup =
|
|
40534
|
+
const cgroup = fs61.readFileSync("/proc/1/cgroup", "utf8");
|
|
40421
40535
|
if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
|
|
40422
40536
|
} catch {
|
|
40423
40537
|
}
|
|
@@ -40463,7 +40577,7 @@ var init_containment = __esm({
|
|
|
40463
40577
|
});
|
|
40464
40578
|
|
|
40465
40579
|
// src/posture/inbound.ts
|
|
40466
|
-
import
|
|
40580
|
+
import fs62 from "fs";
|
|
40467
40581
|
function buildNetworkFix(labels) {
|
|
40468
40582
|
const shielded = [
|
|
40469
40583
|
...new Map(
|
|
@@ -40521,7 +40635,7 @@ function collectListeners() {
|
|
|
40521
40635
|
const byPort = /* @__PURE__ */ new Map();
|
|
40522
40636
|
for (const file2 of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
40523
40637
|
try {
|
|
40524
|
-
for (const l of parseListeners(
|
|
40638
|
+
for (const l of parseListeners(fs62.readFileSync(file2, "utf8"))) {
|
|
40525
40639
|
if (!byPort.has(l.port)) byPort.set(l.port, l);
|
|
40526
40640
|
}
|
|
40527
40641
|
} catch {
|
|
@@ -40533,11 +40647,11 @@ function readProc(pid) {
|
|
|
40533
40647
|
let comm = "unknown";
|
|
40534
40648
|
let cmdline = "";
|
|
40535
40649
|
try {
|
|
40536
|
-
comm =
|
|
40650
|
+
comm = fs62.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
|
|
40537
40651
|
} catch {
|
|
40538
40652
|
}
|
|
40539
40653
|
try {
|
|
40540
|
-
cmdline =
|
|
40654
|
+
cmdline = fs62.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
|
|
40541
40655
|
} catch {
|
|
40542
40656
|
}
|
|
40543
40657
|
return { comm, cmdline };
|
|
@@ -40547,21 +40661,21 @@ function resolveProcesses(inodes) {
|
|
|
40547
40661
|
if (inodes.size === 0) return map;
|
|
40548
40662
|
let pids;
|
|
40549
40663
|
try {
|
|
40550
|
-
pids =
|
|
40664
|
+
pids = fs62.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
40551
40665
|
} catch {
|
|
40552
40666
|
return map;
|
|
40553
40667
|
}
|
|
40554
40668
|
for (const pid of pids) {
|
|
40555
40669
|
let fds;
|
|
40556
40670
|
try {
|
|
40557
|
-
fds =
|
|
40671
|
+
fds = fs62.readdirSync(`/proc/${pid}/fd`);
|
|
40558
40672
|
} catch {
|
|
40559
40673
|
continue;
|
|
40560
40674
|
}
|
|
40561
40675
|
for (const fd of fds) {
|
|
40562
40676
|
let link;
|
|
40563
40677
|
try {
|
|
40564
|
-
link =
|
|
40678
|
+
link = fs62.readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
40565
40679
|
} catch {
|
|
40566
40680
|
continue;
|
|
40567
40681
|
}
|
|
@@ -40666,9 +40780,9 @@ var init_inbound = __esm({
|
|
|
40666
40780
|
});
|
|
40667
40781
|
|
|
40668
40782
|
// src/posture/coverage.ts
|
|
40669
|
-
import
|
|
40783
|
+
import os51 from "os";
|
|
40670
40784
|
function checkCoverage(ctx) {
|
|
40671
|
-
const home = ctx.home ||
|
|
40785
|
+
const home = ctx.home || os51.homedir();
|
|
40672
40786
|
const findings = [];
|
|
40673
40787
|
const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
|
|
40674
40788
|
if (protectedAgents.length === 0) {
|
|
@@ -40686,13 +40800,14 @@ function checkCoverage(ctx) {
|
|
|
40686
40800
|
return findings;
|
|
40687
40801
|
}
|
|
40688
40802
|
const mode = getConfig(ctx.cwd).settings.mode;
|
|
40689
|
-
if (mode
|
|
40803
|
+
if (isRecordOnly(mode)) {
|
|
40804
|
+
const level = levelOf(mode);
|
|
40690
40805
|
findings.push({
|
|
40691
40806
|
category: "Coverage",
|
|
40692
40807
|
severity: "high",
|
|
40693
|
-
title: `node9 is in ${
|
|
40808
|
+
title: `node9 is in ${level} mode \u2014 watching, not blocking`,
|
|
40694
40809
|
what: "node9 is watching but not actually blocking anything.",
|
|
40695
|
-
why: `It's in ${
|
|
40810
|
+
why: `It's in ${level} mode, which logs risky actions but lets them through.`,
|
|
40696
40811
|
who: "The guardrails above are observed, not enforced.",
|
|
40697
40812
|
detail: [],
|
|
40698
40813
|
owner: "node9",
|
|
@@ -40705,12 +40820,13 @@ var init_coverage = __esm({
|
|
|
40705
40820
|
"src/posture/coverage.ts"() {
|
|
40706
40821
|
"use strict";
|
|
40707
40822
|
init_config();
|
|
40823
|
+
init_dist();
|
|
40708
40824
|
init_agent_wiring();
|
|
40709
40825
|
}
|
|
40710
40826
|
});
|
|
40711
40827
|
|
|
40712
40828
|
// src/posture/governance.ts
|
|
40713
|
-
import
|
|
40829
|
+
import path64 from "path";
|
|
40714
40830
|
function checkData(ctx) {
|
|
40715
40831
|
const dlp = getConfig(ctx.cwd).policy.dlp;
|
|
40716
40832
|
if (!dlp?.enabled) {
|
|
@@ -40854,7 +40970,7 @@ var init_governance = __esm({
|
|
|
40854
40970
|
"src/posture/governance.ts"() {
|
|
40855
40971
|
"use strict";
|
|
40856
40972
|
init_config();
|
|
40857
|
-
jailProbePath = (home) =>
|
|
40973
|
+
jailProbePath = (home) => path64.join(home, ".aws", "credentials");
|
|
40858
40974
|
}
|
|
40859
40975
|
});
|
|
40860
40976
|
|
|
@@ -41008,7 +41124,7 @@ async function annotateCoverage(findings, ctx) {
|
|
|
41008
41124
|
const worst = verdicts.some((v) => v === "review") ? "review" : "block";
|
|
41009
41125
|
f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
|
|
41010
41126
|
if (isRecordOnly(mode) && f.fix?.includes("shield enable project-jail")) {
|
|
41011
|
-
f.fix = `node9 already recognises reads of these files, but it is in ${mode
|
|
41127
|
+
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.`;
|
|
41012
41128
|
}
|
|
41013
41129
|
}
|
|
41014
41130
|
continue;
|
|
@@ -41033,7 +41149,7 @@ var init_enforcement = __esm({
|
|
|
41033
41149
|
});
|
|
41034
41150
|
|
|
41035
41151
|
// src/posture/index.ts
|
|
41036
|
-
import
|
|
41152
|
+
import os52 from "os";
|
|
41037
41153
|
function dropEnforcementRedundant(findings) {
|
|
41038
41154
|
const coveragePresent = findings.some((f) => f.category === "Coverage");
|
|
41039
41155
|
if (!coveragePresent) return findings;
|
|
@@ -41059,7 +41175,7 @@ async function runChecks(checks, ctx) {
|
|
|
41059
41175
|
}
|
|
41060
41176
|
async function runPosture(opts = {}) {
|
|
41061
41177
|
const ctx = {
|
|
41062
|
-
home: opts.home ??
|
|
41178
|
+
home: opts.home ?? os52.homedir(),
|
|
41063
41179
|
cwd: opts.cwd ?? process.cwd(),
|
|
41064
41180
|
agent: opts.agent
|
|
41065
41181
|
};
|
|
@@ -41436,20 +41552,20 @@ var init_ship2 = __esm({
|
|
|
41436
41552
|
|
|
41437
41553
|
// src/machine-id.ts
|
|
41438
41554
|
import * as crypto7 from "crypto";
|
|
41439
|
-
import * as
|
|
41440
|
-
import * as
|
|
41441
|
-
import * as
|
|
41442
|
-
function getMachineId(homeDir2 =
|
|
41443
|
-
const file2 =
|
|
41555
|
+
import * as fs63 from "fs";
|
|
41556
|
+
import * as os53 from "os";
|
|
41557
|
+
import * as path65 from "path";
|
|
41558
|
+
function getMachineId(homeDir2 = os53.homedir()) {
|
|
41559
|
+
const file2 = path65.join(homeDir2, ".node9", "machine-id");
|
|
41444
41560
|
try {
|
|
41445
|
-
const existing =
|
|
41561
|
+
const existing = fs63.readFileSync(file2, "utf-8").trim();
|
|
41446
41562
|
if (UUID_RE.test(existing)) return existing;
|
|
41447
41563
|
} catch {
|
|
41448
41564
|
}
|
|
41449
41565
|
const id = crypto7.randomUUID();
|
|
41450
41566
|
try {
|
|
41451
|
-
|
|
41452
|
-
|
|
41567
|
+
fs63.mkdirSync(path65.dirname(file2), { recursive: true });
|
|
41568
|
+
fs63.writeFileSync(file2, id + "\n", { mode: 384 });
|
|
41453
41569
|
} catch {
|
|
41454
41570
|
}
|
|
41455
41571
|
return id;
|
|
@@ -41489,10 +41605,10 @@ var init_sync_trigger = __esm({
|
|
|
41489
41605
|
});
|
|
41490
41606
|
|
|
41491
41607
|
// src/daemon/sync.ts
|
|
41492
|
-
import
|
|
41608
|
+
import fs64 from "fs";
|
|
41493
41609
|
import https4 from "https";
|
|
41494
|
-
import
|
|
41495
|
-
import
|
|
41610
|
+
import os54 from "os";
|
|
41611
|
+
import path66 from "path";
|
|
41496
41612
|
function emptySignals3() {
|
|
41497
41613
|
return {
|
|
41498
41614
|
dlpFindings: 0,
|
|
@@ -41540,7 +41656,7 @@ function readCredentials() {
|
|
|
41540
41656
|
}
|
|
41541
41657
|
function readCachedEtag() {
|
|
41542
41658
|
try {
|
|
41543
|
-
const raw = JSON.parse(
|
|
41659
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41544
41660
|
return typeof raw.etag === "string" ? raw.etag : void 0;
|
|
41545
41661
|
} catch {
|
|
41546
41662
|
return void 0;
|
|
@@ -41548,7 +41664,7 @@ function readCachedEtag() {
|
|
|
41548
41664
|
}
|
|
41549
41665
|
function readCachedSyncIntervalHours() {
|
|
41550
41666
|
try {
|
|
41551
|
-
const raw = JSON.parse(
|
|
41667
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41552
41668
|
return typeof raw.syncIntervalHours === "number" ? raw.syncIntervalHours : void 0;
|
|
41553
41669
|
} catch {
|
|
41554
41670
|
return void 0;
|
|
@@ -41565,7 +41681,7 @@ function effectiveSyncIntervalMs() {
|
|
|
41565
41681
|
}
|
|
41566
41682
|
function readSyncHealth() {
|
|
41567
41683
|
try {
|
|
41568
|
-
const raw = JSON.parse(
|
|
41684
|
+
const raw = JSON.parse(fs64.readFileSync(syncHealthFile(), "utf-8"));
|
|
41569
41685
|
return {
|
|
41570
41686
|
lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
|
|
41571
41687
|
lastChangedAt: typeof raw.lastChangedAt === "string" ? raw.lastChangedAt : void 0,
|
|
@@ -41580,17 +41696,17 @@ function readSyncHealth() {
|
|
|
41580
41696
|
function writeSyncHealth(h) {
|
|
41581
41697
|
try {
|
|
41582
41698
|
const file2 = syncHealthFile();
|
|
41583
|
-
const dir2 =
|
|
41584
|
-
if (!
|
|
41699
|
+
const dir2 = path66.dirname(file2);
|
|
41700
|
+
if (!fs64.existsSync(dir2)) fs64.mkdirSync(dir2, { recursive: true });
|
|
41585
41701
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
41586
|
-
|
|
41587
|
-
|
|
41702
|
+
fs64.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
|
|
41703
|
+
fs64.renameSync(tmp, file2);
|
|
41588
41704
|
} catch {
|
|
41589
41705
|
}
|
|
41590
41706
|
}
|
|
41591
41707
|
function readCacheFetchedAt() {
|
|
41592
41708
|
try {
|
|
41593
|
-
const raw = JSON.parse(
|
|
41709
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41594
41710
|
return typeof raw.fetchedAt === "string" ? raw.fetchedAt : void 0;
|
|
41595
41711
|
} catch {
|
|
41596
41712
|
return void 0;
|
|
@@ -41627,13 +41743,13 @@ function safeNode9Version() {
|
|
|
41627
41743
|
let dir2 = __dirname;
|
|
41628
41744
|
for (let up = 0; up < 5; up++) {
|
|
41629
41745
|
try {
|
|
41630
|
-
const pkg = JSON.parse(
|
|
41746
|
+
const pkg = JSON.parse(fs64.readFileSync(path66.join(dir2, "package.json"), "utf-8"));
|
|
41631
41747
|
if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
|
|
41632
41748
|
return pkg.version;
|
|
41633
41749
|
}
|
|
41634
41750
|
} catch {
|
|
41635
41751
|
}
|
|
41636
|
-
const parent =
|
|
41752
|
+
const parent = path66.dirname(dir2);
|
|
41637
41753
|
if (parent === dir2) break;
|
|
41638
41754
|
dir2 = parent;
|
|
41639
41755
|
}
|
|
@@ -41886,13 +42002,13 @@ function extractManagedConfig(body) {
|
|
|
41886
42002
|
}
|
|
41887
42003
|
function sweepStaleTmp(target) {
|
|
41888
42004
|
try {
|
|
41889
|
-
const dir2 =
|
|
41890
|
-
const prefix = `${
|
|
41891
|
-
for (const name of
|
|
42005
|
+
const dir2 = path66.dirname(target);
|
|
42006
|
+
const prefix = `${path66.basename(target)}.`;
|
|
42007
|
+
for (const name of fs64.readdirSync(dir2)) {
|
|
41892
42008
|
if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
|
|
41893
|
-
const full =
|
|
42009
|
+
const full = path66.join(dir2, name);
|
|
41894
42010
|
try {
|
|
41895
|
-
if (Date.now() -
|
|
42011
|
+
if (Date.now() - fs64.statSync(full).mtimeMs > 5 * 60 * 1e3) fs64.unlinkSync(full);
|
|
41896
42012
|
} catch {
|
|
41897
42013
|
}
|
|
41898
42014
|
}
|
|
@@ -41995,7 +42111,7 @@ async function pushBlastSnapshot(creds) {
|
|
|
41995
42111
|
}
|
|
41996
42112
|
async function pushPostureSnapshot(creds) {
|
|
41997
42113
|
try {
|
|
41998
|
-
const home =
|
|
42114
|
+
const home = os54.homedir();
|
|
41999
42115
|
const result = await runPosture({ home, cwd: home });
|
|
42000
42116
|
await shipPosture(result, creds);
|
|
42001
42117
|
} catch {
|
|
@@ -42169,7 +42285,7 @@ async function runCloudSync() {
|
|
|
42169
42285
|
}
|
|
42170
42286
|
function getCloudSyncStatus() {
|
|
42171
42287
|
try {
|
|
42172
|
-
const raw = JSON.parse(
|
|
42288
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42173
42289
|
if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
|
|
42174
42290
|
return {
|
|
42175
42291
|
cached: true,
|
|
@@ -42186,7 +42302,7 @@ function getCloudSyncStatus() {
|
|
|
42186
42302
|
}
|
|
42187
42303
|
function getCloudRules() {
|
|
42188
42304
|
try {
|
|
42189
|
-
const raw = JSON.parse(
|
|
42305
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42190
42306
|
return Array.isArray(raw.rules) ? raw.rules : null;
|
|
42191
42307
|
} catch {
|
|
42192
42308
|
return null;
|
|
@@ -42256,12 +42372,12 @@ var init_sync = __esm({
|
|
|
42256
42372
|
loop: "loops",
|
|
42257
42373
|
"long-output-redacted": "longOutputRedactions"
|
|
42258
42374
|
};
|
|
42259
|
-
rulesCacheFile = () =>
|
|
42260
|
-
rulesCacheBackupFile = () =>
|
|
42375
|
+
rulesCacheFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.json");
|
|
42376
|
+
rulesCacheBackupFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.last-good.json");
|
|
42261
42377
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
42262
42378
|
MIN_INTERVAL_SECONDS = 15;
|
|
42263
42379
|
MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
42264
|
-
syncHealthFile = () =>
|
|
42380
|
+
syncHealthFile = () => path66.join(os54.homedir(), ".node9", "sync-health.json");
|
|
42265
42381
|
STALE_MIN_MS = 3 * 60 * 60 * 1e3;
|
|
42266
42382
|
STALE_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
42267
42383
|
STALE_FACTOR = 3;
|
|
@@ -42286,9 +42402,9 @@ __export(audit_shipper_exports, {
|
|
|
42286
42402
|
startAuditShipper: () => startAuditShipper,
|
|
42287
42403
|
writeWatermark: () => writeWatermark
|
|
42288
42404
|
});
|
|
42289
|
-
import
|
|
42290
|
-
import
|
|
42291
|
-
import
|
|
42405
|
+
import fs65 from "fs";
|
|
42406
|
+
import path67 from "path";
|
|
42407
|
+
import os55 from "os";
|
|
42292
42408
|
import crypto8 from "crypto";
|
|
42293
42409
|
function exposureOf(v) {
|
|
42294
42410
|
if (!v || typeof v !== "object") return null;
|
|
@@ -42298,21 +42414,21 @@ function exposureOf(v) {
|
|
|
42298
42414
|
return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
|
|
42299
42415
|
}
|
|
42300
42416
|
function fileSignature(filePath) {
|
|
42301
|
-
const fd =
|
|
42417
|
+
const fd = fs65.openSync(filePath, "r");
|
|
42302
42418
|
try {
|
|
42303
42419
|
const buf = Buffer.alloc(512);
|
|
42304
|
-
const read3 =
|
|
42420
|
+
const read3 = fs65.readSync(fd, buf, 0, 512, 0);
|
|
42305
42421
|
const slice = buf.subarray(0, read3);
|
|
42306
42422
|
const nl = slice.indexOf(10);
|
|
42307
42423
|
const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
|
|
42308
42424
|
return crypto8.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
|
|
42309
42425
|
} finally {
|
|
42310
|
-
|
|
42426
|
+
fs65.closeSync(fd);
|
|
42311
42427
|
}
|
|
42312
42428
|
}
|
|
42313
42429
|
function readWatermark(watermarkPath) {
|
|
42314
42430
|
try {
|
|
42315
|
-
const raw = JSON.parse(
|
|
42431
|
+
const raw = JSON.parse(fs65.readFileSync(watermarkPath, "utf-8"));
|
|
42316
42432
|
if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
|
|
42317
42433
|
return raw;
|
|
42318
42434
|
} catch {
|
|
@@ -42321,8 +42437,8 @@ function readWatermark(watermarkPath) {
|
|
|
42321
42437
|
}
|
|
42322
42438
|
function writeWatermark(watermarkPath, wm) {
|
|
42323
42439
|
const tmp = `${watermarkPath}.tmp`;
|
|
42324
|
-
|
|
42325
|
-
|
|
42440
|
+
fs65.writeFileSync(tmp, JSON.stringify(wm));
|
|
42441
|
+
fs65.renameSync(tmp, watermarkPath);
|
|
42326
42442
|
}
|
|
42327
42443
|
function buildWireRows(chunk2) {
|
|
42328
42444
|
const lastNl = chunk2.lastIndexOf(10);
|
|
@@ -42409,11 +42525,11 @@ async function shipOnce(deps = {}) {
|
|
|
42409
42525
|
if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
|
|
42410
42526
|
const endpoint = buildBatchEndpoint(creds.apiUrl);
|
|
42411
42527
|
if (!endpoint) return { status: "no-creds", shipped: 0 };
|
|
42412
|
-
if (!
|
|
42528
|
+
if (!fs65.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
|
|
42413
42529
|
let shipped = 0;
|
|
42414
42530
|
try {
|
|
42415
42531
|
for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
|
|
42416
|
-
const size =
|
|
42532
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42417
42533
|
if (size === 0) break;
|
|
42418
42534
|
const sig = fileSignature(auditLogPath);
|
|
42419
42535
|
const wm = readWatermark(watermarkPath);
|
|
@@ -42421,12 +42537,12 @@ async function shipOnce(deps = {}) {
|
|
|
42421
42537
|
if (offset >= size) break;
|
|
42422
42538
|
const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
|
|
42423
42539
|
const buf = Buffer.alloc(toRead);
|
|
42424
|
-
const fd =
|
|
42540
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42425
42541
|
let read3;
|
|
42426
42542
|
try {
|
|
42427
|
-
read3 =
|
|
42543
|
+
read3 = fs65.readSync(fd, buf, 0, toRead, offset);
|
|
42428
42544
|
} finally {
|
|
42429
|
-
|
|
42545
|
+
fs65.closeSync(fd);
|
|
42430
42546
|
}
|
|
42431
42547
|
const { rows, consumed } = buildWireRows(buf.subarray(0, read3));
|
|
42432
42548
|
if (consumed === 0) break;
|
|
@@ -42473,8 +42589,8 @@ async function shipOnce(deps = {}) {
|
|
|
42473
42589
|
}
|
|
42474
42590
|
function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
|
|
42475
42591
|
try {
|
|
42476
|
-
if (!
|
|
42477
|
-
const size =
|
|
42592
|
+
if (!fs65.existsSync(auditLogPath)) return 0;
|
|
42593
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42478
42594
|
const wm = readWatermark(watermarkPath);
|
|
42479
42595
|
if (!wm) return size;
|
|
42480
42596
|
if (wm.fileSig !== fileSignature(auditLogPath)) return size;
|
|
@@ -42499,20 +42615,20 @@ function startAuditShipper() {
|
|
|
42499
42615
|
}
|
|
42500
42616
|
function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK, maxBytes = MAX_CHUNK_BYTES) {
|
|
42501
42617
|
try {
|
|
42502
|
-
const fd =
|
|
42618
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42503
42619
|
let chunk2;
|
|
42504
42620
|
let truncated;
|
|
42505
42621
|
try {
|
|
42506
|
-
const size =
|
|
42622
|
+
const size = fs65.fstatSync(fd).size;
|
|
42507
42623
|
const wm = readWatermark(watermarkPath);
|
|
42508
42624
|
const offset = wm && wm.fileSig === fileSignature(auditLogPath) && wm.offset <= size ? wm.offset : 0;
|
|
42509
42625
|
const length = Math.min(size - offset, maxBytes);
|
|
42510
42626
|
const buffer = Buffer.alloc(length);
|
|
42511
|
-
const read3 =
|
|
42627
|
+
const read3 = fs65.readSync(fd, buffer, 0, length, offset);
|
|
42512
42628
|
chunk2 = buffer.subarray(0, read3);
|
|
42513
42629
|
truncated = size - offset > length;
|
|
42514
42630
|
} finally {
|
|
42515
|
-
|
|
42631
|
+
fs65.closeSync(fd);
|
|
42516
42632
|
}
|
|
42517
42633
|
const { rows } = buildWireRows(chunk2);
|
|
42518
42634
|
const oldest = rows.reduce((old, row) => {
|
|
@@ -42549,7 +42665,7 @@ var init_audit_shipper = __esm({
|
|
|
42549
42665
|
"policy",
|
|
42550
42666
|
"abandoned"
|
|
42551
42667
|
]);
|
|
42552
|
-
AUDIT_SHIP_WATERMARK =
|
|
42668
|
+
AUDIT_SHIP_WATERMARK = path67.join(os55.homedir(), ".node9", "audit-ship.json");
|
|
42553
42669
|
DEFAULT_INTERVAL_MS = 2e4;
|
|
42554
42670
|
MAX_BATCH = 500;
|
|
42555
42671
|
MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
@@ -42631,12 +42747,12 @@ var init_decision = __esm({
|
|
|
42631
42747
|
});
|
|
42632
42748
|
|
|
42633
42749
|
// src/daemon/dlp-scanner.ts
|
|
42634
|
-
import
|
|
42635
|
-
import
|
|
42636
|
-
import
|
|
42750
|
+
import fs66 from "fs";
|
|
42751
|
+
import path68 from "path";
|
|
42752
|
+
import os56 from "os";
|
|
42637
42753
|
function loadIndex() {
|
|
42638
42754
|
try {
|
|
42639
|
-
const raw = JSON.parse(
|
|
42755
|
+
const raw = JSON.parse(fs66.readFileSync(INDEX_FILE, "utf-8"));
|
|
42640
42756
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
42641
42757
|
const r = raw;
|
|
42642
42758
|
if (r.offsets && typeof r.offsets === "object") return r.offsets;
|
|
@@ -42648,13 +42764,13 @@ function loadIndex() {
|
|
|
42648
42764
|
}
|
|
42649
42765
|
function saveIndex(index) {
|
|
42650
42766
|
try {
|
|
42651
|
-
|
|
42767
|
+
fs66.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
42652
42768
|
} catch {
|
|
42653
42769
|
}
|
|
42654
42770
|
}
|
|
42655
42771
|
function appendAuditEntry(entry) {
|
|
42656
42772
|
try {
|
|
42657
|
-
|
|
42773
|
+
fs66.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
42658
42774
|
} catch {
|
|
42659
42775
|
}
|
|
42660
42776
|
}
|
|
@@ -42668,52 +42784,52 @@ function runDlpScan() {
|
|
|
42668
42784
|
canaryVals = [];
|
|
42669
42785
|
}
|
|
42670
42786
|
const canaryFindings = [];
|
|
42671
|
-
if (!
|
|
42787
|
+
if (!fs66.existsSync(PROJECTS_DIR2)) return;
|
|
42672
42788
|
const index = loadIndex();
|
|
42673
42789
|
const seenThisPass = /* @__PURE__ */ new Set();
|
|
42674
42790
|
const newFindings = [];
|
|
42675
42791
|
let updated = false;
|
|
42676
42792
|
let projDirs;
|
|
42677
42793
|
try {
|
|
42678
|
-
projDirs =
|
|
42794
|
+
projDirs = fs66.readdirSync(PROJECTS_DIR2);
|
|
42679
42795
|
} catch {
|
|
42680
42796
|
return;
|
|
42681
42797
|
}
|
|
42682
42798
|
for (const proj of projDirs) {
|
|
42683
|
-
const projPath =
|
|
42799
|
+
const projPath = path68.join(PROJECTS_DIR2, proj);
|
|
42684
42800
|
try {
|
|
42685
|
-
if (!
|
|
42686
|
-
const real =
|
|
42687
|
-
if (!real.startsWith(PROJECTS_DIR2 +
|
|
42801
|
+
if (!fs66.lstatSync(projPath).isDirectory()) continue;
|
|
42802
|
+
const real = fs66.realpathSync(projPath);
|
|
42803
|
+
if (!real.startsWith(PROJECTS_DIR2 + path68.sep) && real !== PROJECTS_DIR2) continue;
|
|
42688
42804
|
} catch {
|
|
42689
42805
|
continue;
|
|
42690
42806
|
}
|
|
42691
42807
|
let files;
|
|
42692
42808
|
try {
|
|
42693
|
-
files =
|
|
42809
|
+
files = fs66.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
42694
42810
|
} catch {
|
|
42695
42811
|
continue;
|
|
42696
42812
|
}
|
|
42697
42813
|
for (const file2 of files) {
|
|
42698
|
-
const filePath =
|
|
42814
|
+
const filePath = path68.join(projPath, file2);
|
|
42699
42815
|
const lastOffset = index[filePath] ?? 0;
|
|
42700
42816
|
let size;
|
|
42701
42817
|
try {
|
|
42702
|
-
size =
|
|
42818
|
+
size = fs66.statSync(filePath).size;
|
|
42703
42819
|
} catch {
|
|
42704
42820
|
continue;
|
|
42705
42821
|
}
|
|
42706
42822
|
if (size <= lastOffset) continue;
|
|
42707
42823
|
let fd;
|
|
42708
42824
|
try {
|
|
42709
|
-
fd =
|
|
42825
|
+
fd = fs66.openSync(filePath, "r");
|
|
42710
42826
|
} catch {
|
|
42711
42827
|
continue;
|
|
42712
42828
|
}
|
|
42713
42829
|
try {
|
|
42714
42830
|
const chunkSize = size - lastOffset;
|
|
42715
42831
|
const buf = Buffer.alloc(chunkSize);
|
|
42716
|
-
|
|
42832
|
+
fs66.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
42717
42833
|
const chunk2 = buf.toString("utf-8");
|
|
42718
42834
|
for (const line of chunk2.split("\n")) {
|
|
42719
42835
|
if (!line.trim()) continue;
|
|
@@ -42734,7 +42850,7 @@ function runDlpScan() {
|
|
|
42734
42850
|
const match = scanText(text);
|
|
42735
42851
|
const canary = canaryVals.length > 0 ? matchCanary(text, canaryVals) : null;
|
|
42736
42852
|
if (!match && !canary) continue;
|
|
42737
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
42853
|
+
const projLabel = decodeURIComponent(proj).replace(os56.homedir(), "~").slice(0, 40);
|
|
42738
42854
|
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
42739
42855
|
if (canary) {
|
|
42740
42856
|
const rec = canaryById.get(canary.id);
|
|
@@ -42784,7 +42900,7 @@ function runDlpScan() {
|
|
|
42784
42900
|
updated = true;
|
|
42785
42901
|
} finally {
|
|
42786
42902
|
try {
|
|
42787
|
-
|
|
42903
|
+
fs66.closeSync(fd);
|
|
42788
42904
|
} catch {
|
|
42789
42905
|
}
|
|
42790
42906
|
}
|
|
@@ -42843,15 +42959,15 @@ var init_dlp_scanner = __esm({
|
|
|
42843
42959
|
init_registry2();
|
|
42844
42960
|
init_native();
|
|
42845
42961
|
init_state3();
|
|
42846
|
-
INDEX_FILE =
|
|
42847
|
-
PROJECTS_DIR2 =
|
|
42962
|
+
INDEX_FILE = path68.join(os56.homedir(), ".node9", "dlp-index.json");
|
|
42963
|
+
PROJECTS_DIR2 = path68.join(os56.homedir(), ".claude", "projects");
|
|
42848
42964
|
}
|
|
42849
42965
|
});
|
|
42850
42966
|
|
|
42851
42967
|
// src/daemon/mcp-reconciler.ts
|
|
42852
|
-
import
|
|
42853
|
-
import
|
|
42854
|
-
import
|
|
42968
|
+
import fs67 from "fs";
|
|
42969
|
+
import path69 from "path";
|
|
42970
|
+
import os57 from "os";
|
|
42855
42971
|
import crypto9 from "crypto";
|
|
42856
42972
|
function idKey(e) {
|
|
42857
42973
|
const cmdHash = crypto9.createHash("sha256").update(`${e.command} ${e.args.join(" ")}`).digest("hex").slice(0, 16);
|
|
@@ -42859,7 +42975,7 @@ function idKey(e) {
|
|
|
42859
42975
|
}
|
|
42860
42976
|
function loadBaseline() {
|
|
42861
42977
|
try {
|
|
42862
|
-
const raw = JSON.parse(
|
|
42978
|
+
const raw = JSON.parse(fs67.readFileSync(BASELINE_FILE2, "utf-8"));
|
|
42863
42979
|
return new Set(Array.isArray(raw) ? raw : []);
|
|
42864
42980
|
} catch {
|
|
42865
42981
|
return /* @__PURE__ */ new Set();
|
|
@@ -42867,7 +42983,7 @@ function loadBaseline() {
|
|
|
42867
42983
|
}
|
|
42868
42984
|
function saveBaseline(keys) {
|
|
42869
42985
|
try {
|
|
42870
|
-
|
|
42986
|
+
fs67.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
|
|
42871
42987
|
mode: 384
|
|
42872
42988
|
});
|
|
42873
42989
|
} catch {
|
|
@@ -43055,7 +43171,7 @@ var init_mcp_reconciler = __esm({
|
|
|
43055
43171
|
init_cloud();
|
|
43056
43172
|
init_audit();
|
|
43057
43173
|
init_mcp_pin();
|
|
43058
|
-
BASELINE_FILE2 =
|
|
43174
|
+
BASELINE_FILE2 = path69.join(os57.homedir(), ".node9", "mcp-baseline.json");
|
|
43059
43175
|
BASELINE_CAP = 500;
|
|
43060
43176
|
DEFAULT_INTERVAL_MIN = 60;
|
|
43061
43177
|
DEFAULT_STALE_DAYS = 7;
|
|
@@ -43129,114 +43245,6 @@ var init_hook_heal = __esm({
|
|
|
43129
43245
|
}
|
|
43130
43246
|
});
|
|
43131
43247
|
|
|
43132
|
-
// src/daemon/startup-log.ts
|
|
43133
|
-
import fs67 from "fs";
|
|
43134
|
-
import path69 from "path";
|
|
43135
|
-
import os57 from "os";
|
|
43136
|
-
function capStartupLog(file2) {
|
|
43137
|
-
try {
|
|
43138
|
-
if (fs67.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs67.truncateSync(file2);
|
|
43139
|
-
} catch {
|
|
43140
|
-
}
|
|
43141
|
-
}
|
|
43142
|
-
function openStartupLogFd() {
|
|
43143
|
-
try {
|
|
43144
|
-
const file2 = DAEMON_STARTUP_LOG();
|
|
43145
|
-
const dir2 = path69.dirname(file2);
|
|
43146
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43147
|
-
capStartupLog(file2);
|
|
43148
|
-
return fs67.openSync(file2, "a");
|
|
43149
|
-
} catch {
|
|
43150
|
-
return void 0;
|
|
43151
|
-
}
|
|
43152
|
-
}
|
|
43153
|
-
function recordStartupState(outcome, kind, detail) {
|
|
43154
|
-
try {
|
|
43155
|
-
if (outcome === "starting") {
|
|
43156
|
-
const prev = readStartupState();
|
|
43157
|
-
if (prev?.outcome === "starting") {
|
|
43158
|
-
const prevAt = new Date(prev.at).getTime();
|
|
43159
|
-
const age = Math.abs(Date.now() - prevAt);
|
|
43160
|
-
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
43161
|
-
}
|
|
43162
|
-
}
|
|
43163
|
-
const file2 = DAEMON_STARTUP_STATE();
|
|
43164
|
-
const dir2 = path69.dirname(file2);
|
|
43165
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43166
|
-
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
43167
|
-
if (kind) state.kind = kind;
|
|
43168
|
-
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
43169
|
-
const tmp = `${file2}.${process.pid}.tmp`;
|
|
43170
|
-
try {
|
|
43171
|
-
fs67.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
43172
|
-
fs67.renameSync(tmp, file2);
|
|
43173
|
-
} catch (err2) {
|
|
43174
|
-
try {
|
|
43175
|
-
fs67.unlinkSync(tmp);
|
|
43176
|
-
} catch {
|
|
43177
|
-
}
|
|
43178
|
-
throw err2;
|
|
43179
|
-
}
|
|
43180
|
-
} catch {
|
|
43181
|
-
}
|
|
43182
|
-
}
|
|
43183
|
-
function readStartupState() {
|
|
43184
|
-
try {
|
|
43185
|
-
const raw = fs67.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
43186
|
-
const s = JSON.parse(raw);
|
|
43187
|
-
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
43188
|
-
return s;
|
|
43189
|
-
} catch {
|
|
43190
|
-
return null;
|
|
43191
|
-
}
|
|
43192
|
-
}
|
|
43193
|
-
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
43194
|
-
const s = readStartupState();
|
|
43195
|
-
if (!s) return null;
|
|
43196
|
-
const at = new Date(s.at);
|
|
43197
|
-
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
43198
|
-
switch (s.outcome) {
|
|
43199
|
-
case "ok":
|
|
43200
|
-
case "ok-elsewhere":
|
|
43201
|
-
return null;
|
|
43202
|
-
case "starting":
|
|
43203
|
-
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
43204
|
-
return {
|
|
43205
|
-
kind: "did-not-start",
|
|
43206
|
-
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
43207
|
-
at,
|
|
43208
|
-
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
43209
|
-
label: "start attempts failing since"
|
|
43210
|
-
};
|
|
43211
|
-
case "failed":
|
|
43212
|
-
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
43213
|
-
default:
|
|
43214
|
-
return null;
|
|
43215
|
-
}
|
|
43216
|
-
}
|
|
43217
|
-
function logDaemonStartup(kind, detail) {
|
|
43218
|
-
try {
|
|
43219
|
-
const file2 = DAEMON_STARTUP_LOG();
|
|
43220
|
-
const dir2 = path69.dirname(file2);
|
|
43221
|
-
if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
|
|
43222
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
|
|
43223
|
-
`;
|
|
43224
|
-
fs67.appendFileSync(file2, line, "utf-8");
|
|
43225
|
-
} catch {
|
|
43226
|
-
}
|
|
43227
|
-
}
|
|
43228
|
-
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
43229
|
-
var init_startup_log = __esm({
|
|
43230
|
-
"src/daemon/startup-log.ts"() {
|
|
43231
|
-
"use strict";
|
|
43232
|
-
DAEMON_STARTUP_LOG = () => path69.join(os57.homedir(), ".node9", "daemon-startup.log");
|
|
43233
|
-
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
43234
|
-
DAEMON_STARTUP_STATE = () => path69.join(os57.homedir(), ".node9", "daemon-startup-state.json");
|
|
43235
|
-
MAX_DETAIL = 200;
|
|
43236
|
-
STARTING_GRACE_MS = 90 * 1e3;
|
|
43237
|
-
}
|
|
43238
|
-
});
|
|
43239
|
-
|
|
43240
43248
|
// src/supply-chain/zip.ts
|
|
43241
43249
|
import fs68 from "fs";
|
|
43242
43250
|
import zlib from "zlib";
|
|
@@ -77568,7 +77576,7 @@ function buildReportJson(input) {
|
|
|
77568
77576
|
var BLOCK_REASON_LABELS = {
|
|
77569
77577
|
timeout: "Approval timeout",
|
|
77570
77578
|
"smart-rule-block": "Smart rule",
|
|
77571
|
-
"observe-mode-dlp-would-block": "DLP (
|
|
77579
|
+
"observe-mode-dlp-would-block": "DLP (audit)",
|
|
77572
77580
|
"persistent-deny": "Persistent deny",
|
|
77573
77581
|
"local-decision": "User denied",
|
|
77574
77582
|
"dlp-block": "DLP block",
|
|
@@ -77608,10 +77616,10 @@ function fmtCost2(usd) {
|
|
|
77608
77616
|
return "$" + usd.toFixed(2);
|
|
77609
77617
|
}
|
|
77610
77618
|
function registerReportCommand(program2) {
|
|
77611
|
-
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "
|
|
77619
|
+
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) => {
|
|
77612
77620
|
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
77613
77621
|
options.period
|
|
77614
|
-
) ? options.period : "
|
|
77622
|
+
) ? options.period : "30d";
|
|
77615
77623
|
const excludeTests = options.tests === false;
|
|
77616
77624
|
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
77617
77625
|
excludeTests
|
|
@@ -77809,7 +77817,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
77809
77817
|
);
|
|
77810
77818
|
summaryRow(
|
|
77811
77819
|
observeDlp > 0 ? chalk20.blue("\u{1F441}") : chalk20.dim("\u{1F441}"),
|
|
77812
|
-
"DLP (
|
|
77820
|
+
"DLP (audit)",
|
|
77813
77821
|
observeDlp,
|
|
77814
77822
|
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
77815
77823
|
observeDlp > 0 ? (s) => chalk20.blue(s) : (s) => chalk20.dim(s)
|
|
@@ -78182,6 +78190,7 @@ import chalk22 from "chalk";
|
|
|
78182
78190
|
import fs88 from "fs";
|
|
78183
78191
|
import path86 from "path";
|
|
78184
78192
|
import os73 from "os";
|
|
78193
|
+
init_posture();
|
|
78185
78194
|
function printAgentSection(label3, hookPairs, wrapped) {
|
|
78186
78195
|
console.log(chalk22.bold(` ${label3}`));
|
|
78187
78196
|
for (const { name, present } of hookPairs) {
|
|
@@ -78262,6 +78271,13 @@ function registerStatusCommand(program2) {
|
|
|
78262
78271
|
console.log("");
|
|
78263
78272
|
const modeLabel = levelLabel(settings.mode);
|
|
78264
78273
|
console.log(` Mode: ${modeLabel}`);
|
|
78274
|
+
const posture = await runPosture().catch(() => null);
|
|
78275
|
+
if (posture) {
|
|
78276
|
+
const tier = posture.tier === "good" ? chalk22.green("Good") : posture.tier === "at-risk" ? chalk22.yellow("At risk") : chalk22.red("Critical");
|
|
78277
|
+
console.log(
|
|
78278
|
+
` Score: ${chalk22.bold(`${posture.score}/100`)} (${tier})` + chalk22.gray(" \xB7 node9 posture for the breakdown")
|
|
78279
|
+
);
|
|
78280
|
+
}
|
|
78265
78281
|
const wsGoverned = mergedConfig.policySource === "workspace";
|
|
78266
78282
|
if (wsGoverned) {
|
|
78267
78283
|
console.log(` Policy: ${chalk22.cyan("Workspace config (app.node9.ai)")}`);
|
|
@@ -79317,6 +79333,235 @@ import fs92 from "fs";
|
|
|
79317
79333
|
import os78 from "os";
|
|
79318
79334
|
import path91 from "path";
|
|
79319
79335
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
79336
|
+
|
|
79337
|
+
// src/cli/commands/egress.ts
|
|
79338
|
+
init_config();
|
|
79339
|
+
init_keyed_guard();
|
|
79340
|
+
init_dist();
|
|
79341
|
+
init_egress_config();
|
|
79342
|
+
init_egress_config();
|
|
79343
|
+
import chalk27 from "chalk";
|
|
79344
|
+
function guard(fn) {
|
|
79345
|
+
try {
|
|
79346
|
+
fn();
|
|
79347
|
+
return true;
|
|
79348
|
+
} catch (err2) {
|
|
79349
|
+
console.error(chalk27.red(`
|
|
79350
|
+
\u2717 ${err2.message}
|
|
79351
|
+
`));
|
|
79352
|
+
process.exitCode = 1;
|
|
79353
|
+
return false;
|
|
79354
|
+
}
|
|
79355
|
+
}
|
|
79356
|
+
function mutate(action, change) {
|
|
79357
|
+
if (!cliGuardPolicyWrite(action)) return false;
|
|
79358
|
+
return guard(() => setEgress(change));
|
|
79359
|
+
}
|
|
79360
|
+
function addHost(list2, host2) {
|
|
79361
|
+
if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
|
|
79362
|
+
return guard(() => addEgressHost(list2, host2));
|
|
79363
|
+
}
|
|
79364
|
+
function exempt2(address) {
|
|
79365
|
+
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
79366
|
+
return guard(() => addSsrfExemption(address));
|
|
79367
|
+
}
|
|
79368
|
+
var INTERNAL_FIELDS = {
|
|
79369
|
+
allowed: { ssrfStrict: false, allowPrivate: true },
|
|
79370
|
+
listed: { ssrfStrict: false, allowPrivate: false },
|
|
79371
|
+
blocked: { ssrfStrict: true, allowPrivate: false }
|
|
79372
|
+
};
|
|
79373
|
+
var INTERNAL_SAID = {
|
|
79374
|
+
allowed: "reachable without listing them",
|
|
79375
|
+
listed: "reachable only if they are on your allowlist",
|
|
79376
|
+
blocked: "blocked"
|
|
79377
|
+
};
|
|
79378
|
+
function describeInternalState(state) {
|
|
79379
|
+
return INTERNAL_SAID[state];
|
|
79380
|
+
}
|
|
79381
|
+
function readInternalState(e) {
|
|
79382
|
+
if (e.ssrfStrict === true) return "blocked";
|
|
79383
|
+
return e.allowPrivate === false ? "listed" : "allowed";
|
|
79384
|
+
}
|
|
79385
|
+
function setInternal(value) {
|
|
79386
|
+
const state = value.trim().toLowerCase();
|
|
79387
|
+
if (!(state in INTERNAL_FIELDS)) {
|
|
79388
|
+
console.error(
|
|
79389
|
+
chalk27.red(`
|
|
79390
|
+
\u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk27.gray(
|
|
79391
|
+
"\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"
|
|
79392
|
+
)
|
|
79393
|
+
);
|
|
79394
|
+
process.exitCode = 1;
|
|
79395
|
+
return;
|
|
79396
|
+
}
|
|
79397
|
+
if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
|
|
79398
|
+
_resetConfigCache();
|
|
79399
|
+
const effective = readInternalState(getConfig().policy.egress);
|
|
79400
|
+
if (effective !== state) {
|
|
79401
|
+
console.log(
|
|
79402
|
+
chalk27.yellow(
|
|
79403
|
+
`
|
|
79404
|
+
\u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
|
|
79405
|
+
Change it in the dashboard, Enforcement \u2192 Network.
|
|
79406
|
+
`
|
|
79407
|
+
)
|
|
79408
|
+
);
|
|
79409
|
+
return;
|
|
79410
|
+
}
|
|
79411
|
+
const line = `
|
|
79412
|
+
\u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
|
|
79413
|
+
`;
|
|
79414
|
+
console.log(state === "allowed" ? chalk27.yellow(line) : chalk27.green(line));
|
|
79415
|
+
}
|
|
79416
|
+
function showFloor(e, ssrfStrictSource) {
|
|
79417
|
+
console.log(
|
|
79418
|
+
chalk27.gray("\n Protected addresses") + chalk27.gray(" \u2014 in shell commands and declared URLs")
|
|
79419
|
+
);
|
|
79420
|
+
console.log(
|
|
79421
|
+
chalk27.gray(
|
|
79422
|
+
" always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
|
|
79423
|
+
)
|
|
79424
|
+
);
|
|
79425
|
+
const state = readInternalState(e);
|
|
79426
|
+
const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
|
|
79427
|
+
const STATE_LABEL = {
|
|
79428
|
+
allowed: "allowed",
|
|
79429
|
+
listed: "allowlist only",
|
|
79430
|
+
blocked: "blocked"
|
|
79431
|
+
};
|
|
79432
|
+
console.log(
|
|
79433
|
+
` 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]}`)
|
|
79434
|
+
);
|
|
79435
|
+
console.log(chalk27.gray(` set by: ${by}`));
|
|
79436
|
+
const exemptions = e.ssrfAllow ?? [];
|
|
79437
|
+
console.log(chalk27.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
|
|
79438
|
+
console.log(
|
|
79439
|
+
chalk27.gray(
|
|
79440
|
+
" 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."
|
|
79441
|
+
)
|
|
79442
|
+
);
|
|
79443
|
+
}
|
|
79444
|
+
function showStatus() {
|
|
79445
|
+
const cfg = getConfig();
|
|
79446
|
+
const e = cfg.policy.egress;
|
|
79447
|
+
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");
|
|
79448
|
+
console.log(chalk27.cyan.bold("\n\u{1F310} Egress control"));
|
|
79449
|
+
console.log(" State: " + state);
|
|
79450
|
+
if (cfg.policySource === "workspace") {
|
|
79451
|
+
console.log(
|
|
79452
|
+
chalk27.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
|
|
79453
|
+
);
|
|
79454
|
+
}
|
|
79455
|
+
console.log(
|
|
79456
|
+
chalk27.gray(
|
|
79457
|
+
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
79458
|
+
)
|
|
79459
|
+
);
|
|
79460
|
+
showFloor(e, cfg.ssrfStrictSource);
|
|
79461
|
+
if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
|
|
79462
|
+
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
79463
|
+
if (!e.enabled) {
|
|
79464
|
+
console.log(chalk27.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
79465
|
+
console.log(chalk27.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
79466
|
+
}
|
|
79467
|
+
console.log("");
|
|
79468
|
+
}
|
|
79469
|
+
function registerEgressCommand(program2) {
|
|
79470
|
+
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
79471
|
+
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
79472
|
+
if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
|
|
79473
|
+
console.log(chalk27.green("\n\u2713 Egress is now watched (review mode)."));
|
|
79474
|
+
console.log(
|
|
79475
|
+
chalk27.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
79476
|
+
);
|
|
79477
|
+
console.log(
|
|
79478
|
+
chalk27.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
79479
|
+
);
|
|
79480
|
+
});
|
|
79481
|
+
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
79482
|
+
if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
|
|
79483
|
+
console.log(chalk27.green("\n\u2713 Egress is now locked (block mode)."));
|
|
79484
|
+
console.log(chalk27.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
79485
|
+
console.log(chalk27.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
79486
|
+
});
|
|
79487
|
+
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
|
|
79488
|
+
if (!addHost("allow", host2)) return;
|
|
79489
|
+
console.log(chalk27.green(`
|
|
79490
|
+
\u2713 Allowed egress to ${host2}.
|
|
79491
|
+
`));
|
|
79492
|
+
});
|
|
79493
|
+
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
|
|
79494
|
+
if (!addHost("deny", host2)) return;
|
|
79495
|
+
console.log(chalk27.green(`
|
|
79496
|
+
\u2713 Denied egress to ${host2}.
|
|
79497
|
+
`));
|
|
79498
|
+
});
|
|
79499
|
+
egress.command("off").description("Turn egress control off").action(() => {
|
|
79500
|
+
if (!mutate("egress off", { enabled: false })) return;
|
|
79501
|
+
console.log(
|
|
79502
|
+
chalk27.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
79503
|
+
);
|
|
79504
|
+
});
|
|
79505
|
+
egress.command("internal <allowed|listed|blocked>").description(
|
|
79506
|
+
"How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
|
|
79507
|
+
).action((value) => setInternal(value));
|
|
79508
|
+
egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
|
|
79509
|
+
const v = value.trim().toLowerCase();
|
|
79510
|
+
if (v !== "on" && v !== "off") {
|
|
79511
|
+
console.error(chalk27.red(`
|
|
79512
|
+
\u2717 Expected "on" or "off", got "${value}".
|
|
79513
|
+
`));
|
|
79514
|
+
process.exitCode = 1;
|
|
79515
|
+
return;
|
|
79516
|
+
}
|
|
79517
|
+
const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
|
|
79518
|
+
console.log(chalk27.gray(`
|
|
79519
|
+
(\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
|
|
79520
|
+
setInternal(state);
|
|
79521
|
+
});
|
|
79522
|
+
egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
|
|
79523
|
+
const a = normalizeEgressHost(address);
|
|
79524
|
+
const slash = a.indexOf("/");
|
|
79525
|
+
const base2 = slash === -1 ? a : a.slice(0, slash);
|
|
79526
|
+
const prefixText = slash === -1 ? null : a.slice(slash + 1);
|
|
79527
|
+
if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
|
|
79528
|
+
console.error(
|
|
79529
|
+
chalk27.red(`
|
|
79530
|
+
\u2717 "${address}" is not an address or a range.`) + chalk27.gray(
|
|
79531
|
+
"\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"
|
|
79532
|
+
)
|
|
79533
|
+
);
|
|
79534
|
+
process.exitCode = 1;
|
|
79535
|
+
return;
|
|
79536
|
+
}
|
|
79537
|
+
const m = classifySsrf(base2);
|
|
79538
|
+
if (m && !m.overridable) {
|
|
79539
|
+
console.error(
|
|
79540
|
+
chalk27.red(`
|
|
79541
|
+
\u2717 ${a} cannot be exempted.`) + chalk27.gray(
|
|
79542
|
+
`
|
|
79543
|
+
${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
|
|
79544
|
+
`
|
|
79545
|
+
)
|
|
79546
|
+
);
|
|
79547
|
+
process.exitCode = 1;
|
|
79548
|
+
return;
|
|
79549
|
+
}
|
|
79550
|
+
if (!exempt2(a)) return;
|
|
79551
|
+
console.log(chalk27.green(`
|
|
79552
|
+
\u2713 ${a} is exempt from the floor.`));
|
|
79553
|
+
if (!m)
|
|
79554
|
+
console.log(
|
|
79555
|
+
chalk27.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
|
|
79556
|
+
);
|
|
79557
|
+
else console.log("");
|
|
79558
|
+
});
|
|
79559
|
+
egress.command("status").description("Show the current egress state").action(showStatus);
|
|
79560
|
+
egress.action(showStatus);
|
|
79561
|
+
}
|
|
79562
|
+
|
|
79563
|
+
// src/mcp-server/index.ts
|
|
79564
|
+
init_agent_wiring();
|
|
79320
79565
|
function ok(id, result) {
|
|
79321
79566
|
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
|
|
79322
79567
|
}
|
|
@@ -79450,7 +79695,7 @@ var TOOLS = [
|
|
|
79450
79695
|
},
|
|
79451
79696
|
{
|
|
79452
79697
|
name: "node9_scan",
|
|
79453
|
-
description: "Scan all
|
|
79698
|
+
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.",
|
|
79454
79699
|
inputSchema: {
|
|
79455
79700
|
type: "object",
|
|
79456
79701
|
properties: {
|
|
@@ -79470,8 +79715,8 @@ var TOOLS = [
|
|
|
79470
79715
|
properties: {
|
|
79471
79716
|
period: {
|
|
79472
79717
|
type: "string",
|
|
79473
|
-
enum: ["today", "7d", "30d", "month"],
|
|
79474
|
-
description: "Time period for the report (default:
|
|
79718
|
+
enum: ["today", "7d", "30d", "90d", "month"],
|
|
79719
|
+
description: "Time period for the report (default: 30d)."
|
|
79475
79720
|
},
|
|
79476
79721
|
no_tests: {
|
|
79477
79722
|
type: "boolean",
|
|
@@ -79605,7 +79850,7 @@ function handleStatus() {
|
|
|
79605
79850
|
const wsGoverned = config.policySource === "workspace";
|
|
79606
79851
|
const activeShields = wsGoverned ? config.policy.appliedShields ?? [] : readActiveShields();
|
|
79607
79852
|
const lines = [];
|
|
79608
|
-
lines.push(`Mode: ${settings.mode}`);
|
|
79853
|
+
lines.push(`Mode: ${levelOf(settings.mode)}`);
|
|
79609
79854
|
if (wsGoverned) {
|
|
79610
79855
|
lines.push(
|
|
79611
79856
|
"Policy source: workspace config (app.node9.ai) \u2014 local config files are ignored for policy"
|
|
@@ -79621,6 +79866,12 @@ function handleStatus() {
|
|
|
79621
79866
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
79622
79867
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
79623
79868
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
79869
|
+
lines.push("Agent hooks:");
|
|
79870
|
+
for (const a of getAgentWiring()) {
|
|
79871
|
+
const hooked = a.hooks.some((h) => h.wired);
|
|
79872
|
+
const state = !a.installed ? "not installed" : hooked ? "wired" : a.isProtected ? "protected through MCP" : "not wired";
|
|
79873
|
+
lines.push(` ${a.label}: ${state}`);
|
|
79874
|
+
}
|
|
79624
79875
|
const projectConfig = path91.join(process.cwd(), "node9.config.json");
|
|
79625
79876
|
const globalConfig = path91.join(os78.homedir(), ".node9", "config.json");
|
|
79626
79877
|
const presentLabel = wsGoverned ? "present \u2014 ignored (workspace config governs)" : "present";
|
|
@@ -79713,6 +79964,11 @@ function handleShieldDisable(args) {
|
|
|
79713
79964
|
if (disabledApp) recordUserDecision(disabledApp.slug, "off");
|
|
79714
79965
|
return `Shield "${name}" disabled.`;
|
|
79715
79966
|
}
|
|
79967
|
+
function internalAddressLine(e) {
|
|
79968
|
+
const configured = readInternalState(e);
|
|
79969
|
+
const state = !e.enabled && configured === "listed" ? "allowed" : configured;
|
|
79970
|
+
return `Internal addresses: ${state} \u2014 loopback and the private ranges are ${describeInternalState(state)}.`;
|
|
79971
|
+
}
|
|
79716
79972
|
function handleEgressStatus() {
|
|
79717
79973
|
const cfgEgress = getConfig();
|
|
79718
79974
|
const e = cfgEgress.policy.egress;
|
|
@@ -79734,7 +79990,7 @@ function handleEgressStatus() {
|
|
|
79734
79990
|
// actually covers is measured in egress.integration.test.ts.
|
|
79735
79991
|
"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.",
|
|
79736
79992
|
"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.",
|
|
79737
|
-
|
|
79993
|
+
internalAddressLine(e),
|
|
79738
79994
|
`Floor exemptions: ${e.ssrfAllow?.length ? e.ssrfAllow.join(", ") : "(none)"}`
|
|
79739
79995
|
];
|
|
79740
79996
|
return lines.join("\n");
|
|
@@ -80057,7 +80313,7 @@ function registerMcpServerCommand(program2) {
|
|
|
80057
80313
|
init_trusted_hosts();
|
|
80058
80314
|
init_keyed_guard();
|
|
80059
80315
|
init_trusted_recipients();
|
|
80060
|
-
import
|
|
80316
|
+
import chalk28 from "chalk";
|
|
80061
80317
|
function isValidHost(host2) {
|
|
80062
80318
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host2);
|
|
80063
80319
|
}
|
|
@@ -80068,17 +80324,17 @@ function registerTrustCommand(program2) {
|
|
|
80068
80324
|
const normalized = normalizeHost2(host2.trim());
|
|
80069
80325
|
if (!isValidHost(normalized)) {
|
|
80070
80326
|
console.error(
|
|
80071
|
-
|
|
80327
|
+
chalk28.red(`
|
|
80072
80328
|
\u274C Invalid host: "${host2}"
|
|
80073
|
-
`) +
|
|
80329
|
+
`) + chalk28.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
80074
80330
|
);
|
|
80075
80331
|
process.exit(1);
|
|
80076
80332
|
}
|
|
80077
80333
|
addTrustedHost(normalized);
|
|
80078
|
-
console.log(
|
|
80334
|
+
console.log(chalk28.green(`
|
|
80079
80335
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
80080
80336
|
console.log(
|
|
80081
|
-
|
|
80337
|
+
chalk28.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
80082
80338
|
);
|
|
80083
80339
|
});
|
|
80084
80340
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host2) => {
|
|
@@ -80086,27 +80342,27 @@ function registerTrustCommand(program2) {
|
|
|
80086
80342
|
const normalized = normalizeHost2(host2.trim());
|
|
80087
80343
|
const removed = removeTrustedHost(normalized);
|
|
80088
80344
|
if (!removed) {
|
|
80089
|
-
console.error(
|
|
80345
|
+
console.error(chalk28.yellow(`
|
|
80090
80346
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
80091
80347
|
`));
|
|
80092
80348
|
process.exit(1);
|
|
80093
80349
|
}
|
|
80094
|
-
console.log(
|
|
80350
|
+
console.log(chalk28.green(`
|
|
80095
80351
|
\u2705 ${normalized} removed from trusted hosts.
|
|
80096
80352
|
`));
|
|
80097
80353
|
});
|
|
80098
80354
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
80099
80355
|
const hosts = readTrustedHosts();
|
|
80100
80356
|
if (hosts.length === 0) {
|
|
80101
|
-
console.log(
|
|
80102
|
-
console.log(` Add one: ${
|
|
80357
|
+
console.log(chalk28.gray("\n No trusted hosts configured.\n"));
|
|
80358
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust add api.mycompany.com")}
|
|
80103
80359
|
`);
|
|
80104
80360
|
return;
|
|
80105
80361
|
}
|
|
80106
|
-
console.log(
|
|
80362
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
80107
80363
|
for (const entry of hosts) {
|
|
80108
80364
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
80109
|
-
console.log(` ${
|
|
80365
|
+
console.log(` ${chalk28.cyan(entry.host.padEnd(40))} ${chalk28.gray(`added ${date}`)}`);
|
|
80110
80366
|
}
|
|
80111
80367
|
console.log("");
|
|
80112
80368
|
});
|
|
@@ -80118,41 +80374,41 @@ function registerTrustCommand(program2) {
|
|
|
80118
80374
|
const stored = addTrustedRecipient(value, "user");
|
|
80119
80375
|
if (!stored) {
|
|
80120
80376
|
console.error(
|
|
80121
|
-
|
|
80377
|
+
chalk28.red(`
|
|
80122
80378
|
\u274C Not a destination: "${value}"
|
|
80123
|
-
`) +
|
|
80379
|
+
`) + chalk28.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
|
|
80124
80380
|
);
|
|
80125
80381
|
process.exit(1);
|
|
80126
80382
|
}
|
|
80127
|
-
console.log(
|
|
80383
|
+
console.log(chalk28.green(`
|
|
80128
80384
|
\u2705 ${stored} added to trusted recipients.
|
|
80129
80385
|
`));
|
|
80130
80386
|
});
|
|
80131
80387
|
rec.command("remove <value>").description("Remove a trusted recipient").action((value) => {
|
|
80132
80388
|
if (!cliGuardPolicyWrite(`trust recipient remove ${value}`)) return;
|
|
80133
80389
|
if (!removeTrustedRecipient(value)) {
|
|
80134
|
-
console.error(
|
|
80390
|
+
console.error(chalk28.yellow(`
|
|
80135
80391
|
\u26A0\uFE0F "${value}" is not in the trusted recipients list.
|
|
80136
80392
|
`));
|
|
80137
80393
|
process.exit(1);
|
|
80138
80394
|
}
|
|
80139
|
-
console.log(
|
|
80395
|
+
console.log(chalk28.green(`
|
|
80140
80396
|
\u2705 ${value} removed from trusted recipients.
|
|
80141
80397
|
`));
|
|
80142
80398
|
});
|
|
80143
80399
|
rec.command("list").description("Show all trusted recipients").action(() => {
|
|
80144
80400
|
const entries = readTrustedRecipients();
|
|
80145
80401
|
if (entries.length === 0) {
|
|
80146
|
-
console.log(
|
|
80147
|
-
console.log(` Add one: ${
|
|
80402
|
+
console.log(chalk28.gray("\n No trusted recipients.\n"));
|
|
80403
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust recipient add partner@example.com")}
|
|
80148
80404
|
`);
|
|
80149
80405
|
return;
|
|
80150
80406
|
}
|
|
80151
|
-
console.log(
|
|
80407
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Recipients\n"));
|
|
80152
80408
|
for (const e of entries) {
|
|
80153
80409
|
const date = new Date(e.addedAt).toLocaleDateString();
|
|
80154
80410
|
console.log(
|
|
80155
|
-
` ${
|
|
80411
|
+
` ${chalk28.cyan(e.value.padEnd(44))} ${chalk28.gray(`added ${date} (${e.addedBy})`)}`
|
|
80156
80412
|
);
|
|
80157
80413
|
}
|
|
80158
80414
|
console.log("");
|
|
@@ -80161,13 +80417,13 @@ function registerTrustCommand(program2) {
|
|
|
80161
80417
|
|
|
80162
80418
|
// src/cli/commands/mcp-pin.ts
|
|
80163
80419
|
init_mcp_pin();
|
|
80164
|
-
import
|
|
80420
|
+
import chalk30 from "chalk";
|
|
80165
80421
|
import fs93 from "fs";
|
|
80166
80422
|
|
|
80167
80423
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
80168
80424
|
init_mcp_wrap();
|
|
80169
80425
|
init_mcp_status();
|
|
80170
|
-
import
|
|
80426
|
+
import chalk29 from "chalk";
|
|
80171
80427
|
var STATE_WORD = {
|
|
80172
80428
|
gatewayed: "governed",
|
|
80173
80429
|
"node9-self": "node9",
|
|
@@ -80176,10 +80432,10 @@ var STATE_WORD = {
|
|
|
80176
80432
|
};
|
|
80177
80433
|
function stateChip(state) {
|
|
80178
80434
|
const w = STATE_WORD[state].padEnd(12);
|
|
80179
|
-
if (state === "gatewayed") return
|
|
80180
|
-
if (state === "node9-self") return
|
|
80181
|
-
if (state === "remote") return
|
|
80182
|
-
return
|
|
80435
|
+
if (state === "gatewayed") return chalk29.green(w);
|
|
80436
|
+
if (state === "node9-self") return chalk29.gray(w);
|
|
80437
|
+
if (state === "remote") return chalk29.gray(w);
|
|
80438
|
+
return chalk29.yellow(w);
|
|
80183
80439
|
}
|
|
80184
80440
|
function relativeAge(ms, now) {
|
|
80185
80441
|
const s = Math.max(0, Math.round((now - ms) / 1e3));
|
|
@@ -80193,15 +80449,15 @@ function relativeAge(ms, now) {
|
|
|
80193
80449
|
function connectionChip(e, now) {
|
|
80194
80450
|
switch (e.connection) {
|
|
80195
80451
|
case "connected":
|
|
80196
|
-
return
|
|
80452
|
+
return chalk29.green(
|
|
80197
80453
|
`\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
|
|
80198
80454
|
);
|
|
80199
80455
|
case "stale":
|
|
80200
|
-
return
|
|
80456
|
+
return chalk29.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
|
|
80201
80457
|
case "pending-launch":
|
|
80202
|
-
return
|
|
80458
|
+
return chalk29.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
|
|
80203
80459
|
case "unlaunchable":
|
|
80204
|
-
return
|
|
80460
|
+
return chalk29.red(
|
|
80205
80461
|
`\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
|
|
80206
80462
|
);
|
|
80207
80463
|
default:
|
|
@@ -80226,7 +80482,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80226
80482
|
const now = Date.now();
|
|
80227
80483
|
const rows = resolveMcpStatus(void 0, process.env, now);
|
|
80228
80484
|
if (rows.length === 0) {
|
|
80229
|
-
console.error(
|
|
80485
|
+
console.error(chalk29.gray("No MCP servers found in any agent config."));
|
|
80230
80486
|
return;
|
|
80231
80487
|
}
|
|
80232
80488
|
for (const e of rows) {
|
|
@@ -80240,20 +80496,20 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80240
80496
|
const pending = rows.filter((e) => e.connection === "pending-launch").length;
|
|
80241
80497
|
if (ungoverned > 0) {
|
|
80242
80498
|
console.error(
|
|
80243
|
-
|
|
80499
|
+
chalk29.yellow(
|
|
80244
80500
|
`
|
|
80245
|
-
${ungoverned} ungoverned \u2014 run ` +
|
|
80501
|
+
${ungoverned} ungoverned \u2014 run ` + chalk29.bold("node9 mcp gateway --all") + " to govern."
|
|
80246
80502
|
)
|
|
80247
80503
|
);
|
|
80248
80504
|
}
|
|
80249
80505
|
if (pending > 0) {
|
|
80250
80506
|
console.error(
|
|
80251
|
-
|
|
80507
|
+
chalk29.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
80252
80508
|
);
|
|
80253
80509
|
}
|
|
80254
80510
|
if (unlaunchable > 0) {
|
|
80255
80511
|
console.error(
|
|
80256
|
-
|
|
80512
|
+
chalk29.red(
|
|
80257
80513
|
`${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
|
|
80258
80514
|
)
|
|
80259
80515
|
);
|
|
@@ -80272,27 +80528,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80272
80528
|
} else if (name) {
|
|
80273
80529
|
const matches2 = resolve(name, opts.agent);
|
|
80274
80530
|
if (matches2.length === 0) {
|
|
80275
|
-
console.error(
|
|
80531
|
+
console.error(chalk29.red(`No MCP server named "${name}" found.`));
|
|
80276
80532
|
process.exitCode = 1;
|
|
80277
80533
|
return;
|
|
80278
80534
|
}
|
|
80279
80535
|
if (matches2.length > 1) {
|
|
80280
80536
|
console.error(
|
|
80281
|
-
|
|
80537
|
+
chalk29.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
|
|
80282
80538
|
);
|
|
80283
80539
|
process.exitCode = 1;
|
|
80284
80540
|
return;
|
|
80285
80541
|
}
|
|
80286
80542
|
targets = matches2;
|
|
80287
80543
|
} else {
|
|
80288
|
-
console.error(
|
|
80544
|
+
console.error(chalk29.red("Pass a server name or --all. See `node9 mcp status`."));
|
|
80289
80545
|
process.exitCode = 1;
|
|
80290
80546
|
return;
|
|
80291
80547
|
}
|
|
80292
80548
|
const doable = targets.filter((e) => e.state === targetState);
|
|
80293
80549
|
if (doable.length === 0) {
|
|
80294
80550
|
console.error(
|
|
80295
|
-
|
|
80551
|
+
chalk29.gray(
|
|
80296
80552
|
opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
|
|
80297
80553
|
)
|
|
80298
80554
|
);
|
|
@@ -80306,20 +80562,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80306
80562
|
else wrapEntry(e);
|
|
80307
80563
|
agents.add(e.agentLabel);
|
|
80308
80564
|
const verb = opts.rewrap ? "refreshed" : "governed";
|
|
80309
|
-
console.error(
|
|
80565
|
+
console.error(chalk29.green(`\u2713 ${verb} ${e.name}`) + chalk29.gray(` (${e.agentLabel})`));
|
|
80310
80566
|
} catch (err2) {
|
|
80311
80567
|
failed++;
|
|
80312
80568
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80313
80569
|
console.error(
|
|
80314
|
-
|
|
80570
|
+
chalk29.red(`\u2717 failed ${e.name}`) + chalk29.gray(` (${e.agentLabel}): ${msg}`)
|
|
80315
80571
|
);
|
|
80316
80572
|
}
|
|
80317
80573
|
}
|
|
80318
80574
|
if (failed > 0) process.exitCode = 1;
|
|
80319
80575
|
if (agents.size > 0) {
|
|
80320
80576
|
console.error(
|
|
80321
|
-
|
|
80322
|
-
Restart ${[...agents].join(", ")}`) +
|
|
80577
|
+
chalk29.bold(`
|
|
80578
|
+
Restart ${[...agents].join(", ")}`) + chalk29.gray(" to activate. Undo any server with ") + chalk29.cyan("node9 mcp ungateway <name>") + chalk29.gray(".")
|
|
80323
80579
|
);
|
|
80324
80580
|
}
|
|
80325
80581
|
}
|
|
@@ -80327,19 +80583,19 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80327
80583
|
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) => {
|
|
80328
80584
|
const matches2 = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
|
|
80329
80585
|
if (matches2.length === 0) {
|
|
80330
|
-
console.error(
|
|
80586
|
+
console.error(chalk29.red(`No governed MCP server named "${name}" found.`));
|
|
80331
80587
|
process.exitCode = 1;
|
|
80332
80588
|
return;
|
|
80333
80589
|
}
|
|
80334
80590
|
if (matches2.length > 1) {
|
|
80335
|
-
console.error(
|
|
80591
|
+
console.error(chalk29.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
|
|
80336
80592
|
process.exitCode = 1;
|
|
80337
80593
|
return;
|
|
80338
80594
|
}
|
|
80339
80595
|
const e = matches2[0];
|
|
80340
80596
|
const orig = fromGateway(e.raw);
|
|
80341
80597
|
if (!orig) {
|
|
80342
|
-
console.error(
|
|
80598
|
+
console.error(chalk29.red(`Couldn't parse the original command for "${name}".`));
|
|
80343
80599
|
process.exitCode = 1;
|
|
80344
80600
|
return;
|
|
80345
80601
|
}
|
|
@@ -80347,12 +80603,12 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80347
80603
|
writeMcpEntry(e.mcpFile, e.format, e.name, orig);
|
|
80348
80604
|
} catch (err2) {
|
|
80349
80605
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80350
|
-
console.error(
|
|
80606
|
+
console.error(chalk29.red(`Failed to restore "${name}": ${msg}`));
|
|
80351
80607
|
process.exitCode = 1;
|
|
80352
80608
|
return;
|
|
80353
80609
|
}
|
|
80354
80610
|
console.error(
|
|
80355
|
-
|
|
80611
|
+
chalk29.green(`\u2713 restored ${e.name}`) + chalk29.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
|
|
80356
80612
|
);
|
|
80357
80613
|
});
|
|
80358
80614
|
}
|
|
@@ -80378,14 +80634,14 @@ function registerMcpPinCommand(program2) {
|
|
|
80378
80634
|
}
|
|
80379
80635
|
}
|
|
80380
80636
|
if (repoCorrupt) {
|
|
80381
|
-
console.error(
|
|
80637
|
+
console.error(chalk30.red(`
|
|
80382
80638
|
\u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
|
|
80383
80639
|
process.exit(1);
|
|
80384
80640
|
}
|
|
80385
80641
|
if (!homeResult.ok && homeResult.reason === "corrupt") {
|
|
80386
|
-
console.error(
|
|
80642
|
+
console.error(chalk30.red(`
|
|
80387
80643
|
\u274C Home pin file is corrupt: ${homeResult.detail}`));
|
|
80388
|
-
console.error(
|
|
80644
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80389
80645
|
process.exit(1);
|
|
80390
80646
|
}
|
|
80391
80647
|
const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
|
|
@@ -80397,25 +80653,25 @@ function registerMcpPinCommand(program2) {
|
|
|
80397
80653
|
merged.set(key, { entry, source: "repo" });
|
|
80398
80654
|
}
|
|
80399
80655
|
if (merged.size === 0) {
|
|
80400
|
-
console.log(
|
|
80656
|
+
console.log(chalk30.gray("\nNo MCP servers are pinned yet."));
|
|
80401
80657
|
console.log(
|
|
80402
|
-
|
|
80658
|
+
chalk30.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
80403
80659
|
);
|
|
80404
80660
|
return;
|
|
80405
80661
|
}
|
|
80406
|
-
console.log(
|
|
80662
|
+
console.log(chalk30.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
80407
80663
|
const showSource = found.source === "repo";
|
|
80408
80664
|
for (const [key, { entry, source }] of merged) {
|
|
80409
|
-
const tag = showSource ? ` ${
|
|
80410
|
-
console.log(` ${
|
|
80411
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
80412
|
-
console.log(` Hash: ${
|
|
80413
|
-
console.log(` Pinned: ${
|
|
80665
|
+
const tag = showSource ? ` ${chalk30.yellow(`[${source}]`)}` : "";
|
|
80666
|
+
console.log(` ${chalk30.cyan(key)}${tag} ${chalk30.gray(entry.label)}`);
|
|
80667
|
+
console.log(` Tools (${entry.toolCount}): ${chalk30.white(entry.toolNames.join(", "))}`);
|
|
80668
|
+
console.log(` Hash: ${chalk30.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
80669
|
+
console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}`);
|
|
80414
80670
|
console.log("");
|
|
80415
80671
|
}
|
|
80416
80672
|
if (showSource) {
|
|
80417
|
-
console.log(
|
|
80418
|
-
console.log(
|
|
80673
|
+
console.log(chalk30.gray(` [repo] entries come from ${found.path}`));
|
|
80674
|
+
console.log(chalk30.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
|
|
80419
80675
|
}
|
|
80420
80676
|
});
|
|
80421
80677
|
pinSubCmd.command("promote <serverKey>").description(
|
|
@@ -80425,22 +80681,22 @@ function registerMcpPinCommand(program2) {
|
|
|
80425
80681
|
const { repoPath, created } = promotePin(serverKey, process.cwd());
|
|
80426
80682
|
if (created) {
|
|
80427
80683
|
console.log(
|
|
80428
|
-
|
|
80684
|
+
chalk30.green(
|
|
80429
80685
|
`
|
|
80430
|
-
\u2705 Created ${repoPath} with the promoted pin for ${
|
|
80686
|
+
\u2705 Created ${repoPath} with the promoted pin for ${chalk30.cyan(serverKey)}.`
|
|
80431
80687
|
)
|
|
80432
80688
|
);
|
|
80433
80689
|
} else {
|
|
80434
|
-
console.log(
|
|
80435
|
-
\u2705 Promoted ${
|
|
80690
|
+
console.log(chalk30.green(`
|
|
80691
|
+
\u2705 Promoted ${chalk30.cyan(serverKey)} into ${repoPath}.`));
|
|
80436
80692
|
}
|
|
80437
|
-
console.log(
|
|
80438
|
-
console.log(
|
|
80439
|
-
console.log(
|
|
80693
|
+
console.log(chalk30.gray(" Review the change and commit it:"));
|
|
80694
|
+
console.log(chalk30.cyan(` git add ${repoPath}`));
|
|
80695
|
+
console.log(chalk30.cyan(` git commit -m "pin ${serverKey} (node9)"`));
|
|
80440
80696
|
console.log("");
|
|
80441
80697
|
} catch (err2) {
|
|
80442
80698
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80443
|
-
console.error(
|
|
80699
|
+
console.error(chalk30.red(`
|
|
80444
80700
|
\u274C ${msg}
|
|
80445
80701
|
`));
|
|
80446
80702
|
process.exit(1);
|
|
@@ -80453,36 +80709,36 @@ function registerMcpPinCommand(program2) {
|
|
|
80453
80709
|
try {
|
|
80454
80710
|
pins = readMcpPins();
|
|
80455
80711
|
} catch {
|
|
80456
|
-
console.error(
|
|
80457
|
-
console.error(
|
|
80712
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80713
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80458
80714
|
process.exit(1);
|
|
80459
80715
|
}
|
|
80460
80716
|
if (!pins.servers[serverKey]) {
|
|
80461
|
-
console.error(
|
|
80717
|
+
console.error(chalk30.red(`
|
|
80462
80718
|
\u274C No pin found for server key "${serverKey}"
|
|
80463
80719
|
`));
|
|
80464
|
-
console.error(`Run ${
|
|
80720
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80465
80721
|
`);
|
|
80466
80722
|
process.exit(1);
|
|
80467
80723
|
}
|
|
80468
80724
|
const label3 = pins.servers[serverKey].label;
|
|
80469
80725
|
removePin(serverKey);
|
|
80470
|
-
console.log(
|
|
80471
|
-
\u{1F513} Pin removed for ${
|
|
80472
|
-
console.log(
|
|
80473
|
-
console.log(
|
|
80726
|
+
console.log(chalk30.green(`
|
|
80727
|
+
\u{1F513} Pin removed for ${chalk30.cyan(serverKey)}`));
|
|
80728
|
+
console.log(chalk30.gray(` Server: ${label3}`));
|
|
80729
|
+
console.log(chalk30.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
80474
80730
|
});
|
|
80475
80731
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
80476
80732
|
const result = readMcpPinsSafe();
|
|
80477
80733
|
if (!result.ok && result.reason === "missing") {
|
|
80478
|
-
console.log(
|
|
80734
|
+
console.log(chalk30.gray("\nNo pins to clear.\n"));
|
|
80479
80735
|
return;
|
|
80480
80736
|
}
|
|
80481
80737
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
80482
80738
|
clearAllPins();
|
|
80483
|
-
console.log(
|
|
80739
|
+
console.log(chalk30.green(`
|
|
80484
80740
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
80485
|
-
console.log(
|
|
80741
|
+
console.log(chalk30.gray(" Next connection to each server will re-pin.\n"));
|
|
80486
80742
|
});
|
|
80487
80743
|
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) => {
|
|
80488
80744
|
if (opts.stale) {
|
|
@@ -80491,7 +80747,7 @@ function registerMcpPinCommand(program2) {
|
|
|
80491
80747
|
}
|
|
80492
80748
|
if (!serverKey) {
|
|
80493
80749
|
console.error(
|
|
80494
|
-
|
|
80750
|
+
chalk30.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
80495
80751
|
);
|
|
80496
80752
|
process.exit(1);
|
|
80497
80753
|
}
|
|
@@ -80499,15 +80755,15 @@ function registerMcpPinCommand(program2) {
|
|
|
80499
80755
|
try {
|
|
80500
80756
|
pins = readMcpPins();
|
|
80501
80757
|
} catch {
|
|
80502
|
-
console.error(
|
|
80503
|
-
console.error(
|
|
80758
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80759
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80504
80760
|
process.exit(1);
|
|
80505
80761
|
}
|
|
80506
80762
|
if (!pins.servers[serverKey]) {
|
|
80507
|
-
console.error(
|
|
80763
|
+
console.error(chalk30.red(`
|
|
80508
80764
|
\u274C No pin found for server key "${serverKey}"
|
|
80509
80765
|
`));
|
|
80510
|
-
console.error(`Run ${
|
|
80766
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80511
80767
|
`);
|
|
80512
80768
|
process.exit(1);
|
|
80513
80769
|
}
|
|
@@ -80515,10 +80771,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80515
80771
|
const liveKeys = inventoryServerKeys(inv);
|
|
80516
80772
|
if (liveKeys.has(serverKey)) {
|
|
80517
80773
|
const agent = "an agent config";
|
|
80518
|
-
console.error(
|
|
80774
|
+
console.error(chalk30.red(`
|
|
80519
80775
|
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
80520
80776
|
console.error(
|
|
80521
|
-
|
|
80777
|
+
chalk30.yellow(
|
|
80522
80778
|
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
80523
80779
|
`
|
|
80524
80780
|
)
|
|
@@ -80527,10 +80783,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80527
80783
|
}
|
|
80528
80784
|
const label3 = pins.servers[serverKey].label;
|
|
80529
80785
|
removePin(serverKey);
|
|
80530
|
-
console.log(
|
|
80531
|
-
\u2713 Forgot server ${
|
|
80532
|
-
console.log(
|
|
80533
|
-
console.log(
|
|
80786
|
+
console.log(chalk30.green(`
|
|
80787
|
+
\u2713 Forgot server ${chalk30.cyan(serverKey)}`));
|
|
80788
|
+
console.log(chalk30.gray(` Was: ${label3}`));
|
|
80789
|
+
console.log(chalk30.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
80534
80790
|
});
|
|
80535
80791
|
}
|
|
80536
80792
|
function forgetAllStale() {
|
|
@@ -80538,16 +80794,16 @@ function forgetAllStale() {
|
|
|
80538
80794
|
try {
|
|
80539
80795
|
pins = readMcpPins();
|
|
80540
80796
|
} catch {
|
|
80541
|
-
console.error(
|
|
80542
|
-
console.error(
|
|
80797
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80798
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80543
80799
|
process.exit(1);
|
|
80544
80800
|
}
|
|
80545
80801
|
const inv = inventoryMcp();
|
|
80546
80802
|
const liveKeys = inventoryServerKeys(inv);
|
|
80547
80803
|
if (liveKeys.size === 0) {
|
|
80548
|
-
console.error(
|
|
80804
|
+
console.error(chalk30.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
80549
80805
|
console.error(
|
|
80550
|
-
|
|
80806
|
+
chalk30.yellow(
|
|
80551
80807
|
" 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"
|
|
80552
80808
|
)
|
|
80553
80809
|
);
|
|
@@ -80555,118 +80811,118 @@ function forgetAllStale() {
|
|
|
80555
80811
|
}
|
|
80556
80812
|
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
80557
80813
|
if (stale.length === 0) {
|
|
80558
|
-
console.log(
|
|
80814
|
+
console.log(chalk30.gray("\nNo stale servers to remove.\n"));
|
|
80559
80815
|
return;
|
|
80560
80816
|
}
|
|
80561
80817
|
for (const [sk, pin] of stale) {
|
|
80562
80818
|
delete pins.servers[sk];
|
|
80563
|
-
console.log(
|
|
80819
|
+
console.log(chalk30.green(` \u2713 ${chalk30.cyan(sk)} ${chalk30.gray(pin.label)}`));
|
|
80564
80820
|
}
|
|
80565
80821
|
writeMcpPins(pins);
|
|
80566
|
-
console.log(
|
|
80822
|
+
console.log(chalk30.green(`
|
|
80567
80823
|
Removed ${stale.length} stale server(s).
|
|
80568
80824
|
`));
|
|
80569
80825
|
}
|
|
80570
80826
|
|
|
80571
80827
|
// src/cli/commands/sync.ts
|
|
80572
80828
|
init_sync();
|
|
80573
|
-
import
|
|
80829
|
+
import chalk31 from "chalk";
|
|
80574
80830
|
function registerSyncCommand(program2) {
|
|
80575
80831
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
80576
80832
|
policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
|
|
80577
|
-
process.stdout.write(
|
|
80833
|
+
process.stdout.write(chalk31.cyan("Pushing policy to the dashboard\u2026"));
|
|
80578
80834
|
const result = await runPolicyPush();
|
|
80579
80835
|
process.stdout.write("\n");
|
|
80580
80836
|
if (!result.ok) {
|
|
80581
|
-
console.error(
|
|
80837
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80582
80838
|
process.exit(1);
|
|
80583
80839
|
}
|
|
80584
|
-
console.log(
|
|
80585
|
-
console.log(
|
|
80840
|
+
console.log(chalk31.green("\u2713 Policy mirrored to the dashboard"));
|
|
80841
|
+
console.log(chalk31.gray(" See it under Security Policy \u2192 Machines"));
|
|
80586
80842
|
});
|
|
80587
80843
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
80588
|
-
process.stdout.write(
|
|
80844
|
+
process.stdout.write(chalk31.cyan("Syncing cloud policy rules\u2026"));
|
|
80589
80845
|
const result = await runCloudSync();
|
|
80590
80846
|
process.stdout.write("\n");
|
|
80591
80847
|
if (!result.ok) {
|
|
80592
|
-
console.error(
|
|
80848
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80593
80849
|
process.exit(1);
|
|
80594
80850
|
}
|
|
80595
80851
|
if (result.unchanged) {
|
|
80596
80852
|
console.log(
|
|
80597
|
-
|
|
80853
|
+
chalk31.green(
|
|
80598
80854
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
80599
80855
|
)
|
|
80600
80856
|
);
|
|
80601
|
-
console.log(
|
|
80602
|
-
console.log(
|
|
80857
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80858
|
+
console.log(chalk31.gray(` Server returned 304 (no changes since last sync)`));
|
|
80603
80859
|
} else {
|
|
80604
80860
|
console.log(
|
|
80605
|
-
|
|
80861
|
+
chalk31.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
80606
80862
|
);
|
|
80607
|
-
console.log(
|
|
80608
|
-
console.log(
|
|
80863
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80864
|
+
console.log(chalk31.gray(` File: ~/.node9/rules-cache.json`));
|
|
80609
80865
|
}
|
|
80610
80866
|
});
|
|
80611
80867
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
80612
80868
|
const status = getCloudSyncStatus();
|
|
80613
80869
|
if (!status.cached) {
|
|
80614
|
-
console.log(
|
|
80870
|
+
console.log(chalk31.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
80615
80871
|
return;
|
|
80616
80872
|
}
|
|
80617
80873
|
const rules = getCloudRules() ?? [];
|
|
80618
80874
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
80619
80875
|
console.log(
|
|
80620
|
-
|
|
80621
|
-
Cloud policy rules`) +
|
|
80876
|
+
chalk31.bold(`
|
|
80877
|
+
Cloud policy rules`) + chalk31.gray(
|
|
80622
80878
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
80623
80879
|
`
|
|
80624
80880
|
)
|
|
80625
80881
|
);
|
|
80626
80882
|
if (rules.length === 0) {
|
|
80627
|
-
console.log(
|
|
80883
|
+
console.log(chalk31.gray(" No rules defined in cloud policy.\n"));
|
|
80628
80884
|
return;
|
|
80629
80885
|
}
|
|
80630
80886
|
for (const rule of rules) {
|
|
80631
80887
|
const r = rule;
|
|
80632
|
-
const verdictColor = r.verdict === "block" ?
|
|
80888
|
+
const verdictColor = r.verdict === "block" ? chalk31.red : r.verdict === "allow" ? chalk31.green : chalk31.yellow;
|
|
80633
80889
|
console.log(
|
|
80634
80890
|
` ${verdictColor(
|
|
80635
80891
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
80636
|
-
)} ${
|
|
80892
|
+
)} ${chalk31.white(String(r.name ?? "(unnamed)"))}`
|
|
80637
80893
|
);
|
|
80638
|
-
if (r.reason) console.log(
|
|
80894
|
+
if (r.reason) console.log(chalk31.gray(` ${String(r.reason)}`));
|
|
80639
80895
|
}
|
|
80640
80896
|
console.log("");
|
|
80641
80897
|
});
|
|
80642
80898
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
80643
80899
|
const s = getCloudSyncStatus();
|
|
80644
80900
|
if (!s.cached) {
|
|
80645
|
-
console.log(
|
|
80901
|
+
console.log(chalk31.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
80646
80902
|
return;
|
|
80647
80903
|
}
|
|
80648
80904
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
80649
80905
|
console.log(`
|
|
80650
|
-
Rules : ${
|
|
80906
|
+
Rules : ${chalk31.green(String(s.rules))} cloud rules loaded`);
|
|
80651
80907
|
console.log(
|
|
80652
|
-
` Synced : ${
|
|
80908
|
+
` Synced : ${chalk31.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
80653
80909
|
);
|
|
80654
80910
|
if (s.workspaceId) {
|
|
80655
|
-
console.log(` Workspace: ${
|
|
80911
|
+
console.log(` Workspace: ${chalk31.gray(s.workspaceId)}`);
|
|
80656
80912
|
}
|
|
80657
80913
|
if (s.panicMode) {
|
|
80658
80914
|
console.log(
|
|
80659
|
-
` ${
|
|
80915
|
+
` ${chalk31.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk31.dim("(every review-verdict becomes block)")
|
|
80660
80916
|
);
|
|
80661
80917
|
}
|
|
80662
80918
|
if (s.shadowMode) {
|
|
80663
80919
|
console.log(
|
|
80664
|
-
` ${
|
|
80920
|
+
` ${chalk31.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk31.dim("(blocks become would-block log entries)")
|
|
80665
80921
|
);
|
|
80666
80922
|
}
|
|
80667
80923
|
if (s.syncIntervalHours) {
|
|
80668
80924
|
console.log(
|
|
80669
|
-
|
|
80925
|
+
chalk31.gray(
|
|
80670
80926
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
80671
80927
|
)
|
|
80672
80928
|
);
|
|
@@ -80677,7 +80933,7 @@ function registerSyncCommand(program2) {
|
|
|
80677
80933
|
|
|
80678
80934
|
// src/cli/commands/agents.ts
|
|
80679
80935
|
init_setup();
|
|
80680
|
-
import
|
|
80936
|
+
import chalk32 from "chalk";
|
|
80681
80937
|
var SETUP_FN = {
|
|
80682
80938
|
claude: setupClaude,
|
|
80683
80939
|
gemini: setupGemini,
|
|
@@ -80720,30 +80976,30 @@ function registerAgentsCommand(program2) {
|
|
|
80720
80976
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
80721
80977
|
console.log(" " + "\u2500".repeat(44));
|
|
80722
80978
|
for (const s of statuses) {
|
|
80723
|
-
const installed = s.installed ?
|
|
80724
|
-
const wired = !s.installed ?
|
|
80725
|
-
const mode = s.mode ?
|
|
80726
|
-
const hint = s.installed && !s.wired ?
|
|
80979
|
+
const installed = s.installed ? chalk32.green("\u2713") : chalk32.gray("\u2717");
|
|
80980
|
+
const wired = !s.installed ? chalk32.gray("\u2014") : s.wired ? chalk32.green("\u2713") : chalk32.yellow("\u2717");
|
|
80981
|
+
const mode = s.mode ? chalk32.gray(s.mode) : chalk32.gray("\u2014");
|
|
80982
|
+
const hint = s.installed && !s.wired ? chalk32.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
80727
80983
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
80728
80984
|
}
|
|
80729
80985
|
console.log("");
|
|
80730
80986
|
if (!anyInstalled) {
|
|
80731
80987
|
console.log(
|
|
80732
|
-
|
|
80988
|
+
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")
|
|
80733
80989
|
);
|
|
80734
80990
|
return;
|
|
80735
80991
|
}
|
|
80736
80992
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
80737
80993
|
if (unwired.length > 0) {
|
|
80738
80994
|
console.log(
|
|
80739
|
-
|
|
80995
|
+
chalk32.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk32.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
80740
80996
|
);
|
|
80741
80997
|
}
|
|
80742
80998
|
if (statuses.some((s) => s.name === "gemini" && s.installed)) {
|
|
80743
80999
|
console.log(
|
|
80744
|
-
|
|
81000
|
+
chalk32.yellow(
|
|
80745
81001
|
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
|
|
80746
|
-
) +
|
|
81002
|
+
) + chalk32.gray(" Migrate to Antigravity: ") + chalk32.white("node9 agents add antigravity") + "\n"
|
|
80747
81003
|
);
|
|
80748
81004
|
}
|
|
80749
81005
|
});
|
|
@@ -80751,7 +81007,7 @@ function registerAgentsCommand(program2) {
|
|
|
80751
81007
|
const name = resolveAgentName(agent);
|
|
80752
81008
|
const fn = SETUP_FN[name];
|
|
80753
81009
|
if (!fn) {
|
|
80754
|
-
console.error(
|
|
81010
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80755
81011
|
process.exit(1);
|
|
80756
81012
|
}
|
|
80757
81013
|
await fn();
|
|
@@ -80760,14 +81016,14 @@ function registerAgentsCommand(program2) {
|
|
|
80760
81016
|
const name = resolveAgentName(agent);
|
|
80761
81017
|
const fn = TEARDOWN_FN[name];
|
|
80762
81018
|
if (!fn) {
|
|
80763
|
-
console.error(
|
|
81019
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80764
81020
|
process.exit(1);
|
|
80765
81021
|
}
|
|
80766
|
-
console.log(
|
|
81022
|
+
console.log(chalk32.cyan(`
|
|
80767
81023
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
80768
81024
|
`));
|
|
80769
81025
|
fn();
|
|
80770
|
-
console.log(
|
|
81026
|
+
console.log(chalk32.gray("\n Restart the agent for changes to take effect."));
|
|
80771
81027
|
});
|
|
80772
81028
|
}
|
|
80773
81029
|
|
|
@@ -80776,21 +81032,21 @@ init_scan();
|
|
|
80776
81032
|
|
|
80777
81033
|
// src/cli/commands/posture.ts
|
|
80778
81034
|
init_posture();
|
|
80779
|
-
import
|
|
81035
|
+
import chalk34 from "chalk";
|
|
80780
81036
|
|
|
80781
81037
|
// src/posture/render.ts
|
|
80782
81038
|
init_score();
|
|
80783
|
-
import
|
|
81039
|
+
import chalk33 from "chalk";
|
|
80784
81040
|
var ICON = {
|
|
80785
|
-
critical:
|
|
80786
|
-
high:
|
|
80787
|
-
medium:
|
|
80788
|
-
advisory:
|
|
81041
|
+
critical: chalk33.red("\u274C"),
|
|
81042
|
+
high: chalk33.red("\u274C"),
|
|
81043
|
+
medium: chalk33.yellow("\u26A0\uFE0F "),
|
|
81044
|
+
advisory: chalk33.gray("\u26A0\uFE0F ")
|
|
80789
81045
|
};
|
|
80790
81046
|
var TIER_LABEL = {
|
|
80791
|
-
good:
|
|
80792
|
-
"at-risk":
|
|
80793
|
-
critical:
|
|
81047
|
+
good: chalk33.green("Good"),
|
|
81048
|
+
"at-risk": chalk33.yellow("At risk"),
|
|
81049
|
+
critical: chalk33.red("Critical")
|
|
80794
81050
|
};
|
|
80795
81051
|
function wrap(text, width) {
|
|
80796
81052
|
const out = [];
|
|
@@ -80808,7 +81064,7 @@ function wrap(text, width) {
|
|
|
80808
81064
|
}
|
|
80809
81065
|
var LABEL_WIDTH = 14;
|
|
80810
81066
|
function label2(category) {
|
|
80811
|
-
return
|
|
81067
|
+
return chalk33.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
80812
81068
|
}
|
|
80813
81069
|
function guardedWhat(f) {
|
|
80814
81070
|
if (f.detail.length === 0) return "this";
|
|
@@ -80818,31 +81074,31 @@ function guardedWhat(f) {
|
|
|
80818
81074
|
}
|
|
80819
81075
|
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
80820
81076
|
const lines = [];
|
|
80821
|
-
const wt = showWeight && f.scoreWeight ?
|
|
81077
|
+
const wt = showWeight && f.scoreWeight ? chalk33.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
80822
81078
|
lines.push(` ${ICON[f.severity]} ${label2(displayLabel)}${wt}${f.title}`);
|
|
80823
81079
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
80824
81080
|
const width = 80 - indent.length;
|
|
80825
81081
|
for (const s of [f.what, f.why, f.who]) {
|
|
80826
|
-
if (s) for (const l of wrap(s, width)) lines.push(indent +
|
|
81082
|
+
if (s) for (const l of wrap(s, width)) lines.push(indent + chalk33.gray(l));
|
|
80827
81083
|
}
|
|
80828
|
-
for (const d of f.detail) lines.push(indent +
|
|
81084
|
+
for (const d of f.detail) lines.push(indent + chalk33.gray(d));
|
|
80829
81085
|
if (f.fix) {
|
|
80830
81086
|
let first = true;
|
|
80831
81087
|
for (const seg of f.fix.split("\n")) {
|
|
80832
81088
|
for (const l of wrap(seg, width - 2)) {
|
|
80833
|
-
lines.push(indent +
|
|
81089
|
+
lines.push(indent + chalk33.cyan(first ? "\u2192 " + l : " " + l));
|
|
80834
81090
|
first = false;
|
|
80835
81091
|
}
|
|
80836
81092
|
}
|
|
80837
81093
|
}
|
|
80838
81094
|
const tradeoff = [
|
|
80839
|
-
[f.gain, "gain: ",
|
|
80840
|
-
[f.cost, "cost: ",
|
|
81095
|
+
[f.gain, "gain: ", chalk33.green],
|
|
81096
|
+
[f.cost, "cost: ", chalk33.yellow]
|
|
80841
81097
|
];
|
|
80842
81098
|
for (const [text, lbl, color2] of tradeoff) {
|
|
80843
81099
|
if (!text) continue;
|
|
80844
81100
|
wrap(text, width - 6).forEach((l, i) => {
|
|
80845
|
-
lines.push(indent + (i === 0 ? color2(lbl) : " ") +
|
|
81101
|
+
lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk33.gray(l));
|
|
80846
81102
|
});
|
|
80847
81103
|
}
|
|
80848
81104
|
return lines;
|
|
@@ -80852,7 +81108,7 @@ function renderPosture(result) {
|
|
|
80852
81108
|
const tier = TIER_LABEL[result.tier];
|
|
80853
81109
|
lines.push("");
|
|
80854
81110
|
lines.push(
|
|
80855
|
-
|
|
81111
|
+
chalk33.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk33.gray(` \u2014 ${result.agent}`) + ` ${chalk33.bold(`Score: ${result.score}/100`)} (${tier})`
|
|
80856
81112
|
);
|
|
80857
81113
|
const headroom = openHeadroom(result.findings);
|
|
80858
81114
|
if (headroom > 0) {
|
|
@@ -80860,15 +81116,15 @@ function renderPosture(result) {
|
|
|
80860
81116
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
80861
81117
|
).length;
|
|
80862
81118
|
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.`;
|
|
80863
|
-
for (const l of wrap(note, 76)) lines.push(" " +
|
|
81119
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk33.gray(l));
|
|
80864
81120
|
}
|
|
80865
81121
|
lines.push("");
|
|
80866
81122
|
if (result.headline) {
|
|
80867
81123
|
const indent = " ";
|
|
80868
|
-
lines.push(` ${
|
|
80869
|
-
for (const l of wrap(result.headline.risk, 74)) lines.push(indent +
|
|
81124
|
+
lines.push(` ${chalk33.red.bold("\u{1F525} Biggest risk")}`);
|
|
81125
|
+
for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk33.white(l));
|
|
80870
81126
|
const action = wrap(`Do this first: ${result.headline.action}`, 72);
|
|
80871
|
-
action.forEach((l, i) => lines.push(indent +
|
|
81127
|
+
action.forEach((l, i) => lines.push(indent + chalk33.cyan(i === 0 ? "\u2192 " + l : " " + l)));
|
|
80872
81128
|
lines.push("");
|
|
80873
81129
|
}
|
|
80874
81130
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
@@ -80878,13 +81134,13 @@ function renderPosture(result) {
|
|
|
80878
81134
|
);
|
|
80879
81135
|
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
80880
81136
|
if (covered.length > 0) {
|
|
80881
|
-
lines.push(" " +
|
|
81137
|
+
lines.push(" " + chalk33.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
80882
81138
|
for (const f of covered) {
|
|
80883
81139
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
80884
81140
|
const via = f.coverage?.via ?? "node9";
|
|
80885
81141
|
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
80886
81142
|
lines.push(
|
|
80887
|
-
` ${
|
|
81143
|
+
` ${chalk33.green("\u2705")} ${label2(lbl)}${chalk33.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
80888
81144
|
);
|
|
80889
81145
|
}
|
|
80890
81146
|
lines.push("");
|
|
@@ -80893,24 +81149,24 @@ function renderPosture(result) {
|
|
|
80893
81149
|
const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
|
|
80894
81150
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
80895
81151
|
if (node9Open.length > 0) {
|
|
80896
|
-
lines.push(" " +
|
|
81152
|
+
lines.push(" " + chalk33.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
80897
81153
|
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80898
81154
|
}
|
|
80899
81155
|
if (reduceOpen.length > 0) {
|
|
80900
81156
|
if (node9Open.length > 0) lines.push("");
|
|
80901
|
-
lines.push(" " +
|
|
81157
|
+
lines.push(" " + chalk33.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
80902
81158
|
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80903
81159
|
}
|
|
80904
81160
|
if (osOpen.length > 0) {
|
|
80905
81161
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
80906
|
-
lines.push(" " +
|
|
81162
|
+
lines.push(" " + chalk33.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
80907
81163
|
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
80908
81164
|
}
|
|
80909
81165
|
for (const cat of result.passedCategories) {
|
|
80910
|
-
lines.push(` ${
|
|
81166
|
+
lines.push(` ${chalk33.green("\u2705")} ${label2(cat)}${chalk33.gray("no issues found")}`);
|
|
80911
81167
|
}
|
|
80912
81168
|
for (const cat of result.erroredCategories) {
|
|
80913
|
-
lines.push(` ${
|
|
81169
|
+
lines.push(` ${chalk33.gray("\u2022")} ${label2(cat)}${chalk33.gray("could not be checked")}`);
|
|
80914
81170
|
}
|
|
80915
81171
|
lines.push("");
|
|
80916
81172
|
const crit = open.filter((f) => f.severity === "critical").length;
|
|
@@ -80918,16 +81174,16 @@ function renderPosture(result) {
|
|
|
80918
81174
|
const med = open.filter((f) => f.severity === "medium").length;
|
|
80919
81175
|
const adv = open.filter((f) => f.severity === "advisory").length;
|
|
80920
81176
|
const parts = [];
|
|
80921
|
-
if (crit) parts.push(
|
|
80922
|
-
if (high) parts.push(
|
|
80923
|
-
if (med) parts.push(
|
|
80924
|
-
if (adv) parts.push(
|
|
80925
|
-
const summary = parts.length ? parts.join(" \xB7 ") :
|
|
81177
|
+
if (crit) parts.push(chalk33.red(`${crit} critical`));
|
|
81178
|
+
if (high) parts.push(chalk33.red(`${high} high`));
|
|
81179
|
+
if (med) parts.push(chalk33.yellow(`${med} medium`));
|
|
81180
|
+
if (adv) parts.push(chalk33.gray(`${adv} advisory`));
|
|
81181
|
+
const summary = parts.length ? parts.join(" \xB7 ") : chalk33.green("no findings");
|
|
80926
81182
|
lines.push(` ${summary}`);
|
|
80927
81183
|
lines.push("");
|
|
80928
|
-
lines.push(" " +
|
|
81184
|
+
lines.push(" " + chalk33.bold("Track this across your fleet & keep it green:"));
|
|
80929
81185
|
lines.push(
|
|
80930
|
-
" " +
|
|
81186
|
+
" " + chalk33.dim("\u2192 ") + chalk33.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
|
|
80931
81187
|
);
|
|
80932
81188
|
lines.push("");
|
|
80933
81189
|
return lines.join("\n");
|
|
@@ -80947,11 +81203,11 @@ function registerPostureCommand(program2) {
|
|
|
80947
81203
|
if (opts.ship) {
|
|
80948
81204
|
const creds = readCredentials();
|
|
80949
81205
|
if (!creds) {
|
|
80950
|
-
console.error(
|
|
81206
|
+
console.error(chalk34.gray(" Run `node9 login` to ship this to your dashboard."));
|
|
80951
81207
|
} else {
|
|
80952
81208
|
const ok2 = await shipPosture(result, creds);
|
|
80953
81209
|
console.error(
|
|
80954
|
-
ok2 ?
|
|
81210
|
+
ok2 ? chalk34.gray(" \u2713 Shipped to your node9 dashboard.") : chalk34.gray(" Could not reach the dashboard \u2014 saved locally only.")
|
|
80955
81211
|
);
|
|
80956
81212
|
}
|
|
80957
81213
|
}
|
|
@@ -80960,7 +81216,7 @@ function registerPostureCommand(program2) {
|
|
|
80960
81216
|
}
|
|
80961
81217
|
|
|
80962
81218
|
// src/cli/commands/scan-repo.ts
|
|
80963
|
-
import
|
|
81219
|
+
import chalk36 from "chalk";
|
|
80964
81220
|
|
|
80965
81221
|
// src/ci-check/fetch.ts
|
|
80966
81222
|
var import_undici = __toESM(require_undici());
|
|
@@ -86472,7 +86728,7 @@ async function scanRepo(input, onProgress) {
|
|
|
86472
86728
|
}
|
|
86473
86729
|
|
|
86474
86730
|
// src/ci-check/render.ts
|
|
86475
|
-
import
|
|
86731
|
+
import chalk35 from "chalk";
|
|
86476
86732
|
var listed = (fs109) => fs109.filter((f) => f.tier !== "review");
|
|
86477
86733
|
function excerpt(f) {
|
|
86478
86734
|
const m = /`([^`]+)`/.exec(f.signals[0] ?? "");
|
|
@@ -86517,23 +86773,23 @@ function reviewTerminal(findings) {
|
|
|
86517
86773
|
const L = [];
|
|
86518
86774
|
if (items) {
|
|
86519
86775
|
L.push(
|
|
86520
|
-
|
|
86776
|
+
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.")
|
|
86521
86777
|
);
|
|
86522
86778
|
const files = [...byFile.keys()];
|
|
86523
86779
|
for (const file2 of files.slice(0, REVIEW_FILES)) {
|
|
86524
86780
|
const fs109 = byFile.get(file2);
|
|
86525
|
-
L.push(
|
|
86781
|
+
L.push(chalk35.gray(` ${file2}`));
|
|
86526
86782
|
for (const f of fs109.slice(0, REVIEW_LINES_PER_FILE))
|
|
86527
86783
|
L.push(` \u2022 ${f.line ? `line ${f.line}: ` : ""}${excerpt(f)}`);
|
|
86528
86784
|
if (fs109.length > REVIEW_LINES_PER_FILE)
|
|
86529
|
-
L.push(
|
|
86785
|
+
L.push(chalk35.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
|
|
86530
86786
|
}
|
|
86531
86787
|
const more = files.length - REVIEW_FILES;
|
|
86532
|
-
if (more > 0) L.push(
|
|
86788
|
+
if (more > 0) L.push(chalk35.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
|
|
86533
86789
|
}
|
|
86534
86790
|
if (grants)
|
|
86535
86791
|
L.push(
|
|
86536
|
-
|
|
86792
|
+
chalk35.gray(
|
|
86537
86793
|
` ${grants} skill${grants === 1 ? " is" : "s are"} granted broad tools (Bash, Write or Edit); usually what the skill needs.`
|
|
86538
86794
|
)
|
|
86539
86795
|
);
|
|
@@ -86547,45 +86803,45 @@ var ICON2 = {
|
|
|
86547
86803
|
advisory: "\u{1F7E2}"
|
|
86548
86804
|
};
|
|
86549
86805
|
var COLOR = {
|
|
86550
|
-
critical:
|
|
86551
|
-
high:
|
|
86552
|
-
medium:
|
|
86553
|
-
advisory:
|
|
86806
|
+
critical: chalk35.red.bold,
|
|
86807
|
+
high: chalk35.red,
|
|
86808
|
+
medium: chalk35.yellow,
|
|
86809
|
+
advisory: chalk35.gray
|
|
86554
86810
|
};
|
|
86555
86811
|
var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
|
|
86556
86812
|
function renderCta(res) {
|
|
86557
86813
|
const L = [];
|
|
86558
|
-
L.push(
|
|
86814
|
+
L.push(chalk35.dim(" " + "\u2500".repeat(63)));
|
|
86559
86815
|
if (res.worst === "critical" || res.worst === "high") {
|
|
86560
86816
|
const n = res.findings.filter(
|
|
86561
86817
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86562
86818
|
).length;
|
|
86563
86819
|
L.push(
|
|
86564
|
-
" " +
|
|
86820
|
+
" " + chalk35.red.bold(
|
|
86565
86821
|
`\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix, then stop the next at the PR.`
|
|
86566
86822
|
)
|
|
86567
86823
|
);
|
|
86568
86824
|
L.push("");
|
|
86569
|
-
L.push(" " +
|
|
86825
|
+
L.push(" " + chalk35.bold("Catch this class of issue on every PR, automatically:"));
|
|
86570
86826
|
} else if (res.worst) {
|
|
86571
|
-
L.push(" " +
|
|
86827
|
+
L.push(" " + chalk35.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
|
|
86572
86828
|
L.push("");
|
|
86573
|
-
L.push(" " +
|
|
86829
|
+
L.push(" " + chalk35.bold("Check every PR for agent-CI risk:"));
|
|
86574
86830
|
} else if (res.incomplete) {
|
|
86575
|
-
L.push(" " +
|
|
86831
|
+
L.push(" " + chalk35.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
|
|
86576
86832
|
L.push("");
|
|
86577
|
-
L.push(" " +
|
|
86833
|
+
L.push(" " + chalk35.bold("Get a complete check on every PR (CI reads the tree directly):"));
|
|
86578
86834
|
} else {
|
|
86579
86835
|
L.push(
|
|
86580
|
-
" " +
|
|
86836
|
+
" " + chalk35.green(
|
|
86581
86837
|
groupReview(res.findings).byFile.size ? "\u2705 No alerts. Check the items worth a look above." : "\u2705 Agent CI is well-configured: 0 unmitigated issues."
|
|
86582
86838
|
)
|
|
86583
86839
|
);
|
|
86584
86840
|
L.push("");
|
|
86585
|
-
L.push(" " +
|
|
86841
|
+
L.push(" " + chalk35.bold("Keep it green as you add agent workflows. Check every PR:"));
|
|
86586
86842
|
}
|
|
86587
|
-
L.push(" " +
|
|
86588
|
-
L.push(" " +
|
|
86843
|
+
L.push(" " + chalk35.dim("\u2192 ") + chalk35.cyan.underline(ACTION_URL));
|
|
86844
|
+
L.push(" " + chalk35.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
|
|
86589
86845
|
return L;
|
|
86590
86846
|
}
|
|
86591
86847
|
function ownedHint(source) {
|
|
@@ -86625,21 +86881,21 @@ function renderScan(res, diff) {
|
|
|
86625
86881
|
const L = [];
|
|
86626
86882
|
const shown = listed(res.findings);
|
|
86627
86883
|
const n = shown.length;
|
|
86628
|
-
const head = res.worst === "critical" || res.worst === "high" ?
|
|
86629
|
-
L.push(`\u{1F6E1}\uFE0F ${
|
|
86884
|
+
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");
|
|
86885
|
+
L.push(`\u{1F6E1}\uFE0F ${chalk35.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
|
|
86630
86886
|
L.push(
|
|
86631
|
-
|
|
86887
|
+
chalk35.gray(
|
|
86632
86888
|
` inspected ${res.inspected.length} config file(s), ${n} finding(s)` + (res.suppressedCount ? ` \xB7 ${res.suppressedCount} suppressed by .node9-ignore.json` : "")
|
|
86633
86889
|
)
|
|
86634
86890
|
);
|
|
86635
86891
|
if (diff) {
|
|
86636
86892
|
const introduced = listed(diff.added).length + diff.escalated.length;
|
|
86637
86893
|
L.push(
|
|
86638
|
-
diff.base !== "ok" ?
|
|
86894
|
+
diff.base !== "ok" ? chalk35.yellow.bold(
|
|
86639
86895
|
` \u26A0\uFE0F base ${diff.base === "did-not-run" ? "could not be read" : "scan was incomplete"}, so it cannot say what is new; showing everything`
|
|
86640
|
-
) : introduced > 0 ?
|
|
86896
|
+
) : introduced > 0 ? chalk35.red.bold(
|
|
86641
86897
|
` \u26A0\uFE0F this change introduced ${introduced} finding(s)` + (diff.escalated.length ? ` (${diff.escalated.length} by widening an existing one)` : "")
|
|
86642
|
-
) :
|
|
86898
|
+
) : chalk35.green(
|
|
86643
86899
|
` \u2705 this change introduced nothing` + (diff.unchanged.length ? ` (${diff.unchanged.length} pre-existing)` : "") + (diff.removed.length ? `, and fixed ${diff.removed.length}` : "")
|
|
86644
86900
|
)
|
|
86645
86901
|
);
|
|
@@ -86647,7 +86903,7 @@ function renderScan(res, diff) {
|
|
|
86647
86903
|
if (res.incomplete) {
|
|
86648
86904
|
const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
|
|
86649
86905
|
L.push(
|
|
86650
|
-
|
|
86906
|
+
chalk35.yellow.bold(
|
|
86651
86907
|
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."
|
|
86652
86908
|
)
|
|
86653
86909
|
);
|
|
@@ -86656,36 +86912,36 @@ function renderScan(res, diff) {
|
|
|
86656
86912
|
for (const f of shown) {
|
|
86657
86913
|
const ex = f.explain;
|
|
86658
86914
|
L.push(
|
|
86659
|
-
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${
|
|
86915
|
+
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk35.bold(ex ? ex.headline : f.title)}`
|
|
86660
86916
|
);
|
|
86661
|
-
L.push(
|
|
86917
|
+
L.push(chalk35.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
|
|
86662
86918
|
if (ex) {
|
|
86663
86919
|
L.push(` ${ex.happens}`);
|
|
86664
86920
|
for (const s of ex.saw) L.push(` \u2022 ${s}`);
|
|
86665
|
-
ex.fix.forEach((x, i) => L.push(
|
|
86921
|
+
ex.fix.forEach((x, i) => L.push(chalk35.cyan(` ${i + 1}. ${x}`)));
|
|
86666
86922
|
} else {
|
|
86667
86923
|
for (const s of f.signals) L.push(` \u2022 ${s}`);
|
|
86668
86924
|
if (f.mitigations?.length)
|
|
86669
|
-
L.push(
|
|
86670
|
-
L.push(
|
|
86925
|
+
L.push(chalk35.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
|
|
86926
|
+
L.push(chalk35.cyan(` \u2192 ${f.fix}`));
|
|
86671
86927
|
}
|
|
86672
86928
|
L.push("");
|
|
86673
86929
|
}
|
|
86674
86930
|
if (n === 0 && !res.incomplete) {
|
|
86675
86931
|
L.push(
|
|
86676
|
-
|
|
86932
|
+
chalk35.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
|
|
86677
86933
|
);
|
|
86678
86934
|
L.push("");
|
|
86679
86935
|
}
|
|
86680
86936
|
L.push(...reviewTerminal(res.findings));
|
|
86681
|
-
for (const note of res.notes) L.push(
|
|
86937
|
+
for (const note of res.notes) L.push(chalk35.gray(` note: ${note}`));
|
|
86682
86938
|
const hasHigh = shown.some(
|
|
86683
86939
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86684
86940
|
);
|
|
86685
86941
|
if (hasHigh && !ownedHint(res.source)) {
|
|
86686
86942
|
L.push("");
|
|
86687
86943
|
L.push(
|
|
86688
|
-
|
|
86944
|
+
chalk35.yellow(
|
|
86689
86945
|
" \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.)"
|
|
86690
86946
|
)
|
|
86691
86947
|
);
|
|
@@ -86879,7 +87135,7 @@ function makeProgress(target, quiet) {
|
|
|
86879
87135
|
let frame = 0;
|
|
86880
87136
|
let last = `scanning ${target}\u2026`;
|
|
86881
87137
|
const timer = setInterval(() => {
|
|
86882
|
-
process.stderr.write(`\r${
|
|
87138
|
+
process.stderr.write(`\r${chalk36.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
|
|
86883
87139
|
}, 80);
|
|
86884
87140
|
return {
|
|
86885
87141
|
onProgress: (p) => {
|
|
@@ -86942,236 +87198,13 @@ function registerScanRepoCommand(program2) {
|
|
|
86942
87198
|
if (!telemetryDisabled()) {
|
|
86943
87199
|
await sendScanRepoPing(buildScanRepoPing(res, target));
|
|
86944
87200
|
if (!opts.json && !opts.markdown && process.stderr.isTTY) {
|
|
86945
|
-
process.stderr.write(
|
|
87201
|
+
process.stderr.write(chalk36.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
|
|
86946
87202
|
}
|
|
86947
87203
|
}
|
|
86948
87204
|
}
|
|
86949
87205
|
);
|
|
86950
87206
|
}
|
|
86951
87207
|
|
|
86952
|
-
// src/cli/commands/egress.ts
|
|
86953
|
-
init_config();
|
|
86954
|
-
init_keyed_guard();
|
|
86955
|
-
init_dist();
|
|
86956
|
-
init_egress_config();
|
|
86957
|
-
init_egress_config();
|
|
86958
|
-
import chalk36 from "chalk";
|
|
86959
|
-
function guard(fn) {
|
|
86960
|
-
try {
|
|
86961
|
-
fn();
|
|
86962
|
-
return true;
|
|
86963
|
-
} catch (err2) {
|
|
86964
|
-
console.error(chalk36.red(`
|
|
86965
|
-
\u2717 ${err2.message}
|
|
86966
|
-
`));
|
|
86967
|
-
process.exitCode = 1;
|
|
86968
|
-
return false;
|
|
86969
|
-
}
|
|
86970
|
-
}
|
|
86971
|
-
function mutate(action, change) {
|
|
86972
|
-
if (!cliGuardPolicyWrite(action)) return false;
|
|
86973
|
-
return guard(() => setEgress(change));
|
|
86974
|
-
}
|
|
86975
|
-
function addHost(list2, host2) {
|
|
86976
|
-
if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
|
|
86977
|
-
return guard(() => addEgressHost(list2, host2));
|
|
86978
|
-
}
|
|
86979
|
-
function exempt2(address) {
|
|
86980
|
-
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
86981
|
-
return guard(() => addSsrfExemption(address));
|
|
86982
|
-
}
|
|
86983
|
-
var INTERNAL_FIELDS = {
|
|
86984
|
-
allowed: { ssrfStrict: false, allowPrivate: true },
|
|
86985
|
-
listed: { ssrfStrict: false, allowPrivate: false },
|
|
86986
|
-
blocked: { ssrfStrict: true, allowPrivate: false }
|
|
86987
|
-
};
|
|
86988
|
-
var INTERNAL_SAID = {
|
|
86989
|
-
allowed: "reachable without listing them",
|
|
86990
|
-
listed: "reachable only if they are on your allowlist",
|
|
86991
|
-
blocked: "blocked"
|
|
86992
|
-
};
|
|
86993
|
-
function readInternalState(e) {
|
|
86994
|
-
if (e.ssrfStrict === true) return "blocked";
|
|
86995
|
-
return e.allowPrivate === false ? "listed" : "allowed";
|
|
86996
|
-
}
|
|
86997
|
-
function setInternal(value) {
|
|
86998
|
-
const state = value.trim().toLowerCase();
|
|
86999
|
-
if (!(state in INTERNAL_FIELDS)) {
|
|
87000
|
-
console.error(
|
|
87001
|
-
chalk36.red(`
|
|
87002
|
-
\u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk36.gray(
|
|
87003
|
-
"\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"
|
|
87004
|
-
)
|
|
87005
|
-
);
|
|
87006
|
-
process.exitCode = 1;
|
|
87007
|
-
return;
|
|
87008
|
-
}
|
|
87009
|
-
if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
|
|
87010
|
-
_resetConfigCache();
|
|
87011
|
-
const effective = readInternalState(getConfig().policy.egress);
|
|
87012
|
-
if (effective !== state) {
|
|
87013
|
-
console.log(
|
|
87014
|
-
chalk36.yellow(
|
|
87015
|
-
`
|
|
87016
|
-
\u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
|
|
87017
|
-
Change it in the dashboard, Enforcement \u2192 Network.
|
|
87018
|
-
`
|
|
87019
|
-
)
|
|
87020
|
-
);
|
|
87021
|
-
return;
|
|
87022
|
-
}
|
|
87023
|
-
const line = `
|
|
87024
|
-
\u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
|
|
87025
|
-
`;
|
|
87026
|
-
console.log(state === "allowed" ? chalk36.yellow(line) : chalk36.green(line));
|
|
87027
|
-
}
|
|
87028
|
-
function showFloor(e, ssrfStrictSource) {
|
|
87029
|
-
console.log(
|
|
87030
|
-
chalk36.gray("\n Protected addresses") + chalk36.gray(" \u2014 in shell commands and declared URLs")
|
|
87031
|
-
);
|
|
87032
|
-
console.log(
|
|
87033
|
-
chalk36.gray(
|
|
87034
|
-
" always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
|
|
87035
|
-
)
|
|
87036
|
-
);
|
|
87037
|
-
const state = readInternalState(e);
|
|
87038
|
-
const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
|
|
87039
|
-
const STATE_LABEL = {
|
|
87040
|
-
allowed: "allowed",
|
|
87041
|
-
listed: "allowlist only",
|
|
87042
|
-
blocked: "blocked"
|
|
87043
|
-
};
|
|
87044
|
-
console.log(
|
|
87045
|
-
` 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]}`)
|
|
87046
|
-
);
|
|
87047
|
-
console.log(chalk36.gray(` set by: ${by}`));
|
|
87048
|
-
const exemptions = e.ssrfAllow ?? [];
|
|
87049
|
-
console.log(chalk36.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
|
|
87050
|
-
console.log(
|
|
87051
|
-
chalk36.gray(
|
|
87052
|
-
" 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."
|
|
87053
|
-
)
|
|
87054
|
-
);
|
|
87055
|
-
}
|
|
87056
|
-
function showStatus() {
|
|
87057
|
-
const cfg = getConfig();
|
|
87058
|
-
const e = cfg.policy.egress;
|
|
87059
|
-
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");
|
|
87060
|
-
console.log(chalk36.cyan.bold("\n\u{1F310} Egress control"));
|
|
87061
|
-
console.log(" State: " + state);
|
|
87062
|
-
if (cfg.policySource === "workspace") {
|
|
87063
|
-
console.log(
|
|
87064
|
-
chalk36.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
|
|
87065
|
-
);
|
|
87066
|
-
}
|
|
87067
|
-
console.log(
|
|
87068
|
-
chalk36.gray(
|
|
87069
|
-
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
87070
|
-
)
|
|
87071
|
-
);
|
|
87072
|
-
showFloor(e, cfg.ssrfStrictSource);
|
|
87073
|
-
if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
|
|
87074
|
-
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
87075
|
-
if (!e.enabled) {
|
|
87076
|
-
console.log(chalk36.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
87077
|
-
console.log(chalk36.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
87078
|
-
}
|
|
87079
|
-
console.log("");
|
|
87080
|
-
}
|
|
87081
|
-
function registerEgressCommand(program2) {
|
|
87082
|
-
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
87083
|
-
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
87084
|
-
if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
|
|
87085
|
-
console.log(chalk36.green("\n\u2713 Egress is now watched (review mode)."));
|
|
87086
|
-
console.log(
|
|
87087
|
-
chalk36.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
87088
|
-
);
|
|
87089
|
-
console.log(
|
|
87090
|
-
chalk36.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
87091
|
-
);
|
|
87092
|
-
});
|
|
87093
|
-
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
87094
|
-
if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
|
|
87095
|
-
console.log(chalk36.green("\n\u2713 Egress is now locked (block mode)."));
|
|
87096
|
-
console.log(chalk36.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
87097
|
-
console.log(chalk36.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
87098
|
-
});
|
|
87099
|
-
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
|
|
87100
|
-
if (!addHost("allow", host2)) return;
|
|
87101
|
-
console.log(chalk36.green(`
|
|
87102
|
-
\u2713 Allowed egress to ${host2}.
|
|
87103
|
-
`));
|
|
87104
|
-
});
|
|
87105
|
-
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
|
|
87106
|
-
if (!addHost("deny", host2)) return;
|
|
87107
|
-
console.log(chalk36.green(`
|
|
87108
|
-
\u2713 Denied egress to ${host2}.
|
|
87109
|
-
`));
|
|
87110
|
-
});
|
|
87111
|
-
egress.command("off").description("Turn egress control off").action(() => {
|
|
87112
|
-
if (!mutate("egress off", { enabled: false })) return;
|
|
87113
|
-
console.log(
|
|
87114
|
-
chalk36.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
87115
|
-
);
|
|
87116
|
-
});
|
|
87117
|
-
egress.command("internal <allowed|listed|blocked>").description(
|
|
87118
|
-
"How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
|
|
87119
|
-
).action((value) => setInternal(value));
|
|
87120
|
-
egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
|
|
87121
|
-
const v = value.trim().toLowerCase();
|
|
87122
|
-
if (v !== "on" && v !== "off") {
|
|
87123
|
-
console.error(chalk36.red(`
|
|
87124
|
-
\u2717 Expected "on" or "off", got "${value}".
|
|
87125
|
-
`));
|
|
87126
|
-
process.exitCode = 1;
|
|
87127
|
-
return;
|
|
87128
|
-
}
|
|
87129
|
-
const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
|
|
87130
|
-
console.log(chalk36.gray(`
|
|
87131
|
-
(\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
|
|
87132
|
-
setInternal(state);
|
|
87133
|
-
});
|
|
87134
|
-
egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
|
|
87135
|
-
const a = normalizeEgressHost(address);
|
|
87136
|
-
const slash = a.indexOf("/");
|
|
87137
|
-
const base2 = slash === -1 ? a : a.slice(0, slash);
|
|
87138
|
-
const prefixText = slash === -1 ? null : a.slice(slash + 1);
|
|
87139
|
-
if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
|
|
87140
|
-
console.error(
|
|
87141
|
-
chalk36.red(`
|
|
87142
|
-
\u2717 "${address}" is not an address or a range.`) + chalk36.gray(
|
|
87143
|
-
"\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"
|
|
87144
|
-
)
|
|
87145
|
-
);
|
|
87146
|
-
process.exitCode = 1;
|
|
87147
|
-
return;
|
|
87148
|
-
}
|
|
87149
|
-
const m = classifySsrf(base2);
|
|
87150
|
-
if (m && !m.overridable) {
|
|
87151
|
-
console.error(
|
|
87152
|
-
chalk36.red(`
|
|
87153
|
-
\u2717 ${a} cannot be exempted.`) + chalk36.gray(
|
|
87154
|
-
`
|
|
87155
|
-
${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
|
|
87156
|
-
`
|
|
87157
|
-
)
|
|
87158
|
-
);
|
|
87159
|
-
process.exitCode = 1;
|
|
87160
|
-
return;
|
|
87161
|
-
}
|
|
87162
|
-
if (!exempt2(a)) return;
|
|
87163
|
-
console.log(chalk36.green(`
|
|
87164
|
-
\u2713 ${a} is exempt from the floor.`));
|
|
87165
|
-
if (!m)
|
|
87166
|
-
console.log(
|
|
87167
|
-
chalk36.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
|
|
87168
|
-
);
|
|
87169
|
-
else console.log("");
|
|
87170
|
-
});
|
|
87171
|
-
egress.command("status").description("Show the current egress state").action(showStatus);
|
|
87172
|
-
egress.action(showStatus);
|
|
87173
|
-
}
|
|
87174
|
-
|
|
87175
87208
|
// src/cli/commands/checks.ts
|
|
87176
87209
|
init_dist();
|
|
87177
87210
|
init_config();
|
|
@@ -87212,7 +87245,9 @@ function renderChecks(report) {
|
|
|
87212
87245
|
const lines = [];
|
|
87213
87246
|
const where = report.policySource === "workspace" ? "workspace (app.node9.ai)" : "local (~/.node9/config.json)";
|
|
87214
87247
|
lines.push("");
|
|
87215
|
-
lines.push(
|
|
87248
|
+
lines.push(
|
|
87249
|
+
`${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${levelOf(report.mode)}`
|
|
87250
|
+
);
|
|
87216
87251
|
lines.push("");
|
|
87217
87252
|
const row = (c) => {
|
|
87218
87253
|
const value = (VALUE_COLOR[c.value] ?? chalk37.white)(c.value.padEnd(7));
|