@node9/proxy 2.43.1 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1125 -1074
- package/dist/cli.mjs +1089 -1038
- package/dist/dashboard.mjs +443 -21
- package/dist/scan-ink.mjs +5 -1
- 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,7 +35067,7 @@ 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;
|
|
@@ -34955,13 +35080,13 @@ function scoreLabel(score) {
|
|
|
34955
35080
|
return chalk3.red.bold(`${score}/100 Critical`);
|
|
34956
35081
|
}
|
|
34957
35082
|
function runBlast() {
|
|
34958
|
-
const home =
|
|
35083
|
+
const home = os43.homedir();
|
|
34959
35084
|
const cwd = process.cwd();
|
|
34960
35085
|
const paths = buildSensitivePaths(home, cwd);
|
|
34961
35086
|
let scoreDeduction = 0;
|
|
34962
35087
|
const reachable = [];
|
|
34963
35088
|
for (const p of paths) {
|
|
34964
|
-
if (
|
|
35089
|
+
if (fs51.existsSync(p.full) && isReadable(p.full)) {
|
|
34965
35090
|
reachable.push(p);
|
|
34966
35091
|
scoreDeduction += p.score;
|
|
34967
35092
|
}
|
|
@@ -34977,11 +35102,18 @@ function runBlast() {
|
|
|
34977
35102
|
}
|
|
34978
35103
|
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
34979
35104
|
}
|
|
35105
|
+
function labelColumnWidth(labels) {
|
|
35106
|
+
return Math.max(38, ...labels.map((l) => l.length + 2));
|
|
35107
|
+
}
|
|
34980
35108
|
function registerBlastCommand(program2) {
|
|
34981
35109
|
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
34982
|
-
const home =
|
|
35110
|
+
const home = os43.homedir();
|
|
34983
35111
|
const cwd = process.cwd();
|
|
34984
35112
|
const { reachable, envFindings, score } = runBlast();
|
|
35113
|
+
const width = labelColumnWidth([
|
|
35114
|
+
...reachable.map((p) => p.label),
|
|
35115
|
+
...envFindings.map((f) => f.key)
|
|
35116
|
+
]);
|
|
34985
35117
|
console.log("");
|
|
34986
35118
|
console.log(
|
|
34987
35119
|
chalk3.bold(" \u{1F52D} Node9 Blast Radius") + chalk3.dim(" \xB7 what an AI agent can reach from here")
|
|
@@ -34992,7 +35124,7 @@ function registerBlastCommand(program2) {
|
|
|
34992
35124
|
console.log(" " + chalk3.red.bold("Sensitive files reachable:"));
|
|
34993
35125
|
for (const p of reachable) {
|
|
34994
35126
|
console.log(
|
|
34995
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(
|
|
35127
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(width)) + chalk3.dim(p.description)
|
|
34996
35128
|
);
|
|
34997
35129
|
}
|
|
34998
35130
|
console.log("");
|
|
@@ -35001,7 +35133,7 @@ function registerBlastCommand(program2) {
|
|
|
35001
35133
|
console.log(" " + chalk3.red.bold("Secrets in active environment:"));
|
|
35002
35134
|
for (const f of envFindings) {
|
|
35003
35135
|
console.log(
|
|
35004
|
-
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(
|
|
35136
|
+
" " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(width)) + chalk3.dim(f.patternName)
|
|
35005
35137
|
);
|
|
35006
35138
|
}
|
|
35007
35139
|
console.log("");
|
|
@@ -35116,6 +35248,12 @@ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
|
35116
35248
|
if (days > 90) return "90d+";
|
|
35117
35249
|
return `${days}d`;
|
|
35118
35250
|
}
|
|
35251
|
+
function relativeAgo(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
35252
|
+
const rel = relativeDate(timestamp2, now);
|
|
35253
|
+
if (rel === "?" || rel === "today") return rel;
|
|
35254
|
+
if (rel === "1d") return "yesterday";
|
|
35255
|
+
return `${rel} ago`;
|
|
35256
|
+
}
|
|
35119
35257
|
var PANEL_WIDTH;
|
|
35120
35258
|
var init_scan_derive = __esm({
|
|
35121
35259
|
"src/cli/render/scan-derive.ts"() {
|
|
@@ -35169,17 +35307,17 @@ var init_scan_json = __esm({
|
|
|
35169
35307
|
});
|
|
35170
35308
|
|
|
35171
35309
|
// src/cli/render/scan-history.ts
|
|
35172
|
-
import
|
|
35173
|
-
import
|
|
35174
|
-
import
|
|
35310
|
+
import fs52 from "fs";
|
|
35311
|
+
import path54 from "path";
|
|
35312
|
+
import os44 from "os";
|
|
35175
35313
|
function defaultHistoryPath() {
|
|
35176
|
-
return
|
|
35314
|
+
return path54.join(os44.homedir(), ".node9", "scan-history.json");
|
|
35177
35315
|
}
|
|
35178
35316
|
function readPreviousScan(opts = {}) {
|
|
35179
35317
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
35180
35318
|
try {
|
|
35181
|
-
if (!
|
|
35182
|
-
const raw =
|
|
35319
|
+
if (!fs52.existsSync(filePath)) return null;
|
|
35320
|
+
const raw = fs52.readFileSync(filePath, "utf8");
|
|
35183
35321
|
const parsed = JSON.parse(raw);
|
|
35184
35322
|
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
35185
35323
|
const last = parsed[parsed.length - 1];
|
|
@@ -35193,11 +35331,11 @@ function appendScanHistory(record2, opts = {}) {
|
|
|
35193
35331
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
35194
35332
|
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
35195
35333
|
try {
|
|
35196
|
-
|
|
35334
|
+
fs52.mkdirSync(path54.dirname(filePath), { recursive: true });
|
|
35197
35335
|
let history = [];
|
|
35198
|
-
if (
|
|
35336
|
+
if (fs52.existsSync(filePath)) {
|
|
35199
35337
|
try {
|
|
35200
|
-
const parsed = JSON.parse(
|
|
35338
|
+
const parsed = JSON.parse(fs52.readFileSync(filePath, "utf8"));
|
|
35201
35339
|
if (Array.isArray(parsed)) {
|
|
35202
35340
|
history = parsed.filter(isValidRecord);
|
|
35203
35341
|
}
|
|
@@ -35252,9 +35390,9 @@ __export(scan_watermark_exports, {
|
|
|
35252
35390
|
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
35253
35391
|
tickScanWatcher: () => tickScanWatcher
|
|
35254
35392
|
});
|
|
35255
|
-
import
|
|
35256
|
-
import
|
|
35257
|
-
import
|
|
35393
|
+
import fs53 from "fs";
|
|
35394
|
+
import os45 from "os";
|
|
35395
|
+
import path55 from "path";
|
|
35258
35396
|
import readline from "readline";
|
|
35259
35397
|
function freshWatermark() {
|
|
35260
35398
|
return {
|
|
@@ -35267,7 +35405,7 @@ function freshWatermark() {
|
|
|
35267
35405
|
function loadWatermark() {
|
|
35268
35406
|
let raw;
|
|
35269
35407
|
try {
|
|
35270
|
-
raw =
|
|
35408
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35271
35409
|
} catch {
|
|
35272
35410
|
return { status: "fresh", wm: freshWatermark() };
|
|
35273
35411
|
}
|
|
@@ -35319,28 +35457,28 @@ function loadWatermark() {
|
|
|
35319
35457
|
function saveWatermark(wm) {
|
|
35320
35458
|
if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
|
|
35321
35459
|
const target = WATERMARK_FILE();
|
|
35322
|
-
const dir2 =
|
|
35323
|
-
if (!
|
|
35460
|
+
const dir2 = path55.dirname(target);
|
|
35461
|
+
if (!fs53.existsSync(dir2)) fs53.mkdirSync(dir2, { recursive: true });
|
|
35324
35462
|
const tmp = target + ".tmp";
|
|
35325
|
-
|
|
35326
|
-
|
|
35463
|
+
fs53.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
|
|
35464
|
+
fs53.renameSync(tmp, target);
|
|
35327
35465
|
}
|
|
35328
35466
|
function listJsonlFiles() {
|
|
35329
35467
|
const root = PROJECTS_DIR();
|
|
35330
|
-
if (!
|
|
35468
|
+
if (!fs53.existsSync(root)) return [];
|
|
35331
35469
|
const out = [];
|
|
35332
|
-
for (const entry of
|
|
35470
|
+
for (const entry of fs53.readdirSync(root, { withFileTypes: true })) {
|
|
35333
35471
|
if (!entry.isDirectory()) continue;
|
|
35334
|
-
const projectDir =
|
|
35472
|
+
const projectDir = path55.join(root, entry.name);
|
|
35335
35473
|
let inner;
|
|
35336
35474
|
try {
|
|
35337
|
-
inner =
|
|
35475
|
+
inner = fs53.readdirSync(projectDir, { withFileTypes: true });
|
|
35338
35476
|
} catch {
|
|
35339
35477
|
continue;
|
|
35340
35478
|
}
|
|
35341
35479
|
for (const file2 of inner) {
|
|
35342
35480
|
if (file2.isFile() && file2.name.endsWith(".jsonl")) {
|
|
35343
|
-
out.push(
|
|
35481
|
+
out.push(path55.join(projectDir, file2.name));
|
|
35344
35482
|
}
|
|
35345
35483
|
}
|
|
35346
35484
|
}
|
|
@@ -35348,7 +35486,7 @@ function listJsonlFiles() {
|
|
|
35348
35486
|
}
|
|
35349
35487
|
function fileSize(p) {
|
|
35350
35488
|
try {
|
|
35351
|
-
return
|
|
35489
|
+
return fs53.statSync(p).size;
|
|
35352
35490
|
} catch {
|
|
35353
35491
|
return 0;
|
|
35354
35492
|
}
|
|
@@ -35358,7 +35496,7 @@ async function scanDelta(filePath, fromByte, onLine) {
|
|
|
35358
35496
|
if (size <= fromByte) return fromByte;
|
|
35359
35497
|
const lastNl = findLastNewline(filePath, fromByte, size);
|
|
35360
35498
|
const endsWithNewline = lastNl === size - 1;
|
|
35361
|
-
const stream =
|
|
35499
|
+
const stream = fs53.createReadStream(filePath, {
|
|
35362
35500
|
start: fromByte,
|
|
35363
35501
|
end: size - 1,
|
|
35364
35502
|
highWaterMark: 64 * 1024
|
|
@@ -35387,7 +35525,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35387
35525
|
const CHUNK = 64 * 1024;
|
|
35388
35526
|
let fd;
|
|
35389
35527
|
try {
|
|
35390
|
-
fd =
|
|
35528
|
+
fd = fs53.openSync(filePath, "r");
|
|
35391
35529
|
} catch {
|
|
35392
35530
|
return -1;
|
|
35393
35531
|
}
|
|
@@ -35396,7 +35534,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35396
35534
|
let end = size;
|
|
35397
35535
|
while (end > from) {
|
|
35398
35536
|
const start = Math.max(from, end - CHUNK);
|
|
35399
|
-
const n =
|
|
35537
|
+
const n = fs53.readSync(fd, buf, 0, end - start, start);
|
|
35400
35538
|
const idx = buf.subarray(0, n).lastIndexOf(10);
|
|
35401
35539
|
if (idx !== -1) return start + idx;
|
|
35402
35540
|
end = start;
|
|
@@ -35405,7 +35543,7 @@ function findLastNewline(filePath, from, size) {
|
|
|
35405
35543
|
} catch {
|
|
35406
35544
|
return -1;
|
|
35407
35545
|
} finally {
|
|
35408
|
-
|
|
35546
|
+
fs53.closeSync(fd);
|
|
35409
35547
|
}
|
|
35410
35548
|
}
|
|
35411
35549
|
function safeCanaryCtxValues() {
|
|
@@ -35416,7 +35554,7 @@ function safeCanaryCtxValues() {
|
|
|
35416
35554
|
}
|
|
35417
35555
|
}
|
|
35418
35556
|
function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
35419
|
-
const cwd = typeof obj.cwd === "string" &&
|
|
35557
|
+
const cwd = typeof obj.cwd === "string" && path55.isAbsolute(obj.cwd) ? obj.cwd : void 0;
|
|
35420
35558
|
return {
|
|
35421
35559
|
sessionId,
|
|
35422
35560
|
lineIndex,
|
|
@@ -35432,7 +35570,7 @@ function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
|
|
|
35432
35570
|
// line-level DLP runs above already
|
|
35433
35571
|
canaryValues: canaryVals,
|
|
35434
35572
|
...cwd && { cwd },
|
|
35435
|
-
homeDirs: [
|
|
35573
|
+
homeDirs: [os45.homedir()]
|
|
35436
35574
|
};
|
|
35437
35575
|
}
|
|
35438
35576
|
function extractFindingsFromLine(line, sessionId, lineIndex, canaryVals = []) {
|
|
@@ -35520,7 +35658,7 @@ async function tickForensicBroadcast(offsets) {
|
|
|
35520
35658
|
continue;
|
|
35521
35659
|
}
|
|
35522
35660
|
if (size <= offset) continue;
|
|
35523
|
-
const sessionId =
|
|
35661
|
+
const sessionId = path55.basename(file2, ".jsonl");
|
|
35524
35662
|
const newOffset = await scanDelta(file2, offset, (obj, lineIndex) => {
|
|
35525
35663
|
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex, canaryVals));
|
|
35526
35664
|
});
|
|
@@ -35576,7 +35714,7 @@ function emptyTick(uploadAs) {
|
|
|
35576
35714
|
function readRawWatermarkPreservingOffsets() {
|
|
35577
35715
|
let raw;
|
|
35578
35716
|
try {
|
|
35579
|
-
raw =
|
|
35717
|
+
raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
|
|
35580
35718
|
} catch {
|
|
35581
35719
|
return null;
|
|
35582
35720
|
}
|
|
@@ -35611,13 +35749,13 @@ async function runActualTick(wm) {
|
|
|
35611
35749
|
if (!known) {
|
|
35612
35750
|
let mtimeMs = 0;
|
|
35613
35751
|
try {
|
|
35614
|
-
mtimeMs =
|
|
35752
|
+
mtimeMs = fs53.statSync(filePath).mtime.getTime();
|
|
35615
35753
|
} catch {
|
|
35616
35754
|
continue;
|
|
35617
35755
|
}
|
|
35618
35756
|
if (mtimeMs >= watermarkCreatedAt) {
|
|
35619
35757
|
filesNew++;
|
|
35620
|
-
const sessionId2 =
|
|
35758
|
+
const sessionId2 = path55.basename(filePath, ".jsonl");
|
|
35621
35759
|
const newScannedTo2 = await scanDelta(filePath, 0, (obj, lineIndex) => {
|
|
35622
35760
|
totalToolCalls++;
|
|
35623
35761
|
toolCallsBySession[sessionId2] = (toolCallsBySession[sessionId2] ?? 0) + 1;
|
|
@@ -35635,7 +35773,7 @@ async function runActualTick(wm) {
|
|
|
35635
35773
|
filesSkipped++;
|
|
35636
35774
|
continue;
|
|
35637
35775
|
}
|
|
35638
|
-
const sessionId =
|
|
35776
|
+
const sessionId = path55.basename(filePath, ".jsonl");
|
|
35639
35777
|
const newScannedTo = await scanDelta(filePath, known.scannedTo, (obj, lineIndex) => {
|
|
35640
35778
|
totalToolCalls++;
|
|
35641
35779
|
toolCallsBySession[sessionId] = (toolCallsBySession[sessionId] ?? 0) + 1;
|
|
@@ -35670,8 +35808,8 @@ var init_scan_watermark = __esm({
|
|
|
35670
35808
|
init_registry2();
|
|
35671
35809
|
init_config();
|
|
35672
35810
|
init_dist();
|
|
35673
|
-
PROJECTS_DIR = () =>
|
|
35674
|
-
WATERMARK_FILE = () =>
|
|
35811
|
+
PROJECTS_DIR = () => path55.join(os45.homedir(), ".claude", "projects");
|
|
35812
|
+
WATERMARK_FILE = () => path55.join(os45.homedir(), ".node9", "scan-watermark.json");
|
|
35675
35813
|
MAX_LINE_BYTES = 2 * 1024 * 1024;
|
|
35676
35814
|
WATERMARK_SCHEMA_VERSION = 2;
|
|
35677
35815
|
LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
|
|
@@ -35687,10 +35825,10 @@ __export(scan_upload_history_exports, {
|
|
|
35687
35825
|
parseSinceCutoff: () => parseSinceCutoff,
|
|
35688
35826
|
runUploadHistory: () => runUploadHistory
|
|
35689
35827
|
});
|
|
35690
|
-
import
|
|
35828
|
+
import fs54 from "fs";
|
|
35691
35829
|
import https from "https";
|
|
35692
|
-
import
|
|
35693
|
-
import
|
|
35830
|
+
import os46 from "os";
|
|
35831
|
+
import path56 from "path";
|
|
35694
35832
|
import chalk5 from "chalk";
|
|
35695
35833
|
function emptySignals2() {
|
|
35696
35834
|
return {
|
|
@@ -35725,40 +35863,40 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
|
|
|
35725
35863
|
return now.getTime() - 90 * 864e5;
|
|
35726
35864
|
}
|
|
35727
35865
|
function* iterateJsonlFiles(cutoffMs) {
|
|
35728
|
-
const projectsDir =
|
|
35866
|
+
const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
|
|
35729
35867
|
let dirs;
|
|
35730
35868
|
try {
|
|
35731
|
-
dirs =
|
|
35869
|
+
dirs = fs54.readdirSync(projectsDir);
|
|
35732
35870
|
} catch {
|
|
35733
35871
|
return;
|
|
35734
35872
|
}
|
|
35735
35873
|
for (const dir2 of dirs) {
|
|
35736
|
-
const dirPath =
|
|
35874
|
+
const dirPath = path56.join(projectsDir, dir2);
|
|
35737
35875
|
let stats;
|
|
35738
35876
|
try {
|
|
35739
|
-
stats =
|
|
35877
|
+
stats = fs54.statSync(dirPath);
|
|
35740
35878
|
} catch {
|
|
35741
35879
|
continue;
|
|
35742
35880
|
}
|
|
35743
35881
|
if (!stats.isDirectory()) continue;
|
|
35744
35882
|
let files;
|
|
35745
35883
|
try {
|
|
35746
|
-
files =
|
|
35884
|
+
files = fs54.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
35747
35885
|
} catch {
|
|
35748
35886
|
continue;
|
|
35749
35887
|
}
|
|
35750
35888
|
for (const file2 of files) {
|
|
35751
|
-
const filePath =
|
|
35889
|
+
const filePath = path56.join(dirPath, file2);
|
|
35752
35890
|
let mtime = 0;
|
|
35753
35891
|
try {
|
|
35754
|
-
mtime =
|
|
35892
|
+
mtime = fs54.statSync(filePath).mtimeMs;
|
|
35755
35893
|
} catch {
|
|
35756
35894
|
continue;
|
|
35757
35895
|
}
|
|
35758
35896
|
if (mtime < cutoffMs) continue;
|
|
35759
35897
|
yield {
|
|
35760
35898
|
filePath,
|
|
35761
|
-
sessionId:
|
|
35899
|
+
sessionId: path56.basename(file2, ".jsonl"),
|
|
35762
35900
|
projectDir: dir2
|
|
35763
35901
|
};
|
|
35764
35902
|
}
|
|
@@ -35826,7 +35964,7 @@ async function runUploadHistory(opts) {
|
|
|
35826
35964
|
filesScanned++;
|
|
35827
35965
|
let content;
|
|
35828
35966
|
try {
|
|
35829
|
-
content =
|
|
35967
|
+
content = fs54.readFileSync(filePath, "utf8");
|
|
35830
35968
|
} catch {
|
|
35831
35969
|
continue;
|
|
35832
35970
|
}
|
|
@@ -35900,10 +36038,10 @@ async function runUploadHistory(opts) {
|
|
|
35900
36038
|
const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
|
|
35901
36039
|
let username = "unknown";
|
|
35902
36040
|
try {
|
|
35903
|
-
username =
|
|
36041
|
+
username = os46.userInfo().username;
|
|
35904
36042
|
} catch {
|
|
35905
36043
|
}
|
|
35906
|
-
const machineId = `${
|
|
36044
|
+
const machineId = `${os46.hostname()}:${username}`;
|
|
35907
36045
|
await postJson(costUrl, creds.apiKey, {
|
|
35908
36046
|
machineId,
|
|
35909
36047
|
entries: dailyEntries
|
|
@@ -35977,9 +36115,9 @@ var init_scan_upload_history = __esm({
|
|
|
35977
36115
|
|
|
35978
36116
|
// src/cli/commands/scan.ts
|
|
35979
36117
|
import chalk6 from "chalk";
|
|
35980
|
-
import
|
|
35981
|
-
import
|
|
35982
|
-
import
|
|
36118
|
+
import fs55 from "fs";
|
|
36119
|
+
import path57 from "path";
|
|
36120
|
+
import os47 from "os";
|
|
35983
36121
|
import stringWidth2 from "string-width";
|
|
35984
36122
|
function dayKey(ts) {
|
|
35985
36123
|
if (!ts) return "unknown";
|
|
@@ -36122,7 +36260,7 @@ function emptyScanDedup() {
|
|
|
36122
36260
|
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
|
|
36123
36261
|
}
|
|
36124
36262
|
function displayHome(p) {
|
|
36125
|
-
const home =
|
|
36263
|
+
const home = os47.homedir();
|
|
36126
36264
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
36127
36265
|
}
|
|
36128
36266
|
function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
|
|
@@ -36261,7 +36399,7 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
36261
36399
|
return findings.sort((a, b) => b.count - a.count);
|
|
36262
36400
|
}
|
|
36263
36401
|
function geminiChatFiles(chatsDir) {
|
|
36264
|
-
const files =
|
|
36402
|
+
const files = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
36265
36403
|
const seen = /* @__PURE__ */ new Set();
|
|
36266
36404
|
return files.filter((f) => {
|
|
36267
36405
|
const id = f.replace(/\.jsonl?$/, "");
|
|
@@ -36273,14 +36411,14 @@ function geminiChatFiles(chatsDir) {
|
|
|
36273
36411
|
function countScanFiles(startDate = null) {
|
|
36274
36412
|
let total = 0;
|
|
36275
36413
|
const counts = (p) => fileMayHoldWindow(fileStamp(p), startDate);
|
|
36276
|
-
const claudeDir =
|
|
36277
|
-
if (
|
|
36414
|
+
const claudeDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36415
|
+
if (fs55.existsSync(claudeDir)) {
|
|
36278
36416
|
try {
|
|
36279
|
-
for (const proj of
|
|
36280
|
-
const p =
|
|
36417
|
+
for (const proj of fs55.readdirSync(claudeDir)) {
|
|
36418
|
+
const p = path57.join(claudeDir, proj);
|
|
36281
36419
|
try {
|
|
36282
|
-
if (!
|
|
36283
|
-
total += listSessionFiles(p).filter((f) => counts(
|
|
36420
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36421
|
+
total += listSessionFiles(p).filter((f) => counts(path57.join(p, f))).length;
|
|
36284
36422
|
} catch {
|
|
36285
36423
|
continue;
|
|
36286
36424
|
}
|
|
@@ -36288,18 +36426,18 @@ function countScanFiles(startDate = null) {
|
|
|
36288
36426
|
} catch {
|
|
36289
36427
|
}
|
|
36290
36428
|
}
|
|
36291
|
-
const geminiDir =
|
|
36292
|
-
if (
|
|
36429
|
+
const geminiDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36430
|
+
if (fs55.existsSync(geminiDir)) {
|
|
36293
36431
|
try {
|
|
36294
|
-
for (const slug2 of
|
|
36295
|
-
const p =
|
|
36432
|
+
for (const slug2 of fs55.readdirSync(geminiDir)) {
|
|
36433
|
+
const p = path57.join(geminiDir, slug2);
|
|
36296
36434
|
try {
|
|
36297
|
-
if (!
|
|
36298
|
-
const chatsDir =
|
|
36299
|
-
if (
|
|
36435
|
+
if (!fs55.statSync(p).isDirectory()) continue;
|
|
36436
|
+
const chatsDir = path57.join(p, "chats");
|
|
36437
|
+
if (fs55.existsSync(chatsDir)) {
|
|
36300
36438
|
try {
|
|
36301
36439
|
total += geminiChatFiles(chatsDir).filter(
|
|
36302
|
-
(f) => counts(
|
|
36440
|
+
(f) => counts(path57.join(chatsDir, f))
|
|
36303
36441
|
).length;
|
|
36304
36442
|
} catch {
|
|
36305
36443
|
}
|
|
@@ -36312,13 +36450,13 @@ function countScanFiles(startDate = null) {
|
|
|
36312
36450
|
}
|
|
36313
36451
|
}
|
|
36314
36452
|
for (const surface of ["antigravity-cli", "antigravity-ide"]) {
|
|
36315
|
-
const brainDir =
|
|
36316
|
-
if (!
|
|
36453
|
+
const brainDir = path57.join(os47.homedir(), ".gemini", surface, "brain");
|
|
36454
|
+
if (!fs55.existsSync(brainDir)) continue;
|
|
36317
36455
|
try {
|
|
36318
|
-
for (const conv of
|
|
36319
|
-
const convPath =
|
|
36456
|
+
for (const conv of fs55.readdirSync(brainDir)) {
|
|
36457
|
+
const convPath = path57.join(brainDir, conv);
|
|
36320
36458
|
try {
|
|
36321
|
-
if (!
|
|
36459
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36322
36460
|
const transcript = antigravityTranscriptPath(convPath);
|
|
36323
36461
|
if (transcript && counts(transcript)) total += 1;
|
|
36324
36462
|
} catch {
|
|
@@ -36328,12 +36466,12 @@ function countScanFiles(startDate = null) {
|
|
|
36328
36466
|
} catch {
|
|
36329
36467
|
}
|
|
36330
36468
|
}
|
|
36331
|
-
const copilotDir =
|
|
36332
|
-
if (
|
|
36469
|
+
const copilotDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
36470
|
+
if (fs55.existsSync(copilotDir)) {
|
|
36333
36471
|
try {
|
|
36334
|
-
for (const sid of
|
|
36335
|
-
const events =
|
|
36336
|
-
if (
|
|
36472
|
+
for (const sid of fs55.readdirSync(copilotDir)) {
|
|
36473
|
+
const events = path57.join(copilotDir, sid, "events.jsonl");
|
|
36474
|
+
if (fs55.existsSync(events) && counts(events)) total += 1;
|
|
36337
36475
|
}
|
|
36338
36476
|
} catch {
|
|
36339
36477
|
}
|
|
@@ -36357,7 +36495,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36357
36495
|
const session = { sessionId, costUSD: 0, toolCalls: 0 };
|
|
36358
36496
|
let raw;
|
|
36359
36497
|
try {
|
|
36360
|
-
raw =
|
|
36498
|
+
raw = fs55.readFileSync(path57.join(projPath, file2), "utf-8");
|
|
36361
36499
|
} catch {
|
|
36362
36500
|
return false;
|
|
36363
36501
|
}
|
|
@@ -36426,7 +36564,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36426
36564
|
if (block.type !== "tool_result") continue;
|
|
36427
36565
|
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
36428
36566
|
if (filePath) {
|
|
36429
|
-
const ext =
|
|
36567
|
+
const ext = path57.extname(filePath).toLowerCase();
|
|
36430
36568
|
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
36431
36569
|
}
|
|
36432
36570
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
@@ -36546,9 +36684,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
|
|
|
36546
36684
|
return true;
|
|
36547
36685
|
}
|
|
36548
36686
|
async function processClaudeProject(proj, projectsDir, judge, cache7, startDate, result, dedup, canaryVals, onProgress, onLine) {
|
|
36549
|
-
const projPath =
|
|
36687
|
+
const projPath = path57.join(projectsDir, proj);
|
|
36550
36688
|
try {
|
|
36551
|
-
if (!
|
|
36689
|
+
if (!fs55.statSync(projPath).isDirectory()) return;
|
|
36552
36690
|
} catch {
|
|
36553
36691
|
return;
|
|
36554
36692
|
}
|
|
@@ -36560,7 +36698,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36560
36698
|
return;
|
|
36561
36699
|
}
|
|
36562
36700
|
for (const file2 of files) {
|
|
36563
|
-
const filePath =
|
|
36701
|
+
const filePath = path57.join(projPath, file2);
|
|
36564
36702
|
const stamp = fileStamp(filePath);
|
|
36565
36703
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36566
36704
|
result.filesScanned++;
|
|
@@ -36591,7 +36729,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
|
|
|
36591
36729
|
}
|
|
36592
36730
|
function claudeProjectLabel(dirName) {
|
|
36593
36731
|
const name = decodeURIComponent(dirName);
|
|
36594
|
-
const homeDashed =
|
|
36732
|
+
const homeDashed = os47.homedir().replace(/\//g, "-");
|
|
36595
36733
|
const label3 = name.startsWith(homeDashed) ? "~" + decodeProjectDirName(name.slice(homeDashed.length)) : decodeProjectDirName(name);
|
|
36596
36734
|
return stripTerminalEscapes(label3).slice(0, 40);
|
|
36597
36735
|
}
|
|
@@ -36702,12 +36840,12 @@ function mergeFileScan(result, file2, dedup, startDate = null) {
|
|
|
36702
36840
|
}
|
|
36703
36841
|
}
|
|
36704
36842
|
async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36705
|
-
const projectsDir =
|
|
36843
|
+
const projectsDir = path57.join(os47.homedir(), ".claude", "projects");
|
|
36706
36844
|
const result = emptyClaudeScan();
|
|
36707
|
-
if (!
|
|
36845
|
+
if (!fs55.existsSync(projectsDir)) return result;
|
|
36708
36846
|
let projDirs;
|
|
36709
36847
|
try {
|
|
36710
|
-
projDirs =
|
|
36848
|
+
projDirs = fs55.readdirSync(projectsDir);
|
|
36711
36849
|
} catch {
|
|
36712
36850
|
return result;
|
|
36713
36851
|
}
|
|
@@ -36735,7 +36873,7 @@ async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36735
36873
|
}
|
|
36736
36874
|
async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
36737
36875
|
const canaryVals = safeCanaryScanValues();
|
|
36738
|
-
const tmpDir =
|
|
36876
|
+
const tmpDir = path57.join(os47.homedir(), ".gemini", "tmp");
|
|
36739
36877
|
const result = {
|
|
36740
36878
|
filesScanned: 0,
|
|
36741
36879
|
sessions: 0,
|
|
@@ -36752,31 +36890,31 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36752
36890
|
perSession: []
|
|
36753
36891
|
};
|
|
36754
36892
|
const dedup = emptyScanDedup();
|
|
36755
|
-
if (!
|
|
36893
|
+
if (!fs55.existsSync(tmpDir)) return result;
|
|
36756
36894
|
let slugDirs;
|
|
36757
36895
|
try {
|
|
36758
|
-
slugDirs =
|
|
36896
|
+
slugDirs = fs55.readdirSync(tmpDir);
|
|
36759
36897
|
} catch {
|
|
36760
36898
|
return result;
|
|
36761
36899
|
}
|
|
36762
36900
|
const judge = new HistoryJudge();
|
|
36763
36901
|
const cache7 = openScanCache(judge, opts);
|
|
36764
36902
|
for (const slug2 of slugDirs) {
|
|
36765
|
-
const slugPath =
|
|
36903
|
+
const slugPath = path57.join(tmpDir, slug2);
|
|
36766
36904
|
try {
|
|
36767
|
-
if (!
|
|
36905
|
+
if (!fs55.statSync(slugPath).isDirectory()) continue;
|
|
36768
36906
|
} catch {
|
|
36769
36907
|
continue;
|
|
36770
36908
|
}
|
|
36771
36909
|
let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
|
|
36772
36910
|
try {
|
|
36773
36911
|
projLabel = stripTerminalEscapes(
|
|
36774
|
-
|
|
36775
|
-
).replace(
|
|
36912
|
+
fs55.readFileSync(path57.join(slugPath, ".project_root"), "utf-8").trim()
|
|
36913
|
+
).replace(os47.homedir(), "~").slice(0, 40);
|
|
36776
36914
|
} catch {
|
|
36777
36915
|
}
|
|
36778
|
-
const chatsDir =
|
|
36779
|
-
if (!
|
|
36916
|
+
const chatsDir = path57.join(slugPath, "chats");
|
|
36917
|
+
if (!fs55.existsSync(chatsDir)) continue;
|
|
36780
36918
|
let chatFiles;
|
|
36781
36919
|
try {
|
|
36782
36920
|
chatFiles = geminiChatFiles(chatsDir);
|
|
@@ -36785,7 +36923,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36785
36923
|
}
|
|
36786
36924
|
for (const chatFile of chatFiles) {
|
|
36787
36925
|
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
36788
|
-
const filePath =
|
|
36926
|
+
const filePath = path57.join(chatsDir, chatFile);
|
|
36789
36927
|
const stamp = fileStamp(filePath);
|
|
36790
36928
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
36791
36929
|
result.filesScanned++;
|
|
@@ -36799,7 +36937,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36799
36937
|
const fileDedup = emptyScanDedup();
|
|
36800
36938
|
let raw;
|
|
36801
36939
|
try {
|
|
36802
|
-
raw =
|
|
36940
|
+
raw = fs55.readFileSync(path57.join(chatsDir, chatFile), "utf-8");
|
|
36803
36941
|
} catch {
|
|
36804
36942
|
continue;
|
|
36805
36943
|
}
|
|
@@ -36936,13 +37074,13 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
36936
37074
|
return result;
|
|
36937
37075
|
}
|
|
36938
37076
|
function antigravityBrainDirs() {
|
|
36939
|
-
return ["antigravity-cli", "antigravity-ide"].map((surface) =>
|
|
37077
|
+
return ["antigravity-cli", "antigravity-ide"].map((surface) => path57.join(os47.homedir(), ".gemini", surface, "brain")).filter((p) => fs55.existsSync(p));
|
|
36940
37078
|
}
|
|
36941
37079
|
function antigravityTranscriptPath(convPath) {
|
|
36942
|
-
const logsDir =
|
|
37080
|
+
const logsDir = path57.join(convPath, ".system_generated", "logs");
|
|
36943
37081
|
for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
|
|
36944
|
-
const p =
|
|
36945
|
-
if (
|
|
37082
|
+
const p = path57.join(logsDir, name);
|
|
37083
|
+
if (fs55.existsSync(p)) return p;
|
|
36946
37084
|
}
|
|
36947
37085
|
return null;
|
|
36948
37086
|
}
|
|
@@ -36972,14 +37110,14 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36972
37110
|
for (const brainDir of brainDirs) {
|
|
36973
37111
|
let convDirs;
|
|
36974
37112
|
try {
|
|
36975
|
-
convDirs =
|
|
37113
|
+
convDirs = fs55.readdirSync(brainDir);
|
|
36976
37114
|
} catch {
|
|
36977
37115
|
continue;
|
|
36978
37116
|
}
|
|
36979
37117
|
for (const conv of convDirs) {
|
|
36980
|
-
const convPath =
|
|
37118
|
+
const convPath = path57.join(brainDir, conv);
|
|
36981
37119
|
try {
|
|
36982
|
-
if (!
|
|
37120
|
+
if (!fs55.statSync(convPath).isDirectory()) continue;
|
|
36983
37121
|
} catch {
|
|
36984
37122
|
continue;
|
|
36985
37123
|
}
|
|
@@ -36998,7 +37136,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
36998
37136
|
const fileDedup = emptyScanDedup();
|
|
36999
37137
|
let raw;
|
|
37000
37138
|
try {
|
|
37001
|
-
raw =
|
|
37139
|
+
raw = fs55.readFileSync(transcriptFile, "utf-8");
|
|
37002
37140
|
} catch {
|
|
37003
37141
|
continue;
|
|
37004
37142
|
}
|
|
@@ -37073,7 +37211,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37073
37211
|
day.bashCalls++;
|
|
37074
37212
|
const cwd = String(input.cwd ?? "");
|
|
37075
37213
|
if (cwd && projLabel === conv.slice(0, 8)) {
|
|
37076
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37214
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37077
37215
|
}
|
|
37078
37216
|
}
|
|
37079
37217
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
@@ -37099,7 +37237,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37099
37237
|
timestamp: timestamp2,
|
|
37100
37238
|
sessionId,
|
|
37101
37239
|
project: projLabel,
|
|
37102
|
-
cwd: typeof input.cwd === "string" &&
|
|
37240
|
+
cwd: typeof input.cwd === "string" && path57.isAbsolute(input.cwd) ? input.cwd : void 0
|
|
37103
37241
|
},
|
|
37104
37242
|
fileResult,
|
|
37105
37243
|
fileDedup
|
|
@@ -37122,7 +37260,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
|
|
|
37122
37260
|
}
|
|
37123
37261
|
async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
37124
37262
|
const canaryVals = safeCanaryScanValues();
|
|
37125
|
-
const sessionDir =
|
|
37263
|
+
const sessionDir = path57.join(os47.homedir(), ".copilot", "session-state");
|
|
37126
37264
|
const result = {
|
|
37127
37265
|
filesScanned: 0,
|
|
37128
37266
|
sessions: 0,
|
|
@@ -37140,18 +37278,18 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37140
37278
|
perSession: []
|
|
37141
37279
|
};
|
|
37142
37280
|
const dedup = emptyScanDedup();
|
|
37143
|
-
if (!
|
|
37281
|
+
if (!fs55.existsSync(sessionDir)) return result;
|
|
37144
37282
|
let sessionIds;
|
|
37145
37283
|
try {
|
|
37146
|
-
sessionIds =
|
|
37284
|
+
sessionIds = fs55.readdirSync(sessionDir);
|
|
37147
37285
|
} catch {
|
|
37148
37286
|
return result;
|
|
37149
37287
|
}
|
|
37150
37288
|
const judge = new HistoryJudge();
|
|
37151
37289
|
const cache7 = openScanCache(judge, opts);
|
|
37152
37290
|
for (const sessionId of sessionIds) {
|
|
37153
|
-
const eventsPath =
|
|
37154
|
-
if (!
|
|
37291
|
+
const eventsPath = path57.join(sessionDir, sessionId, "events.jsonl");
|
|
37292
|
+
if (!fs55.existsSync(eventsPath)) continue;
|
|
37155
37293
|
const stamp = fileStamp(eventsPath);
|
|
37156
37294
|
if (!fileMayHoldWindow(stamp, startDate)) continue;
|
|
37157
37295
|
result.filesScanned++;
|
|
@@ -37165,7 +37303,7 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37165
37303
|
const fileDedup = emptyScanDedup();
|
|
37166
37304
|
let raw;
|
|
37167
37305
|
try {
|
|
37168
|
-
raw =
|
|
37306
|
+
raw = fs55.readFileSync(eventsPath, "utf-8");
|
|
37169
37307
|
} catch {
|
|
37170
37308
|
continue;
|
|
37171
37309
|
}
|
|
@@ -37190,8 +37328,8 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37190
37328
|
if (ev.type === "session.start") {
|
|
37191
37329
|
const cwd = ev.data?.context?.cwd;
|
|
37192
37330
|
if (typeof cwd === "string" && cwd) {
|
|
37193
|
-
projLabel = stripTerminalEscapes(cwd).replace(
|
|
37194
|
-
if (
|
|
37331
|
+
projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
|
|
37332
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37195
37333
|
}
|
|
37196
37334
|
continue;
|
|
37197
37335
|
}
|
|
@@ -37324,7 +37462,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37324
37462
|
const fileDedup = emptyScanDedup();
|
|
37325
37463
|
let lines;
|
|
37326
37464
|
try {
|
|
37327
|
-
lines =
|
|
37465
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37328
37466
|
} catch {
|
|
37329
37467
|
continue;
|
|
37330
37468
|
}
|
|
@@ -37347,8 +37485,8 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37347
37485
|
sessionId = String(payload["id"] ?? filePath);
|
|
37348
37486
|
startTime = String(payload["timestamp"] ?? "");
|
|
37349
37487
|
const cwd = String(payload["cwd"] ?? "");
|
|
37350
|
-
projLabel = stripTerminalEscapes(cwd.replace(
|
|
37351
|
-
if (
|
|
37488
|
+
projLabel = stripTerminalEscapes(cwd.replace(os47.homedir(), "~")).slice(0, 40);
|
|
37489
|
+
if (path57.isAbsolute(cwd)) sessionCwd = cwd;
|
|
37352
37490
|
continue;
|
|
37353
37491
|
}
|
|
37354
37492
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
@@ -37465,17 +37603,17 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
|
|
|
37465
37603
|
return result;
|
|
37466
37604
|
}
|
|
37467
37605
|
function scanShellConfig() {
|
|
37468
|
-
const home =
|
|
37606
|
+
const home = os47.homedir();
|
|
37469
37607
|
const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
|
|
37470
|
-
(f) =>
|
|
37608
|
+
(f) => path57.join(home, f)
|
|
37471
37609
|
);
|
|
37472
37610
|
const findings = [];
|
|
37473
37611
|
const seen = /* @__PURE__ */ new Set();
|
|
37474
37612
|
for (const filePath of configFiles) {
|
|
37475
|
-
if (!
|
|
37613
|
+
if (!fs55.existsSync(filePath)) continue;
|
|
37476
37614
|
let lines;
|
|
37477
37615
|
try {
|
|
37478
|
-
lines =
|
|
37616
|
+
lines = fs55.readFileSync(filePath, "utf-8").split("\n");
|
|
37479
37617
|
} catch {
|
|
37480
37618
|
continue;
|
|
37481
37619
|
}
|
|
@@ -37805,25 +37943,25 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
37805
37943
|
const topLines = [];
|
|
37806
37944
|
if (scan.canaryFindings.length > 0) {
|
|
37807
37945
|
const latest = scan.canaryFindings[0];
|
|
37808
|
-
const rel =
|
|
37946
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37809
37947
|
const n = scan.canaryFindings.length;
|
|
37810
37948
|
topLines.push(
|
|
37811
37949
|
mkLine(
|
|
37812
37950
|
["\u{1FAA4} ", chalk6.red],
|
|
37813
37951
|
[`${n} decoy credential${n !== 1 ? "s" : ""} tripped `, chalk6.bold],
|
|
37814
|
-
[`(latest: ${rel}
|
|
37952
|
+
[`(latest: ${rel}, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
|
|
37815
37953
|
)
|
|
37816
37954
|
);
|
|
37817
37955
|
}
|
|
37818
37956
|
if (scan.dlpFindings.length > 0) {
|
|
37819
37957
|
const latest = scan.dlpFindings[0];
|
|
37820
|
-
const rel =
|
|
37958
|
+
const rel = relativeAgo(latest.timestamp, now);
|
|
37821
37959
|
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
37822
37960
|
topLines.push(
|
|
37823
37961
|
mkLine(
|
|
37824
37962
|
["\u{1F6A8} ", chalk6.red],
|
|
37825
37963
|
[`${scan.dlpFindings.length} ${noun} in tool input `, chalk6.bold],
|
|
37826
|
-
[`(latest: ${rel}
|
|
37964
|
+
[`(latest: ${rel}, ${latest.patternName})`, chalk6.dim]
|
|
37827
37965
|
)
|
|
37828
37966
|
);
|
|
37829
37967
|
}
|
|
@@ -38392,7 +38530,7 @@ function registerScanCommand(program2) {
|
|
|
38392
38530
|
if (!drillDown) {
|
|
38393
38531
|
const useInk2 = !options.classic;
|
|
38394
38532
|
if (useInk2) {
|
|
38395
|
-
const scanInkPath =
|
|
38533
|
+
const scanInkPath = path57.join(__dirname, "scan-ink.mjs");
|
|
38396
38534
|
const dynamicImport = new Function("id", "return import(id)");
|
|
38397
38535
|
const mod = await dynamicImport(`file://${scanInkPath}`);
|
|
38398
38536
|
const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
|
|
@@ -38749,12 +38887,12 @@ var init_scan = __esm({
|
|
|
38749
38887
|
});
|
|
38750
38888
|
|
|
38751
38889
|
// src/daemon/build-id.ts
|
|
38752
|
-
import
|
|
38753
|
-
import
|
|
38890
|
+
import fs56 from "fs";
|
|
38891
|
+
import path58 from "path";
|
|
38754
38892
|
function readOwnVersion() {
|
|
38755
38893
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
38756
38894
|
try {
|
|
38757
|
-
const raw =
|
|
38895
|
+
const raw = fs56.readFileSync(path58.join(__dirname, rel), "utf-8");
|
|
38758
38896
|
const v = JSON.parse(raw).version;
|
|
38759
38897
|
if (typeof v === "string" && v.length > 0) return v;
|
|
38760
38898
|
} catch {
|
|
@@ -38765,7 +38903,7 @@ function readOwnVersion() {
|
|
|
38765
38903
|
function computeBuildId(entry = process.argv[1] ?? "") {
|
|
38766
38904
|
let mtimeMs = 0;
|
|
38767
38905
|
try {
|
|
38768
|
-
if (entry) mtimeMs =
|
|
38906
|
+
if (entry) mtimeMs = fs56.statSync(entry).mtimeMs;
|
|
38769
38907
|
} catch {
|
|
38770
38908
|
}
|
|
38771
38909
|
return { version: readOwnVersion(), mtimeMs };
|
|
@@ -38906,8 +39044,8 @@ var init_suggestion_tracker = __esm({
|
|
|
38906
39044
|
});
|
|
38907
39045
|
|
|
38908
39046
|
// src/daemon/taint-store.ts
|
|
38909
|
-
import
|
|
38910
|
-
import
|
|
39047
|
+
import fs57 from "fs";
|
|
39048
|
+
import path59 from "path";
|
|
38911
39049
|
var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS2, SessionTaintStore;
|
|
38912
39050
|
var init_taint_store = __esm({
|
|
38913
39051
|
"src/daemon/taint-store.ts"() {
|
|
@@ -38977,9 +39115,9 @@ var init_taint_store = __esm({
|
|
|
38977
39115
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
38978
39116
|
_resolve(filePath) {
|
|
38979
39117
|
try {
|
|
38980
|
-
return
|
|
39118
|
+
return fs57.realpathSync.native(path59.resolve(filePath));
|
|
38981
39119
|
} catch {
|
|
38982
|
-
return
|
|
39120
|
+
return path59.resolve(filePath);
|
|
38983
39121
|
}
|
|
38984
39122
|
}
|
|
38985
39123
|
};
|
|
@@ -39035,7 +39173,7 @@ var init_taint_store = __esm({
|
|
|
39035
39173
|
});
|
|
39036
39174
|
|
|
39037
39175
|
// src/daemon/written-store.ts
|
|
39038
|
-
import
|
|
39176
|
+
import path60 from "path";
|
|
39039
39177
|
var SESSION_WRITE_TTL_MS, MAX_WRITTEN, MAX_PATH, SessionWriteStore;
|
|
39040
39178
|
var init_written_store = __esm({
|
|
39041
39179
|
"src/daemon/written-store.ts"() {
|
|
@@ -39047,7 +39185,7 @@ var init_written_store = __esm({
|
|
|
39047
39185
|
records = /* @__PURE__ */ new Map();
|
|
39048
39186
|
key(p) {
|
|
39049
39187
|
if (typeof p !== "string" || !p || p.length > MAX_PATH) return null;
|
|
39050
|
-
return
|
|
39188
|
+
return path60.resolve(p);
|
|
39051
39189
|
}
|
|
39052
39190
|
/** Record that these absolute paths were (or are about to be) written. */
|
|
39053
39191
|
record(paths, ttlMs = SESSION_WRITE_TTL_MS) {
|
|
@@ -39378,9 +39516,9 @@ var init_cards = __esm({
|
|
|
39378
39516
|
|
|
39379
39517
|
// src/daemon/state.ts
|
|
39380
39518
|
import net2 from "net";
|
|
39381
|
-
import
|
|
39382
|
-
import
|
|
39383
|
-
import
|
|
39519
|
+
import fs58 from "fs";
|
|
39520
|
+
import path61 from "path";
|
|
39521
|
+
import os48 from "os";
|
|
39384
39522
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
39385
39523
|
function onCardDecided(card) {
|
|
39386
39524
|
const r = card.record;
|
|
@@ -39406,8 +39544,8 @@ function onCardDecided(card) {
|
|
|
39406
39544
|
}
|
|
39407
39545
|
function loadInsightCounts() {
|
|
39408
39546
|
try {
|
|
39409
|
-
if (!
|
|
39410
|
-
const data = JSON.parse(
|
|
39547
|
+
if (!fs58.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
39548
|
+
const data = JSON.parse(fs58.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
39411
39549
|
for (const [tool, count] of Object.entries(data)) {
|
|
39412
39550
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
39413
39551
|
}
|
|
@@ -39475,16 +39613,16 @@ function appendAuditLog(data) {
|
|
|
39475
39613
|
...data.channel && { channel: data.channel },
|
|
39476
39614
|
...data.cardId && { cardId: data.cardId }
|
|
39477
39615
|
};
|
|
39478
|
-
const dir2 =
|
|
39479
|
-
if (!
|
|
39480
|
-
|
|
39616
|
+
const dir2 = path61.dirname(AUDIT_LOG_FILE);
|
|
39617
|
+
if (!fs58.existsSync(dir2)) fs58.mkdirSync(dir2, { recursive: true });
|
|
39618
|
+
fs58.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
39481
39619
|
} catch {
|
|
39482
39620
|
}
|
|
39483
39621
|
}
|
|
39484
39622
|
function getAuditHistory(limit = 20) {
|
|
39485
39623
|
try {
|
|
39486
|
-
if (!
|
|
39487
|
-
const lines =
|
|
39624
|
+
if (!fs58.existsSync(AUDIT_LOG_FILE)) return [];
|
|
39625
|
+
const lines = fs58.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
39488
39626
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
39489
39627
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
39490
39628
|
} catch {
|
|
@@ -39493,7 +39631,7 @@ function getAuditHistory(limit = 20) {
|
|
|
39493
39631
|
}
|
|
39494
39632
|
function getOrgName() {
|
|
39495
39633
|
try {
|
|
39496
|
-
if (
|
|
39634
|
+
if (fs58.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
39497
39635
|
} catch {
|
|
39498
39636
|
}
|
|
39499
39637
|
return null;
|
|
@@ -39505,8 +39643,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39505
39643
|
try {
|
|
39506
39644
|
let trust = { entries: [] };
|
|
39507
39645
|
try {
|
|
39508
|
-
if (
|
|
39509
|
-
trust = JSON.parse(
|
|
39646
|
+
if (fs58.existsSync(TRUST_FILE2))
|
|
39647
|
+
trust = JSON.parse(fs58.readFileSync(TRUST_FILE2, "utf-8"));
|
|
39510
39648
|
} catch {
|
|
39511
39649
|
}
|
|
39512
39650
|
trust.entries = trust.entries.filter(
|
|
@@ -39523,8 +39661,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
39523
39661
|
}
|
|
39524
39662
|
function readPersistentDecisions() {
|
|
39525
39663
|
try {
|
|
39526
|
-
if (
|
|
39527
|
-
return JSON.parse(
|
|
39664
|
+
if (fs58.existsSync(DECISIONS_FILE)) {
|
|
39665
|
+
return JSON.parse(fs58.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
39528
39666
|
}
|
|
39529
39667
|
} catch {
|
|
39530
39668
|
}
|
|
@@ -39552,7 +39690,7 @@ function estimateToolCost(tool, args) {
|
|
|
39552
39690
|
const filePath = a.file_path ?? a.path;
|
|
39553
39691
|
if (filePath) {
|
|
39554
39692
|
try {
|
|
39555
|
-
const bytes =
|
|
39693
|
+
const bytes = fs58.statSync(filePath).size;
|
|
39556
39694
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
39557
39695
|
} catch {
|
|
39558
39696
|
}
|
|
@@ -39620,7 +39758,7 @@ function abandonPending() {
|
|
|
39620
39758
|
cards.abandonLocalOnly();
|
|
39621
39759
|
if (autoStarted && cards.open().length === 0) {
|
|
39622
39760
|
try {
|
|
39623
|
-
|
|
39761
|
+
fs58.unlinkSync(DAEMON_PID_FILE);
|
|
39624
39762
|
} catch {
|
|
39625
39763
|
}
|
|
39626
39764
|
setTimeout(() => {
|
|
@@ -39631,8 +39769,8 @@ function abandonPending() {
|
|
|
39631
39769
|
}
|
|
39632
39770
|
function logActivitySocket(msg) {
|
|
39633
39771
|
try {
|
|
39634
|
-
|
|
39635
|
-
|
|
39772
|
+
fs58.appendFileSync(
|
|
39773
|
+
path61.join(homeDir, ".node9", "hook-debug.log"),
|
|
39636
39774
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
|
|
39637
39775
|
`
|
|
39638
39776
|
);
|
|
@@ -39654,13 +39792,13 @@ function shouldRebind(now = Date.now()) {
|
|
|
39654
39792
|
function startActivitySocket() {
|
|
39655
39793
|
bindActivitySocket();
|
|
39656
39794
|
activityHealthInterval = setInterval(() => {
|
|
39657
|
-
if (!
|
|
39795
|
+
if (!fs58.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
|
|
39658
39796
|
}, ACTIVITY_HEALTH_PROBE_MS);
|
|
39659
39797
|
activityHealthInterval.unref();
|
|
39660
39798
|
process.on("exit", () => {
|
|
39661
39799
|
if (activityHealthInterval) clearInterval(activityHealthInterval);
|
|
39662
39800
|
try {
|
|
39663
|
-
|
|
39801
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39664
39802
|
} catch {
|
|
39665
39803
|
}
|
|
39666
39804
|
});
|
|
@@ -39688,7 +39826,7 @@ function attemptRebind(reason) {
|
|
|
39688
39826
|
}
|
|
39689
39827
|
function bindActivitySocket() {
|
|
39690
39828
|
try {
|
|
39691
|
-
|
|
39829
|
+
fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
39692
39830
|
} catch {
|
|
39693
39831
|
}
|
|
39694
39832
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -39797,14 +39935,14 @@ var init_state3 = __esm({
|
|
|
39797
39935
|
init_session_history();
|
|
39798
39936
|
init_atomic_write();
|
|
39799
39937
|
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 =
|
|
39938
|
+
homeDir = os48.homedir();
|
|
39939
|
+
DAEMON_PID_FILE = path61.join(homeDir, ".node9", "daemon.pid");
|
|
39940
|
+
DECISIONS_FILE = path61.join(homeDir, ".node9", "decisions.json");
|
|
39941
|
+
AUDIT_LOG_FILE = path61.join(homeDir, ".node9", "audit.log");
|
|
39942
|
+
TRUST_FILE2 = path61.join(homeDir, ".node9", "trust.json");
|
|
39943
|
+
GLOBAL_CONFIG_FILE = path61.join(homeDir, ".node9", "config.json");
|
|
39944
|
+
CREDENTIALS_FILE = path61.join(homeDir, ".node9", "credentials.json");
|
|
39945
|
+
INSIGHT_COUNTS_FILE = path61.join(homeDir, ".node9", "insight-counts.json");
|
|
39808
39946
|
sseClients = /* @__PURE__ */ new Set();
|
|
39809
39947
|
cards = new CardStore({
|
|
39810
39948
|
onDecided: onCardDecided,
|
|
@@ -39827,7 +39965,7 @@ var init_state3 = __esm({
|
|
|
39827
39965
|
"2h": 2 * 60 * 6e4
|
|
39828
39966
|
};
|
|
39829
39967
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
39830
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
39968
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path61.join(os48.tmpdir(), "node9-activity.sock");
|
|
39831
39969
|
ACTIVITY_RING_SIZE = 100;
|
|
39832
39970
|
activityRing = [];
|
|
39833
39971
|
LARGE_RESPONSE_RING_SIZE = 20;
|
|
@@ -39866,13 +40004,13 @@ var init_state3 = __esm({
|
|
|
39866
40004
|
});
|
|
39867
40005
|
|
|
39868
40006
|
// src/posture/secrets.ts
|
|
39869
|
-
import
|
|
39870
|
-
import
|
|
39871
|
-
import
|
|
40007
|
+
import fs59 from "fs";
|
|
40008
|
+
import path62 from "path";
|
|
40009
|
+
import os49 from "os";
|
|
39872
40010
|
function displayPath(p, home) {
|
|
39873
40011
|
if (p === home) return "~";
|
|
39874
|
-
const prefix = home.endsWith(
|
|
39875
|
-
if (p.startsWith(prefix)) return "~" +
|
|
40012
|
+
const prefix = home.endsWith(path62.sep) ? home : home + path62.sep;
|
|
40013
|
+
if (p.startsWith(prefix)) return "~" + path62.sep + p.slice(prefix.length);
|
|
39876
40014
|
return p;
|
|
39877
40015
|
}
|
|
39878
40016
|
function safeRead(file2) {
|
|
@@ -39883,8 +40021,8 @@ function safeRead(file2) {
|
|
|
39883
40021
|
function candidateFiles(home, cwd) {
|
|
39884
40022
|
const files = /* @__PURE__ */ new Set();
|
|
39885
40023
|
try {
|
|
39886
|
-
for (const name of
|
|
39887
|
-
if (name === ".env" || name.startsWith(".env.")) files.add(
|
|
40024
|
+
for (const name of fs59.readdirSync(cwd)) {
|
|
40025
|
+
if (name === ".env" || name.startsWith(".env.")) files.add(path62.join(cwd, name));
|
|
39888
40026
|
}
|
|
39889
40027
|
} catch {
|
|
39890
40028
|
}
|
|
@@ -39892,7 +40030,7 @@ function candidateFiles(home, cwd) {
|
|
|
39892
40030
|
if (spec.hookFile) files.add(spec.hookFile(home));
|
|
39893
40031
|
if (spec.mcpFile) files.add(spec.mcpFile(home));
|
|
39894
40032
|
}
|
|
39895
|
-
files.add(
|
|
40033
|
+
files.add(path62.join(home, ".env"));
|
|
39896
40034
|
return [...files];
|
|
39897
40035
|
}
|
|
39898
40036
|
function credentialMaterial(home) {
|
|
@@ -39906,7 +40044,7 @@ function plantedDecoyPaths() {
|
|
|
39906
40044
|
}
|
|
39907
40045
|
}
|
|
39908
40046
|
function checkSecrets(ctx) {
|
|
39909
|
-
const home = ctx.home ||
|
|
40047
|
+
const home = ctx.home || os49.homedir();
|
|
39910
40048
|
const findings = [];
|
|
39911
40049
|
const planted = plantedDecoyPaths();
|
|
39912
40050
|
const plaintext = [];
|
|
@@ -39942,7 +40080,7 @@ function checkSecrets(ctx) {
|
|
|
39942
40080
|
for (const file2 of credentialMaterial(home)) {
|
|
39943
40081
|
if (planted.has(file2)) continue;
|
|
39944
40082
|
try {
|
|
39945
|
-
if (
|
|
40083
|
+
if (fs59.statSync(file2).isFile()) {
|
|
39946
40084
|
creds.push(displayPath(file2, home));
|
|
39947
40085
|
credPaths.push(file2);
|
|
39948
40086
|
}
|
|
@@ -40112,10 +40250,10 @@ var init_templates = __esm({
|
|
|
40112
40250
|
});
|
|
40113
40251
|
|
|
40114
40252
|
// src/posture/egress.ts
|
|
40115
|
-
import
|
|
40253
|
+
import fs60 from "fs";
|
|
40116
40254
|
function sandboxEgressWallActive() {
|
|
40117
40255
|
try {
|
|
40118
|
-
return
|
|
40256
|
+
return fs60.existsSync(ALLOWED_DOMAINS_PATH);
|
|
40119
40257
|
} catch {
|
|
40120
40258
|
return false;
|
|
40121
40259
|
}
|
|
@@ -40127,7 +40265,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40127
40265
|
severity: "high",
|
|
40128
40266
|
title: "Egress is locked, but node9 is not enforcing it",
|
|
40129
40267
|
what: "Egress is set to block, but node9 is not applying the policy.",
|
|
40130
|
-
why: "node9 isn't wired in (or is in
|
|
40268
|
+
why: "node9 isn't wired in (or is in audit mode), so the lock has no effect.",
|
|
40131
40269
|
who: "The lock protects nothing until node9 is enforcing in-path.",
|
|
40132
40270
|
owner: "node9",
|
|
40133
40271
|
detail: [],
|
|
@@ -40144,7 +40282,7 @@ function evaluateEgressConfig(egress) {
|
|
|
40144
40282
|
severity: "medium",
|
|
40145
40283
|
title: "Egress is in review, but node9 is not enforcing it",
|
|
40146
40284
|
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
|
|
40285
|
+
why: "node9 isn't wired in (or is in audit mode), so the gate has no effect.",
|
|
40148
40286
|
who: "Nothing gates outbound until node9 is enforcing in-path.",
|
|
40149
40287
|
owner: "node9",
|
|
40150
40288
|
detail: [],
|
|
@@ -40285,14 +40423,14 @@ var init_gate = __esm({
|
|
|
40285
40423
|
});
|
|
40286
40424
|
|
|
40287
40425
|
// src/posture/supply-chain.ts
|
|
40288
|
-
import
|
|
40289
|
-
import
|
|
40426
|
+
import os50 from "os";
|
|
40427
|
+
import path63 from "path";
|
|
40290
40428
|
import { parse as parseToml6 } from "smol-toml";
|
|
40291
40429
|
function isNode9Managed(command, args = []) {
|
|
40292
40430
|
if (!command) return false;
|
|
40293
|
-
if (
|
|
40294
|
-
if (PACKAGE_RUNNERS.has(
|
|
40295
|
-
return args.some((a) => a === "node9" ||
|
|
40431
|
+
if (path63.basename(command).toLowerCase() === "node9") return true;
|
|
40432
|
+
if (PACKAGE_RUNNERS.has(path63.basename(command).toLowerCase())) {
|
|
40433
|
+
return args.some((a) => a === "node9" || path63.basename(a).toLowerCase() === "node9");
|
|
40296
40434
|
}
|
|
40297
40435
|
return false;
|
|
40298
40436
|
}
|
|
@@ -40314,7 +40452,7 @@ function readServers(file2, format, agent) {
|
|
|
40314
40452
|
}
|
|
40315
40453
|
}
|
|
40316
40454
|
function checkSupplyChain(ctx) {
|
|
40317
|
-
const home = ctx.home ||
|
|
40455
|
+
const home = ctx.home || os50.homedir();
|
|
40318
40456
|
const servers = [];
|
|
40319
40457
|
for (const spec of AGENT_SPECS) {
|
|
40320
40458
|
if (!spec.mcpFile) continue;
|
|
@@ -40413,11 +40551,11 @@ var init_privilege = __esm({
|
|
|
40413
40551
|
});
|
|
40414
40552
|
|
|
40415
40553
|
// src/posture/containment.ts
|
|
40416
|
-
import
|
|
40554
|
+
import fs61 from "fs";
|
|
40417
40555
|
function inContainer() {
|
|
40418
|
-
if (
|
|
40556
|
+
if (fs61.existsSync("/.dockerenv") || fs61.existsSync("/run/.containerenv")) return true;
|
|
40419
40557
|
try {
|
|
40420
|
-
const cgroup =
|
|
40558
|
+
const cgroup = fs61.readFileSync("/proc/1/cgroup", "utf8");
|
|
40421
40559
|
if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
|
|
40422
40560
|
} catch {
|
|
40423
40561
|
}
|
|
@@ -40463,7 +40601,7 @@ var init_containment = __esm({
|
|
|
40463
40601
|
});
|
|
40464
40602
|
|
|
40465
40603
|
// src/posture/inbound.ts
|
|
40466
|
-
import
|
|
40604
|
+
import fs62 from "fs";
|
|
40467
40605
|
function buildNetworkFix(labels) {
|
|
40468
40606
|
const shielded = [
|
|
40469
40607
|
...new Map(
|
|
@@ -40521,7 +40659,7 @@ function collectListeners() {
|
|
|
40521
40659
|
const byPort = /* @__PURE__ */ new Map();
|
|
40522
40660
|
for (const file2 of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
40523
40661
|
try {
|
|
40524
|
-
for (const l of parseListeners(
|
|
40662
|
+
for (const l of parseListeners(fs62.readFileSync(file2, "utf8"))) {
|
|
40525
40663
|
if (!byPort.has(l.port)) byPort.set(l.port, l);
|
|
40526
40664
|
}
|
|
40527
40665
|
} catch {
|
|
@@ -40533,11 +40671,11 @@ function readProc(pid) {
|
|
|
40533
40671
|
let comm = "unknown";
|
|
40534
40672
|
let cmdline = "";
|
|
40535
40673
|
try {
|
|
40536
|
-
comm =
|
|
40674
|
+
comm = fs62.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
|
|
40537
40675
|
} catch {
|
|
40538
40676
|
}
|
|
40539
40677
|
try {
|
|
40540
|
-
cmdline =
|
|
40678
|
+
cmdline = fs62.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
|
|
40541
40679
|
} catch {
|
|
40542
40680
|
}
|
|
40543
40681
|
return { comm, cmdline };
|
|
@@ -40547,21 +40685,21 @@ function resolveProcesses(inodes) {
|
|
|
40547
40685
|
if (inodes.size === 0) return map;
|
|
40548
40686
|
let pids;
|
|
40549
40687
|
try {
|
|
40550
|
-
pids =
|
|
40688
|
+
pids = fs62.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
40551
40689
|
} catch {
|
|
40552
40690
|
return map;
|
|
40553
40691
|
}
|
|
40554
40692
|
for (const pid of pids) {
|
|
40555
40693
|
let fds;
|
|
40556
40694
|
try {
|
|
40557
|
-
fds =
|
|
40695
|
+
fds = fs62.readdirSync(`/proc/${pid}/fd`);
|
|
40558
40696
|
} catch {
|
|
40559
40697
|
continue;
|
|
40560
40698
|
}
|
|
40561
40699
|
for (const fd of fds) {
|
|
40562
40700
|
let link;
|
|
40563
40701
|
try {
|
|
40564
|
-
link =
|
|
40702
|
+
link = fs62.readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
40565
40703
|
} catch {
|
|
40566
40704
|
continue;
|
|
40567
40705
|
}
|
|
@@ -40666,9 +40804,9 @@ var init_inbound = __esm({
|
|
|
40666
40804
|
});
|
|
40667
40805
|
|
|
40668
40806
|
// src/posture/coverage.ts
|
|
40669
|
-
import
|
|
40807
|
+
import os51 from "os";
|
|
40670
40808
|
function checkCoverage(ctx) {
|
|
40671
|
-
const home = ctx.home ||
|
|
40809
|
+
const home = ctx.home || os51.homedir();
|
|
40672
40810
|
const findings = [];
|
|
40673
40811
|
const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
|
|
40674
40812
|
if (protectedAgents.length === 0) {
|
|
@@ -40686,13 +40824,14 @@ function checkCoverage(ctx) {
|
|
|
40686
40824
|
return findings;
|
|
40687
40825
|
}
|
|
40688
40826
|
const mode = getConfig(ctx.cwd).settings.mode;
|
|
40689
|
-
if (mode
|
|
40827
|
+
if (isRecordOnly(mode)) {
|
|
40828
|
+
const level = levelOf(mode);
|
|
40690
40829
|
findings.push({
|
|
40691
40830
|
category: "Coverage",
|
|
40692
40831
|
severity: "high",
|
|
40693
|
-
title: `node9 is in ${
|
|
40832
|
+
title: `node9 is in ${level} mode \u2014 watching, not blocking`,
|
|
40694
40833
|
what: "node9 is watching but not actually blocking anything.",
|
|
40695
|
-
why: `It's in ${
|
|
40834
|
+
why: `It's in ${level} mode, which logs risky actions but lets them through.`,
|
|
40696
40835
|
who: "The guardrails above are observed, not enforced.",
|
|
40697
40836
|
detail: [],
|
|
40698
40837
|
owner: "node9",
|
|
@@ -40705,12 +40844,13 @@ var init_coverage = __esm({
|
|
|
40705
40844
|
"src/posture/coverage.ts"() {
|
|
40706
40845
|
"use strict";
|
|
40707
40846
|
init_config();
|
|
40847
|
+
init_dist();
|
|
40708
40848
|
init_agent_wiring();
|
|
40709
40849
|
}
|
|
40710
40850
|
});
|
|
40711
40851
|
|
|
40712
40852
|
// src/posture/governance.ts
|
|
40713
|
-
import
|
|
40853
|
+
import path64 from "path";
|
|
40714
40854
|
function checkData(ctx) {
|
|
40715
40855
|
const dlp = getConfig(ctx.cwd).policy.dlp;
|
|
40716
40856
|
if (!dlp?.enabled) {
|
|
@@ -40854,7 +40994,7 @@ var init_governance = __esm({
|
|
|
40854
40994
|
"src/posture/governance.ts"() {
|
|
40855
40995
|
"use strict";
|
|
40856
40996
|
init_config();
|
|
40857
|
-
jailProbePath = (home) =>
|
|
40997
|
+
jailProbePath = (home) => path64.join(home, ".aws", "credentials");
|
|
40858
40998
|
}
|
|
40859
40999
|
});
|
|
40860
41000
|
|
|
@@ -41008,7 +41148,7 @@ async function annotateCoverage(findings, ctx) {
|
|
|
41008
41148
|
const worst = verdicts.some((v) => v === "review") ? "review" : "block";
|
|
41009
41149
|
f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
|
|
41010
41150
|
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
|
|
41151
|
+
f.fix = `node9 already recognises reads of these files, but it is in ${levelOf(mode)} mode, which logs them and lets them through. Fix it now: set mode to \`standard\` (or \`strict\`) to stop them.`;
|
|
41012
41152
|
}
|
|
41013
41153
|
}
|
|
41014
41154
|
continue;
|
|
@@ -41033,7 +41173,7 @@ var init_enforcement = __esm({
|
|
|
41033
41173
|
});
|
|
41034
41174
|
|
|
41035
41175
|
// src/posture/index.ts
|
|
41036
|
-
import
|
|
41176
|
+
import os52 from "os";
|
|
41037
41177
|
function dropEnforcementRedundant(findings) {
|
|
41038
41178
|
const coveragePresent = findings.some((f) => f.category === "Coverage");
|
|
41039
41179
|
if (!coveragePresent) return findings;
|
|
@@ -41059,7 +41199,7 @@ async function runChecks(checks, ctx) {
|
|
|
41059
41199
|
}
|
|
41060
41200
|
async function runPosture(opts = {}) {
|
|
41061
41201
|
const ctx = {
|
|
41062
|
-
home: opts.home ??
|
|
41202
|
+
home: opts.home ?? os52.homedir(),
|
|
41063
41203
|
cwd: opts.cwd ?? process.cwd(),
|
|
41064
41204
|
agent: opts.agent
|
|
41065
41205
|
};
|
|
@@ -41436,20 +41576,20 @@ var init_ship2 = __esm({
|
|
|
41436
41576
|
|
|
41437
41577
|
// src/machine-id.ts
|
|
41438
41578
|
import * as crypto7 from "crypto";
|
|
41439
|
-
import * as
|
|
41440
|
-
import * as
|
|
41441
|
-
import * as
|
|
41442
|
-
function getMachineId(homeDir2 =
|
|
41443
|
-
const file2 =
|
|
41579
|
+
import * as fs63 from "fs";
|
|
41580
|
+
import * as os53 from "os";
|
|
41581
|
+
import * as path65 from "path";
|
|
41582
|
+
function getMachineId(homeDir2 = os53.homedir()) {
|
|
41583
|
+
const file2 = path65.join(homeDir2, ".node9", "machine-id");
|
|
41444
41584
|
try {
|
|
41445
|
-
const existing =
|
|
41585
|
+
const existing = fs63.readFileSync(file2, "utf-8").trim();
|
|
41446
41586
|
if (UUID_RE.test(existing)) return existing;
|
|
41447
41587
|
} catch {
|
|
41448
41588
|
}
|
|
41449
41589
|
const id = crypto7.randomUUID();
|
|
41450
41590
|
try {
|
|
41451
|
-
|
|
41452
|
-
|
|
41591
|
+
fs63.mkdirSync(path65.dirname(file2), { recursive: true });
|
|
41592
|
+
fs63.writeFileSync(file2, id + "\n", { mode: 384 });
|
|
41453
41593
|
} catch {
|
|
41454
41594
|
}
|
|
41455
41595
|
return id;
|
|
@@ -41489,10 +41629,10 @@ var init_sync_trigger = __esm({
|
|
|
41489
41629
|
});
|
|
41490
41630
|
|
|
41491
41631
|
// src/daemon/sync.ts
|
|
41492
|
-
import
|
|
41632
|
+
import fs64 from "fs";
|
|
41493
41633
|
import https4 from "https";
|
|
41494
|
-
import
|
|
41495
|
-
import
|
|
41634
|
+
import os54 from "os";
|
|
41635
|
+
import path66 from "path";
|
|
41496
41636
|
function emptySignals3() {
|
|
41497
41637
|
return {
|
|
41498
41638
|
dlpFindings: 0,
|
|
@@ -41540,7 +41680,7 @@ function readCredentials() {
|
|
|
41540
41680
|
}
|
|
41541
41681
|
function readCachedEtag() {
|
|
41542
41682
|
try {
|
|
41543
|
-
const raw = JSON.parse(
|
|
41683
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41544
41684
|
return typeof raw.etag === "string" ? raw.etag : void 0;
|
|
41545
41685
|
} catch {
|
|
41546
41686
|
return void 0;
|
|
@@ -41548,7 +41688,7 @@ function readCachedEtag() {
|
|
|
41548
41688
|
}
|
|
41549
41689
|
function readCachedSyncIntervalHours() {
|
|
41550
41690
|
try {
|
|
41551
|
-
const raw = JSON.parse(
|
|
41691
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41552
41692
|
return typeof raw.syncIntervalHours === "number" ? raw.syncIntervalHours : void 0;
|
|
41553
41693
|
} catch {
|
|
41554
41694
|
return void 0;
|
|
@@ -41565,7 +41705,7 @@ function effectiveSyncIntervalMs() {
|
|
|
41565
41705
|
}
|
|
41566
41706
|
function readSyncHealth() {
|
|
41567
41707
|
try {
|
|
41568
|
-
const raw = JSON.parse(
|
|
41708
|
+
const raw = JSON.parse(fs64.readFileSync(syncHealthFile(), "utf-8"));
|
|
41569
41709
|
return {
|
|
41570
41710
|
lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
|
|
41571
41711
|
lastChangedAt: typeof raw.lastChangedAt === "string" ? raw.lastChangedAt : void 0,
|
|
@@ -41580,17 +41720,17 @@ function readSyncHealth() {
|
|
|
41580
41720
|
function writeSyncHealth(h) {
|
|
41581
41721
|
try {
|
|
41582
41722
|
const file2 = syncHealthFile();
|
|
41583
|
-
const dir2 =
|
|
41584
|
-
if (!
|
|
41723
|
+
const dir2 = path66.dirname(file2);
|
|
41724
|
+
if (!fs64.existsSync(dir2)) fs64.mkdirSync(dir2, { recursive: true });
|
|
41585
41725
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
41586
|
-
|
|
41587
|
-
|
|
41726
|
+
fs64.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
|
|
41727
|
+
fs64.renameSync(tmp, file2);
|
|
41588
41728
|
} catch {
|
|
41589
41729
|
}
|
|
41590
41730
|
}
|
|
41591
41731
|
function readCacheFetchedAt() {
|
|
41592
41732
|
try {
|
|
41593
|
-
const raw = JSON.parse(
|
|
41733
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
41594
41734
|
return typeof raw.fetchedAt === "string" ? raw.fetchedAt : void 0;
|
|
41595
41735
|
} catch {
|
|
41596
41736
|
return void 0;
|
|
@@ -41627,13 +41767,13 @@ function safeNode9Version() {
|
|
|
41627
41767
|
let dir2 = __dirname;
|
|
41628
41768
|
for (let up = 0; up < 5; up++) {
|
|
41629
41769
|
try {
|
|
41630
|
-
const pkg = JSON.parse(
|
|
41770
|
+
const pkg = JSON.parse(fs64.readFileSync(path66.join(dir2, "package.json"), "utf-8"));
|
|
41631
41771
|
if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
|
|
41632
41772
|
return pkg.version;
|
|
41633
41773
|
}
|
|
41634
41774
|
} catch {
|
|
41635
41775
|
}
|
|
41636
|
-
const parent =
|
|
41776
|
+
const parent = path66.dirname(dir2);
|
|
41637
41777
|
if (parent === dir2) break;
|
|
41638
41778
|
dir2 = parent;
|
|
41639
41779
|
}
|
|
@@ -41886,13 +42026,13 @@ function extractManagedConfig(body) {
|
|
|
41886
42026
|
}
|
|
41887
42027
|
function sweepStaleTmp(target) {
|
|
41888
42028
|
try {
|
|
41889
|
-
const dir2 =
|
|
41890
|
-
const prefix = `${
|
|
41891
|
-
for (const name of
|
|
42029
|
+
const dir2 = path66.dirname(target);
|
|
42030
|
+
const prefix = `${path66.basename(target)}.`;
|
|
42031
|
+
for (const name of fs64.readdirSync(dir2)) {
|
|
41892
42032
|
if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
|
|
41893
|
-
const full =
|
|
42033
|
+
const full = path66.join(dir2, name);
|
|
41894
42034
|
try {
|
|
41895
|
-
if (Date.now() -
|
|
42035
|
+
if (Date.now() - fs64.statSync(full).mtimeMs > 5 * 60 * 1e3) fs64.unlinkSync(full);
|
|
41896
42036
|
} catch {
|
|
41897
42037
|
}
|
|
41898
42038
|
}
|
|
@@ -41995,7 +42135,7 @@ async function pushBlastSnapshot(creds) {
|
|
|
41995
42135
|
}
|
|
41996
42136
|
async function pushPostureSnapshot(creds) {
|
|
41997
42137
|
try {
|
|
41998
|
-
const home =
|
|
42138
|
+
const home = os54.homedir();
|
|
41999
42139
|
const result = await runPosture({ home, cwd: home });
|
|
42000
42140
|
await shipPosture(result, creds);
|
|
42001
42141
|
} catch {
|
|
@@ -42169,7 +42309,7 @@ async function runCloudSync() {
|
|
|
42169
42309
|
}
|
|
42170
42310
|
function getCloudSyncStatus() {
|
|
42171
42311
|
try {
|
|
42172
|
-
const raw = JSON.parse(
|
|
42312
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42173
42313
|
if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
|
|
42174
42314
|
return {
|
|
42175
42315
|
cached: true,
|
|
@@ -42186,7 +42326,7 @@ function getCloudSyncStatus() {
|
|
|
42186
42326
|
}
|
|
42187
42327
|
function getCloudRules() {
|
|
42188
42328
|
try {
|
|
42189
|
-
const raw = JSON.parse(
|
|
42329
|
+
const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
|
|
42190
42330
|
return Array.isArray(raw.rules) ? raw.rules : null;
|
|
42191
42331
|
} catch {
|
|
42192
42332
|
return null;
|
|
@@ -42256,12 +42396,12 @@ var init_sync = __esm({
|
|
|
42256
42396
|
loop: "loops",
|
|
42257
42397
|
"long-output-redacted": "longOutputRedactions"
|
|
42258
42398
|
};
|
|
42259
|
-
rulesCacheFile = () =>
|
|
42260
|
-
rulesCacheBackupFile = () =>
|
|
42399
|
+
rulesCacheFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.json");
|
|
42400
|
+
rulesCacheBackupFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.last-good.json");
|
|
42261
42401
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
42262
42402
|
MIN_INTERVAL_SECONDS = 15;
|
|
42263
42403
|
MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
42264
|
-
syncHealthFile = () =>
|
|
42404
|
+
syncHealthFile = () => path66.join(os54.homedir(), ".node9", "sync-health.json");
|
|
42265
42405
|
STALE_MIN_MS = 3 * 60 * 60 * 1e3;
|
|
42266
42406
|
STALE_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
42267
42407
|
STALE_FACTOR = 3;
|
|
@@ -42286,9 +42426,9 @@ __export(audit_shipper_exports, {
|
|
|
42286
42426
|
startAuditShipper: () => startAuditShipper,
|
|
42287
42427
|
writeWatermark: () => writeWatermark
|
|
42288
42428
|
});
|
|
42289
|
-
import
|
|
42290
|
-
import
|
|
42291
|
-
import
|
|
42429
|
+
import fs65 from "fs";
|
|
42430
|
+
import path67 from "path";
|
|
42431
|
+
import os55 from "os";
|
|
42292
42432
|
import crypto8 from "crypto";
|
|
42293
42433
|
function exposureOf(v) {
|
|
42294
42434
|
if (!v || typeof v !== "object") return null;
|
|
@@ -42298,21 +42438,21 @@ function exposureOf(v) {
|
|
|
42298
42438
|
return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
|
|
42299
42439
|
}
|
|
42300
42440
|
function fileSignature(filePath) {
|
|
42301
|
-
const fd =
|
|
42441
|
+
const fd = fs65.openSync(filePath, "r");
|
|
42302
42442
|
try {
|
|
42303
42443
|
const buf = Buffer.alloc(512);
|
|
42304
|
-
const read3 =
|
|
42444
|
+
const read3 = fs65.readSync(fd, buf, 0, 512, 0);
|
|
42305
42445
|
const slice = buf.subarray(0, read3);
|
|
42306
42446
|
const nl = slice.indexOf(10);
|
|
42307
42447
|
const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
|
|
42308
42448
|
return crypto8.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
|
|
42309
42449
|
} finally {
|
|
42310
|
-
|
|
42450
|
+
fs65.closeSync(fd);
|
|
42311
42451
|
}
|
|
42312
42452
|
}
|
|
42313
42453
|
function readWatermark(watermarkPath) {
|
|
42314
42454
|
try {
|
|
42315
|
-
const raw = JSON.parse(
|
|
42455
|
+
const raw = JSON.parse(fs65.readFileSync(watermarkPath, "utf-8"));
|
|
42316
42456
|
if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
|
|
42317
42457
|
return raw;
|
|
42318
42458
|
} catch {
|
|
@@ -42321,8 +42461,8 @@ function readWatermark(watermarkPath) {
|
|
|
42321
42461
|
}
|
|
42322
42462
|
function writeWatermark(watermarkPath, wm) {
|
|
42323
42463
|
const tmp = `${watermarkPath}.tmp`;
|
|
42324
|
-
|
|
42325
|
-
|
|
42464
|
+
fs65.writeFileSync(tmp, JSON.stringify(wm));
|
|
42465
|
+
fs65.renameSync(tmp, watermarkPath);
|
|
42326
42466
|
}
|
|
42327
42467
|
function buildWireRows(chunk2) {
|
|
42328
42468
|
const lastNl = chunk2.lastIndexOf(10);
|
|
@@ -42409,11 +42549,11 @@ async function shipOnce(deps = {}) {
|
|
|
42409
42549
|
if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
|
|
42410
42550
|
const endpoint = buildBatchEndpoint(creds.apiUrl);
|
|
42411
42551
|
if (!endpoint) return { status: "no-creds", shipped: 0 };
|
|
42412
|
-
if (!
|
|
42552
|
+
if (!fs65.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
|
|
42413
42553
|
let shipped = 0;
|
|
42414
42554
|
try {
|
|
42415
42555
|
for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
|
|
42416
|
-
const size =
|
|
42556
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42417
42557
|
if (size === 0) break;
|
|
42418
42558
|
const sig = fileSignature(auditLogPath);
|
|
42419
42559
|
const wm = readWatermark(watermarkPath);
|
|
@@ -42421,12 +42561,12 @@ async function shipOnce(deps = {}) {
|
|
|
42421
42561
|
if (offset >= size) break;
|
|
42422
42562
|
const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
|
|
42423
42563
|
const buf = Buffer.alloc(toRead);
|
|
42424
|
-
const fd =
|
|
42564
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42425
42565
|
let read3;
|
|
42426
42566
|
try {
|
|
42427
|
-
read3 =
|
|
42567
|
+
read3 = fs65.readSync(fd, buf, 0, toRead, offset);
|
|
42428
42568
|
} finally {
|
|
42429
|
-
|
|
42569
|
+
fs65.closeSync(fd);
|
|
42430
42570
|
}
|
|
42431
42571
|
const { rows, consumed } = buildWireRows(buf.subarray(0, read3));
|
|
42432
42572
|
if (consumed === 0) break;
|
|
@@ -42473,8 +42613,8 @@ async function shipOnce(deps = {}) {
|
|
|
42473
42613
|
}
|
|
42474
42614
|
function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
|
|
42475
42615
|
try {
|
|
42476
|
-
if (!
|
|
42477
|
-
const size =
|
|
42616
|
+
if (!fs65.existsSync(auditLogPath)) return 0;
|
|
42617
|
+
const size = fs65.statSync(auditLogPath).size;
|
|
42478
42618
|
const wm = readWatermark(watermarkPath);
|
|
42479
42619
|
if (!wm) return size;
|
|
42480
42620
|
if (wm.fileSig !== fileSignature(auditLogPath)) return size;
|
|
@@ -42499,20 +42639,20 @@ function startAuditShipper() {
|
|
|
42499
42639
|
}
|
|
42500
42640
|
function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK, maxBytes = MAX_CHUNK_BYTES) {
|
|
42501
42641
|
try {
|
|
42502
|
-
const fd =
|
|
42642
|
+
const fd = fs65.openSync(auditLogPath, "r");
|
|
42503
42643
|
let chunk2;
|
|
42504
42644
|
let truncated;
|
|
42505
42645
|
try {
|
|
42506
|
-
const size =
|
|
42646
|
+
const size = fs65.fstatSync(fd).size;
|
|
42507
42647
|
const wm = readWatermark(watermarkPath);
|
|
42508
42648
|
const offset = wm && wm.fileSig === fileSignature(auditLogPath) && wm.offset <= size ? wm.offset : 0;
|
|
42509
42649
|
const length = Math.min(size - offset, maxBytes);
|
|
42510
42650
|
const buffer = Buffer.alloc(length);
|
|
42511
|
-
const read3 =
|
|
42651
|
+
const read3 = fs65.readSync(fd, buffer, 0, length, offset);
|
|
42512
42652
|
chunk2 = buffer.subarray(0, read3);
|
|
42513
42653
|
truncated = size - offset > length;
|
|
42514
42654
|
} finally {
|
|
42515
|
-
|
|
42655
|
+
fs65.closeSync(fd);
|
|
42516
42656
|
}
|
|
42517
42657
|
const { rows } = buildWireRows(chunk2);
|
|
42518
42658
|
const oldest = rows.reduce((old, row) => {
|
|
@@ -42549,7 +42689,7 @@ var init_audit_shipper = __esm({
|
|
|
42549
42689
|
"policy",
|
|
42550
42690
|
"abandoned"
|
|
42551
42691
|
]);
|
|
42552
|
-
AUDIT_SHIP_WATERMARK =
|
|
42692
|
+
AUDIT_SHIP_WATERMARK = path67.join(os55.homedir(), ".node9", "audit-ship.json");
|
|
42553
42693
|
DEFAULT_INTERVAL_MS = 2e4;
|
|
42554
42694
|
MAX_BATCH = 500;
|
|
42555
42695
|
MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
@@ -42631,12 +42771,12 @@ var init_decision = __esm({
|
|
|
42631
42771
|
});
|
|
42632
42772
|
|
|
42633
42773
|
// src/daemon/dlp-scanner.ts
|
|
42634
|
-
import
|
|
42635
|
-
import
|
|
42636
|
-
import
|
|
42774
|
+
import fs66 from "fs";
|
|
42775
|
+
import path68 from "path";
|
|
42776
|
+
import os56 from "os";
|
|
42637
42777
|
function loadIndex() {
|
|
42638
42778
|
try {
|
|
42639
|
-
const raw = JSON.parse(
|
|
42779
|
+
const raw = JSON.parse(fs66.readFileSync(INDEX_FILE, "utf-8"));
|
|
42640
42780
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
42641
42781
|
const r = raw;
|
|
42642
42782
|
if (r.offsets && typeof r.offsets === "object") return r.offsets;
|
|
@@ -42648,13 +42788,13 @@ function loadIndex() {
|
|
|
42648
42788
|
}
|
|
42649
42789
|
function saveIndex(index) {
|
|
42650
42790
|
try {
|
|
42651
|
-
|
|
42791
|
+
fs66.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
42652
42792
|
} catch {
|
|
42653
42793
|
}
|
|
42654
42794
|
}
|
|
42655
42795
|
function appendAuditEntry(entry) {
|
|
42656
42796
|
try {
|
|
42657
|
-
|
|
42797
|
+
fs66.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
42658
42798
|
} catch {
|
|
42659
42799
|
}
|
|
42660
42800
|
}
|
|
@@ -42668,52 +42808,52 @@ function runDlpScan() {
|
|
|
42668
42808
|
canaryVals = [];
|
|
42669
42809
|
}
|
|
42670
42810
|
const canaryFindings = [];
|
|
42671
|
-
if (!
|
|
42811
|
+
if (!fs66.existsSync(PROJECTS_DIR2)) return;
|
|
42672
42812
|
const index = loadIndex();
|
|
42673
42813
|
const seenThisPass = /* @__PURE__ */ new Set();
|
|
42674
42814
|
const newFindings = [];
|
|
42675
42815
|
let updated = false;
|
|
42676
42816
|
let projDirs;
|
|
42677
42817
|
try {
|
|
42678
|
-
projDirs =
|
|
42818
|
+
projDirs = fs66.readdirSync(PROJECTS_DIR2);
|
|
42679
42819
|
} catch {
|
|
42680
42820
|
return;
|
|
42681
42821
|
}
|
|
42682
42822
|
for (const proj of projDirs) {
|
|
42683
|
-
const projPath =
|
|
42823
|
+
const projPath = path68.join(PROJECTS_DIR2, proj);
|
|
42684
42824
|
try {
|
|
42685
|
-
if (!
|
|
42686
|
-
const real =
|
|
42687
|
-
if (!real.startsWith(PROJECTS_DIR2 +
|
|
42825
|
+
if (!fs66.lstatSync(projPath).isDirectory()) continue;
|
|
42826
|
+
const real = fs66.realpathSync(projPath);
|
|
42827
|
+
if (!real.startsWith(PROJECTS_DIR2 + path68.sep) && real !== PROJECTS_DIR2) continue;
|
|
42688
42828
|
} catch {
|
|
42689
42829
|
continue;
|
|
42690
42830
|
}
|
|
42691
42831
|
let files;
|
|
42692
42832
|
try {
|
|
42693
|
-
files =
|
|
42833
|
+
files = fs66.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
42694
42834
|
} catch {
|
|
42695
42835
|
continue;
|
|
42696
42836
|
}
|
|
42697
42837
|
for (const file2 of files) {
|
|
42698
|
-
const filePath =
|
|
42838
|
+
const filePath = path68.join(projPath, file2);
|
|
42699
42839
|
const lastOffset = index[filePath] ?? 0;
|
|
42700
42840
|
let size;
|
|
42701
42841
|
try {
|
|
42702
|
-
size =
|
|
42842
|
+
size = fs66.statSync(filePath).size;
|
|
42703
42843
|
} catch {
|
|
42704
42844
|
continue;
|
|
42705
42845
|
}
|
|
42706
42846
|
if (size <= lastOffset) continue;
|
|
42707
42847
|
let fd;
|
|
42708
42848
|
try {
|
|
42709
|
-
fd =
|
|
42849
|
+
fd = fs66.openSync(filePath, "r");
|
|
42710
42850
|
} catch {
|
|
42711
42851
|
continue;
|
|
42712
42852
|
}
|
|
42713
42853
|
try {
|
|
42714
42854
|
const chunkSize = size - lastOffset;
|
|
42715
42855
|
const buf = Buffer.alloc(chunkSize);
|
|
42716
|
-
|
|
42856
|
+
fs66.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
42717
42857
|
const chunk2 = buf.toString("utf-8");
|
|
42718
42858
|
for (const line of chunk2.split("\n")) {
|
|
42719
42859
|
if (!line.trim()) continue;
|
|
@@ -42734,7 +42874,7 @@ function runDlpScan() {
|
|
|
42734
42874
|
const match = scanText(text);
|
|
42735
42875
|
const canary = canaryVals.length > 0 ? matchCanary(text, canaryVals) : null;
|
|
42736
42876
|
if (!match && !canary) continue;
|
|
42737
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
42877
|
+
const projLabel = decodeURIComponent(proj).replace(os56.homedir(), "~").slice(0, 40);
|
|
42738
42878
|
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
42739
42879
|
if (canary) {
|
|
42740
42880
|
const rec = canaryById.get(canary.id);
|
|
@@ -42784,7 +42924,7 @@ function runDlpScan() {
|
|
|
42784
42924
|
updated = true;
|
|
42785
42925
|
} finally {
|
|
42786
42926
|
try {
|
|
42787
|
-
|
|
42927
|
+
fs66.closeSync(fd);
|
|
42788
42928
|
} catch {
|
|
42789
42929
|
}
|
|
42790
42930
|
}
|
|
@@ -42843,15 +42983,15 @@ var init_dlp_scanner = __esm({
|
|
|
42843
42983
|
init_registry2();
|
|
42844
42984
|
init_native();
|
|
42845
42985
|
init_state3();
|
|
42846
|
-
INDEX_FILE =
|
|
42847
|
-
PROJECTS_DIR2 =
|
|
42986
|
+
INDEX_FILE = path68.join(os56.homedir(), ".node9", "dlp-index.json");
|
|
42987
|
+
PROJECTS_DIR2 = path68.join(os56.homedir(), ".claude", "projects");
|
|
42848
42988
|
}
|
|
42849
42989
|
});
|
|
42850
42990
|
|
|
42851
42991
|
// src/daemon/mcp-reconciler.ts
|
|
42852
|
-
import
|
|
42853
|
-
import
|
|
42854
|
-
import
|
|
42992
|
+
import fs67 from "fs";
|
|
42993
|
+
import path69 from "path";
|
|
42994
|
+
import os57 from "os";
|
|
42855
42995
|
import crypto9 from "crypto";
|
|
42856
42996
|
function idKey(e) {
|
|
42857
42997
|
const cmdHash = crypto9.createHash("sha256").update(`${e.command} ${e.args.join(" ")}`).digest("hex").slice(0, 16);
|
|
@@ -42859,7 +42999,7 @@ function idKey(e) {
|
|
|
42859
42999
|
}
|
|
42860
43000
|
function loadBaseline() {
|
|
42861
43001
|
try {
|
|
42862
|
-
const raw = JSON.parse(
|
|
43002
|
+
const raw = JSON.parse(fs67.readFileSync(BASELINE_FILE2, "utf-8"));
|
|
42863
43003
|
return new Set(Array.isArray(raw) ? raw : []);
|
|
42864
43004
|
} catch {
|
|
42865
43005
|
return /* @__PURE__ */ new Set();
|
|
@@ -42867,7 +43007,7 @@ function loadBaseline() {
|
|
|
42867
43007
|
}
|
|
42868
43008
|
function saveBaseline(keys) {
|
|
42869
43009
|
try {
|
|
42870
|
-
|
|
43010
|
+
fs67.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
|
|
42871
43011
|
mode: 384
|
|
42872
43012
|
});
|
|
42873
43013
|
} catch {
|
|
@@ -43055,7 +43195,7 @@ var init_mcp_reconciler = __esm({
|
|
|
43055
43195
|
init_cloud();
|
|
43056
43196
|
init_audit();
|
|
43057
43197
|
init_mcp_pin();
|
|
43058
|
-
BASELINE_FILE2 =
|
|
43198
|
+
BASELINE_FILE2 = path69.join(os57.homedir(), ".node9", "mcp-baseline.json");
|
|
43059
43199
|
BASELINE_CAP = 500;
|
|
43060
43200
|
DEFAULT_INTERVAL_MIN = 60;
|
|
43061
43201
|
DEFAULT_STALE_DAYS = 7;
|
|
@@ -43129,114 +43269,6 @@ var init_hook_heal = __esm({
|
|
|
43129
43269
|
}
|
|
43130
43270
|
});
|
|
43131
43271
|
|
|
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
43272
|
// src/supply-chain/zip.ts
|
|
43241
43273
|
import fs68 from "fs";
|
|
43242
43274
|
import zlib from "zlib";
|
|
@@ -77568,7 +77600,7 @@ function buildReportJson(input) {
|
|
|
77568
77600
|
var BLOCK_REASON_LABELS = {
|
|
77569
77601
|
timeout: "Approval timeout",
|
|
77570
77602
|
"smart-rule-block": "Smart rule",
|
|
77571
|
-
"observe-mode-dlp-would-block": "DLP (
|
|
77603
|
+
"observe-mode-dlp-would-block": "DLP (audit)",
|
|
77572
77604
|
"persistent-deny": "Persistent deny",
|
|
77573
77605
|
"local-decision": "User denied",
|
|
77574
77606
|
"dlp-block": "DLP block",
|
|
@@ -77608,10 +77640,10 @@ function fmtCost2(usd) {
|
|
|
77608
77640
|
return "$" + usd.toFixed(2);
|
|
77609
77641
|
}
|
|
77610
77642
|
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", "
|
|
77643
|
+
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | 90d | month", "30d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
|
|
77612
77644
|
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
77613
77645
|
options.period
|
|
77614
|
-
) ? options.period : "
|
|
77646
|
+
) ? options.period : "30d";
|
|
77615
77647
|
const excludeTests = options.tests === false;
|
|
77616
77648
|
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
77617
77649
|
excludeTests
|
|
@@ -77809,7 +77841,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
77809
77841
|
);
|
|
77810
77842
|
summaryRow(
|
|
77811
77843
|
observeDlp > 0 ? chalk20.blue("\u{1F441}") : chalk20.dim("\u{1F441}"),
|
|
77812
|
-
"DLP (
|
|
77844
|
+
"DLP (audit)",
|
|
77813
77845
|
observeDlp,
|
|
77814
77846
|
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
77815
77847
|
observeDlp > 0 ? (s) => chalk20.blue(s) : (s) => chalk20.dim(s)
|
|
@@ -79317,6 +79349,235 @@ import fs92 from "fs";
|
|
|
79317
79349
|
import os78 from "os";
|
|
79318
79350
|
import path91 from "path";
|
|
79319
79351
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
79352
|
+
|
|
79353
|
+
// src/cli/commands/egress.ts
|
|
79354
|
+
init_config();
|
|
79355
|
+
init_keyed_guard();
|
|
79356
|
+
init_dist();
|
|
79357
|
+
init_egress_config();
|
|
79358
|
+
init_egress_config();
|
|
79359
|
+
import chalk27 from "chalk";
|
|
79360
|
+
function guard(fn) {
|
|
79361
|
+
try {
|
|
79362
|
+
fn();
|
|
79363
|
+
return true;
|
|
79364
|
+
} catch (err2) {
|
|
79365
|
+
console.error(chalk27.red(`
|
|
79366
|
+
\u2717 ${err2.message}
|
|
79367
|
+
`));
|
|
79368
|
+
process.exitCode = 1;
|
|
79369
|
+
return false;
|
|
79370
|
+
}
|
|
79371
|
+
}
|
|
79372
|
+
function mutate(action, change) {
|
|
79373
|
+
if (!cliGuardPolicyWrite(action)) return false;
|
|
79374
|
+
return guard(() => setEgress(change));
|
|
79375
|
+
}
|
|
79376
|
+
function addHost(list2, host2) {
|
|
79377
|
+
if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
|
|
79378
|
+
return guard(() => addEgressHost(list2, host2));
|
|
79379
|
+
}
|
|
79380
|
+
function exempt2(address) {
|
|
79381
|
+
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
79382
|
+
return guard(() => addSsrfExemption(address));
|
|
79383
|
+
}
|
|
79384
|
+
var INTERNAL_FIELDS = {
|
|
79385
|
+
allowed: { ssrfStrict: false, allowPrivate: true },
|
|
79386
|
+
listed: { ssrfStrict: false, allowPrivate: false },
|
|
79387
|
+
blocked: { ssrfStrict: true, allowPrivate: false }
|
|
79388
|
+
};
|
|
79389
|
+
var INTERNAL_SAID = {
|
|
79390
|
+
allowed: "reachable without listing them",
|
|
79391
|
+
listed: "reachable only if they are on your allowlist",
|
|
79392
|
+
blocked: "blocked"
|
|
79393
|
+
};
|
|
79394
|
+
function describeInternalState(state) {
|
|
79395
|
+
return INTERNAL_SAID[state];
|
|
79396
|
+
}
|
|
79397
|
+
function readInternalState(e) {
|
|
79398
|
+
if (e.ssrfStrict === true) return "blocked";
|
|
79399
|
+
return e.allowPrivate === false ? "listed" : "allowed";
|
|
79400
|
+
}
|
|
79401
|
+
function setInternal(value) {
|
|
79402
|
+
const state = value.trim().toLowerCase();
|
|
79403
|
+
if (!(state in INTERNAL_FIELDS)) {
|
|
79404
|
+
console.error(
|
|
79405
|
+
chalk27.red(`
|
|
79406
|
+
\u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk27.gray(
|
|
79407
|
+
"\n allowed loopback, 10/172.16/192.168 and CGNAT are reachable (default)\n listed they are reachable only if you allowlist them\n blocked they are blocked at the floor\n"
|
|
79408
|
+
)
|
|
79409
|
+
);
|
|
79410
|
+
process.exitCode = 1;
|
|
79411
|
+
return;
|
|
79412
|
+
}
|
|
79413
|
+
if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
|
|
79414
|
+
_resetConfigCache();
|
|
79415
|
+
const effective = readInternalState(getConfig().policy.egress);
|
|
79416
|
+
if (effective !== state) {
|
|
79417
|
+
console.log(
|
|
79418
|
+
chalk27.yellow(
|
|
79419
|
+
`
|
|
79420
|
+
\u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
|
|
79421
|
+
Change it in the dashboard, Enforcement \u2192 Network.
|
|
79422
|
+
`
|
|
79423
|
+
)
|
|
79424
|
+
);
|
|
79425
|
+
return;
|
|
79426
|
+
}
|
|
79427
|
+
const line = `
|
|
79428
|
+
\u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
|
|
79429
|
+
`;
|
|
79430
|
+
console.log(state === "allowed" ? chalk27.yellow(line) : chalk27.green(line));
|
|
79431
|
+
}
|
|
79432
|
+
function showFloor(e, ssrfStrictSource) {
|
|
79433
|
+
console.log(
|
|
79434
|
+
chalk27.gray("\n Protected addresses") + chalk27.gray(" \u2014 in shell commands and declared URLs")
|
|
79435
|
+
);
|
|
79436
|
+
console.log(
|
|
79437
|
+
chalk27.gray(
|
|
79438
|
+
" always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
|
|
79439
|
+
)
|
|
79440
|
+
);
|
|
79441
|
+
const state = readInternalState(e);
|
|
79442
|
+
const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
|
|
79443
|
+
const STATE_LABEL = {
|
|
79444
|
+
allowed: "allowed",
|
|
79445
|
+
listed: "allowlist only",
|
|
79446
|
+
blocked: "blocked"
|
|
79447
|
+
};
|
|
79448
|
+
console.log(
|
|
79449
|
+
` Internal addresses: ${state === "blocked" ? chalk27.green(STATE_LABEL[state]) : chalk27.yellow(STATE_LABEL[state])}` + chalk27.gray(` loopback, 10/172.16/192.168 and CGNAT are ${INTERNAL_SAID[state]}`)
|
|
79450
|
+
);
|
|
79451
|
+
console.log(chalk27.gray(` set by: ${by}`));
|
|
79452
|
+
const exemptions = e.ssrfAllow ?? [];
|
|
79453
|
+
console.log(chalk27.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
|
|
79454
|
+
console.log(
|
|
79455
|
+
chalk27.gray(
|
|
79456
|
+
" Covered: shell commands, and tools that declare a URL (WebFetch, an MCP\n fetch tool, browser navigate).\n Not covered: an interpreter one-liner (node -e, python3 -c) hides its\n destination inside a program and does not reach this gate.\n CGNAT (100.64/10) is NOT in the always-blocked set: it is reachable\n until Internal addresses is on, and an exemption can release it."
|
|
79457
|
+
)
|
|
79458
|
+
);
|
|
79459
|
+
}
|
|
79460
|
+
function showStatus() {
|
|
79461
|
+
const cfg = getConfig();
|
|
79462
|
+
const e = cfg.policy.egress;
|
|
79463
|
+
const state = !e.enabled ? chalk27.red("OFF \u2014 your agent can reach any host, except the protected ones below") : e.mode === "block" ? chalk27.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk27.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
|
|
79464
|
+
console.log(chalk27.cyan.bold("\n\u{1F310} Egress control"));
|
|
79465
|
+
console.log(" State: " + state);
|
|
79466
|
+
if (cfg.policySource === "workspace") {
|
|
79467
|
+
console.log(
|
|
79468
|
+
chalk27.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
|
|
79469
|
+
);
|
|
79470
|
+
}
|
|
79471
|
+
console.log(
|
|
79472
|
+
chalk27.gray(
|
|
79473
|
+
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
79474
|
+
)
|
|
79475
|
+
);
|
|
79476
|
+
showFloor(e, cfg.ssrfStrictSource);
|
|
79477
|
+
if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
|
|
79478
|
+
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
79479
|
+
if (!e.enabled) {
|
|
79480
|
+
console.log(chalk27.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
79481
|
+
console.log(chalk27.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
79482
|
+
}
|
|
79483
|
+
console.log("");
|
|
79484
|
+
}
|
|
79485
|
+
function registerEgressCommand(program2) {
|
|
79486
|
+
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
79487
|
+
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
79488
|
+
if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
|
|
79489
|
+
console.log(chalk27.green("\n\u2713 Egress is now watched (review mode)."));
|
|
79490
|
+
console.log(
|
|
79491
|
+
chalk27.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
79492
|
+
);
|
|
79493
|
+
console.log(
|
|
79494
|
+
chalk27.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
79495
|
+
);
|
|
79496
|
+
});
|
|
79497
|
+
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
79498
|
+
if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
|
|
79499
|
+
console.log(chalk27.green("\n\u2713 Egress is now locked (block mode)."));
|
|
79500
|
+
console.log(chalk27.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
79501
|
+
console.log(chalk27.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
79502
|
+
});
|
|
79503
|
+
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
|
|
79504
|
+
if (!addHost("allow", host2)) return;
|
|
79505
|
+
console.log(chalk27.green(`
|
|
79506
|
+
\u2713 Allowed egress to ${host2}.
|
|
79507
|
+
`));
|
|
79508
|
+
});
|
|
79509
|
+
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
|
|
79510
|
+
if (!addHost("deny", host2)) return;
|
|
79511
|
+
console.log(chalk27.green(`
|
|
79512
|
+
\u2713 Denied egress to ${host2}.
|
|
79513
|
+
`));
|
|
79514
|
+
});
|
|
79515
|
+
egress.command("off").description("Turn egress control off").action(() => {
|
|
79516
|
+
if (!mutate("egress off", { enabled: false })) return;
|
|
79517
|
+
console.log(
|
|
79518
|
+
chalk27.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
79519
|
+
);
|
|
79520
|
+
});
|
|
79521
|
+
egress.command("internal <allowed|listed|blocked>").description(
|
|
79522
|
+
"How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
|
|
79523
|
+
).action((value) => setInternal(value));
|
|
79524
|
+
egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
|
|
79525
|
+
const v = value.trim().toLowerCase();
|
|
79526
|
+
if (v !== "on" && v !== "off") {
|
|
79527
|
+
console.error(chalk27.red(`
|
|
79528
|
+
\u2717 Expected "on" or "off", got "${value}".
|
|
79529
|
+
`));
|
|
79530
|
+
process.exitCode = 1;
|
|
79531
|
+
return;
|
|
79532
|
+
}
|
|
79533
|
+
const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
|
|
79534
|
+
console.log(chalk27.gray(`
|
|
79535
|
+
(\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
|
|
79536
|
+
setInternal(state);
|
|
79537
|
+
});
|
|
79538
|
+
egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
|
|
79539
|
+
const a = normalizeEgressHost(address);
|
|
79540
|
+
const slash = a.indexOf("/");
|
|
79541
|
+
const base2 = slash === -1 ? a : a.slice(0, slash);
|
|
79542
|
+
const prefixText = slash === -1 ? null : a.slice(slash + 1);
|
|
79543
|
+
if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
|
|
79544
|
+
console.error(
|
|
79545
|
+
chalk27.red(`
|
|
79546
|
+
\u2717 "${address}" is not an address or a range.`) + chalk27.gray(
|
|
79547
|
+
"\n Exemptions are matched as an address (10.0.0.5) or a CIDR\n range (100.64.0.0/10), never a name.\n"
|
|
79548
|
+
)
|
|
79549
|
+
);
|
|
79550
|
+
process.exitCode = 1;
|
|
79551
|
+
return;
|
|
79552
|
+
}
|
|
79553
|
+
const m = classifySsrf(base2);
|
|
79554
|
+
if (m && !m.overridable) {
|
|
79555
|
+
console.error(
|
|
79556
|
+
chalk27.red(`
|
|
79557
|
+
\u2717 ${a} cannot be exempted.`) + chalk27.gray(
|
|
79558
|
+
`
|
|
79559
|
+
${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
|
|
79560
|
+
`
|
|
79561
|
+
)
|
|
79562
|
+
);
|
|
79563
|
+
process.exitCode = 1;
|
|
79564
|
+
return;
|
|
79565
|
+
}
|
|
79566
|
+
if (!exempt2(a)) return;
|
|
79567
|
+
console.log(chalk27.green(`
|
|
79568
|
+
\u2713 ${a} is exempt from the floor.`));
|
|
79569
|
+
if (!m)
|
|
79570
|
+
console.log(
|
|
79571
|
+
chalk27.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
|
|
79572
|
+
);
|
|
79573
|
+
else console.log("");
|
|
79574
|
+
});
|
|
79575
|
+
egress.command("status").description("Show the current egress state").action(showStatus);
|
|
79576
|
+
egress.action(showStatus);
|
|
79577
|
+
}
|
|
79578
|
+
|
|
79579
|
+
// src/mcp-server/index.ts
|
|
79580
|
+
init_agent_wiring();
|
|
79320
79581
|
function ok(id, result) {
|
|
79321
79582
|
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
|
|
79322
79583
|
}
|
|
@@ -79450,7 +79711,7 @@ var TOOLS = [
|
|
|
79450
79711
|
},
|
|
79451
79712
|
{
|
|
79452
79713
|
name: "node9_scan",
|
|
79453
|
-
description: "Scan all
|
|
79714
|
+
description: "Scan the history of all supported coding agents (Claude Code, Gemini, Codex, Copilot, Antigravity) and report what node9 would have blocked or flagged. Shows blocked operations, reviewed commands, credential leaks, and agent spend. Use this to audit past activity and find security gaps before they become incidents.",
|
|
79454
79715
|
inputSchema: {
|
|
79455
79716
|
type: "object",
|
|
79456
79717
|
properties: {
|
|
@@ -79470,8 +79731,8 @@ var TOOLS = [
|
|
|
79470
79731
|
properties: {
|
|
79471
79732
|
period: {
|
|
79472
79733
|
type: "string",
|
|
79473
|
-
enum: ["today", "7d", "30d", "month"],
|
|
79474
|
-
description: "Time period for the report (default:
|
|
79734
|
+
enum: ["today", "7d", "30d", "90d", "month"],
|
|
79735
|
+
description: "Time period for the report (default: 30d)."
|
|
79475
79736
|
},
|
|
79476
79737
|
no_tests: {
|
|
79477
79738
|
type: "boolean",
|
|
@@ -79605,7 +79866,7 @@ function handleStatus() {
|
|
|
79605
79866
|
const wsGoverned = config.policySource === "workspace";
|
|
79606
79867
|
const activeShields = wsGoverned ? config.policy.appliedShields ?? [] : readActiveShields();
|
|
79607
79868
|
const lines = [];
|
|
79608
|
-
lines.push(`Mode: ${settings.mode}`);
|
|
79869
|
+
lines.push(`Mode: ${levelOf(settings.mode)}`);
|
|
79609
79870
|
if (wsGoverned) {
|
|
79610
79871
|
lines.push(
|
|
79611
79872
|
"Policy source: workspace config (app.node9.ai) \u2014 local config files are ignored for policy"
|
|
@@ -79621,6 +79882,12 @@ function handleStatus() {
|
|
|
79621
79882
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
79622
79883
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
79623
79884
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
79885
|
+
lines.push("Agent hooks:");
|
|
79886
|
+
for (const a of getAgentWiring()) {
|
|
79887
|
+
const hooked = a.hooks.some((h) => h.wired);
|
|
79888
|
+
const state = !a.installed ? "not installed" : hooked ? "wired" : a.isProtected ? "protected through MCP" : "not wired";
|
|
79889
|
+
lines.push(` ${a.label}: ${state}`);
|
|
79890
|
+
}
|
|
79624
79891
|
const projectConfig = path91.join(process.cwd(), "node9.config.json");
|
|
79625
79892
|
const globalConfig = path91.join(os78.homedir(), ".node9", "config.json");
|
|
79626
79893
|
const presentLabel = wsGoverned ? "present \u2014 ignored (workspace config governs)" : "present";
|
|
@@ -79713,6 +79980,11 @@ function handleShieldDisable(args) {
|
|
|
79713
79980
|
if (disabledApp) recordUserDecision(disabledApp.slug, "off");
|
|
79714
79981
|
return `Shield "${name}" disabled.`;
|
|
79715
79982
|
}
|
|
79983
|
+
function internalAddressLine(e) {
|
|
79984
|
+
const configured = readInternalState(e);
|
|
79985
|
+
const state = !e.enabled && configured === "listed" ? "allowed" : configured;
|
|
79986
|
+
return `Internal addresses: ${state} \u2014 loopback and the private ranges are ${describeInternalState(state)}.`;
|
|
79987
|
+
}
|
|
79716
79988
|
function handleEgressStatus() {
|
|
79717
79989
|
const cfgEgress = getConfig();
|
|
79718
79990
|
const e = cfgEgress.policy.egress;
|
|
@@ -79734,7 +80006,7 @@ function handleEgressStatus() {
|
|
|
79734
80006
|
// actually covers is measured in egress.integration.test.ts.
|
|
79735
80007
|
"Protected addresses: cloud metadata, link-local and multicast are blocked before any of the above is consulted, in shell commands AND in tools that declare a URL (WebFetch, an MCP fetch tool, browser navigate). No setting releases them, though `node9 pause` suspends all enforcement.",
|
|
79736
80008
|
"NOT covered by that: an interpreter one-liner (node -e, python3 -c) carries its destination inside a program and does not reach this gate. CGNAT (100.64/10) is NOT in the always-blocked set \u2014 it is reachable until the strict tier is on, and an exemption can release it.",
|
|
79737
|
-
|
|
80009
|
+
internalAddressLine(e),
|
|
79738
80010
|
`Floor exemptions: ${e.ssrfAllow?.length ? e.ssrfAllow.join(", ") : "(none)"}`
|
|
79739
80011
|
];
|
|
79740
80012
|
return lines.join("\n");
|
|
@@ -80057,7 +80329,7 @@ function registerMcpServerCommand(program2) {
|
|
|
80057
80329
|
init_trusted_hosts();
|
|
80058
80330
|
init_keyed_guard();
|
|
80059
80331
|
init_trusted_recipients();
|
|
80060
|
-
import
|
|
80332
|
+
import chalk28 from "chalk";
|
|
80061
80333
|
function isValidHost(host2) {
|
|
80062
80334
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host2);
|
|
80063
80335
|
}
|
|
@@ -80068,17 +80340,17 @@ function registerTrustCommand(program2) {
|
|
|
80068
80340
|
const normalized = normalizeHost2(host2.trim());
|
|
80069
80341
|
if (!isValidHost(normalized)) {
|
|
80070
80342
|
console.error(
|
|
80071
|
-
|
|
80343
|
+
chalk28.red(`
|
|
80072
80344
|
\u274C Invalid host: "${host2}"
|
|
80073
|
-
`) +
|
|
80345
|
+
`) + chalk28.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
80074
80346
|
);
|
|
80075
80347
|
process.exit(1);
|
|
80076
80348
|
}
|
|
80077
80349
|
addTrustedHost(normalized);
|
|
80078
|
-
console.log(
|
|
80350
|
+
console.log(chalk28.green(`
|
|
80079
80351
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
80080
80352
|
console.log(
|
|
80081
|
-
|
|
80353
|
+
chalk28.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
80082
80354
|
);
|
|
80083
80355
|
});
|
|
80084
80356
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host2) => {
|
|
@@ -80086,27 +80358,27 @@ function registerTrustCommand(program2) {
|
|
|
80086
80358
|
const normalized = normalizeHost2(host2.trim());
|
|
80087
80359
|
const removed = removeTrustedHost(normalized);
|
|
80088
80360
|
if (!removed) {
|
|
80089
|
-
console.error(
|
|
80361
|
+
console.error(chalk28.yellow(`
|
|
80090
80362
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
80091
80363
|
`));
|
|
80092
80364
|
process.exit(1);
|
|
80093
80365
|
}
|
|
80094
|
-
console.log(
|
|
80366
|
+
console.log(chalk28.green(`
|
|
80095
80367
|
\u2705 ${normalized} removed from trusted hosts.
|
|
80096
80368
|
`));
|
|
80097
80369
|
});
|
|
80098
80370
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
80099
80371
|
const hosts = readTrustedHosts();
|
|
80100
80372
|
if (hosts.length === 0) {
|
|
80101
|
-
console.log(
|
|
80102
|
-
console.log(` Add one: ${
|
|
80373
|
+
console.log(chalk28.gray("\n No trusted hosts configured.\n"));
|
|
80374
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust add api.mycompany.com")}
|
|
80103
80375
|
`);
|
|
80104
80376
|
return;
|
|
80105
80377
|
}
|
|
80106
|
-
console.log(
|
|
80378
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
80107
80379
|
for (const entry of hosts) {
|
|
80108
80380
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
80109
|
-
console.log(` ${
|
|
80381
|
+
console.log(` ${chalk28.cyan(entry.host.padEnd(40))} ${chalk28.gray(`added ${date}`)}`);
|
|
80110
80382
|
}
|
|
80111
80383
|
console.log("");
|
|
80112
80384
|
});
|
|
@@ -80118,41 +80390,41 @@ function registerTrustCommand(program2) {
|
|
|
80118
80390
|
const stored = addTrustedRecipient(value, "user");
|
|
80119
80391
|
if (!stored) {
|
|
80120
80392
|
console.error(
|
|
80121
|
-
|
|
80393
|
+
chalk28.red(`
|
|
80122
80394
|
\u274C Not a destination: "${value}"
|
|
80123
|
-
`) +
|
|
80395
|
+
`) + chalk28.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
|
|
80124
80396
|
);
|
|
80125
80397
|
process.exit(1);
|
|
80126
80398
|
}
|
|
80127
|
-
console.log(
|
|
80399
|
+
console.log(chalk28.green(`
|
|
80128
80400
|
\u2705 ${stored} added to trusted recipients.
|
|
80129
80401
|
`));
|
|
80130
80402
|
});
|
|
80131
80403
|
rec.command("remove <value>").description("Remove a trusted recipient").action((value) => {
|
|
80132
80404
|
if (!cliGuardPolicyWrite(`trust recipient remove ${value}`)) return;
|
|
80133
80405
|
if (!removeTrustedRecipient(value)) {
|
|
80134
|
-
console.error(
|
|
80406
|
+
console.error(chalk28.yellow(`
|
|
80135
80407
|
\u26A0\uFE0F "${value}" is not in the trusted recipients list.
|
|
80136
80408
|
`));
|
|
80137
80409
|
process.exit(1);
|
|
80138
80410
|
}
|
|
80139
|
-
console.log(
|
|
80411
|
+
console.log(chalk28.green(`
|
|
80140
80412
|
\u2705 ${value} removed from trusted recipients.
|
|
80141
80413
|
`));
|
|
80142
80414
|
});
|
|
80143
80415
|
rec.command("list").description("Show all trusted recipients").action(() => {
|
|
80144
80416
|
const entries = readTrustedRecipients();
|
|
80145
80417
|
if (entries.length === 0) {
|
|
80146
|
-
console.log(
|
|
80147
|
-
console.log(` Add one: ${
|
|
80418
|
+
console.log(chalk28.gray("\n No trusted recipients.\n"));
|
|
80419
|
+
console.log(` Add one: ${chalk28.cyan("node9 trust recipient add partner@example.com")}
|
|
80148
80420
|
`);
|
|
80149
80421
|
return;
|
|
80150
80422
|
}
|
|
80151
|
-
console.log(
|
|
80423
|
+
console.log(chalk28.bold("\n\u{1F513} Trusted Recipients\n"));
|
|
80152
80424
|
for (const e of entries) {
|
|
80153
80425
|
const date = new Date(e.addedAt).toLocaleDateString();
|
|
80154
80426
|
console.log(
|
|
80155
|
-
` ${
|
|
80427
|
+
` ${chalk28.cyan(e.value.padEnd(44))} ${chalk28.gray(`added ${date} (${e.addedBy})`)}`
|
|
80156
80428
|
);
|
|
80157
80429
|
}
|
|
80158
80430
|
console.log("");
|
|
@@ -80161,13 +80433,13 @@ function registerTrustCommand(program2) {
|
|
|
80161
80433
|
|
|
80162
80434
|
// src/cli/commands/mcp-pin.ts
|
|
80163
80435
|
init_mcp_pin();
|
|
80164
|
-
import
|
|
80436
|
+
import chalk30 from "chalk";
|
|
80165
80437
|
import fs93 from "fs";
|
|
80166
80438
|
|
|
80167
80439
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
80168
80440
|
init_mcp_wrap();
|
|
80169
80441
|
init_mcp_status();
|
|
80170
|
-
import
|
|
80442
|
+
import chalk29 from "chalk";
|
|
80171
80443
|
var STATE_WORD = {
|
|
80172
80444
|
gatewayed: "governed",
|
|
80173
80445
|
"node9-self": "node9",
|
|
@@ -80176,10 +80448,10 @@ var STATE_WORD = {
|
|
|
80176
80448
|
};
|
|
80177
80449
|
function stateChip(state) {
|
|
80178
80450
|
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
|
|
80451
|
+
if (state === "gatewayed") return chalk29.green(w);
|
|
80452
|
+
if (state === "node9-self") return chalk29.gray(w);
|
|
80453
|
+
if (state === "remote") return chalk29.gray(w);
|
|
80454
|
+
return chalk29.yellow(w);
|
|
80183
80455
|
}
|
|
80184
80456
|
function relativeAge(ms, now) {
|
|
80185
80457
|
const s = Math.max(0, Math.round((now - ms) / 1e3));
|
|
@@ -80193,15 +80465,15 @@ function relativeAge(ms, now) {
|
|
|
80193
80465
|
function connectionChip(e, now) {
|
|
80194
80466
|
switch (e.connection) {
|
|
80195
80467
|
case "connected":
|
|
80196
|
-
return
|
|
80468
|
+
return chalk29.green(
|
|
80197
80469
|
`\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
|
|
80198
80470
|
);
|
|
80199
80471
|
case "stale":
|
|
80200
|
-
return
|
|
80472
|
+
return chalk29.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
|
|
80201
80473
|
case "pending-launch":
|
|
80202
|
-
return
|
|
80474
|
+
return chalk29.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
|
|
80203
80475
|
case "unlaunchable":
|
|
80204
|
-
return
|
|
80476
|
+
return chalk29.red(
|
|
80205
80477
|
`\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
|
|
80206
80478
|
);
|
|
80207
80479
|
default:
|
|
@@ -80226,7 +80498,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80226
80498
|
const now = Date.now();
|
|
80227
80499
|
const rows = resolveMcpStatus(void 0, process.env, now);
|
|
80228
80500
|
if (rows.length === 0) {
|
|
80229
|
-
console.error(
|
|
80501
|
+
console.error(chalk29.gray("No MCP servers found in any agent config."));
|
|
80230
80502
|
return;
|
|
80231
80503
|
}
|
|
80232
80504
|
for (const e of rows) {
|
|
@@ -80240,20 +80512,20 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
80240
80512
|
const pending = rows.filter((e) => e.connection === "pending-launch").length;
|
|
80241
80513
|
if (ungoverned > 0) {
|
|
80242
80514
|
console.error(
|
|
80243
|
-
|
|
80515
|
+
chalk29.yellow(
|
|
80244
80516
|
`
|
|
80245
|
-
${ungoverned} ungoverned \u2014 run ` +
|
|
80517
|
+
${ungoverned} ungoverned \u2014 run ` + chalk29.bold("node9 mcp gateway --all") + " to govern."
|
|
80246
80518
|
)
|
|
80247
80519
|
);
|
|
80248
80520
|
}
|
|
80249
80521
|
if (pending > 0) {
|
|
80250
80522
|
console.error(
|
|
80251
|
-
|
|
80523
|
+
chalk29.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
80252
80524
|
);
|
|
80253
80525
|
}
|
|
80254
80526
|
if (unlaunchable > 0) {
|
|
80255
80527
|
console.error(
|
|
80256
|
-
|
|
80528
|
+
chalk29.red(
|
|
80257
80529
|
`${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
|
|
80258
80530
|
)
|
|
80259
80531
|
);
|
|
@@ -80272,27 +80544,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80272
80544
|
} else if (name) {
|
|
80273
80545
|
const matches2 = resolve(name, opts.agent);
|
|
80274
80546
|
if (matches2.length === 0) {
|
|
80275
|
-
console.error(
|
|
80547
|
+
console.error(chalk29.red(`No MCP server named "${name}" found.`));
|
|
80276
80548
|
process.exitCode = 1;
|
|
80277
80549
|
return;
|
|
80278
80550
|
}
|
|
80279
80551
|
if (matches2.length > 1) {
|
|
80280
80552
|
console.error(
|
|
80281
|
-
|
|
80553
|
+
chalk29.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
|
|
80282
80554
|
);
|
|
80283
80555
|
process.exitCode = 1;
|
|
80284
80556
|
return;
|
|
80285
80557
|
}
|
|
80286
80558
|
targets = matches2;
|
|
80287
80559
|
} else {
|
|
80288
|
-
console.error(
|
|
80560
|
+
console.error(chalk29.red("Pass a server name or --all. See `node9 mcp status`."));
|
|
80289
80561
|
process.exitCode = 1;
|
|
80290
80562
|
return;
|
|
80291
80563
|
}
|
|
80292
80564
|
const doable = targets.filter((e) => e.state === targetState);
|
|
80293
80565
|
if (doable.length === 0) {
|
|
80294
80566
|
console.error(
|
|
80295
|
-
|
|
80567
|
+
chalk29.gray(
|
|
80296
80568
|
opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
|
|
80297
80569
|
)
|
|
80298
80570
|
);
|
|
@@ -80306,20 +80578,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
|
|
|
80306
80578
|
else wrapEntry(e);
|
|
80307
80579
|
agents.add(e.agentLabel);
|
|
80308
80580
|
const verb = opts.rewrap ? "refreshed" : "governed";
|
|
80309
|
-
console.error(
|
|
80581
|
+
console.error(chalk29.green(`\u2713 ${verb} ${e.name}`) + chalk29.gray(` (${e.agentLabel})`));
|
|
80310
80582
|
} catch (err2) {
|
|
80311
80583
|
failed++;
|
|
80312
80584
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80313
80585
|
console.error(
|
|
80314
|
-
|
|
80586
|
+
chalk29.red(`\u2717 failed ${e.name}`) + chalk29.gray(` (${e.agentLabel}): ${msg}`)
|
|
80315
80587
|
);
|
|
80316
80588
|
}
|
|
80317
80589
|
}
|
|
80318
80590
|
if (failed > 0) process.exitCode = 1;
|
|
80319
80591
|
if (agents.size > 0) {
|
|
80320
80592
|
console.error(
|
|
80321
|
-
|
|
80322
|
-
Restart ${[...agents].join(", ")}`) +
|
|
80593
|
+
chalk29.bold(`
|
|
80594
|
+
Restart ${[...agents].join(", ")}`) + chalk29.gray(" to activate. Undo any server with ") + chalk29.cyan("node9 mcp ungateway <name>") + chalk29.gray(".")
|
|
80323
80595
|
);
|
|
80324
80596
|
}
|
|
80325
80597
|
}
|
|
@@ -80327,19 +80599,19 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80327
80599
|
mcp.command("ungateway <name>").description("Restore an MCP server to its original (un-route it from node9)").option("--agent <id>", "disambiguate a name in more than one agent").action((name, opts) => {
|
|
80328
80600
|
const matches2 = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
|
|
80329
80601
|
if (matches2.length === 0) {
|
|
80330
|
-
console.error(
|
|
80602
|
+
console.error(chalk29.red(`No governed MCP server named "${name}" found.`));
|
|
80331
80603
|
process.exitCode = 1;
|
|
80332
80604
|
return;
|
|
80333
80605
|
}
|
|
80334
80606
|
if (matches2.length > 1) {
|
|
80335
|
-
console.error(
|
|
80607
|
+
console.error(chalk29.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
|
|
80336
80608
|
process.exitCode = 1;
|
|
80337
80609
|
return;
|
|
80338
80610
|
}
|
|
80339
80611
|
const e = matches2[0];
|
|
80340
80612
|
const orig = fromGateway(e.raw);
|
|
80341
80613
|
if (!orig) {
|
|
80342
|
-
console.error(
|
|
80614
|
+
console.error(chalk29.red(`Couldn't parse the original command for "${name}".`));
|
|
80343
80615
|
process.exitCode = 1;
|
|
80344
80616
|
return;
|
|
80345
80617
|
}
|
|
@@ -80347,12 +80619,12 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
|
|
|
80347
80619
|
writeMcpEntry(e.mcpFile, e.format, e.name, orig);
|
|
80348
80620
|
} catch (err2) {
|
|
80349
80621
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80350
|
-
console.error(
|
|
80622
|
+
console.error(chalk29.red(`Failed to restore "${name}": ${msg}`));
|
|
80351
80623
|
process.exitCode = 1;
|
|
80352
80624
|
return;
|
|
80353
80625
|
}
|
|
80354
80626
|
console.error(
|
|
80355
|
-
|
|
80627
|
+
chalk29.green(`\u2713 restored ${e.name}`) + chalk29.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
|
|
80356
80628
|
);
|
|
80357
80629
|
});
|
|
80358
80630
|
}
|
|
@@ -80378,14 +80650,14 @@ function registerMcpPinCommand(program2) {
|
|
|
80378
80650
|
}
|
|
80379
80651
|
}
|
|
80380
80652
|
if (repoCorrupt) {
|
|
80381
|
-
console.error(
|
|
80653
|
+
console.error(chalk30.red(`
|
|
80382
80654
|
\u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
|
|
80383
80655
|
process.exit(1);
|
|
80384
80656
|
}
|
|
80385
80657
|
if (!homeResult.ok && homeResult.reason === "corrupt") {
|
|
80386
|
-
console.error(
|
|
80658
|
+
console.error(chalk30.red(`
|
|
80387
80659
|
\u274C Home pin file is corrupt: ${homeResult.detail}`));
|
|
80388
|
-
console.error(
|
|
80660
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80389
80661
|
process.exit(1);
|
|
80390
80662
|
}
|
|
80391
80663
|
const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
|
|
@@ -80397,25 +80669,25 @@ function registerMcpPinCommand(program2) {
|
|
|
80397
80669
|
merged.set(key, { entry, source: "repo" });
|
|
80398
80670
|
}
|
|
80399
80671
|
if (merged.size === 0) {
|
|
80400
|
-
console.log(
|
|
80672
|
+
console.log(chalk30.gray("\nNo MCP servers are pinned yet."));
|
|
80401
80673
|
console.log(
|
|
80402
|
-
|
|
80674
|
+
chalk30.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
80403
80675
|
);
|
|
80404
80676
|
return;
|
|
80405
80677
|
}
|
|
80406
|
-
console.log(
|
|
80678
|
+
console.log(chalk30.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
80407
80679
|
const showSource = found.source === "repo";
|
|
80408
80680
|
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: ${
|
|
80681
|
+
const tag = showSource ? ` ${chalk30.yellow(`[${source}]`)}` : "";
|
|
80682
|
+
console.log(` ${chalk30.cyan(key)}${tag} ${chalk30.gray(entry.label)}`);
|
|
80683
|
+
console.log(` Tools (${entry.toolCount}): ${chalk30.white(entry.toolNames.join(", "))}`);
|
|
80684
|
+
console.log(` Hash: ${chalk30.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
80685
|
+
console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}`);
|
|
80414
80686
|
console.log("");
|
|
80415
80687
|
}
|
|
80416
80688
|
if (showSource) {
|
|
80417
|
-
console.log(
|
|
80418
|
-
console.log(
|
|
80689
|
+
console.log(chalk30.gray(` [repo] entries come from ${found.path}`));
|
|
80690
|
+
console.log(chalk30.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
|
|
80419
80691
|
}
|
|
80420
80692
|
});
|
|
80421
80693
|
pinSubCmd.command("promote <serverKey>").description(
|
|
@@ -80425,22 +80697,22 @@ function registerMcpPinCommand(program2) {
|
|
|
80425
80697
|
const { repoPath, created } = promotePin(serverKey, process.cwd());
|
|
80426
80698
|
if (created) {
|
|
80427
80699
|
console.log(
|
|
80428
|
-
|
|
80700
|
+
chalk30.green(
|
|
80429
80701
|
`
|
|
80430
|
-
\u2705 Created ${repoPath} with the promoted pin for ${
|
|
80702
|
+
\u2705 Created ${repoPath} with the promoted pin for ${chalk30.cyan(serverKey)}.`
|
|
80431
80703
|
)
|
|
80432
80704
|
);
|
|
80433
80705
|
} else {
|
|
80434
|
-
console.log(
|
|
80435
|
-
\u2705 Promoted ${
|
|
80706
|
+
console.log(chalk30.green(`
|
|
80707
|
+
\u2705 Promoted ${chalk30.cyan(serverKey)} into ${repoPath}.`));
|
|
80436
80708
|
}
|
|
80437
|
-
console.log(
|
|
80438
|
-
console.log(
|
|
80439
|
-
console.log(
|
|
80709
|
+
console.log(chalk30.gray(" Review the change and commit it:"));
|
|
80710
|
+
console.log(chalk30.cyan(` git add ${repoPath}`));
|
|
80711
|
+
console.log(chalk30.cyan(` git commit -m "pin ${serverKey} (node9)"`));
|
|
80440
80712
|
console.log("");
|
|
80441
80713
|
} catch (err2) {
|
|
80442
80714
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
80443
|
-
console.error(
|
|
80715
|
+
console.error(chalk30.red(`
|
|
80444
80716
|
\u274C ${msg}
|
|
80445
80717
|
`));
|
|
80446
80718
|
process.exit(1);
|
|
@@ -80453,36 +80725,36 @@ function registerMcpPinCommand(program2) {
|
|
|
80453
80725
|
try {
|
|
80454
80726
|
pins = readMcpPins();
|
|
80455
80727
|
} catch {
|
|
80456
|
-
console.error(
|
|
80457
|
-
console.error(
|
|
80728
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80729
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80458
80730
|
process.exit(1);
|
|
80459
80731
|
}
|
|
80460
80732
|
if (!pins.servers[serverKey]) {
|
|
80461
|
-
console.error(
|
|
80733
|
+
console.error(chalk30.red(`
|
|
80462
80734
|
\u274C No pin found for server key "${serverKey}"
|
|
80463
80735
|
`));
|
|
80464
|
-
console.error(`Run ${
|
|
80736
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80465
80737
|
`);
|
|
80466
80738
|
process.exit(1);
|
|
80467
80739
|
}
|
|
80468
80740
|
const label3 = pins.servers[serverKey].label;
|
|
80469
80741
|
removePin(serverKey);
|
|
80470
|
-
console.log(
|
|
80471
|
-
\u{1F513} Pin removed for ${
|
|
80472
|
-
console.log(
|
|
80473
|
-
console.log(
|
|
80742
|
+
console.log(chalk30.green(`
|
|
80743
|
+
\u{1F513} Pin removed for ${chalk30.cyan(serverKey)}`));
|
|
80744
|
+
console.log(chalk30.gray(` Server: ${label3}`));
|
|
80745
|
+
console.log(chalk30.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
80474
80746
|
});
|
|
80475
80747
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
80476
80748
|
const result = readMcpPinsSafe();
|
|
80477
80749
|
if (!result.ok && result.reason === "missing") {
|
|
80478
|
-
console.log(
|
|
80750
|
+
console.log(chalk30.gray("\nNo pins to clear.\n"));
|
|
80479
80751
|
return;
|
|
80480
80752
|
}
|
|
80481
80753
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
80482
80754
|
clearAllPins();
|
|
80483
|
-
console.log(
|
|
80755
|
+
console.log(chalk30.green(`
|
|
80484
80756
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
80485
|
-
console.log(
|
|
80757
|
+
console.log(chalk30.gray(" Next connection to each server will re-pin.\n"));
|
|
80486
80758
|
});
|
|
80487
80759
|
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
80488
80760
|
if (opts.stale) {
|
|
@@ -80491,7 +80763,7 @@ function registerMcpPinCommand(program2) {
|
|
|
80491
80763
|
}
|
|
80492
80764
|
if (!serverKey) {
|
|
80493
80765
|
console.error(
|
|
80494
|
-
|
|
80766
|
+
chalk30.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
80495
80767
|
);
|
|
80496
80768
|
process.exit(1);
|
|
80497
80769
|
}
|
|
@@ -80499,15 +80771,15 @@ function registerMcpPinCommand(program2) {
|
|
|
80499
80771
|
try {
|
|
80500
80772
|
pins = readMcpPins();
|
|
80501
80773
|
} catch {
|
|
80502
|
-
console.error(
|
|
80503
|
-
console.error(
|
|
80774
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80775
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80504
80776
|
process.exit(1);
|
|
80505
80777
|
}
|
|
80506
80778
|
if (!pins.servers[serverKey]) {
|
|
80507
|
-
console.error(
|
|
80779
|
+
console.error(chalk30.red(`
|
|
80508
80780
|
\u274C No pin found for server key "${serverKey}"
|
|
80509
80781
|
`));
|
|
80510
|
-
console.error(`Run ${
|
|
80782
|
+
console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
|
|
80511
80783
|
`);
|
|
80512
80784
|
process.exit(1);
|
|
80513
80785
|
}
|
|
@@ -80515,10 +80787,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80515
80787
|
const liveKeys = inventoryServerKeys(inv);
|
|
80516
80788
|
if (liveKeys.has(serverKey)) {
|
|
80517
80789
|
const agent = "an agent config";
|
|
80518
|
-
console.error(
|
|
80790
|
+
console.error(chalk30.red(`
|
|
80519
80791
|
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
80520
80792
|
console.error(
|
|
80521
|
-
|
|
80793
|
+
chalk30.yellow(
|
|
80522
80794
|
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
80523
80795
|
`
|
|
80524
80796
|
)
|
|
@@ -80527,10 +80799,10 @@ function registerMcpPinCommand(program2) {
|
|
|
80527
80799
|
}
|
|
80528
80800
|
const label3 = pins.servers[serverKey].label;
|
|
80529
80801
|
removePin(serverKey);
|
|
80530
|
-
console.log(
|
|
80531
|
-
\u2713 Forgot server ${
|
|
80532
|
-
console.log(
|
|
80533
|
-
console.log(
|
|
80802
|
+
console.log(chalk30.green(`
|
|
80803
|
+
\u2713 Forgot server ${chalk30.cyan(serverKey)}`));
|
|
80804
|
+
console.log(chalk30.gray(` Was: ${label3}`));
|
|
80805
|
+
console.log(chalk30.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
80534
80806
|
});
|
|
80535
80807
|
}
|
|
80536
80808
|
function forgetAllStale() {
|
|
@@ -80538,16 +80810,16 @@ function forgetAllStale() {
|
|
|
80538
80810
|
try {
|
|
80539
80811
|
pins = readMcpPins();
|
|
80540
80812
|
} catch {
|
|
80541
|
-
console.error(
|
|
80542
|
-
console.error(
|
|
80813
|
+
console.error(chalk30.red("\n\u274C Pin file is corrupt."));
|
|
80814
|
+
console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
|
|
80543
80815
|
process.exit(1);
|
|
80544
80816
|
}
|
|
80545
80817
|
const inv = inventoryMcp();
|
|
80546
80818
|
const liveKeys = inventoryServerKeys(inv);
|
|
80547
80819
|
if (liveKeys.size === 0) {
|
|
80548
|
-
console.error(
|
|
80820
|
+
console.error(chalk30.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
80549
80821
|
console.error(
|
|
80550
|
-
|
|
80822
|
+
chalk30.yellow(
|
|
80551
80823
|
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
80552
80824
|
)
|
|
80553
80825
|
);
|
|
@@ -80555,118 +80827,118 @@ function forgetAllStale() {
|
|
|
80555
80827
|
}
|
|
80556
80828
|
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
80557
80829
|
if (stale.length === 0) {
|
|
80558
|
-
console.log(
|
|
80830
|
+
console.log(chalk30.gray("\nNo stale servers to remove.\n"));
|
|
80559
80831
|
return;
|
|
80560
80832
|
}
|
|
80561
80833
|
for (const [sk, pin] of stale) {
|
|
80562
80834
|
delete pins.servers[sk];
|
|
80563
|
-
console.log(
|
|
80835
|
+
console.log(chalk30.green(` \u2713 ${chalk30.cyan(sk)} ${chalk30.gray(pin.label)}`));
|
|
80564
80836
|
}
|
|
80565
80837
|
writeMcpPins(pins);
|
|
80566
|
-
console.log(
|
|
80838
|
+
console.log(chalk30.green(`
|
|
80567
80839
|
Removed ${stale.length} stale server(s).
|
|
80568
80840
|
`));
|
|
80569
80841
|
}
|
|
80570
80842
|
|
|
80571
80843
|
// src/cli/commands/sync.ts
|
|
80572
80844
|
init_sync();
|
|
80573
|
-
import
|
|
80845
|
+
import chalk31 from "chalk";
|
|
80574
80846
|
function registerSyncCommand(program2) {
|
|
80575
80847
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
80576
80848
|
policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
|
|
80577
|
-
process.stdout.write(
|
|
80849
|
+
process.stdout.write(chalk31.cyan("Pushing policy to the dashboard\u2026"));
|
|
80578
80850
|
const result = await runPolicyPush();
|
|
80579
80851
|
process.stdout.write("\n");
|
|
80580
80852
|
if (!result.ok) {
|
|
80581
|
-
console.error(
|
|
80853
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80582
80854
|
process.exit(1);
|
|
80583
80855
|
}
|
|
80584
|
-
console.log(
|
|
80585
|
-
console.log(
|
|
80856
|
+
console.log(chalk31.green("\u2713 Policy mirrored to the dashboard"));
|
|
80857
|
+
console.log(chalk31.gray(" See it under Security Policy \u2192 Machines"));
|
|
80586
80858
|
});
|
|
80587
80859
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
80588
|
-
process.stdout.write(
|
|
80860
|
+
process.stdout.write(chalk31.cyan("Syncing cloud policy rules\u2026"));
|
|
80589
80861
|
const result = await runCloudSync();
|
|
80590
80862
|
process.stdout.write("\n");
|
|
80591
80863
|
if (!result.ok) {
|
|
80592
|
-
console.error(
|
|
80864
|
+
console.error(chalk31.red(`\u2717 ${result.reason}`));
|
|
80593
80865
|
process.exit(1);
|
|
80594
80866
|
}
|
|
80595
80867
|
if (result.unchanged) {
|
|
80596
80868
|
console.log(
|
|
80597
|
-
|
|
80869
|
+
chalk31.green(
|
|
80598
80870
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
80599
80871
|
)
|
|
80600
80872
|
);
|
|
80601
|
-
console.log(
|
|
80602
|
-
console.log(
|
|
80873
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80874
|
+
console.log(chalk31.gray(` Server returned 304 (no changes since last sync)`));
|
|
80603
80875
|
} else {
|
|
80604
80876
|
console.log(
|
|
80605
|
-
|
|
80877
|
+
chalk31.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
80606
80878
|
);
|
|
80607
|
-
console.log(
|
|
80608
|
-
console.log(
|
|
80879
|
+
console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
|
|
80880
|
+
console.log(chalk31.gray(` File: ~/.node9/rules-cache.json`));
|
|
80609
80881
|
}
|
|
80610
80882
|
});
|
|
80611
80883
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
80612
80884
|
const status = getCloudSyncStatus();
|
|
80613
80885
|
if (!status.cached) {
|
|
80614
|
-
console.log(
|
|
80886
|
+
console.log(chalk31.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
80615
80887
|
return;
|
|
80616
80888
|
}
|
|
80617
80889
|
const rules = getCloudRules() ?? [];
|
|
80618
80890
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
80619
80891
|
console.log(
|
|
80620
|
-
|
|
80621
|
-
Cloud policy rules`) +
|
|
80892
|
+
chalk31.bold(`
|
|
80893
|
+
Cloud policy rules`) + chalk31.gray(
|
|
80622
80894
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
80623
80895
|
`
|
|
80624
80896
|
)
|
|
80625
80897
|
);
|
|
80626
80898
|
if (rules.length === 0) {
|
|
80627
|
-
console.log(
|
|
80899
|
+
console.log(chalk31.gray(" No rules defined in cloud policy.\n"));
|
|
80628
80900
|
return;
|
|
80629
80901
|
}
|
|
80630
80902
|
for (const rule of rules) {
|
|
80631
80903
|
const r = rule;
|
|
80632
|
-
const verdictColor = r.verdict === "block" ?
|
|
80904
|
+
const verdictColor = r.verdict === "block" ? chalk31.red : r.verdict === "allow" ? chalk31.green : chalk31.yellow;
|
|
80633
80905
|
console.log(
|
|
80634
80906
|
` ${verdictColor(
|
|
80635
80907
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
80636
|
-
)} ${
|
|
80908
|
+
)} ${chalk31.white(String(r.name ?? "(unnamed)"))}`
|
|
80637
80909
|
);
|
|
80638
|
-
if (r.reason) console.log(
|
|
80910
|
+
if (r.reason) console.log(chalk31.gray(` ${String(r.reason)}`));
|
|
80639
80911
|
}
|
|
80640
80912
|
console.log("");
|
|
80641
80913
|
});
|
|
80642
80914
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
80643
80915
|
const s = getCloudSyncStatus();
|
|
80644
80916
|
if (!s.cached) {
|
|
80645
|
-
console.log(
|
|
80917
|
+
console.log(chalk31.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
80646
80918
|
return;
|
|
80647
80919
|
}
|
|
80648
80920
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
80649
80921
|
console.log(`
|
|
80650
|
-
Rules : ${
|
|
80922
|
+
Rules : ${chalk31.green(String(s.rules))} cloud rules loaded`);
|
|
80651
80923
|
console.log(
|
|
80652
|
-
` Synced : ${
|
|
80924
|
+
` Synced : ${chalk31.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
80653
80925
|
);
|
|
80654
80926
|
if (s.workspaceId) {
|
|
80655
|
-
console.log(` Workspace: ${
|
|
80927
|
+
console.log(` Workspace: ${chalk31.gray(s.workspaceId)}`);
|
|
80656
80928
|
}
|
|
80657
80929
|
if (s.panicMode) {
|
|
80658
80930
|
console.log(
|
|
80659
|
-
` ${
|
|
80931
|
+
` ${chalk31.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk31.dim("(every review-verdict becomes block)")
|
|
80660
80932
|
);
|
|
80661
80933
|
}
|
|
80662
80934
|
if (s.shadowMode) {
|
|
80663
80935
|
console.log(
|
|
80664
|
-
` ${
|
|
80936
|
+
` ${chalk31.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk31.dim("(blocks become would-block log entries)")
|
|
80665
80937
|
);
|
|
80666
80938
|
}
|
|
80667
80939
|
if (s.syncIntervalHours) {
|
|
80668
80940
|
console.log(
|
|
80669
|
-
|
|
80941
|
+
chalk31.gray(
|
|
80670
80942
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
80671
80943
|
)
|
|
80672
80944
|
);
|
|
@@ -80677,7 +80949,7 @@ function registerSyncCommand(program2) {
|
|
|
80677
80949
|
|
|
80678
80950
|
// src/cli/commands/agents.ts
|
|
80679
80951
|
init_setup();
|
|
80680
|
-
import
|
|
80952
|
+
import chalk32 from "chalk";
|
|
80681
80953
|
var SETUP_FN = {
|
|
80682
80954
|
claude: setupClaude,
|
|
80683
80955
|
gemini: setupGemini,
|
|
@@ -80720,30 +80992,30 @@ function registerAgentsCommand(program2) {
|
|
|
80720
80992
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
80721
80993
|
console.log(" " + "\u2500".repeat(44));
|
|
80722
80994
|
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 ?
|
|
80995
|
+
const installed = s.installed ? chalk32.green("\u2713") : chalk32.gray("\u2717");
|
|
80996
|
+
const wired = !s.installed ? chalk32.gray("\u2014") : s.wired ? chalk32.green("\u2713") : chalk32.yellow("\u2717");
|
|
80997
|
+
const mode = s.mode ? chalk32.gray(s.mode) : chalk32.gray("\u2014");
|
|
80998
|
+
const hint = s.installed && !s.wired ? chalk32.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
80727
80999
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
80728
81000
|
}
|
|
80729
81001
|
console.log("");
|
|
80730
81002
|
if (!anyInstalled) {
|
|
80731
81003
|
console.log(
|
|
80732
|
-
|
|
81004
|
+
chalk32.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk32.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
|
|
80733
81005
|
);
|
|
80734
81006
|
return;
|
|
80735
81007
|
}
|
|
80736
81008
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
80737
81009
|
if (unwired.length > 0) {
|
|
80738
81010
|
console.log(
|
|
80739
|
-
|
|
81011
|
+
chalk32.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk32.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
80740
81012
|
);
|
|
80741
81013
|
}
|
|
80742
81014
|
if (statuses.some((s) => s.name === "gemini" && s.installed)) {
|
|
80743
81015
|
console.log(
|
|
80744
|
-
|
|
81016
|
+
chalk32.yellow(
|
|
80745
81017
|
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
|
|
80746
|
-
) +
|
|
81018
|
+
) + chalk32.gray(" Migrate to Antigravity: ") + chalk32.white("node9 agents add antigravity") + "\n"
|
|
80747
81019
|
);
|
|
80748
81020
|
}
|
|
80749
81021
|
});
|
|
@@ -80751,7 +81023,7 @@ function registerAgentsCommand(program2) {
|
|
|
80751
81023
|
const name = resolveAgentName(agent);
|
|
80752
81024
|
const fn = SETUP_FN[name];
|
|
80753
81025
|
if (!fn) {
|
|
80754
|
-
console.error(
|
|
81026
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80755
81027
|
process.exit(1);
|
|
80756
81028
|
}
|
|
80757
81029
|
await fn();
|
|
@@ -80760,14 +81032,14 @@ function registerAgentsCommand(program2) {
|
|
|
80760
81032
|
const name = resolveAgentName(agent);
|
|
80761
81033
|
const fn = TEARDOWN_FN[name];
|
|
80762
81034
|
if (!fn) {
|
|
80763
|
-
console.error(
|
|
81035
|
+
console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
80764
81036
|
process.exit(1);
|
|
80765
81037
|
}
|
|
80766
|
-
console.log(
|
|
81038
|
+
console.log(chalk32.cyan(`
|
|
80767
81039
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
80768
81040
|
`));
|
|
80769
81041
|
fn();
|
|
80770
|
-
console.log(
|
|
81042
|
+
console.log(chalk32.gray("\n Restart the agent for changes to take effect."));
|
|
80771
81043
|
});
|
|
80772
81044
|
}
|
|
80773
81045
|
|
|
@@ -80776,21 +81048,21 @@ init_scan();
|
|
|
80776
81048
|
|
|
80777
81049
|
// src/cli/commands/posture.ts
|
|
80778
81050
|
init_posture();
|
|
80779
|
-
import
|
|
81051
|
+
import chalk34 from "chalk";
|
|
80780
81052
|
|
|
80781
81053
|
// src/posture/render.ts
|
|
80782
81054
|
init_score();
|
|
80783
|
-
import
|
|
81055
|
+
import chalk33 from "chalk";
|
|
80784
81056
|
var ICON = {
|
|
80785
|
-
critical:
|
|
80786
|
-
high:
|
|
80787
|
-
medium:
|
|
80788
|
-
advisory:
|
|
81057
|
+
critical: chalk33.red("\u274C"),
|
|
81058
|
+
high: chalk33.red("\u274C"),
|
|
81059
|
+
medium: chalk33.yellow("\u26A0\uFE0F "),
|
|
81060
|
+
advisory: chalk33.gray("\u26A0\uFE0F ")
|
|
80789
81061
|
};
|
|
80790
81062
|
var TIER_LABEL = {
|
|
80791
|
-
good:
|
|
80792
|
-
"at-risk":
|
|
80793
|
-
critical:
|
|
81063
|
+
good: chalk33.green("Good"),
|
|
81064
|
+
"at-risk": chalk33.yellow("At risk"),
|
|
81065
|
+
critical: chalk33.red("Critical")
|
|
80794
81066
|
};
|
|
80795
81067
|
function wrap(text, width) {
|
|
80796
81068
|
const out = [];
|
|
@@ -80808,7 +81080,7 @@ function wrap(text, width) {
|
|
|
80808
81080
|
}
|
|
80809
81081
|
var LABEL_WIDTH = 14;
|
|
80810
81082
|
function label2(category) {
|
|
80811
|
-
return
|
|
81083
|
+
return chalk33.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
80812
81084
|
}
|
|
80813
81085
|
function guardedWhat(f) {
|
|
80814
81086
|
if (f.detail.length === 0) return "this";
|
|
@@ -80818,31 +81090,31 @@ function guardedWhat(f) {
|
|
|
80818
81090
|
}
|
|
80819
81091
|
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
80820
81092
|
const lines = [];
|
|
80821
|
-
const wt = showWeight && f.scoreWeight ?
|
|
81093
|
+
const wt = showWeight && f.scoreWeight ? chalk33.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
80822
81094
|
lines.push(` ${ICON[f.severity]} ${label2(displayLabel)}${wt}${f.title}`);
|
|
80823
81095
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
80824
81096
|
const width = 80 - indent.length;
|
|
80825
81097
|
for (const s of [f.what, f.why, f.who]) {
|
|
80826
|
-
if (s) for (const l of wrap(s, width)) lines.push(indent +
|
|
81098
|
+
if (s) for (const l of wrap(s, width)) lines.push(indent + chalk33.gray(l));
|
|
80827
81099
|
}
|
|
80828
|
-
for (const d of f.detail) lines.push(indent +
|
|
81100
|
+
for (const d of f.detail) lines.push(indent + chalk33.gray(d));
|
|
80829
81101
|
if (f.fix) {
|
|
80830
81102
|
let first = true;
|
|
80831
81103
|
for (const seg of f.fix.split("\n")) {
|
|
80832
81104
|
for (const l of wrap(seg, width - 2)) {
|
|
80833
|
-
lines.push(indent +
|
|
81105
|
+
lines.push(indent + chalk33.cyan(first ? "\u2192 " + l : " " + l));
|
|
80834
81106
|
first = false;
|
|
80835
81107
|
}
|
|
80836
81108
|
}
|
|
80837
81109
|
}
|
|
80838
81110
|
const tradeoff = [
|
|
80839
|
-
[f.gain, "gain: ",
|
|
80840
|
-
[f.cost, "cost: ",
|
|
81111
|
+
[f.gain, "gain: ", chalk33.green],
|
|
81112
|
+
[f.cost, "cost: ", chalk33.yellow]
|
|
80841
81113
|
];
|
|
80842
81114
|
for (const [text, lbl, color2] of tradeoff) {
|
|
80843
81115
|
if (!text) continue;
|
|
80844
81116
|
wrap(text, width - 6).forEach((l, i) => {
|
|
80845
|
-
lines.push(indent + (i === 0 ? color2(lbl) : " ") +
|
|
81117
|
+
lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk33.gray(l));
|
|
80846
81118
|
});
|
|
80847
81119
|
}
|
|
80848
81120
|
return lines;
|
|
@@ -80852,7 +81124,7 @@ function renderPosture(result) {
|
|
|
80852
81124
|
const tier = TIER_LABEL[result.tier];
|
|
80853
81125
|
lines.push("");
|
|
80854
81126
|
lines.push(
|
|
80855
|
-
|
|
81127
|
+
chalk33.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk33.gray(` \u2014 ${result.agent}`) + ` ${chalk33.bold(`Score: ${result.score}/100`)} (${tier})`
|
|
80856
81128
|
);
|
|
80857
81129
|
const headroom = openHeadroom(result.findings);
|
|
80858
81130
|
if (headroom > 0) {
|
|
@@ -80860,15 +81132,15 @@ function renderPosture(result) {
|
|
|
80860
81132
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
80861
81133
|
).length;
|
|
80862
81134
|
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
80863
|
-
for (const l of wrap(note, 76)) lines.push(" " +
|
|
81135
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk33.gray(l));
|
|
80864
81136
|
}
|
|
80865
81137
|
lines.push("");
|
|
80866
81138
|
if (result.headline) {
|
|
80867
81139
|
const indent = " ";
|
|
80868
|
-
lines.push(` ${
|
|
80869
|
-
for (const l of wrap(result.headline.risk, 74)) lines.push(indent +
|
|
81140
|
+
lines.push(` ${chalk33.red.bold("\u{1F525} Biggest risk")}`);
|
|
81141
|
+
for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk33.white(l));
|
|
80870
81142
|
const action = wrap(`Do this first: ${result.headline.action}`, 72);
|
|
80871
|
-
action.forEach((l, i) => lines.push(indent +
|
|
81143
|
+
action.forEach((l, i) => lines.push(indent + chalk33.cyan(i === 0 ? "\u2192 " + l : " " + l)));
|
|
80872
81144
|
lines.push("");
|
|
80873
81145
|
}
|
|
80874
81146
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
@@ -80878,13 +81150,13 @@ function renderPosture(result) {
|
|
|
80878
81150
|
);
|
|
80879
81151
|
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
80880
81152
|
if (covered.length > 0) {
|
|
80881
|
-
lines.push(" " +
|
|
81153
|
+
lines.push(" " + chalk33.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
80882
81154
|
for (const f of covered) {
|
|
80883
81155
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
80884
81156
|
const via = f.coverage?.via ?? "node9";
|
|
80885
81157
|
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
80886
81158
|
lines.push(
|
|
80887
|
-
` ${
|
|
81159
|
+
` ${chalk33.green("\u2705")} ${label2(lbl)}${chalk33.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
80888
81160
|
);
|
|
80889
81161
|
}
|
|
80890
81162
|
lines.push("");
|
|
@@ -80893,24 +81165,24 @@ function renderPosture(result) {
|
|
|
80893
81165
|
const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
|
|
80894
81166
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
80895
81167
|
if (node9Open.length > 0) {
|
|
80896
|
-
lines.push(" " +
|
|
81168
|
+
lines.push(" " + chalk33.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
80897
81169
|
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80898
81170
|
}
|
|
80899
81171
|
if (reduceOpen.length > 0) {
|
|
80900
81172
|
if (node9Open.length > 0) lines.push("");
|
|
80901
|
-
lines.push(" " +
|
|
81173
|
+
lines.push(" " + chalk33.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
80902
81174
|
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
80903
81175
|
}
|
|
80904
81176
|
if (osOpen.length > 0) {
|
|
80905
81177
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
80906
|
-
lines.push(" " +
|
|
81178
|
+
lines.push(" " + chalk33.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
80907
81179
|
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
80908
81180
|
}
|
|
80909
81181
|
for (const cat of result.passedCategories) {
|
|
80910
|
-
lines.push(` ${
|
|
81182
|
+
lines.push(` ${chalk33.green("\u2705")} ${label2(cat)}${chalk33.gray("no issues found")}`);
|
|
80911
81183
|
}
|
|
80912
81184
|
for (const cat of result.erroredCategories) {
|
|
80913
|
-
lines.push(` ${
|
|
81185
|
+
lines.push(` ${chalk33.gray("\u2022")} ${label2(cat)}${chalk33.gray("could not be checked")}`);
|
|
80914
81186
|
}
|
|
80915
81187
|
lines.push("");
|
|
80916
81188
|
const crit = open.filter((f) => f.severity === "critical").length;
|
|
@@ -80918,16 +81190,16 @@ function renderPosture(result) {
|
|
|
80918
81190
|
const med = open.filter((f) => f.severity === "medium").length;
|
|
80919
81191
|
const adv = open.filter((f) => f.severity === "advisory").length;
|
|
80920
81192
|
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 ") :
|
|
81193
|
+
if (crit) parts.push(chalk33.red(`${crit} critical`));
|
|
81194
|
+
if (high) parts.push(chalk33.red(`${high} high`));
|
|
81195
|
+
if (med) parts.push(chalk33.yellow(`${med} medium`));
|
|
81196
|
+
if (adv) parts.push(chalk33.gray(`${adv} advisory`));
|
|
81197
|
+
const summary = parts.length ? parts.join(" \xB7 ") : chalk33.green("no findings");
|
|
80926
81198
|
lines.push(` ${summary}`);
|
|
80927
81199
|
lines.push("");
|
|
80928
|
-
lines.push(" " +
|
|
81200
|
+
lines.push(" " + chalk33.bold("Track this across your fleet & keep it green:"));
|
|
80929
81201
|
lines.push(
|
|
80930
|
-
" " +
|
|
81202
|
+
" " + chalk33.dim("\u2192 ") + chalk33.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
|
|
80931
81203
|
);
|
|
80932
81204
|
lines.push("");
|
|
80933
81205
|
return lines.join("\n");
|
|
@@ -80947,11 +81219,11 @@ function registerPostureCommand(program2) {
|
|
|
80947
81219
|
if (opts.ship) {
|
|
80948
81220
|
const creds = readCredentials();
|
|
80949
81221
|
if (!creds) {
|
|
80950
|
-
console.error(
|
|
81222
|
+
console.error(chalk34.gray(" Run `node9 login` to ship this to your dashboard."));
|
|
80951
81223
|
} else {
|
|
80952
81224
|
const ok2 = await shipPosture(result, creds);
|
|
80953
81225
|
console.error(
|
|
80954
|
-
ok2 ?
|
|
81226
|
+
ok2 ? chalk34.gray(" \u2713 Shipped to your node9 dashboard.") : chalk34.gray(" Could not reach the dashboard \u2014 saved locally only.")
|
|
80955
81227
|
);
|
|
80956
81228
|
}
|
|
80957
81229
|
}
|
|
@@ -80960,7 +81232,7 @@ function registerPostureCommand(program2) {
|
|
|
80960
81232
|
}
|
|
80961
81233
|
|
|
80962
81234
|
// src/cli/commands/scan-repo.ts
|
|
80963
|
-
import
|
|
81235
|
+
import chalk36 from "chalk";
|
|
80964
81236
|
|
|
80965
81237
|
// src/ci-check/fetch.ts
|
|
80966
81238
|
var import_undici = __toESM(require_undici());
|
|
@@ -86472,7 +86744,7 @@ async function scanRepo(input, onProgress) {
|
|
|
86472
86744
|
}
|
|
86473
86745
|
|
|
86474
86746
|
// src/ci-check/render.ts
|
|
86475
|
-
import
|
|
86747
|
+
import chalk35 from "chalk";
|
|
86476
86748
|
var listed = (fs109) => fs109.filter((f) => f.tier !== "review");
|
|
86477
86749
|
function excerpt(f) {
|
|
86478
86750
|
const m = /`([^`]+)`/.exec(f.signals[0] ?? "");
|
|
@@ -86517,23 +86789,23 @@ function reviewTerminal(findings) {
|
|
|
86517
86789
|
const L = [];
|
|
86518
86790
|
if (items) {
|
|
86519
86791
|
L.push(
|
|
86520
|
-
|
|
86792
|
+
chalk35.bold(`\u{1F50D} ${items} ${items === 1 ? "item" : "items"} worth a look (not counted)`) + chalk35.gray(": risky-looking text in a context that is usually harmless. Please confirm.")
|
|
86521
86793
|
);
|
|
86522
86794
|
const files = [...byFile.keys()];
|
|
86523
86795
|
for (const file2 of files.slice(0, REVIEW_FILES)) {
|
|
86524
86796
|
const fs109 = byFile.get(file2);
|
|
86525
|
-
L.push(
|
|
86797
|
+
L.push(chalk35.gray(` ${file2}`));
|
|
86526
86798
|
for (const f of fs109.slice(0, REVIEW_LINES_PER_FILE))
|
|
86527
86799
|
L.push(` \u2022 ${f.line ? `line ${f.line}: ` : ""}${excerpt(f)}`);
|
|
86528
86800
|
if (fs109.length > REVIEW_LINES_PER_FILE)
|
|
86529
|
-
L.push(
|
|
86801
|
+
L.push(chalk35.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
|
|
86530
86802
|
}
|
|
86531
86803
|
const more = files.length - REVIEW_FILES;
|
|
86532
|
-
if (more > 0) L.push(
|
|
86804
|
+
if (more > 0) L.push(chalk35.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
|
|
86533
86805
|
}
|
|
86534
86806
|
if (grants)
|
|
86535
86807
|
L.push(
|
|
86536
|
-
|
|
86808
|
+
chalk35.gray(
|
|
86537
86809
|
` ${grants} skill${grants === 1 ? " is" : "s are"} granted broad tools (Bash, Write or Edit); usually what the skill needs.`
|
|
86538
86810
|
)
|
|
86539
86811
|
);
|
|
@@ -86547,45 +86819,45 @@ var ICON2 = {
|
|
|
86547
86819
|
advisory: "\u{1F7E2}"
|
|
86548
86820
|
};
|
|
86549
86821
|
var COLOR = {
|
|
86550
|
-
critical:
|
|
86551
|
-
high:
|
|
86552
|
-
medium:
|
|
86553
|
-
advisory:
|
|
86822
|
+
critical: chalk35.red.bold,
|
|
86823
|
+
high: chalk35.red,
|
|
86824
|
+
medium: chalk35.yellow,
|
|
86825
|
+
advisory: chalk35.gray
|
|
86554
86826
|
};
|
|
86555
86827
|
var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
|
|
86556
86828
|
function renderCta(res) {
|
|
86557
86829
|
const L = [];
|
|
86558
|
-
L.push(
|
|
86830
|
+
L.push(chalk35.dim(" " + "\u2500".repeat(63)));
|
|
86559
86831
|
if (res.worst === "critical" || res.worst === "high") {
|
|
86560
86832
|
const n = res.findings.filter(
|
|
86561
86833
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86562
86834
|
).length;
|
|
86563
86835
|
L.push(
|
|
86564
|
-
" " +
|
|
86836
|
+
" " + chalk35.red.bold(
|
|
86565
86837
|
`\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix, then stop the next at the PR.`
|
|
86566
86838
|
)
|
|
86567
86839
|
);
|
|
86568
86840
|
L.push("");
|
|
86569
|
-
L.push(" " +
|
|
86841
|
+
L.push(" " + chalk35.bold("Catch this class of issue on every PR, automatically:"));
|
|
86570
86842
|
} else if (res.worst) {
|
|
86571
|
-
L.push(" " +
|
|
86843
|
+
L.push(" " + chalk35.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
|
|
86572
86844
|
L.push("");
|
|
86573
|
-
L.push(" " +
|
|
86845
|
+
L.push(" " + chalk35.bold("Check every PR for agent-CI risk:"));
|
|
86574
86846
|
} else if (res.incomplete) {
|
|
86575
|
-
L.push(" " +
|
|
86847
|
+
L.push(" " + chalk35.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
|
|
86576
86848
|
L.push("");
|
|
86577
|
-
L.push(" " +
|
|
86849
|
+
L.push(" " + chalk35.bold("Get a complete check on every PR (CI reads the tree directly):"));
|
|
86578
86850
|
} else {
|
|
86579
86851
|
L.push(
|
|
86580
|
-
" " +
|
|
86852
|
+
" " + chalk35.green(
|
|
86581
86853
|
groupReview(res.findings).byFile.size ? "\u2705 No alerts. Check the items worth a look above." : "\u2705 Agent CI is well-configured: 0 unmitigated issues."
|
|
86582
86854
|
)
|
|
86583
86855
|
);
|
|
86584
86856
|
L.push("");
|
|
86585
|
-
L.push(" " +
|
|
86857
|
+
L.push(" " + chalk35.bold("Keep it green as you add agent workflows. Check every PR:"));
|
|
86586
86858
|
}
|
|
86587
|
-
L.push(" " +
|
|
86588
|
-
L.push(" " +
|
|
86859
|
+
L.push(" " + chalk35.dim("\u2192 ") + chalk35.cyan.underline(ACTION_URL));
|
|
86860
|
+
L.push(" " + chalk35.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
|
|
86589
86861
|
return L;
|
|
86590
86862
|
}
|
|
86591
86863
|
function ownedHint(source) {
|
|
@@ -86625,21 +86897,21 @@ function renderScan(res, diff) {
|
|
|
86625
86897
|
const L = [];
|
|
86626
86898
|
const shown = listed(res.findings);
|
|
86627
86899
|
const n = shown.length;
|
|
86628
|
-
const head = res.worst === "critical" || res.worst === "high" ?
|
|
86629
|
-
L.push(`\u{1F6E1}\uFE0F ${
|
|
86900
|
+
const head = res.worst === "critical" || res.worst === "high" ? chalk35.red.bold("\u26A0\uFE0F agent-security risk found") : res.worst ? chalk35.yellow("agent-security notes") : res.incomplete ? chalk35.yellow.bold("\u26A0\uFE0F INCOMPLETE: could not read all files") : groupReview(res.findings).byFile.size ? chalk35.green("\u2705 agent-security: no alerts (items worth a look below)") : chalk35.green("\u2705 agent-security: clean");
|
|
86901
|
+
L.push(`\u{1F6E1}\uFE0F ${chalk35.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
|
|
86630
86902
|
L.push(
|
|
86631
|
-
|
|
86903
|
+
chalk35.gray(
|
|
86632
86904
|
` inspected ${res.inspected.length} config file(s), ${n} finding(s)` + (res.suppressedCount ? ` \xB7 ${res.suppressedCount} suppressed by .node9-ignore.json` : "")
|
|
86633
86905
|
)
|
|
86634
86906
|
);
|
|
86635
86907
|
if (diff) {
|
|
86636
86908
|
const introduced = listed(diff.added).length + diff.escalated.length;
|
|
86637
86909
|
L.push(
|
|
86638
|
-
diff.base !== "ok" ?
|
|
86910
|
+
diff.base !== "ok" ? chalk35.yellow.bold(
|
|
86639
86911
|
` \u26A0\uFE0F base ${diff.base === "did-not-run" ? "could not be read" : "scan was incomplete"}, so it cannot say what is new; showing everything`
|
|
86640
|
-
) : introduced > 0 ?
|
|
86912
|
+
) : introduced > 0 ? chalk35.red.bold(
|
|
86641
86913
|
` \u26A0\uFE0F this change introduced ${introduced} finding(s)` + (diff.escalated.length ? ` (${diff.escalated.length} by widening an existing one)` : "")
|
|
86642
|
-
) :
|
|
86914
|
+
) : chalk35.green(
|
|
86643
86915
|
` \u2705 this change introduced nothing` + (diff.unchanged.length ? ` (${diff.unchanged.length} pre-existing)` : "") + (diff.removed.length ? `, and fixed ${diff.removed.length}` : "")
|
|
86644
86916
|
)
|
|
86645
86917
|
);
|
|
@@ -86647,7 +86919,7 @@ function renderScan(res, diff) {
|
|
|
86647
86919
|
if (res.incomplete) {
|
|
86648
86920
|
const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
|
|
86649
86921
|
L.push(
|
|
86650
|
-
|
|
86922
|
+
chalk35.yellow.bold(
|
|
86651
86923
|
rateLimited ? " \u26A0\uFE0F Rate-limited: some files were unread. NOT a clean bill of health; set GITHUB_TOKEN (or run `gh auth login`) and re-run." : " \u26A0\uFE0F A network error left some files unread. NOT a clean bill of health; re-run."
|
|
86652
86924
|
)
|
|
86653
86925
|
);
|
|
@@ -86656,36 +86928,36 @@ function renderScan(res, diff) {
|
|
|
86656
86928
|
for (const f of shown) {
|
|
86657
86929
|
const ex = f.explain;
|
|
86658
86930
|
L.push(
|
|
86659
|
-
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${
|
|
86931
|
+
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk35.bold(ex ? ex.headline : f.title)}`
|
|
86660
86932
|
);
|
|
86661
|
-
L.push(
|
|
86933
|
+
L.push(chalk35.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
|
|
86662
86934
|
if (ex) {
|
|
86663
86935
|
L.push(` ${ex.happens}`);
|
|
86664
86936
|
for (const s of ex.saw) L.push(` \u2022 ${s}`);
|
|
86665
|
-
ex.fix.forEach((x, i) => L.push(
|
|
86937
|
+
ex.fix.forEach((x, i) => L.push(chalk35.cyan(` ${i + 1}. ${x}`)));
|
|
86666
86938
|
} else {
|
|
86667
86939
|
for (const s of f.signals) L.push(` \u2022 ${s}`);
|
|
86668
86940
|
if (f.mitigations?.length)
|
|
86669
|
-
L.push(
|
|
86670
|
-
L.push(
|
|
86941
|
+
L.push(chalk35.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
|
|
86942
|
+
L.push(chalk35.cyan(` \u2192 ${f.fix}`));
|
|
86671
86943
|
}
|
|
86672
86944
|
L.push("");
|
|
86673
86945
|
}
|
|
86674
86946
|
if (n === 0 && !res.incomplete) {
|
|
86675
86947
|
L.push(
|
|
86676
|
-
|
|
86948
|
+
chalk35.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
|
|
86677
86949
|
);
|
|
86678
86950
|
L.push("");
|
|
86679
86951
|
}
|
|
86680
86952
|
L.push(...reviewTerminal(res.findings));
|
|
86681
|
-
for (const note of res.notes) L.push(
|
|
86953
|
+
for (const note of res.notes) L.push(chalk35.gray(` note: ${note}`));
|
|
86682
86954
|
const hasHigh = shown.some(
|
|
86683
86955
|
(f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
|
|
86684
86956
|
);
|
|
86685
86957
|
if (hasHigh && !ownedHint(res.source)) {
|
|
86686
86958
|
L.push("");
|
|
86687
86959
|
L.push(
|
|
86688
|
-
|
|
86960
|
+
chalk35.yellow(
|
|
86689
86961
|
" \u26A0\uFE0F This looks like a live issue on a repo you may not own. Disclose it privately\n to the maintainers; do not publish it. (node9 never weaponizes findings.)"
|
|
86690
86962
|
)
|
|
86691
86963
|
);
|
|
@@ -86879,7 +87151,7 @@ function makeProgress(target, quiet) {
|
|
|
86879
87151
|
let frame = 0;
|
|
86880
87152
|
let last = `scanning ${target}\u2026`;
|
|
86881
87153
|
const timer = setInterval(() => {
|
|
86882
|
-
process.stderr.write(`\r${
|
|
87154
|
+
process.stderr.write(`\r${chalk36.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
|
|
86883
87155
|
}, 80);
|
|
86884
87156
|
return {
|
|
86885
87157
|
onProgress: (p) => {
|
|
@@ -86942,236 +87214,13 @@ function registerScanRepoCommand(program2) {
|
|
|
86942
87214
|
if (!telemetryDisabled()) {
|
|
86943
87215
|
await sendScanRepoPing(buildScanRepoPing(res, target));
|
|
86944
87216
|
if (!opts.json && !opts.markdown && process.stderr.isTTY) {
|
|
86945
|
-
process.stderr.write(
|
|
87217
|
+
process.stderr.write(chalk36.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
|
|
86946
87218
|
}
|
|
86947
87219
|
}
|
|
86948
87220
|
}
|
|
86949
87221
|
);
|
|
86950
87222
|
}
|
|
86951
87223
|
|
|
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
87224
|
// src/cli/commands/checks.ts
|
|
87176
87225
|
init_dist();
|
|
87177
87226
|
init_config();
|
|
@@ -87212,7 +87261,9 @@ function renderChecks(report) {
|
|
|
87212
87261
|
const lines = [];
|
|
87213
87262
|
const where = report.policySource === "workspace" ? "workspace (app.node9.ai)" : "local (~/.node9/config.json)";
|
|
87214
87263
|
lines.push("");
|
|
87215
|
-
lines.push(
|
|
87264
|
+
lines.push(
|
|
87265
|
+
`${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${levelOf(report.mode)}`
|
|
87266
|
+
);
|
|
87216
87267
|
lines.push("");
|
|
87217
87268
|
const row = (c) => {
|
|
87218
87269
|
const value = (VALUE_COLOR[c.value] ?? chalk37.white)(c.value.padEnd(7));
|