@node9/proxy 1.3.1 → 1.4.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 +979 -365
- package/dist/cli.mjs +965 -352
- package/dist/index.js +548 -53
- package/dist/index.mjs +548 -53
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -94,8 +94,8 @@ function sanitizeConfig(raw) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
const lines = result.error.issues.map((issue) => {
|
|
97
|
-
const
|
|
98
|
-
return ` \u2022 ${
|
|
97
|
+
const path25 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
98
|
+
return ` \u2022 ${path25}: ${issue.message}`;
|
|
99
99
|
});
|
|
100
100
|
return {
|
|
101
101
|
sanitized,
|
|
@@ -1157,13 +1157,501 @@ var init_dlp = __esm({
|
|
|
1157
1157
|
}
|
|
1158
1158
|
});
|
|
1159
1159
|
|
|
1160
|
-
// src/
|
|
1160
|
+
// src/utils/provenance.ts
|
|
1161
1161
|
import fs5 from "fs";
|
|
1162
1162
|
import path5 from "path";
|
|
1163
1163
|
import os4 from "os";
|
|
1164
|
+
function findInPath(cmd) {
|
|
1165
|
+
if (path5.posix.isAbsolute(cmd)) return cmd;
|
|
1166
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1167
|
+
for (const dir of pathEnv.split(path5.delimiter)) {
|
|
1168
|
+
if (!dir) continue;
|
|
1169
|
+
const full = path5.join(dir, cmd);
|
|
1170
|
+
try {
|
|
1171
|
+
fs5.accessSync(full, fs5.constants.X_OK);
|
|
1172
|
+
return full;
|
|
1173
|
+
} catch {
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
function _classifyPath(resolved, cwd) {
|
|
1179
|
+
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
1180
|
+
return { trustLevel: "user", reason: "binary in project directory" };
|
|
1181
|
+
}
|
|
1182
|
+
const osTmp = os4.tmpdir();
|
|
1183
|
+
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
1184
|
+
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1185
|
+
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
1186
|
+
}
|
|
1187
|
+
if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1188
|
+
return { trustLevel: "system", reason: "" };
|
|
1189
|
+
}
|
|
1190
|
+
if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1191
|
+
return { trustLevel: "managed", reason: "" };
|
|
1192
|
+
}
|
|
1193
|
+
if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1194
|
+
return { trustLevel: "user", reason: "" };
|
|
1195
|
+
}
|
|
1196
|
+
return { trustLevel: "unknown", reason: "binary in unrecognized location" };
|
|
1197
|
+
}
|
|
1198
|
+
function checkProvenance(cmd, cwd) {
|
|
1199
|
+
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
1200
|
+
if (path5.posix.isAbsolute(bare)) {
|
|
1201
|
+
const early = _classifyPath(bare, cwd);
|
|
1202
|
+
if (early.trustLevel === "suspect") {
|
|
1203
|
+
return { resolvedPath: bare, ...early };
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
let resolved;
|
|
1207
|
+
try {
|
|
1208
|
+
const found = findInPath(bare);
|
|
1209
|
+
if (!found) {
|
|
1210
|
+
return {
|
|
1211
|
+
resolvedPath: cmd,
|
|
1212
|
+
trustLevel: "unknown",
|
|
1213
|
+
reason: "binary not found in PATH"
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
resolved = fs5.realpathSync(found);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return {
|
|
1219
|
+
resolvedPath: cmd,
|
|
1220
|
+
trustLevel: "unknown",
|
|
1221
|
+
reason: "binary not found in PATH"
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
try {
|
|
1225
|
+
const stat = fs5.statSync(resolved);
|
|
1226
|
+
if (stat.mode & 2) {
|
|
1227
|
+
return {
|
|
1228
|
+
resolvedPath: resolved,
|
|
1229
|
+
trustLevel: "suspect",
|
|
1230
|
+
reason: "binary is world-writable"
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
return {
|
|
1235
|
+
resolvedPath: resolved,
|
|
1236
|
+
trustLevel: "unknown",
|
|
1237
|
+
reason: "could not stat binary"
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
const classify = _classifyPath(resolved, cwd);
|
|
1241
|
+
return { resolvedPath: resolved, ...classify };
|
|
1242
|
+
}
|
|
1243
|
+
var SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
|
|
1244
|
+
var init_provenance = __esm({
|
|
1245
|
+
"src/utils/provenance.ts"() {
|
|
1246
|
+
"use strict";
|
|
1247
|
+
SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
1248
|
+
MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
1249
|
+
USER_PREFIXES = [
|
|
1250
|
+
path5.join(os4.homedir(), "bin"),
|
|
1251
|
+
path5.join(os4.homedir(), ".local", "bin"),
|
|
1252
|
+
path5.join(os4.homedir(), ".cargo", "bin"),
|
|
1253
|
+
path5.join(os4.homedir(), ".npm-global", "bin"),
|
|
1254
|
+
path5.join(os4.homedir(), ".volta", "bin")
|
|
1255
|
+
];
|
|
1256
|
+
SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
// src/policy/pipe-chain.ts
|
|
1261
|
+
function isSensitivePath(p) {
|
|
1262
|
+
return SENSITIVE_PATTERNS.some((re) => re.test(p));
|
|
1263
|
+
}
|
|
1264
|
+
function splitOnPipe(cmd) {
|
|
1265
|
+
const segments = [];
|
|
1266
|
+
let current = "";
|
|
1267
|
+
let inSingle = false;
|
|
1268
|
+
let inDouble = false;
|
|
1269
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
1270
|
+
const ch = cmd[i];
|
|
1271
|
+
if (ch === "'" && !inDouble) {
|
|
1272
|
+
inSingle = !inSingle;
|
|
1273
|
+
current += ch;
|
|
1274
|
+
} else if (ch === '"' && !inSingle) {
|
|
1275
|
+
inDouble = !inDouble;
|
|
1276
|
+
current += ch;
|
|
1277
|
+
} else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
|
|
1278
|
+
segments.push(current.trim());
|
|
1279
|
+
current = "";
|
|
1280
|
+
} else {
|
|
1281
|
+
current += ch;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (current.trim()) segments.push(current.trim());
|
|
1285
|
+
return segments.filter(Boolean);
|
|
1286
|
+
}
|
|
1287
|
+
function positionalTokens(segment) {
|
|
1288
|
+
return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
1289
|
+
}
|
|
1290
|
+
function analyzePipeChain(command) {
|
|
1291
|
+
const segments = splitOnPipe(command);
|
|
1292
|
+
if (segments.length < 2) {
|
|
1293
|
+
return {
|
|
1294
|
+
isPipeline: false,
|
|
1295
|
+
hasSensitiveSource: false,
|
|
1296
|
+
hasExternalSink: false,
|
|
1297
|
+
hasObfuscation: false,
|
|
1298
|
+
sourceFiles: [],
|
|
1299
|
+
sinkTargets: [],
|
|
1300
|
+
risk: "none"
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
const sourceFiles = [];
|
|
1304
|
+
const sinkTargets = [];
|
|
1305
|
+
let hasSensitiveSource = false;
|
|
1306
|
+
let hasExternalSink = false;
|
|
1307
|
+
let hasObfuscation = false;
|
|
1308
|
+
for (const segment of segments) {
|
|
1309
|
+
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
1310
|
+
if (tokens.length === 0) continue;
|
|
1311
|
+
const binary = tokens[0].toLowerCase();
|
|
1312
|
+
const args = positionalTokens(segment);
|
|
1313
|
+
if (SOURCE_COMMANDS.has(binary)) {
|
|
1314
|
+
sourceFiles.push(...args);
|
|
1315
|
+
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
1316
|
+
}
|
|
1317
|
+
if (OBFUSCATORS.has(binary)) hasObfuscation = true;
|
|
1318
|
+
if (SINK_COMMANDS.has(binary)) {
|
|
1319
|
+
const targets = args.filter(
|
|
1320
|
+
(a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
|
|
1321
|
+
);
|
|
1322
|
+
sinkTargets.push(...targets);
|
|
1323
|
+
if (targets.length > 0) hasExternalSink = true;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const fullCmd = command.toLowerCase();
|
|
1327
|
+
if (!hasSensitiveSource) {
|
|
1328
|
+
const redirMatch = fullCmd.match(/<\s*(\S+)/);
|
|
1329
|
+
if (redirMatch && isSensitivePath(redirMatch[1])) {
|
|
1330
|
+
hasSensitiveSource = true;
|
|
1331
|
+
sourceFiles.push(redirMatch[1]);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
|
|
1335
|
+
return {
|
|
1336
|
+
isPipeline: true,
|
|
1337
|
+
hasSensitiveSource,
|
|
1338
|
+
hasExternalSink,
|
|
1339
|
+
hasObfuscation,
|
|
1340
|
+
sourceFiles,
|
|
1341
|
+
sinkTargets,
|
|
1342
|
+
risk
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
var SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS;
|
|
1346
|
+
var init_pipe_chain = __esm({
|
|
1347
|
+
"src/policy/pipe-chain.ts"() {
|
|
1348
|
+
"use strict";
|
|
1349
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1350
|
+
"cat",
|
|
1351
|
+
"head",
|
|
1352
|
+
"tail",
|
|
1353
|
+
"grep",
|
|
1354
|
+
"awk",
|
|
1355
|
+
"sed",
|
|
1356
|
+
"cut",
|
|
1357
|
+
"sort",
|
|
1358
|
+
"tee",
|
|
1359
|
+
"less",
|
|
1360
|
+
"more",
|
|
1361
|
+
"strings",
|
|
1362
|
+
"xxd"
|
|
1363
|
+
]);
|
|
1364
|
+
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
1365
|
+
"curl",
|
|
1366
|
+
"wget",
|
|
1367
|
+
"nc",
|
|
1368
|
+
"ncat",
|
|
1369
|
+
"netcat",
|
|
1370
|
+
"ssh",
|
|
1371
|
+
"scp",
|
|
1372
|
+
"rsync",
|
|
1373
|
+
"socat",
|
|
1374
|
+
"ftp",
|
|
1375
|
+
"sftp",
|
|
1376
|
+
"telnet"
|
|
1377
|
+
]);
|
|
1378
|
+
OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
1379
|
+
"base64",
|
|
1380
|
+
"gzip",
|
|
1381
|
+
"gunzip",
|
|
1382
|
+
"bzip2",
|
|
1383
|
+
"xz",
|
|
1384
|
+
"zstd",
|
|
1385
|
+
"openssl",
|
|
1386
|
+
"gpg",
|
|
1387
|
+
"python",
|
|
1388
|
+
"python3",
|
|
1389
|
+
"perl",
|
|
1390
|
+
"ruby",
|
|
1391
|
+
"node"
|
|
1392
|
+
]);
|
|
1393
|
+
SENSITIVE_PATTERNS = [
|
|
1394
|
+
/(?:^|\/)\.env(?:\.|$)/i,
|
|
1395
|
+
// .env, .env.local, .env.production
|
|
1396
|
+
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
1397
|
+
// SSH private keys
|
|
1398
|
+
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
1399
|
+
// certificate files
|
|
1400
|
+
/(?:^|\/)\.ssh\//i,
|
|
1401
|
+
// ~/.ssh/ directory
|
|
1402
|
+
/(?:^|\/)\.aws\/credentials/i,
|
|
1403
|
+
// AWS credentials
|
|
1404
|
+
/(?:^|\/)\.netrc$/i,
|
|
1405
|
+
// netrc (stores HTTP credentials)
|
|
1406
|
+
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
1407
|
+
// /etc/passwd, /etc/shadow
|
|
1408
|
+
/(?:^|\/)credentials(?:\.json)?$/i
|
|
1409
|
+
// generic credentials files
|
|
1410
|
+
];
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
// src/policy/flag-tables.ts
|
|
1415
|
+
import path6 from "path";
|
|
1416
|
+
function extractPositionalArgs(tokens, binary) {
|
|
1417
|
+
const binaryName = path6.basename(binary).replace(/\.exe$/i, "");
|
|
1418
|
+
const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
|
|
1419
|
+
const positional = [];
|
|
1420
|
+
let skipNext = false;
|
|
1421
|
+
for (const token of tokens) {
|
|
1422
|
+
if (skipNext) {
|
|
1423
|
+
skipNext = false;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (token.startsWith("--") && token.includes("=")) continue;
|
|
1427
|
+
if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
|
|
1428
|
+
skipNext = true;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (token.startsWith("--") && flagsWithValues.has(token)) {
|
|
1432
|
+
skipNext = true;
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
const shortFlag = token.slice(0, 2);
|
|
1436
|
+
if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
|
|
1437
|
+
if (token.startsWith("-")) continue;
|
|
1438
|
+
if (token.startsWith("@")) continue;
|
|
1439
|
+
positional.push(token);
|
|
1440
|
+
}
|
|
1441
|
+
return positional;
|
|
1442
|
+
}
|
|
1443
|
+
function extractNetworkTargets(tokens, binary) {
|
|
1444
|
+
return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
|
|
1445
|
+
const colonIdx = t.indexOf(":");
|
|
1446
|
+
if (colonIdx === -1) return t;
|
|
1447
|
+
const afterColon = t.slice(colonIdx + 1);
|
|
1448
|
+
if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
|
|
1449
|
+
return t;
|
|
1450
|
+
}).filter(Boolean);
|
|
1451
|
+
}
|
|
1452
|
+
var FLAGS_WITH_VALUES;
|
|
1453
|
+
var init_flag_tables = __esm({
|
|
1454
|
+
"src/policy/flag-tables.ts"() {
|
|
1455
|
+
"use strict";
|
|
1456
|
+
FLAGS_WITH_VALUES = {
|
|
1457
|
+
curl: /* @__PURE__ */ new Set([
|
|
1458
|
+
"-H",
|
|
1459
|
+
"--header",
|
|
1460
|
+
"-A",
|
|
1461
|
+
"--user-agent",
|
|
1462
|
+
"-e",
|
|
1463
|
+
"--referer",
|
|
1464
|
+
"-x",
|
|
1465
|
+
"--proxy",
|
|
1466
|
+
"-u",
|
|
1467
|
+
"--user",
|
|
1468
|
+
"-d",
|
|
1469
|
+
"--data",
|
|
1470
|
+
"--data-raw",
|
|
1471
|
+
"--data-binary",
|
|
1472
|
+
"-o",
|
|
1473
|
+
"--output",
|
|
1474
|
+
"-F",
|
|
1475
|
+
"--form",
|
|
1476
|
+
"--connect-to",
|
|
1477
|
+
"--resolve",
|
|
1478
|
+
"--cacert",
|
|
1479
|
+
"--cert",
|
|
1480
|
+
"--key",
|
|
1481
|
+
"-m",
|
|
1482
|
+
"--max-time"
|
|
1483
|
+
]),
|
|
1484
|
+
wget: /* @__PURE__ */ new Set([
|
|
1485
|
+
"-O",
|
|
1486
|
+
"--output-document",
|
|
1487
|
+
"-P",
|
|
1488
|
+
"--directory-prefix",
|
|
1489
|
+
"-U",
|
|
1490
|
+
"--user-agent",
|
|
1491
|
+
"-e",
|
|
1492
|
+
"--execute",
|
|
1493
|
+
"--proxy",
|
|
1494
|
+
"--ca-certificate"
|
|
1495
|
+
]),
|
|
1496
|
+
nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
|
|
1497
|
+
ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
|
|
1498
|
+
netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
|
|
1499
|
+
ssh: /* @__PURE__ */ new Set([
|
|
1500
|
+
"-i",
|
|
1501
|
+
"-l",
|
|
1502
|
+
"-p",
|
|
1503
|
+
"-o",
|
|
1504
|
+
"-E",
|
|
1505
|
+
"-F",
|
|
1506
|
+
"-J",
|
|
1507
|
+
"-L",
|
|
1508
|
+
"-R",
|
|
1509
|
+
"-W",
|
|
1510
|
+
"-b",
|
|
1511
|
+
"-c",
|
|
1512
|
+
"-D",
|
|
1513
|
+
"-e",
|
|
1514
|
+
"-I",
|
|
1515
|
+
"-S"
|
|
1516
|
+
]),
|
|
1517
|
+
scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
|
|
1518
|
+
rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
|
|
1519
|
+
socat: /* @__PURE__ */ new Set([])
|
|
1520
|
+
// socat uses address syntax, not flags — no value-flags
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
// src/policy/ssh-parser.ts
|
|
1526
|
+
function tokenize(cmd) {
|
|
1527
|
+
const tokens = [];
|
|
1528
|
+
let current = "";
|
|
1529
|
+
let inSingle = false;
|
|
1530
|
+
let inDouble = false;
|
|
1531
|
+
for (const ch of cmd) {
|
|
1532
|
+
if (ch === "'" && !inDouble) {
|
|
1533
|
+
inSingle = !inSingle;
|
|
1534
|
+
} else if (ch === '"' && !inSingle) {
|
|
1535
|
+
inDouble = !inDouble;
|
|
1536
|
+
} else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
|
|
1537
|
+
if (current) {
|
|
1538
|
+
tokens.push(current);
|
|
1539
|
+
current = "";
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
current += ch;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (current) tokens.push(current);
|
|
1546
|
+
return tokens;
|
|
1547
|
+
}
|
|
1548
|
+
function parseHost(raw) {
|
|
1549
|
+
return raw.split("@").pop().split(":")[0];
|
|
1550
|
+
}
|
|
1551
|
+
function extractAllSshHosts(tokens) {
|
|
1552
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1553
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1554
|
+
const t = tokens[i];
|
|
1555
|
+
if (t === "-J" && tokens[i + 1]) {
|
|
1556
|
+
for (const hop of tokens[++i].split(",")) {
|
|
1557
|
+
const h = parseHost(hop);
|
|
1558
|
+
if (h) hosts.add(h);
|
|
1559
|
+
}
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
|
|
1563
|
+
const val = tokens[++i].split("=").slice(1).join("=");
|
|
1564
|
+
for (const hop of val.split(",")) {
|
|
1565
|
+
const h = parseHost(hop);
|
|
1566
|
+
if (h) hosts.add(h);
|
|
1567
|
+
}
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
|
|
1571
|
+
const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
|
|
1572
|
+
const subTokens = tokenize(raw);
|
|
1573
|
+
const binary = subTokens[0] ?? "";
|
|
1574
|
+
extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
|
|
1575
|
+
extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
if (!t.startsWith("-")) {
|
|
1579
|
+
const h = parseHost(t);
|
|
1580
|
+
if (h) hosts.add(h);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return [...hosts].filter(Boolean);
|
|
1584
|
+
}
|
|
1585
|
+
var init_ssh_parser = __esm({
|
|
1586
|
+
"src/policy/ssh-parser.ts"() {
|
|
1587
|
+
"use strict";
|
|
1588
|
+
init_flag_tables();
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
// src/auth/trusted-hosts.ts
|
|
1593
|
+
import fs6 from "fs";
|
|
1594
|
+
import path7 from "path";
|
|
1595
|
+
import os5 from "os";
|
|
1596
|
+
function getTrustedHostsPath() {
|
|
1597
|
+
return path7.join(os5.homedir(), ".node9", "trusted-hosts.json");
|
|
1598
|
+
}
|
|
1599
|
+
function readTrustedHosts() {
|
|
1600
|
+
try {
|
|
1601
|
+
const raw = fs6.readFileSync(getTrustedHostsPath(), "utf8");
|
|
1602
|
+
const parsed = JSON.parse(raw);
|
|
1603
|
+
return Array.isArray(parsed.hosts) ? parsed.hosts : [];
|
|
1604
|
+
} catch {
|
|
1605
|
+
return [];
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function writeTrustedHosts(hosts) {
|
|
1609
|
+
const filePath = getTrustedHostsPath();
|
|
1610
|
+
fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
|
|
1611
|
+
const tmp = filePath + ".node9-tmp";
|
|
1612
|
+
fs6.writeFileSync(tmp, JSON.stringify({ hosts }, null, 2));
|
|
1613
|
+
fs6.renameSync(tmp, filePath);
|
|
1614
|
+
}
|
|
1615
|
+
function addTrustedHost(host) {
|
|
1616
|
+
const hosts = readTrustedHosts();
|
|
1617
|
+
if (hosts.some((h) => h.host === host)) return;
|
|
1618
|
+
hosts.push({ host, addedAt: Date.now(), addedBy: "user" });
|
|
1619
|
+
writeTrustedHosts(hosts);
|
|
1620
|
+
}
|
|
1621
|
+
function removeTrustedHost(host) {
|
|
1622
|
+
const hosts = readTrustedHosts();
|
|
1623
|
+
const filtered = hosts.filter((h) => h.host !== host);
|
|
1624
|
+
if (filtered.length === hosts.length) return false;
|
|
1625
|
+
writeTrustedHosts(filtered);
|
|
1626
|
+
return true;
|
|
1627
|
+
}
|
|
1628
|
+
function normalizeHost(raw) {
|
|
1629
|
+
return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
|
|
1630
|
+
}
|
|
1631
|
+
function isTrustedHost(host) {
|
|
1632
|
+
const normalized = normalizeHost(host);
|
|
1633
|
+
return readTrustedHosts().some((entry) => {
|
|
1634
|
+
const entryHost = entry.host.toLowerCase();
|
|
1635
|
+
if (entryHost.startsWith("*.")) {
|
|
1636
|
+
const domain = entryHost.slice(2);
|
|
1637
|
+
return normalized === domain || normalized.endsWith("." + domain);
|
|
1638
|
+
}
|
|
1639
|
+
return normalized === entryHost;
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
var init_trusted_hosts = __esm({
|
|
1643
|
+
"src/auth/trusted-hosts.ts"() {
|
|
1644
|
+
"use strict";
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
// src/policy/index.ts
|
|
1649
|
+
import fs7 from "fs";
|
|
1650
|
+
import path8 from "path";
|
|
1651
|
+
import os6 from "os";
|
|
1164
1652
|
import pm from "picomatch";
|
|
1165
1653
|
import { parse } from "sh-syntax";
|
|
1166
|
-
function
|
|
1654
|
+
function tokenize2(toolName) {
|
|
1167
1655
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
1168
1656
|
}
|
|
1169
1657
|
function matchesPattern(text, patterns) {
|
|
@@ -1176,9 +1664,9 @@ function matchesPattern(text, patterns) {
|
|
|
1176
1664
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1177
1665
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1178
1666
|
}
|
|
1179
|
-
function getNestedValue(obj,
|
|
1667
|
+
function getNestedValue(obj, path25) {
|
|
1180
1668
|
if (!obj || typeof obj !== "object") return null;
|
|
1181
|
-
return
|
|
1669
|
+
return path25.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1182
1670
|
}
|
|
1183
1671
|
function shouldSnapshot(toolName, args, config) {
|
|
1184
1672
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1313,7 +1801,7 @@ async function analyzeShellCommand(command) {
|
|
|
1313
1801
|
}
|
|
1314
1802
|
return { actions, paths, allTokens };
|
|
1315
1803
|
}
|
|
1316
|
-
async function evaluatePolicy(toolName, args, agent) {
|
|
1804
|
+
async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
1317
1805
|
const config = getConfig();
|
|
1318
1806
|
if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
|
|
1319
1807
|
if (config.policy.smartRules.length > 0) {
|
|
@@ -1343,11 +1831,66 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1343
1831
|
if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
|
|
1344
1832
|
return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
|
|
1345
1833
|
}
|
|
1834
|
+
const pipeAnalysis = analyzePipeChain(shellCommand);
|
|
1835
|
+
if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
|
|
1836
|
+
const sinks = pipeAnalysis.sinkTargets;
|
|
1837
|
+
const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
|
|
1838
|
+
if (pipeAnalysis.risk === "critical") {
|
|
1839
|
+
if (allTrusted) {
|
|
1840
|
+
return {
|
|
1841
|
+
decision: "review",
|
|
1842
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1843
|
+
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1844
|
+
tier: 3
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
return {
|
|
1848
|
+
decision: "block",
|
|
1849
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1850
|
+
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1851
|
+
tier: 3
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
if (allTrusted) {
|
|
1855
|
+
return { decision: "allow" };
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
decision: "review",
|
|
1859
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1860
|
+
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1861
|
+
tier: 3
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
const firstToken = analyzed.actions[0] ?? "";
|
|
1865
|
+
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1866
|
+
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
1867
|
+
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
1868
|
+
allTokens.push(...sshHosts);
|
|
1869
|
+
}
|
|
1870
|
+
if (firstToken && path8.posix.isAbsolute(firstToken)) {
|
|
1871
|
+
const prov = checkProvenance(firstToken, cwd);
|
|
1872
|
+
if (prov.trustLevel === "suspect") {
|
|
1873
|
+
return {
|
|
1874
|
+
decision: config.settings.mode === "strict" ? "block" : "review",
|
|
1875
|
+
blockedByLabel: "Node9: Suspect Binary",
|
|
1876
|
+
reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
|
|
1877
|
+
tier: 3
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
|
|
1881
|
+
return {
|
|
1882
|
+
decision: "review",
|
|
1883
|
+
blockedByLabel: "Node9: Unknown Binary (strict mode)",
|
|
1884
|
+
reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
|
|
1885
|
+
tier: 3
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1346
1889
|
if (isSqlTool(toolName, config.policy.toolInspection)) {
|
|
1347
1890
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
1348
1891
|
}
|
|
1349
1892
|
} else {
|
|
1350
|
-
allTokens =
|
|
1893
|
+
allTokens = tokenize2(toolName);
|
|
1351
1894
|
if (args && typeof args === "object") {
|
|
1352
1895
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
1353
1896
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
@@ -1421,9 +1964,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1421
1964
|
}
|
|
1422
1965
|
async function explainPolicy(toolName, args) {
|
|
1423
1966
|
const steps = [];
|
|
1424
|
-
const globalPath =
|
|
1425
|
-
const projectPath =
|
|
1426
|
-
const credsPath =
|
|
1967
|
+
const globalPath = path8.join(os6.homedir(), ".node9", "config.json");
|
|
1968
|
+
const projectPath = path8.join(process.cwd(), "node9.config.json");
|
|
1969
|
+
const credsPath = path8.join(os6.homedir(), ".node9", "credentials.json");
|
|
1427
1970
|
const waterfall = [
|
|
1428
1971
|
{
|
|
1429
1972
|
tier: 1,
|
|
@@ -1434,19 +1977,19 @@ async function explainPolicy(toolName, args) {
|
|
|
1434
1977
|
{
|
|
1435
1978
|
tier: 2,
|
|
1436
1979
|
label: "Cloud policy",
|
|
1437
|
-
status:
|
|
1438
|
-
note:
|
|
1980
|
+
status: fs7.existsSync(credsPath) ? "active" : "missing",
|
|
1981
|
+
note: fs7.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
1439
1982
|
},
|
|
1440
1983
|
{
|
|
1441
1984
|
tier: 3,
|
|
1442
1985
|
label: "Project config",
|
|
1443
|
-
status:
|
|
1986
|
+
status: fs7.existsSync(projectPath) ? "active" : "missing",
|
|
1444
1987
|
path: projectPath
|
|
1445
1988
|
},
|
|
1446
1989
|
{
|
|
1447
1990
|
tier: 4,
|
|
1448
1991
|
label: "Global config",
|
|
1449
|
-
status:
|
|
1992
|
+
status: fs7.existsSync(globalPath) ? "active" : "missing",
|
|
1450
1993
|
path: globalPath
|
|
1451
1994
|
},
|
|
1452
1995
|
{
|
|
@@ -1578,7 +2121,7 @@ async function explainPolicy(toolName, args) {
|
|
|
1578
2121
|
});
|
|
1579
2122
|
}
|
|
1580
2123
|
} else {
|
|
1581
|
-
allTokens =
|
|
2124
|
+
allTokens = tokenize2(toolName);
|
|
1582
2125
|
let detail = `No toolInspection match for "${toolName}" \u2014 tokens: [${allTokens.join(", ")}]`;
|
|
1583
2126
|
if (args && typeof args === "object") {
|
|
1584
2127
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
@@ -1690,21 +2233,25 @@ var init_policy = __esm({
|
|
|
1690
2233
|
init_dlp();
|
|
1691
2234
|
init_config();
|
|
1692
2235
|
init_regex();
|
|
2236
|
+
init_provenance();
|
|
2237
|
+
init_pipe_chain();
|
|
2238
|
+
init_ssh_parser();
|
|
2239
|
+
init_trusted_hosts();
|
|
1693
2240
|
SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
1694
2241
|
}
|
|
1695
2242
|
});
|
|
1696
2243
|
|
|
1697
2244
|
// src/auth/state.ts
|
|
1698
|
-
import
|
|
1699
|
-
import
|
|
1700
|
-
import
|
|
2245
|
+
import fs8 from "fs";
|
|
2246
|
+
import path9 from "path";
|
|
2247
|
+
import os7 from "os";
|
|
1701
2248
|
function checkPause() {
|
|
1702
2249
|
try {
|
|
1703
|
-
if (!
|
|
1704
|
-
const state = JSON.parse(
|
|
2250
|
+
if (!fs8.existsSync(PAUSED_FILE)) return { paused: false };
|
|
2251
|
+
const state = JSON.parse(fs8.readFileSync(PAUSED_FILE, "utf-8"));
|
|
1705
2252
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
1706
2253
|
try {
|
|
1707
|
-
|
|
2254
|
+
fs8.unlinkSync(PAUSED_FILE);
|
|
1708
2255
|
} catch {
|
|
1709
2256
|
}
|
|
1710
2257
|
return { paused: false };
|
|
@@ -1715,11 +2262,11 @@ function checkPause() {
|
|
|
1715
2262
|
}
|
|
1716
2263
|
}
|
|
1717
2264
|
function atomicWriteSync(filePath, data, options) {
|
|
1718
|
-
const dir =
|
|
1719
|
-
if (!
|
|
1720
|
-
const tmpPath = `${filePath}.${
|
|
1721
|
-
|
|
1722
|
-
|
|
2265
|
+
const dir = path9.dirname(filePath);
|
|
2266
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
2267
|
+
const tmpPath = `${filePath}.${os7.hostname()}.${process.pid}.tmp`;
|
|
2268
|
+
fs8.writeFileSync(tmpPath, data, options);
|
|
2269
|
+
fs8.renameSync(tmpPath, filePath);
|
|
1723
2270
|
}
|
|
1724
2271
|
function pauseNode9(durationMs, durationStr) {
|
|
1725
2272
|
const state = { expiry: Date.now() + durationMs, duration: durationStr };
|
|
@@ -1727,18 +2274,18 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
1727
2274
|
}
|
|
1728
2275
|
function resumeNode9() {
|
|
1729
2276
|
try {
|
|
1730
|
-
if (
|
|
2277
|
+
if (fs8.existsSync(PAUSED_FILE)) fs8.unlinkSync(PAUSED_FILE);
|
|
1731
2278
|
} catch {
|
|
1732
2279
|
}
|
|
1733
2280
|
}
|
|
1734
2281
|
function getActiveTrustSession(toolName) {
|
|
1735
2282
|
try {
|
|
1736
|
-
if (!
|
|
1737
|
-
const trust = JSON.parse(
|
|
2283
|
+
if (!fs8.existsSync(TRUST_FILE)) return false;
|
|
2284
|
+
const trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
|
|
1738
2285
|
const now = Date.now();
|
|
1739
2286
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
1740
2287
|
if (active.length !== trust.entries.length) {
|
|
1741
|
-
|
|
2288
|
+
fs8.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
1742
2289
|
}
|
|
1743
2290
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
1744
2291
|
} catch {
|
|
@@ -1749,8 +2296,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1749
2296
|
try {
|
|
1750
2297
|
let trust = { entries: [] };
|
|
1751
2298
|
try {
|
|
1752
|
-
if (
|
|
1753
|
-
trust = JSON.parse(
|
|
2299
|
+
if (fs8.existsSync(TRUST_FILE)) {
|
|
2300
|
+
trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
|
|
1754
2301
|
}
|
|
1755
2302
|
} catch {
|
|
1756
2303
|
}
|
|
@@ -1766,9 +2313,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1766
2313
|
}
|
|
1767
2314
|
function getPersistentDecision(toolName) {
|
|
1768
2315
|
try {
|
|
1769
|
-
const file =
|
|
1770
|
-
if (!
|
|
1771
|
-
const decisions = JSON.parse(
|
|
2316
|
+
const file = path9.join(os7.homedir(), ".node9", "decisions.json");
|
|
2317
|
+
if (!fs8.existsSync(file)) return null;
|
|
2318
|
+
const decisions = JSON.parse(fs8.readFileSync(file, "utf-8"));
|
|
1772
2319
|
const d = decisions[toolName];
|
|
1773
2320
|
if (d === "allow" || d === "deny") return d;
|
|
1774
2321
|
} catch {
|
|
@@ -1780,21 +2327,21 @@ var init_state = __esm({
|
|
|
1780
2327
|
"src/auth/state.ts"() {
|
|
1781
2328
|
"use strict";
|
|
1782
2329
|
init_policy();
|
|
1783
|
-
PAUSED_FILE =
|
|
1784
|
-
TRUST_FILE =
|
|
2330
|
+
PAUSED_FILE = path9.join(os7.homedir(), ".node9", "PAUSED");
|
|
2331
|
+
TRUST_FILE = path9.join(os7.homedir(), ".node9", "trust.json");
|
|
1785
2332
|
}
|
|
1786
2333
|
});
|
|
1787
2334
|
|
|
1788
2335
|
// src/auth/daemon.ts
|
|
1789
|
-
import
|
|
1790
|
-
import
|
|
1791
|
-
import
|
|
2336
|
+
import fs9 from "fs";
|
|
2337
|
+
import path10 from "path";
|
|
2338
|
+
import os8 from "os";
|
|
1792
2339
|
import { spawnSync } from "child_process";
|
|
1793
2340
|
function getInternalToken() {
|
|
1794
2341
|
try {
|
|
1795
|
-
const pidFile =
|
|
1796
|
-
if (!
|
|
1797
|
-
const data = JSON.parse(
|
|
2342
|
+
const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
|
|
2343
|
+
if (!fs9.existsSync(pidFile)) return null;
|
|
2344
|
+
const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
|
|
1798
2345
|
process.kill(data.pid, 0);
|
|
1799
2346
|
return data.internalToken ?? null;
|
|
1800
2347
|
} catch {
|
|
@@ -1802,10 +2349,10 @@ function getInternalToken() {
|
|
|
1802
2349
|
}
|
|
1803
2350
|
}
|
|
1804
2351
|
function isDaemonRunning() {
|
|
1805
|
-
const pidFile =
|
|
1806
|
-
if (
|
|
2352
|
+
const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
|
|
2353
|
+
if (fs9.existsSync(pidFile)) {
|
|
1807
2354
|
try {
|
|
1808
|
-
const { pid, port } = JSON.parse(
|
|
2355
|
+
const { pid, port } = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
|
|
1809
2356
|
if (port !== DAEMON_PORT) return false;
|
|
1810
2357
|
process.kill(pid, 0);
|
|
1811
2358
|
return true;
|
|
@@ -1908,7 +2455,7 @@ var init_daemon = __esm({
|
|
|
1908
2455
|
});
|
|
1909
2456
|
|
|
1910
2457
|
// src/context-sniper.ts
|
|
1911
|
-
import
|
|
2458
|
+
import path11 from "path";
|
|
1912
2459
|
function smartTruncate(str, maxLen = 500) {
|
|
1913
2460
|
if (str.length <= maxLen) return str;
|
|
1914
2461
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -1960,7 +2507,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
1960
2507
|
intent = "EDIT";
|
|
1961
2508
|
if (obj.file_path) {
|
|
1962
2509
|
editFilePath = String(obj.file_path);
|
|
1963
|
-
editFileName =
|
|
2510
|
+
editFileName = path11.basename(editFilePath);
|
|
1964
2511
|
}
|
|
1965
2512
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
1966
2513
|
contextSnippet = result.snippet;
|
|
@@ -2017,7 +2564,7 @@ var init_context_sniper = __esm({
|
|
|
2017
2564
|
|
|
2018
2565
|
// src/ui/native.ts
|
|
2019
2566
|
import { spawn } from "child_process";
|
|
2020
|
-
import
|
|
2567
|
+
import path12 from "path";
|
|
2021
2568
|
function formatArgs(args, matchedField, matchedWord) {
|
|
2022
2569
|
if (args === null || args === void 0) return { message: "(none)", intent: "EXEC" };
|
|
2023
2570
|
let parsed = args;
|
|
@@ -2036,7 +2583,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
2036
2583
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2037
2584
|
const obj = parsed;
|
|
2038
2585
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
2039
|
-
const file = obj.file_path ?
|
|
2586
|
+
const file = obj.file_path ? path12.basename(String(obj.file_path)) : "file";
|
|
2040
2587
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
2041
2588
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
2042
2589
|
return {
|
|
@@ -2221,8 +2768,8 @@ var init_native = __esm({
|
|
|
2221
2768
|
});
|
|
2222
2769
|
|
|
2223
2770
|
// src/auth/cloud.ts
|
|
2224
|
-
import
|
|
2225
|
-
import
|
|
2771
|
+
import fs10 from "fs";
|
|
2772
|
+
import os9 from "os";
|
|
2226
2773
|
function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
2227
2774
|
return fetch(`${creds.apiUrl}/audit`, {
|
|
2228
2775
|
method: "POST",
|
|
@@ -2234,9 +2781,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2234
2781
|
context: {
|
|
2235
2782
|
agent: meta?.agent,
|
|
2236
2783
|
mcpServer: meta?.mcpServer,
|
|
2237
|
-
hostname:
|
|
2784
|
+
hostname: os9.hostname(),
|
|
2238
2785
|
cwd: process.cwd(),
|
|
2239
|
-
platform:
|
|
2786
|
+
platform: os9.platform()
|
|
2240
2787
|
}
|
|
2241
2788
|
}),
|
|
2242
2789
|
signal: AbortSignal.timeout(5e3)
|
|
@@ -2257,9 +2804,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2257
2804
|
context: {
|
|
2258
2805
|
agent: meta?.agent,
|
|
2259
2806
|
mcpServer: meta?.mcpServer,
|
|
2260
|
-
hostname:
|
|
2807
|
+
hostname: os9.hostname(),
|
|
2261
2808
|
cwd: process.cwd(),
|
|
2262
|
-
platform:
|
|
2809
|
+
platform: os9.platform()
|
|
2263
2810
|
},
|
|
2264
2811
|
...riskMetadata && { riskMetadata }
|
|
2265
2812
|
}),
|
|
@@ -2315,14 +2862,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
2315
2862
|
});
|
|
2316
2863
|
clearTimeout(timer);
|
|
2317
2864
|
if (!res.ok) {
|
|
2318
|
-
|
|
2865
|
+
fs10.appendFileSync(
|
|
2319
2866
|
HOOK_DEBUG_LOG,
|
|
2320
2867
|
`[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
|
|
2321
2868
|
`
|
|
2322
2869
|
);
|
|
2323
2870
|
}
|
|
2324
2871
|
} catch (err) {
|
|
2325
|
-
|
|
2872
|
+
fs10.appendFileSync(
|
|
2326
2873
|
HOOK_DEBUG_LOG,
|
|
2327
2874
|
`[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
|
|
2328
2875
|
`
|
|
@@ -2338,8 +2885,8 @@ var init_cloud = __esm({
|
|
|
2338
2885
|
|
|
2339
2886
|
// src/auth/orchestrator.ts
|
|
2340
2887
|
import net from "net";
|
|
2341
|
-
import
|
|
2342
|
-
import
|
|
2888
|
+
import path13 from "path";
|
|
2889
|
+
import os10 from "os";
|
|
2343
2890
|
import { randomUUID } from "crypto";
|
|
2344
2891
|
function notifyActivity(data) {
|
|
2345
2892
|
return new Promise((resolve) => {
|
|
@@ -2422,7 +2969,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2422
2969
|
}
|
|
2423
2970
|
if (config.settings.mode === "audit") {
|
|
2424
2971
|
if (!isIgnoredTool(toolName)) {
|
|
2425
|
-
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
2972
|
+
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
2426
2973
|
if (policyResult.decision === "review") {
|
|
2427
2974
|
appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
|
|
2428
2975
|
if (approvers.cloud && creds?.apiKey) {
|
|
@@ -2700,7 +3247,7 @@ var init_orchestrator = __esm({
|
|
|
2700
3247
|
init_state();
|
|
2701
3248
|
init_daemon();
|
|
2702
3249
|
init_cloud();
|
|
2703
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
3250
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path13.join(os10.tmpdir(), "node9-activity.sock");
|
|
2704
3251
|
}
|
|
2705
3252
|
});
|
|
2706
3253
|
|
|
@@ -4172,9 +4719,9 @@ var init_ui2 = __esm({
|
|
|
4172
4719
|
|
|
4173
4720
|
// src/daemon/state.ts
|
|
4174
4721
|
import net2 from "net";
|
|
4175
|
-
import
|
|
4176
|
-
import
|
|
4177
|
-
import
|
|
4722
|
+
import fs12 from "fs";
|
|
4723
|
+
import path15 from "path";
|
|
4724
|
+
import os12 from "os";
|
|
4178
4725
|
import { spawn as spawn2 } from "child_process";
|
|
4179
4726
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4180
4727
|
function getAbandonTimer() {
|
|
@@ -4199,11 +4746,11 @@ function markRejectionHandlerRegistered() {
|
|
|
4199
4746
|
daemonRejectionHandlerRegistered = true;
|
|
4200
4747
|
}
|
|
4201
4748
|
function atomicWriteSync2(filePath, data, options) {
|
|
4202
|
-
const dir =
|
|
4203
|
-
if (!
|
|
4749
|
+
const dir = path15.dirname(filePath);
|
|
4750
|
+
if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
|
|
4204
4751
|
const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
|
|
4205
|
-
|
|
4206
|
-
|
|
4752
|
+
fs12.writeFileSync(tmpPath, data, options);
|
|
4753
|
+
fs12.renameSync(tmpPath, filePath);
|
|
4207
4754
|
}
|
|
4208
4755
|
function redactArgs(value) {
|
|
4209
4756
|
if (!value || typeof value !== "object") return value;
|
|
@@ -4223,16 +4770,16 @@ function appendAuditLog(data) {
|
|
|
4223
4770
|
decision: data.decision,
|
|
4224
4771
|
source: "daemon"
|
|
4225
4772
|
};
|
|
4226
|
-
const dir =
|
|
4227
|
-
if (!
|
|
4228
|
-
|
|
4773
|
+
const dir = path15.dirname(AUDIT_LOG_FILE);
|
|
4774
|
+
if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
|
|
4775
|
+
fs12.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
4229
4776
|
} catch {
|
|
4230
4777
|
}
|
|
4231
4778
|
}
|
|
4232
4779
|
function getAuditHistory(limit = 20) {
|
|
4233
4780
|
try {
|
|
4234
|
-
if (!
|
|
4235
|
-
const lines =
|
|
4781
|
+
if (!fs12.existsSync(AUDIT_LOG_FILE)) return [];
|
|
4782
|
+
const lines = fs12.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
4236
4783
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4237
4784
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4238
4785
|
} catch {
|
|
@@ -4241,19 +4788,19 @@ function getAuditHistory(limit = 20) {
|
|
|
4241
4788
|
}
|
|
4242
4789
|
function getOrgName() {
|
|
4243
4790
|
try {
|
|
4244
|
-
if (
|
|
4791
|
+
if (fs12.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
4245
4792
|
} catch {
|
|
4246
4793
|
}
|
|
4247
4794
|
return null;
|
|
4248
4795
|
}
|
|
4249
4796
|
function hasStoredSlackKey() {
|
|
4250
|
-
return
|
|
4797
|
+
return fs12.existsSync(CREDENTIALS_FILE);
|
|
4251
4798
|
}
|
|
4252
4799
|
function writeGlobalSetting(key, value) {
|
|
4253
4800
|
let config = {};
|
|
4254
4801
|
try {
|
|
4255
|
-
if (
|
|
4256
|
-
config = JSON.parse(
|
|
4802
|
+
if (fs12.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
4803
|
+
config = JSON.parse(fs12.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
4257
4804
|
}
|
|
4258
4805
|
} catch {
|
|
4259
4806
|
}
|
|
@@ -4265,8 +4812,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4265
4812
|
try {
|
|
4266
4813
|
let trust = { entries: [] };
|
|
4267
4814
|
try {
|
|
4268
|
-
if (
|
|
4269
|
-
trust = JSON.parse(
|
|
4815
|
+
if (fs12.existsSync(TRUST_FILE2))
|
|
4816
|
+
trust = JSON.parse(fs12.readFileSync(TRUST_FILE2, "utf-8"));
|
|
4270
4817
|
} catch {
|
|
4271
4818
|
}
|
|
4272
4819
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -4277,8 +4824,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4277
4824
|
}
|
|
4278
4825
|
function readPersistentDecisions() {
|
|
4279
4826
|
try {
|
|
4280
|
-
if (
|
|
4281
|
-
return JSON.parse(
|
|
4827
|
+
if (fs12.existsSync(DECISIONS_FILE)) {
|
|
4828
|
+
return JSON.parse(fs12.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4282
4829
|
}
|
|
4283
4830
|
} catch {
|
|
4284
4831
|
}
|
|
@@ -4343,7 +4890,7 @@ function abandonPending() {
|
|
|
4343
4890
|
});
|
|
4344
4891
|
if (autoStarted) {
|
|
4345
4892
|
try {
|
|
4346
|
-
|
|
4893
|
+
fs12.unlinkSync(DAEMON_PID_FILE);
|
|
4347
4894
|
} catch {
|
|
4348
4895
|
}
|
|
4349
4896
|
setTimeout(() => {
|
|
@@ -4354,7 +4901,7 @@ function abandonPending() {
|
|
|
4354
4901
|
}
|
|
4355
4902
|
function startActivitySocket() {
|
|
4356
4903
|
try {
|
|
4357
|
-
|
|
4904
|
+
fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4358
4905
|
} catch {
|
|
4359
4906
|
}
|
|
4360
4907
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4396,7 +4943,7 @@ function startActivitySocket() {
|
|
|
4396
4943
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4397
4944
|
process.on("exit", () => {
|
|
4398
4945
|
try {
|
|
4399
|
-
|
|
4946
|
+
fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4400
4947
|
} catch {
|
|
4401
4948
|
}
|
|
4402
4949
|
});
|
|
@@ -4406,13 +4953,13 @@ var init_state2 = __esm({
|
|
|
4406
4953
|
"src/daemon/state.ts"() {
|
|
4407
4954
|
"use strict";
|
|
4408
4955
|
init_daemon();
|
|
4409
|
-
homeDir =
|
|
4410
|
-
DAEMON_PID_FILE =
|
|
4411
|
-
DECISIONS_FILE =
|
|
4412
|
-
AUDIT_LOG_FILE =
|
|
4413
|
-
TRUST_FILE2 =
|
|
4414
|
-
GLOBAL_CONFIG_FILE =
|
|
4415
|
-
CREDENTIALS_FILE =
|
|
4956
|
+
homeDir = os12.homedir();
|
|
4957
|
+
DAEMON_PID_FILE = path15.join(homeDir, ".node9", "daemon.pid");
|
|
4958
|
+
DECISIONS_FILE = path15.join(homeDir, ".node9", "decisions.json");
|
|
4959
|
+
AUDIT_LOG_FILE = path15.join(homeDir, ".node9", "audit.log");
|
|
4960
|
+
TRUST_FILE2 = path15.join(homeDir, ".node9", "trust.json");
|
|
4961
|
+
GLOBAL_CONFIG_FILE = path15.join(homeDir, ".node9", "config.json");
|
|
4962
|
+
CREDENTIALS_FILE = path15.join(homeDir, ".node9", "credentials.json");
|
|
4416
4963
|
pending = /* @__PURE__ */ new Map();
|
|
4417
4964
|
sseClients = /* @__PURE__ */ new Set();
|
|
4418
4965
|
_abandonTimer = null;
|
|
@@ -4426,7 +4973,7 @@ var init_state2 = __esm({
|
|
|
4426
4973
|
"2h": 2 * 60 * 6e4
|
|
4427
4974
|
};
|
|
4428
4975
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
4429
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
4976
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path15.join(os12.tmpdir(), "node9-activity.sock");
|
|
4430
4977
|
ACTIVITY_RING_SIZE = 100;
|
|
4431
4978
|
activityRing = [];
|
|
4432
4979
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -4435,8 +4982,8 @@ var init_state2 = __esm({
|
|
|
4435
4982
|
|
|
4436
4983
|
// src/daemon/server.ts
|
|
4437
4984
|
import http from "http";
|
|
4438
|
-
import
|
|
4439
|
-
import
|
|
4985
|
+
import fs13 from "fs";
|
|
4986
|
+
import path16 from "path";
|
|
4440
4987
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4441
4988
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4442
4989
|
import chalk2 from "chalk";
|
|
@@ -4455,7 +5002,7 @@ function startDaemon() {
|
|
|
4455
5002
|
idleTimer = setTimeout(() => {
|
|
4456
5003
|
if (autoStarted) {
|
|
4457
5004
|
try {
|
|
4458
|
-
|
|
5005
|
+
fs13.unlinkSync(DAEMON_PID_FILE);
|
|
4459
5006
|
} catch {
|
|
4460
5007
|
}
|
|
4461
5008
|
}
|
|
@@ -4597,7 +5144,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4597
5144
|
status: "pending"
|
|
4598
5145
|
});
|
|
4599
5146
|
}
|
|
4600
|
-
const projectCwd = typeof cwd === "string" &&
|
|
5147
|
+
const projectCwd = typeof cwd === "string" && path16.isAbsolute(cwd) ? cwd : void 0;
|
|
4601
5148
|
const projectConfig = getConfig(projectCwd);
|
|
4602
5149
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
4603
5150
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -4901,14 +5448,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4901
5448
|
server.on("error", (e) => {
|
|
4902
5449
|
if (e.code === "EADDRINUSE") {
|
|
4903
5450
|
try {
|
|
4904
|
-
if (
|
|
4905
|
-
const { pid } = JSON.parse(
|
|
5451
|
+
if (fs13.existsSync(DAEMON_PID_FILE)) {
|
|
5452
|
+
const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4906
5453
|
process.kill(pid, 0);
|
|
4907
5454
|
return process.exit(0);
|
|
4908
5455
|
}
|
|
4909
5456
|
} catch {
|
|
4910
5457
|
try {
|
|
4911
|
-
|
|
5458
|
+
fs13.unlinkSync(DAEMON_PID_FILE);
|
|
4912
5459
|
} catch {
|
|
4913
5460
|
}
|
|
4914
5461
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -4978,28 +5525,28 @@ var init_server = __esm({
|
|
|
4978
5525
|
});
|
|
4979
5526
|
|
|
4980
5527
|
// src/daemon/index.ts
|
|
4981
|
-
import
|
|
5528
|
+
import fs14 from "fs";
|
|
4982
5529
|
import chalk3 from "chalk";
|
|
4983
5530
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4984
5531
|
function stopDaemon() {
|
|
4985
|
-
if (!
|
|
5532
|
+
if (!fs14.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
|
|
4986
5533
|
try {
|
|
4987
|
-
const { pid } = JSON.parse(
|
|
5534
|
+
const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4988
5535
|
process.kill(pid, "SIGTERM");
|
|
4989
5536
|
console.log(chalk3.green("\u2705 Stopped."));
|
|
4990
5537
|
} catch {
|
|
4991
5538
|
console.log(chalk3.gray("Cleaned up stale PID file."));
|
|
4992
5539
|
} finally {
|
|
4993
5540
|
try {
|
|
4994
|
-
|
|
5541
|
+
fs14.unlinkSync(DAEMON_PID_FILE);
|
|
4995
5542
|
} catch {
|
|
4996
5543
|
}
|
|
4997
5544
|
}
|
|
4998
5545
|
}
|
|
4999
5546
|
function daemonStatus() {
|
|
5000
|
-
if (
|
|
5547
|
+
if (fs14.existsSync(DAEMON_PID_FILE)) {
|
|
5001
5548
|
try {
|
|
5002
|
-
const { pid } = JSON.parse(
|
|
5549
|
+
const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
5003
5550
|
process.kill(pid, 0);
|
|
5004
5551
|
console.log(chalk3.green("Node9 daemon: running"));
|
|
5005
5552
|
return;
|
|
@@ -5033,10 +5580,10 @@ __export(tail_exports, {
|
|
|
5033
5580
|
startTail: () => startTail
|
|
5034
5581
|
});
|
|
5035
5582
|
import http2 from "http";
|
|
5036
|
-
import
|
|
5037
|
-
import
|
|
5038
|
-
import
|
|
5039
|
-
import
|
|
5583
|
+
import chalk15 from "chalk";
|
|
5584
|
+
import fs21 from "fs";
|
|
5585
|
+
import os19 from "os";
|
|
5586
|
+
import path23 from "path";
|
|
5040
5587
|
import readline3 from "readline";
|
|
5041
5588
|
import { spawn as spawn9, execSync as execSync3 } from "child_process";
|
|
5042
5589
|
function getIcon(tool) {
|
|
@@ -5052,17 +5599,17 @@ function formatBase(activity) {
|
|
|
5052
5599
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
5053
5600
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
5054
5601
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
5055
|
-
return `${
|
|
5602
|
+
return `${chalk15.gray(time)} ${icon} ${chalk15.white.bold(toolName)} ${chalk15.dim(argsPreview)}`;
|
|
5056
5603
|
}
|
|
5057
5604
|
function renderResult(activity, result) {
|
|
5058
5605
|
const base = formatBase(activity);
|
|
5059
5606
|
let status;
|
|
5060
5607
|
if (result.status === "allow") {
|
|
5061
|
-
status =
|
|
5608
|
+
status = chalk15.green("\u2713 ALLOW");
|
|
5062
5609
|
} else if (result.status === "dlp") {
|
|
5063
|
-
status =
|
|
5610
|
+
status = chalk15.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
5064
5611
|
} else {
|
|
5065
|
-
status =
|
|
5612
|
+
status = chalk15.red("\u2717 BLOCK");
|
|
5066
5613
|
}
|
|
5067
5614
|
if (process.stdout.isTTY) {
|
|
5068
5615
|
readline3.clearLine(process.stdout, 0);
|
|
@@ -5072,16 +5619,16 @@ function renderResult(activity, result) {
|
|
|
5072
5619
|
}
|
|
5073
5620
|
function renderPending(activity) {
|
|
5074
5621
|
if (!process.stdout.isTTY) return;
|
|
5075
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
5622
|
+
process.stdout.write(`${formatBase(activity)} ${chalk15.yellow("\u25CF \u2026")}\r`);
|
|
5076
5623
|
}
|
|
5077
5624
|
async function ensureDaemon() {
|
|
5078
5625
|
let pidPort = null;
|
|
5079
|
-
if (
|
|
5626
|
+
if (fs21.existsSync(PID_FILE)) {
|
|
5080
5627
|
try {
|
|
5081
|
-
const { port } = JSON.parse(
|
|
5628
|
+
const { port } = JSON.parse(fs21.readFileSync(PID_FILE, "utf-8"));
|
|
5082
5629
|
pidPort = port;
|
|
5083
5630
|
} catch {
|
|
5084
|
-
console.error(
|
|
5631
|
+
console.error(chalk15.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
5085
5632
|
}
|
|
5086
5633
|
}
|
|
5087
5634
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -5092,7 +5639,7 @@ async function ensureDaemon() {
|
|
|
5092
5639
|
if (res.ok) return checkPort;
|
|
5093
5640
|
} catch {
|
|
5094
5641
|
}
|
|
5095
|
-
console.log(
|
|
5642
|
+
console.log(chalk15.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
5096
5643
|
const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
|
|
5097
5644
|
detached: true,
|
|
5098
5645
|
stdio: "ignore",
|
|
@@ -5109,7 +5656,7 @@ async function ensureDaemon() {
|
|
|
5109
5656
|
} catch {
|
|
5110
5657
|
}
|
|
5111
5658
|
}
|
|
5112
|
-
console.error(
|
|
5659
|
+
console.error(chalk15.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
5113
5660
|
process.exit(1);
|
|
5114
5661
|
}
|
|
5115
5662
|
function postDecisionHttp(id, decision, csrfToken, port) {
|
|
@@ -5180,7 +5727,7 @@ async function startTail(options = {}) {
|
|
|
5180
5727
|
req2.end();
|
|
5181
5728
|
});
|
|
5182
5729
|
if (result.ok) {
|
|
5183
|
-
console.log(
|
|
5730
|
+
console.log(chalk15.green("\u2713 Flight Recorder buffer cleared."));
|
|
5184
5731
|
} else if (result.code === "ECONNREFUSED") {
|
|
5185
5732
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
5186
5733
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -5243,16 +5790,16 @@ async function startTail(options = {}) {
|
|
|
5243
5790
|
process.stdout.write(SHOW_CURSOR);
|
|
5244
5791
|
postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
|
|
5245
5792
|
try {
|
|
5246
|
-
|
|
5247
|
-
|
|
5793
|
+
fs21.appendFileSync(
|
|
5794
|
+
path23.join(os19.homedir(), ".node9", "hook-debug.log"),
|
|
5248
5795
|
`[tail] POST /decision failed: ${String(err)}
|
|
5249
5796
|
`
|
|
5250
5797
|
);
|
|
5251
5798
|
} catch {
|
|
5252
5799
|
}
|
|
5253
5800
|
});
|
|
5254
|
-
const decisionLabel = decision === "allow" ?
|
|
5255
|
-
console.log(`${
|
|
5801
|
+
const decisionLabel = decision === "allow" ? chalk15.green("\u2713 ALLOWED (terminal)") : chalk15.red("\u2717 DENIED (terminal)");
|
|
5802
|
+
console.log(`${chalk15.cyan("\u25C6")} ${chalk15.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
|
|
5256
5803
|
approvalQueue.shift();
|
|
5257
5804
|
cardActive = false;
|
|
5258
5805
|
showNextCard();
|
|
@@ -5289,16 +5836,16 @@ async function startTail(options = {}) {
|
|
|
5289
5836
|
}
|
|
5290
5837
|
} catch {
|
|
5291
5838
|
}
|
|
5292
|
-
console.log(
|
|
5293
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
5839
|
+
console.log(chalk15.cyan.bold(`
|
|
5840
|
+
\u{1F6F0}\uFE0F Node9 tail `) + chalk15.dim(`\u2192 ${dashboardUrl}`));
|
|
5294
5841
|
if (canApprove) {
|
|
5295
|
-
console.log(
|
|
5842
|
+
console.log(chalk15.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
|
|
5296
5843
|
}
|
|
5297
5844
|
if (options.history) {
|
|
5298
|
-
console.log(
|
|
5845
|
+
console.log(chalk15.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
5299
5846
|
} else {
|
|
5300
5847
|
console.log(
|
|
5301
|
-
|
|
5848
|
+
chalk15.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
5302
5849
|
);
|
|
5303
5850
|
}
|
|
5304
5851
|
process.on("SIGINT", () => {
|
|
@@ -5308,13 +5855,13 @@ async function startTail(options = {}) {
|
|
|
5308
5855
|
readline3.clearLine(process.stdout, 0);
|
|
5309
5856
|
readline3.cursorTo(process.stdout, 0);
|
|
5310
5857
|
}
|
|
5311
|
-
console.log(
|
|
5858
|
+
console.log(chalk15.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
5312
5859
|
process.exit(0);
|
|
5313
5860
|
});
|
|
5314
5861
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
5315
5862
|
const req = http2.get(sseUrl, (res) => {
|
|
5316
5863
|
if (res.statusCode !== 200) {
|
|
5317
|
-
console.error(
|
|
5864
|
+
console.error(chalk15.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
5318
5865
|
process.exit(1);
|
|
5319
5866
|
}
|
|
5320
5867
|
let currentEvent = "";
|
|
@@ -5344,7 +5891,7 @@ async function startTail(options = {}) {
|
|
|
5344
5891
|
readline3.clearLine(process.stdout, 0);
|
|
5345
5892
|
readline3.cursorTo(process.stdout, 0);
|
|
5346
5893
|
}
|
|
5347
|
-
console.log(
|
|
5894
|
+
console.log(chalk15.red("\n\u274C Daemon disconnected."));
|
|
5348
5895
|
process.exit(1);
|
|
5349
5896
|
});
|
|
5350
5897
|
});
|
|
@@ -5424,7 +5971,7 @@ async function startTail(options = {}) {
|
|
|
5424
5971
|
}
|
|
5425
5972
|
req.on("error", (err) => {
|
|
5426
5973
|
const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
|
|
5427
|
-
console.error(
|
|
5974
|
+
console.error(chalk15.red(`
|
|
5428
5975
|
\u274C ${msg}`));
|
|
5429
5976
|
process.exit(1);
|
|
5430
5977
|
});
|
|
@@ -5435,7 +5982,7 @@ var init_tail = __esm({
|
|
|
5435
5982
|
"use strict";
|
|
5436
5983
|
init_daemon2();
|
|
5437
5984
|
init_core();
|
|
5438
|
-
PID_FILE =
|
|
5985
|
+
PID_FILE = path23.join(os19.homedir(), ".node9", "daemon.pid");
|
|
5439
5986
|
ICONS = {
|
|
5440
5987
|
bash: "\u{1F4BB}",
|
|
5441
5988
|
shell: "\u{1F4BB}",
|
|
@@ -5473,9 +6020,9 @@ init_core();
|
|
|
5473
6020
|
import { Command } from "commander";
|
|
5474
6021
|
|
|
5475
6022
|
// src/setup.ts
|
|
5476
|
-
import
|
|
5477
|
-
import
|
|
5478
|
-
import
|
|
6023
|
+
import fs11 from "fs";
|
|
6024
|
+
import path14 from "path";
|
|
6025
|
+
import os11 from "os";
|
|
5479
6026
|
import chalk from "chalk";
|
|
5480
6027
|
import { confirm } from "@inquirer/prompts";
|
|
5481
6028
|
function printDaemonTip() {
|
|
@@ -5492,26 +6039,26 @@ function fullPathCommand(subcommand) {
|
|
|
5492
6039
|
}
|
|
5493
6040
|
function readJson(filePath) {
|
|
5494
6041
|
try {
|
|
5495
|
-
if (
|
|
5496
|
-
return JSON.parse(
|
|
6042
|
+
if (fs11.existsSync(filePath)) {
|
|
6043
|
+
return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
|
|
5497
6044
|
}
|
|
5498
6045
|
} catch {
|
|
5499
6046
|
}
|
|
5500
6047
|
return null;
|
|
5501
6048
|
}
|
|
5502
6049
|
function writeJson(filePath, data) {
|
|
5503
|
-
const dir =
|
|
5504
|
-
if (!
|
|
5505
|
-
|
|
6050
|
+
const dir = path14.dirname(filePath);
|
|
6051
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
6052
|
+
fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
5506
6053
|
}
|
|
5507
6054
|
function isNode9Hook(cmd) {
|
|
5508
6055
|
if (!cmd) return false;
|
|
5509
6056
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
5510
6057
|
}
|
|
5511
6058
|
function teardownClaude() {
|
|
5512
|
-
const homeDir2 =
|
|
5513
|
-
const hooksPath =
|
|
5514
|
-
const mcpPath =
|
|
6059
|
+
const homeDir2 = os11.homedir();
|
|
6060
|
+
const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
|
|
6061
|
+
const mcpPath = path14.join(homeDir2, ".claude.json");
|
|
5515
6062
|
let changed = false;
|
|
5516
6063
|
const settings = readJson(hooksPath);
|
|
5517
6064
|
if (settings?.hooks) {
|
|
@@ -5559,8 +6106,8 @@ function teardownClaude() {
|
|
|
5559
6106
|
}
|
|
5560
6107
|
}
|
|
5561
6108
|
function teardownGemini() {
|
|
5562
|
-
const homeDir2 =
|
|
5563
|
-
const settingsPath =
|
|
6109
|
+
const homeDir2 = os11.homedir();
|
|
6110
|
+
const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
|
|
5564
6111
|
const settings = readJson(settingsPath);
|
|
5565
6112
|
if (!settings) {
|
|
5566
6113
|
console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5598,8 +6145,8 @@ function teardownGemini() {
|
|
|
5598
6145
|
}
|
|
5599
6146
|
}
|
|
5600
6147
|
function teardownCursor() {
|
|
5601
|
-
const homeDir2 =
|
|
5602
|
-
const mcpPath =
|
|
6148
|
+
const homeDir2 = os11.homedir();
|
|
6149
|
+
const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
|
|
5603
6150
|
const mcpConfig = readJson(mcpPath);
|
|
5604
6151
|
if (!mcpConfig?.mcpServers) {
|
|
5605
6152
|
console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5625,9 +6172,9 @@ function teardownCursor() {
|
|
|
5625
6172
|
}
|
|
5626
6173
|
}
|
|
5627
6174
|
async function setupClaude() {
|
|
5628
|
-
const homeDir2 =
|
|
5629
|
-
const mcpPath =
|
|
5630
|
-
const hooksPath =
|
|
6175
|
+
const homeDir2 = os11.homedir();
|
|
6176
|
+
const mcpPath = path14.join(homeDir2, ".claude.json");
|
|
6177
|
+
const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
|
|
5631
6178
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5632
6179
|
const settings = readJson(hooksPath) ?? {};
|
|
5633
6180
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5701,8 +6248,8 @@ async function setupClaude() {
|
|
|
5701
6248
|
}
|
|
5702
6249
|
}
|
|
5703
6250
|
async function setupGemini() {
|
|
5704
|
-
const homeDir2 =
|
|
5705
|
-
const settingsPath =
|
|
6251
|
+
const homeDir2 = os11.homedir();
|
|
6252
|
+
const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
|
|
5706
6253
|
const settings = readJson(settingsPath) ?? {};
|
|
5707
6254
|
const servers = settings.mcpServers ?? {};
|
|
5708
6255
|
let anythingChanged = false;
|
|
@@ -5784,8 +6331,8 @@ async function setupGemini() {
|
|
|
5784
6331
|
}
|
|
5785
6332
|
}
|
|
5786
6333
|
async function setupCursor() {
|
|
5787
|
-
const homeDir2 =
|
|
5788
|
-
const mcpPath =
|
|
6334
|
+
const homeDir2 = os11.homedir();
|
|
6335
|
+
const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
|
|
5789
6336
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5790
6337
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5791
6338
|
let anythingChanged = false;
|
|
@@ -5841,10 +6388,10 @@ async function setupCursor() {
|
|
|
5841
6388
|
|
|
5842
6389
|
// src/cli.ts
|
|
5843
6390
|
init_daemon2();
|
|
5844
|
-
import
|
|
5845
|
-
import
|
|
5846
|
-
import
|
|
5847
|
-
import
|
|
6391
|
+
import chalk16 from "chalk";
|
|
6392
|
+
import fs22 from "fs";
|
|
6393
|
+
import path24 from "path";
|
|
6394
|
+
import os20 from "os";
|
|
5848
6395
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
5849
6396
|
|
|
5850
6397
|
// src/utils/duration.ts
|
|
@@ -6069,32 +6616,32 @@ init_daemon();
|
|
|
6069
6616
|
init_config();
|
|
6070
6617
|
init_policy();
|
|
6071
6618
|
import chalk5 from "chalk";
|
|
6072
|
-
import
|
|
6073
|
-
import
|
|
6074
|
-
import
|
|
6619
|
+
import fs16 from "fs";
|
|
6620
|
+
import path18 from "path";
|
|
6621
|
+
import os14 from "os";
|
|
6075
6622
|
|
|
6076
6623
|
// src/undo.ts
|
|
6077
6624
|
import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
|
|
6078
6625
|
import crypto2 from "crypto";
|
|
6079
|
-
import
|
|
6080
|
-
import
|
|
6081
|
-
import
|
|
6082
|
-
var SNAPSHOT_STACK_PATH =
|
|
6083
|
-
var UNDO_LATEST_PATH =
|
|
6626
|
+
import fs15 from "fs";
|
|
6627
|
+
import path17 from "path";
|
|
6628
|
+
import os13 from "os";
|
|
6629
|
+
var SNAPSHOT_STACK_PATH = path17.join(os13.homedir(), ".node9", "snapshots.json");
|
|
6630
|
+
var UNDO_LATEST_PATH = path17.join(os13.homedir(), ".node9", "undo_latest.txt");
|
|
6084
6631
|
var MAX_SNAPSHOTS = 10;
|
|
6085
6632
|
var GIT_TIMEOUT = 15e3;
|
|
6086
6633
|
function readStack() {
|
|
6087
6634
|
try {
|
|
6088
|
-
if (
|
|
6089
|
-
return JSON.parse(
|
|
6635
|
+
if (fs15.existsSync(SNAPSHOT_STACK_PATH))
|
|
6636
|
+
return JSON.parse(fs15.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
6090
6637
|
} catch {
|
|
6091
6638
|
}
|
|
6092
6639
|
return [];
|
|
6093
6640
|
}
|
|
6094
6641
|
function writeStack(stack) {
|
|
6095
|
-
const dir =
|
|
6096
|
-
if (!
|
|
6097
|
-
|
|
6642
|
+
const dir = path17.dirname(SNAPSHOT_STACK_PATH);
|
|
6643
|
+
if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
|
|
6644
|
+
fs15.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
6098
6645
|
}
|
|
6099
6646
|
function buildArgsSummary(tool, args) {
|
|
6100
6647
|
if (!args || typeof args !== "object") return "";
|
|
@@ -6110,7 +6657,7 @@ function buildArgsSummary(tool, args) {
|
|
|
6110
6657
|
function normalizeCwdForHash(cwd) {
|
|
6111
6658
|
let normalized;
|
|
6112
6659
|
try {
|
|
6113
|
-
normalized =
|
|
6660
|
+
normalized = fs15.realpathSync(cwd);
|
|
6114
6661
|
} catch {
|
|
6115
6662
|
normalized = cwd;
|
|
6116
6663
|
}
|
|
@@ -6120,16 +6667,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
6120
6667
|
}
|
|
6121
6668
|
function getShadowRepoDir(cwd) {
|
|
6122
6669
|
const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
6123
|
-
return
|
|
6670
|
+
return path17.join(os13.homedir(), ".node9", "snapshots", hash);
|
|
6124
6671
|
}
|
|
6125
6672
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
6126
6673
|
try {
|
|
6127
6674
|
const cutoff = Date.now() - 6e4;
|
|
6128
|
-
for (const f of
|
|
6675
|
+
for (const f of fs15.readdirSync(shadowDir)) {
|
|
6129
6676
|
if (f.startsWith("index_")) {
|
|
6130
|
-
const fp =
|
|
6677
|
+
const fp = path17.join(shadowDir, f);
|
|
6131
6678
|
try {
|
|
6132
|
-
if (
|
|
6679
|
+
if (fs15.statSync(fp).mtimeMs < cutoff) fs15.unlinkSync(fp);
|
|
6133
6680
|
} catch {
|
|
6134
6681
|
}
|
|
6135
6682
|
}
|
|
@@ -6141,7 +6688,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
6141
6688
|
const hardcoded = [".git", ".node9"];
|
|
6142
6689
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
6143
6690
|
try {
|
|
6144
|
-
|
|
6691
|
+
fs15.writeFileSync(path17.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
6145
6692
|
} catch {
|
|
6146
6693
|
}
|
|
6147
6694
|
}
|
|
@@ -6154,25 +6701,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6154
6701
|
timeout: 3e3
|
|
6155
6702
|
});
|
|
6156
6703
|
if (check.status === 0) {
|
|
6157
|
-
const ptPath =
|
|
6704
|
+
const ptPath = path17.join(shadowDir, "project-path.txt");
|
|
6158
6705
|
try {
|
|
6159
|
-
const stored =
|
|
6706
|
+
const stored = fs15.readFileSync(ptPath, "utf8").trim();
|
|
6160
6707
|
if (stored === normalizedCwd) return true;
|
|
6161
6708
|
if (process.env.NODE9_DEBUG === "1")
|
|
6162
6709
|
console.error(
|
|
6163
6710
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
6164
6711
|
);
|
|
6165
|
-
|
|
6712
|
+
fs15.rmSync(shadowDir, { recursive: true, force: true });
|
|
6166
6713
|
} catch {
|
|
6167
6714
|
try {
|
|
6168
|
-
|
|
6715
|
+
fs15.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
6169
6716
|
} catch {
|
|
6170
6717
|
}
|
|
6171
6718
|
return true;
|
|
6172
6719
|
}
|
|
6173
6720
|
}
|
|
6174
6721
|
try {
|
|
6175
|
-
|
|
6722
|
+
fs15.mkdirSync(shadowDir, { recursive: true });
|
|
6176
6723
|
} catch {
|
|
6177
6724
|
}
|
|
6178
6725
|
const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -6181,7 +6728,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6181
6728
|
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
6182
6729
|
return false;
|
|
6183
6730
|
}
|
|
6184
|
-
const configFile =
|
|
6731
|
+
const configFile = path17.join(shadowDir, "config");
|
|
6185
6732
|
spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
6186
6733
|
timeout: 3e3
|
|
6187
6734
|
});
|
|
@@ -6189,7 +6736,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6189
6736
|
timeout: 3e3
|
|
6190
6737
|
});
|
|
6191
6738
|
try {
|
|
6192
|
-
|
|
6739
|
+
fs15.writeFileSync(path17.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
6193
6740
|
} catch {
|
|
6194
6741
|
}
|
|
6195
6742
|
return true;
|
|
@@ -6212,7 +6759,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6212
6759
|
const shadowDir = getShadowRepoDir(cwd);
|
|
6213
6760
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
6214
6761
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
6215
|
-
indexFile =
|
|
6762
|
+
indexFile = path17.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
6216
6763
|
const shadowEnv = {
|
|
6217
6764
|
...process.env,
|
|
6218
6765
|
GIT_DIR: shadowDir,
|
|
@@ -6241,7 +6788,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6241
6788
|
const shouldGc = stack.length % 5 === 0;
|
|
6242
6789
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
6243
6790
|
writeStack(stack);
|
|
6244
|
-
|
|
6791
|
+
fs15.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
6245
6792
|
if (shouldGc) {
|
|
6246
6793
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
6247
6794
|
}
|
|
@@ -6252,7 +6799,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6252
6799
|
} finally {
|
|
6253
6800
|
if (indexFile) {
|
|
6254
6801
|
try {
|
|
6255
|
-
|
|
6802
|
+
fs15.unlinkSync(indexFile);
|
|
6256
6803
|
} catch {
|
|
6257
6804
|
}
|
|
6258
6805
|
}
|
|
@@ -6321,9 +6868,9 @@ function applyUndo(hash, cwd) {
|
|
|
6321
6868
|
timeout: GIT_TIMEOUT
|
|
6322
6869
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
6323
6870
|
for (const file of [...tracked, ...untracked]) {
|
|
6324
|
-
const fullPath =
|
|
6325
|
-
if (!snapshotFiles.has(file) &&
|
|
6326
|
-
|
|
6871
|
+
const fullPath = path17.join(dir, file);
|
|
6872
|
+
if (!snapshotFiles.has(file) && fs15.existsSync(fullPath)) {
|
|
6873
|
+
fs15.unlinkSync(fullPath);
|
|
6327
6874
|
}
|
|
6328
6875
|
}
|
|
6329
6876
|
return true;
|
|
@@ -6347,9 +6894,9 @@ function registerCheckCommand(program2) {
|
|
|
6347
6894
|
} catch (err) {
|
|
6348
6895
|
const tempConfig = getConfig();
|
|
6349
6896
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6350
|
-
const logPath =
|
|
6897
|
+
const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
|
|
6351
6898
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6352
|
-
|
|
6899
|
+
fs16.appendFileSync(
|
|
6353
6900
|
logPath,
|
|
6354
6901
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6355
6902
|
RAW: ${raw}
|
|
@@ -6360,10 +6907,10 @@ RAW: ${raw}
|
|
|
6360
6907
|
}
|
|
6361
6908
|
const config = getConfig(payload.cwd || void 0);
|
|
6362
6909
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6363
|
-
const logPath =
|
|
6364
|
-
if (!
|
|
6365
|
-
|
|
6366
|
-
|
|
6910
|
+
const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
|
|
6911
|
+
if (!fs16.existsSync(path18.dirname(logPath)))
|
|
6912
|
+
fs16.mkdirSync(path18.dirname(logPath), { recursive: true });
|
|
6913
|
+
fs16.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
6367
6914
|
`);
|
|
6368
6915
|
}
|
|
6369
6916
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6376,8 +6923,8 @@ RAW: ${raw}
|
|
|
6376
6923
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
6377
6924
|
let ttyFd = null;
|
|
6378
6925
|
try {
|
|
6379
|
-
ttyFd =
|
|
6380
|
-
const writeTty = (line) =>
|
|
6926
|
+
ttyFd = fs16.openSync("/dev/tty", "w");
|
|
6927
|
+
const writeTty = (line) => fs16.writeSync(ttyFd, line + "\n");
|
|
6381
6928
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
6382
6929
|
writeTty(chalk5.bgRed.white.bold(`
|
|
6383
6930
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -6393,7 +6940,7 @@ RAW: ${raw}
|
|
|
6393
6940
|
} finally {
|
|
6394
6941
|
if (ttyFd !== null)
|
|
6395
6942
|
try {
|
|
6396
|
-
|
|
6943
|
+
fs16.closeSync(ttyFd);
|
|
6397
6944
|
} catch {
|
|
6398
6945
|
}
|
|
6399
6946
|
}
|
|
@@ -6424,7 +6971,7 @@ RAW: ${raw}
|
|
|
6424
6971
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6425
6972
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6426
6973
|
}
|
|
6427
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
6974
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path18.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6428
6975
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
6429
6976
|
cwd: safeCwdForAuth
|
|
6430
6977
|
});
|
|
@@ -6436,12 +6983,12 @@ RAW: ${raw}
|
|
|
6436
6983
|
}
|
|
6437
6984
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
6438
6985
|
try {
|
|
6439
|
-
const tty =
|
|
6440
|
-
|
|
6986
|
+
const tty = fs16.openSync("/dev/tty", "w");
|
|
6987
|
+
fs16.writeSync(
|
|
6441
6988
|
tty,
|
|
6442
6989
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
6443
6990
|
);
|
|
6444
|
-
|
|
6991
|
+
fs16.closeSync(tty);
|
|
6445
6992
|
} catch {
|
|
6446
6993
|
}
|
|
6447
6994
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -6468,9 +7015,9 @@ RAW: ${raw}
|
|
|
6468
7015
|
});
|
|
6469
7016
|
} catch (err) {
|
|
6470
7017
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6471
|
-
const logPath =
|
|
7018
|
+
const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
|
|
6472
7019
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6473
|
-
|
|
7020
|
+
fs16.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6474
7021
|
`);
|
|
6475
7022
|
}
|
|
6476
7023
|
process.exit(0);
|
|
@@ -6507,9 +7054,9 @@ RAW: ${raw}
|
|
|
6507
7054
|
init_audit();
|
|
6508
7055
|
init_config();
|
|
6509
7056
|
init_policy();
|
|
6510
|
-
import
|
|
6511
|
-
import
|
|
6512
|
-
import
|
|
7057
|
+
import fs17 from "fs";
|
|
7058
|
+
import path19 from "path";
|
|
7059
|
+
import os15 from "os";
|
|
6513
7060
|
function sanitize3(value) {
|
|
6514
7061
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
6515
7062
|
}
|
|
@@ -6528,11 +7075,11 @@ function registerLogCommand(program2) {
|
|
|
6528
7075
|
decision: "allowed",
|
|
6529
7076
|
source: "post-hook"
|
|
6530
7077
|
};
|
|
6531
|
-
const logPath =
|
|
6532
|
-
if (!
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
7078
|
+
const logPath = path19.join(os15.homedir(), ".node9", "audit.log");
|
|
7079
|
+
if (!fs17.existsSync(path19.dirname(logPath)))
|
|
7080
|
+
fs17.mkdirSync(path19.dirname(logPath), { recursive: true });
|
|
7081
|
+
fs17.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
7082
|
+
const safeCwd = typeof payload.cwd === "string" && path19.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6536
7083
|
const config = getConfig(safeCwd);
|
|
6537
7084
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6538
7085
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -6541,9 +7088,9 @@ function registerLogCommand(program2) {
|
|
|
6541
7088
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6542
7089
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
6543
7090
|
`);
|
|
6544
|
-
const debugPath =
|
|
7091
|
+
const debugPath = path19.join(os15.homedir(), ".node9", "hook-debug.log");
|
|
6545
7092
|
try {
|
|
6546
|
-
|
|
7093
|
+
fs17.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
6547
7094
|
`);
|
|
6548
7095
|
} catch {
|
|
6549
7096
|
}
|
|
@@ -6848,13 +7395,13 @@ function registerConfigShowCommand(program2) {
|
|
|
6848
7395
|
// src/cli/commands/doctor.ts
|
|
6849
7396
|
init_daemon();
|
|
6850
7397
|
import chalk7 from "chalk";
|
|
6851
|
-
import
|
|
6852
|
-
import
|
|
6853
|
-
import
|
|
7398
|
+
import fs18 from "fs";
|
|
7399
|
+
import path20 from "path";
|
|
7400
|
+
import os16 from "os";
|
|
6854
7401
|
import { execSync as execSync2 } from "child_process";
|
|
6855
7402
|
function registerDoctorCommand(program2, version2) {
|
|
6856
7403
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
6857
|
-
const homeDir2 =
|
|
7404
|
+
const homeDir2 = os16.homedir();
|
|
6858
7405
|
let failures = 0;
|
|
6859
7406
|
function pass(msg) {
|
|
6860
7407
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -6903,10 +7450,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6903
7450
|
);
|
|
6904
7451
|
}
|
|
6905
7452
|
section("Configuration");
|
|
6906
|
-
const globalConfigPath =
|
|
6907
|
-
if (
|
|
7453
|
+
const globalConfigPath = path20.join(homeDir2, ".node9", "config.json");
|
|
7454
|
+
if (fs18.existsSync(globalConfigPath)) {
|
|
6908
7455
|
try {
|
|
6909
|
-
JSON.parse(
|
|
7456
|
+
JSON.parse(fs18.readFileSync(globalConfigPath, "utf-8"));
|
|
6910
7457
|
pass("~/.node9/config.json found and valid");
|
|
6911
7458
|
} catch {
|
|
6912
7459
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -6914,10 +7461,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6914
7461
|
} else {
|
|
6915
7462
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
6916
7463
|
}
|
|
6917
|
-
const projectConfigPath =
|
|
6918
|
-
if (
|
|
7464
|
+
const projectConfigPath = path20.join(process.cwd(), "node9.config.json");
|
|
7465
|
+
if (fs18.existsSync(projectConfigPath)) {
|
|
6919
7466
|
try {
|
|
6920
|
-
JSON.parse(
|
|
7467
|
+
JSON.parse(fs18.readFileSync(projectConfigPath, "utf-8"));
|
|
6921
7468
|
pass("node9.config.json found and valid (project)");
|
|
6922
7469
|
} catch {
|
|
6923
7470
|
fail(
|
|
@@ -6926,8 +7473,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6926
7473
|
);
|
|
6927
7474
|
}
|
|
6928
7475
|
}
|
|
6929
|
-
const credsPath =
|
|
6930
|
-
if (
|
|
7476
|
+
const credsPath = path20.join(homeDir2, ".node9", "credentials.json");
|
|
7477
|
+
if (fs18.existsSync(credsPath)) {
|
|
6931
7478
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
6932
7479
|
} else {
|
|
6933
7480
|
warn(
|
|
@@ -6936,10 +7483,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6936
7483
|
);
|
|
6937
7484
|
}
|
|
6938
7485
|
section("Agent Hooks");
|
|
6939
|
-
const claudeSettingsPath =
|
|
6940
|
-
if (
|
|
7486
|
+
const claudeSettingsPath = path20.join(homeDir2, ".claude", "settings.json");
|
|
7487
|
+
if (fs18.existsSync(claudeSettingsPath)) {
|
|
6941
7488
|
try {
|
|
6942
|
-
const cs = JSON.parse(
|
|
7489
|
+
const cs = JSON.parse(fs18.readFileSync(claudeSettingsPath, "utf-8"));
|
|
6943
7490
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
6944
7491
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6945
7492
|
);
|
|
@@ -6955,10 +7502,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6955
7502
|
} else {
|
|
6956
7503
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
6957
7504
|
}
|
|
6958
|
-
const geminiSettingsPath =
|
|
6959
|
-
if (
|
|
7505
|
+
const geminiSettingsPath = path20.join(homeDir2, ".gemini", "settings.json");
|
|
7506
|
+
if (fs18.existsSync(geminiSettingsPath)) {
|
|
6960
7507
|
try {
|
|
6961
|
-
const gs = JSON.parse(
|
|
7508
|
+
const gs = JSON.parse(fs18.readFileSync(geminiSettingsPath, "utf-8"));
|
|
6962
7509
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
6963
7510
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6964
7511
|
);
|
|
@@ -6974,10 +7521,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6974
7521
|
} else {
|
|
6975
7522
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
6976
7523
|
}
|
|
6977
|
-
const cursorHooksPath =
|
|
6978
|
-
if (
|
|
7524
|
+
const cursorHooksPath = path20.join(homeDir2, ".cursor", "hooks.json");
|
|
7525
|
+
if (fs18.existsSync(cursorHooksPath)) {
|
|
6979
7526
|
try {
|
|
6980
|
-
const cur = JSON.parse(
|
|
7527
|
+
const cur = JSON.parse(fs18.readFileSync(cursorHooksPath, "utf-8"));
|
|
6981
7528
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
6982
7529
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
6983
7530
|
);
|
|
@@ -7015,9 +7562,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
7015
7562
|
|
|
7016
7563
|
// src/cli/commands/audit.ts
|
|
7017
7564
|
import chalk8 from "chalk";
|
|
7018
|
-
import
|
|
7019
|
-
import
|
|
7020
|
-
import
|
|
7565
|
+
import fs19 from "fs";
|
|
7566
|
+
import path21 from "path";
|
|
7567
|
+
import os17 from "os";
|
|
7021
7568
|
function formatRelativeTime(timestamp) {
|
|
7022
7569
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
7023
7570
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -7030,14 +7577,14 @@ function formatRelativeTime(timestamp) {
|
|
|
7030
7577
|
}
|
|
7031
7578
|
function registerAuditCommand(program2) {
|
|
7032
7579
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
7033
|
-
const logPath =
|
|
7034
|
-
if (!
|
|
7580
|
+
const logPath = path21.join(os17.homedir(), ".node9", "audit.log");
|
|
7581
|
+
if (!fs19.existsSync(logPath)) {
|
|
7035
7582
|
console.log(
|
|
7036
7583
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
7037
7584
|
);
|
|
7038
7585
|
return;
|
|
7039
7586
|
}
|
|
7040
|
-
const raw =
|
|
7587
|
+
const raw = fs19.readFileSync(logPath, "utf-8");
|
|
7041
7588
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
7042
7589
|
let entries = lines.flatMap((line) => {
|
|
7043
7590
|
try {
|
|
@@ -7157,9 +7704,9 @@ function registerDaemonCommand(program2) {
|
|
|
7157
7704
|
init_core();
|
|
7158
7705
|
init_daemon();
|
|
7159
7706
|
import chalk10 from "chalk";
|
|
7160
|
-
import
|
|
7161
|
-
import
|
|
7162
|
-
import
|
|
7707
|
+
import fs20 from "fs";
|
|
7708
|
+
import path22 from "path";
|
|
7709
|
+
import os18 from "os";
|
|
7163
7710
|
function registerStatusCommand(program2) {
|
|
7164
7711
|
program2.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
|
|
7165
7712
|
const creds = getCredentials();
|
|
@@ -7194,13 +7741,13 @@ function registerStatusCommand(program2) {
|
|
|
7194
7741
|
console.log("");
|
|
7195
7742
|
const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
|
|
7196
7743
|
console.log(` Mode: ${modeLabel}`);
|
|
7197
|
-
const projectConfig =
|
|
7198
|
-
const globalConfig =
|
|
7744
|
+
const projectConfig = path22.join(process.cwd(), "node9.config.json");
|
|
7745
|
+
const globalConfig = path22.join(os18.homedir(), ".node9", "config.json");
|
|
7199
7746
|
console.log(
|
|
7200
|
-
` Local: ${
|
|
7747
|
+
` Local: ${fs20.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
|
|
7201
7748
|
);
|
|
7202
7749
|
console.log(
|
|
7203
|
-
` Global: ${
|
|
7750
|
+
` Global: ${fs20.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
|
|
7204
7751
|
);
|
|
7205
7752
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
7206
7753
|
console.log(
|
|
@@ -7379,6 +7926,7 @@ import readline2 from "readline";
|
|
|
7379
7926
|
import chalk13 from "chalk";
|
|
7380
7927
|
import { spawn as spawn8 } from "child_process";
|
|
7381
7928
|
import { execa as execa2 } from "execa";
|
|
7929
|
+
init_provenance();
|
|
7382
7930
|
function sanitize4(value) {
|
|
7383
7931
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
7384
7932
|
}
|
|
@@ -7391,7 +7939,7 @@ function extractMcpServer(toolName) {
|
|
|
7391
7939
|
const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
7392
7940
|
return match?.[1];
|
|
7393
7941
|
}
|
|
7394
|
-
function
|
|
7942
|
+
function tokenize3(cmd) {
|
|
7395
7943
|
const tokens = [];
|
|
7396
7944
|
let current = "";
|
|
7397
7945
|
let inDouble = false;
|
|
@@ -7426,7 +7974,7 @@ function tokenize2(cmd) {
|
|
|
7426
7974
|
return tokens;
|
|
7427
7975
|
}
|
|
7428
7976
|
async function runMcpGateway(upstreamCommand) {
|
|
7429
|
-
const commandParts =
|
|
7977
|
+
const commandParts = tokenize3(upstreamCommand);
|
|
7430
7978
|
const cmd = commandParts[0];
|
|
7431
7979
|
const cmdArgs = commandParts.slice(1);
|
|
7432
7980
|
let executable = cmd;
|
|
@@ -7435,6 +7983,15 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
7435
7983
|
if (stdout) executable = stdout.trim();
|
|
7436
7984
|
} catch {
|
|
7437
7985
|
}
|
|
7986
|
+
const prov = checkProvenance(executable);
|
|
7987
|
+
if (prov.trustLevel === "suspect") {
|
|
7988
|
+
console.error(
|
|
7989
|
+
chalk13.red(
|
|
7990
|
+
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
7991
|
+
)
|
|
7992
|
+
);
|
|
7993
|
+
console.error(chalk13.red(" Verify this binary is trusted before proceeding."));
|
|
7994
|
+
}
|
|
7438
7995
|
console.error(chalk13.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
7439
7996
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
7440
7997
|
"NODE_OPTIONS",
|
|
@@ -7574,22 +8131,77 @@ function registerMcpGatewayCommand(program2) {
|
|
|
7574
8131
|
});
|
|
7575
8132
|
}
|
|
7576
8133
|
|
|
8134
|
+
// src/cli/commands/trust.ts
|
|
8135
|
+
init_trusted_hosts();
|
|
8136
|
+
import chalk14 from "chalk";
|
|
8137
|
+
function isValidHost(host) {
|
|
8138
|
+
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
8139
|
+
}
|
|
8140
|
+
function registerTrustCommand(program2) {
|
|
8141
|
+
const trustCmd = program2.command("trust").description("Manage trusted network hosts (reduces approval friction for known destinations)");
|
|
8142
|
+
trustCmd.command("add <host>").description("Add a trusted host \u2014 pipe-chain blocks targeting this host are downgraded").action((host) => {
|
|
8143
|
+
const normalized = normalizeHost(host.trim());
|
|
8144
|
+
if (!isValidHost(normalized)) {
|
|
8145
|
+
console.error(
|
|
8146
|
+
chalk14.red(`
|
|
8147
|
+
\u274C Invalid host: "${host}"
|
|
8148
|
+
`) + chalk14.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
8149
|
+
);
|
|
8150
|
+
process.exit(1);
|
|
8151
|
+
}
|
|
8152
|
+
addTrustedHost(normalized);
|
|
8153
|
+
console.log(chalk14.green(`
|
|
8154
|
+
\u2705 ${normalized} added to trusted hosts.`));
|
|
8155
|
+
console.log(
|
|
8156
|
+
chalk14.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
8157
|
+
);
|
|
8158
|
+
});
|
|
8159
|
+
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
8160
|
+
const normalized = normalizeHost(host.trim());
|
|
8161
|
+
const removed = removeTrustedHost(normalized);
|
|
8162
|
+
if (!removed) {
|
|
8163
|
+
console.error(chalk14.yellow(`
|
|
8164
|
+
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
8165
|
+
`));
|
|
8166
|
+
process.exit(1);
|
|
8167
|
+
}
|
|
8168
|
+
console.log(chalk14.green(`
|
|
8169
|
+
\u2705 ${normalized} removed from trusted hosts.
|
|
8170
|
+
`));
|
|
8171
|
+
});
|
|
8172
|
+
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
8173
|
+
const hosts = readTrustedHosts();
|
|
8174
|
+
if (hosts.length === 0) {
|
|
8175
|
+
console.log(chalk14.gray("\n No trusted hosts configured.\n"));
|
|
8176
|
+
console.log(` Add one: ${chalk14.cyan("node9 trust add api.mycompany.com")}
|
|
8177
|
+
`);
|
|
8178
|
+
return;
|
|
8179
|
+
}
|
|
8180
|
+
console.log(chalk14.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
8181
|
+
for (const entry of hosts) {
|
|
8182
|
+
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
8183
|
+
console.log(` ${chalk14.cyan(entry.host.padEnd(40))} ${chalk14.gray(`added ${date}`)}`);
|
|
8184
|
+
}
|
|
8185
|
+
console.log("");
|
|
8186
|
+
});
|
|
8187
|
+
}
|
|
8188
|
+
|
|
7577
8189
|
// src/cli.ts
|
|
7578
8190
|
var { version } = JSON.parse(
|
|
7579
|
-
|
|
8191
|
+
fs22.readFileSync(path24.join(__dirname, "../package.json"), "utf-8")
|
|
7580
8192
|
);
|
|
7581
8193
|
var program = new Command();
|
|
7582
8194
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
7583
8195
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
7584
8196
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
7585
|
-
const credPath =
|
|
7586
|
-
if (!
|
|
7587
|
-
|
|
8197
|
+
const credPath = path24.join(os20.homedir(), ".node9", "credentials.json");
|
|
8198
|
+
if (!fs22.existsSync(path24.dirname(credPath)))
|
|
8199
|
+
fs22.mkdirSync(path24.dirname(credPath), { recursive: true });
|
|
7588
8200
|
const profileName = options.profile || "default";
|
|
7589
8201
|
let existingCreds = {};
|
|
7590
8202
|
try {
|
|
7591
|
-
if (
|
|
7592
|
-
const raw = JSON.parse(
|
|
8203
|
+
if (fs22.existsSync(credPath)) {
|
|
8204
|
+
const raw = JSON.parse(fs22.readFileSync(credPath, "utf-8"));
|
|
7593
8205
|
if (raw.apiKey) {
|
|
7594
8206
|
existingCreds = {
|
|
7595
8207
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -7601,13 +8213,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7601
8213
|
} catch {
|
|
7602
8214
|
}
|
|
7603
8215
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
7604
|
-
|
|
8216
|
+
fs22.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
7605
8217
|
if (profileName === "default") {
|
|
7606
|
-
const configPath =
|
|
8218
|
+
const configPath = path24.join(os20.homedir(), ".node9", "config.json");
|
|
7607
8219
|
let config = {};
|
|
7608
8220
|
try {
|
|
7609
|
-
if (
|
|
7610
|
-
config = JSON.parse(
|
|
8221
|
+
if (fs22.existsSync(configPath))
|
|
8222
|
+
config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
|
|
7611
8223
|
} catch {
|
|
7612
8224
|
}
|
|
7613
8225
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -7622,36 +8234,36 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7622
8234
|
approvers.cloud = false;
|
|
7623
8235
|
}
|
|
7624
8236
|
s.approvers = approvers;
|
|
7625
|
-
if (!
|
|
7626
|
-
|
|
7627
|
-
|
|
8237
|
+
if (!fs22.existsSync(path24.dirname(configPath)))
|
|
8238
|
+
fs22.mkdirSync(path24.dirname(configPath), { recursive: true });
|
|
8239
|
+
fs22.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
7628
8240
|
}
|
|
7629
8241
|
if (options.profile && profileName !== "default") {
|
|
7630
|
-
console.log(
|
|
7631
|
-
console.log(
|
|
8242
|
+
console.log(chalk16.green(`\u2705 Profile "${profileName}" saved`));
|
|
8243
|
+
console.log(chalk16.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
7632
8244
|
} else if (options.local) {
|
|
7633
|
-
console.log(
|
|
7634
|
-
console.log(
|
|
8245
|
+
console.log(chalk16.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
8246
|
+
console.log(chalk16.gray(` All decisions stay on this machine.`));
|
|
7635
8247
|
} else {
|
|
7636
|
-
console.log(
|
|
7637
|
-
console.log(
|
|
8248
|
+
console.log(chalk16.green(`\u2705 Logged in \u2014 agent mode`));
|
|
8249
|
+
console.log(chalk16.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
7638
8250
|
}
|
|
7639
8251
|
});
|
|
7640
8252
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to protect: claude | gemini | cursor").action(async (target) => {
|
|
7641
8253
|
if (target === "gemini") return await setupGemini();
|
|
7642
8254
|
if (target === "claude") return await setupClaude();
|
|
7643
8255
|
if (target === "cursor") return await setupCursor();
|
|
7644
|
-
console.error(
|
|
8256
|
+
console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7645
8257
|
process.exit(1);
|
|
7646
8258
|
});
|
|
7647
8259
|
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor").argument("[target]", "The agent to protect: claude | gemini | cursor").action(async (target) => {
|
|
7648
8260
|
if (!target) {
|
|
7649
|
-
console.log(
|
|
7650
|
-
console.log(" Usage: " +
|
|
8261
|
+
console.log(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
8262
|
+
console.log(" Usage: " + chalk16.white("node9 setup <target>") + "\n");
|
|
7651
8263
|
console.log(" Targets:");
|
|
7652
|
-
console.log(" " +
|
|
7653
|
-
console.log(" " +
|
|
7654
|
-
console.log(" " +
|
|
8264
|
+
console.log(" " + chalk16.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
8265
|
+
console.log(" " + chalk16.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
8266
|
+
console.log(" " + chalk16.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
7655
8267
|
console.log("");
|
|
7656
8268
|
return;
|
|
7657
8269
|
}
|
|
@@ -7659,7 +8271,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
7659
8271
|
if (t === "gemini") return await setupGemini();
|
|
7660
8272
|
if (t === "claude") return await setupClaude();
|
|
7661
8273
|
if (t === "cursor") return await setupCursor();
|
|
7662
|
-
console.error(
|
|
8274
|
+
console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7663
8275
|
process.exit(1);
|
|
7664
8276
|
});
|
|
7665
8277
|
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
@@ -7668,30 +8280,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
7668
8280
|
else if (target === "gemini") fn = teardownGemini;
|
|
7669
8281
|
else if (target === "cursor") fn = teardownCursor;
|
|
7670
8282
|
else {
|
|
7671
|
-
console.error(
|
|
8283
|
+
console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7672
8284
|
process.exit(1);
|
|
7673
8285
|
}
|
|
7674
|
-
console.log(
|
|
8286
|
+
console.log(chalk16.cyan(`
|
|
7675
8287
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
7676
8288
|
`));
|
|
7677
8289
|
try {
|
|
7678
8290
|
fn();
|
|
7679
8291
|
} catch (err) {
|
|
7680
|
-
console.error(
|
|
8292
|
+
console.error(chalk16.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
7681
8293
|
process.exit(1);
|
|
7682
8294
|
}
|
|
7683
|
-
console.log(
|
|
8295
|
+
console.log(chalk16.gray("\n Restart the agent for changes to take effect."));
|
|
7684
8296
|
});
|
|
7685
8297
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
7686
|
-
console.log(
|
|
7687
|
-
console.log(
|
|
8298
|
+
console.log(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
8299
|
+
console.log(chalk16.bold("Stopping daemon..."));
|
|
7688
8300
|
try {
|
|
7689
8301
|
stopDaemon();
|
|
7690
|
-
console.log(
|
|
8302
|
+
console.log(chalk16.green(" \u2705 Daemon stopped"));
|
|
7691
8303
|
} catch {
|
|
7692
|
-
console.log(
|
|
8304
|
+
console.log(chalk16.blue(" \u2139\uFE0F Daemon was not running"));
|
|
7693
8305
|
}
|
|
7694
|
-
console.log(
|
|
8306
|
+
console.log(chalk16.bold("\nRemoving hooks..."));
|
|
7695
8307
|
let teardownFailed = false;
|
|
7696
8308
|
for (const [label, fn] of [
|
|
7697
8309
|
["Claude", teardownClaude],
|
|
@@ -7703,45 +8315,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
7703
8315
|
} catch (err) {
|
|
7704
8316
|
teardownFailed = true;
|
|
7705
8317
|
console.error(
|
|
7706
|
-
|
|
8318
|
+
chalk16.red(
|
|
7707
8319
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
7708
8320
|
)
|
|
7709
8321
|
);
|
|
7710
8322
|
}
|
|
7711
8323
|
}
|
|
7712
8324
|
if (options.purge) {
|
|
7713
|
-
const node9Dir =
|
|
7714
|
-
if (
|
|
8325
|
+
const node9Dir = path24.join(os20.homedir(), ".node9");
|
|
8326
|
+
if (fs22.existsSync(node9Dir)) {
|
|
7715
8327
|
const confirmed = await confirm3({
|
|
7716
8328
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
7717
8329
|
default: false
|
|
7718
8330
|
});
|
|
7719
8331
|
if (confirmed) {
|
|
7720
|
-
|
|
7721
|
-
if (
|
|
8332
|
+
fs22.rmSync(node9Dir, { recursive: true });
|
|
8333
|
+
if (fs22.existsSync(node9Dir)) {
|
|
7722
8334
|
console.error(
|
|
7723
|
-
|
|
8335
|
+
chalk16.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
7724
8336
|
);
|
|
7725
8337
|
} else {
|
|
7726
|
-
console.log(
|
|
8338
|
+
console.log(chalk16.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
7727
8339
|
}
|
|
7728
8340
|
} else {
|
|
7729
|
-
console.log(
|
|
8341
|
+
console.log(chalk16.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
7730
8342
|
}
|
|
7731
8343
|
} else {
|
|
7732
|
-
console.log(
|
|
8344
|
+
console.log(chalk16.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
7733
8345
|
}
|
|
7734
8346
|
} else {
|
|
7735
8347
|
console.log(
|
|
7736
|
-
|
|
8348
|
+
chalk16.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
7737
8349
|
);
|
|
7738
8350
|
}
|
|
7739
8351
|
if (teardownFailed) {
|
|
7740
|
-
console.error(
|
|
8352
|
+
console.error(chalk16.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
7741
8353
|
process.exit(1);
|
|
7742
8354
|
}
|
|
7743
|
-
console.log(
|
|
7744
|
-
console.log(
|
|
8355
|
+
console.log(chalk16.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
8356
|
+
console.log(chalk16.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
7745
8357
|
});
|
|
7746
8358
|
registerDoctorCommand(program, version);
|
|
7747
8359
|
program.command("explain").description(
|
|
@@ -7754,7 +8366,7 @@ program.command("explain").description(
|
|
|
7754
8366
|
try {
|
|
7755
8367
|
args = JSON.parse(trimmed);
|
|
7756
8368
|
} catch {
|
|
7757
|
-
console.error(
|
|
8369
|
+
console.error(chalk16.red(`
|
|
7758
8370
|
\u274C Invalid JSON: ${trimmed}
|
|
7759
8371
|
`));
|
|
7760
8372
|
process.exit(1);
|
|
@@ -7765,63 +8377,63 @@ program.command("explain").description(
|
|
|
7765
8377
|
}
|
|
7766
8378
|
const result = await explainPolicy(tool, args);
|
|
7767
8379
|
console.log("");
|
|
7768
|
-
console.log(
|
|
8380
|
+
console.log(chalk16.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
7769
8381
|
console.log("");
|
|
7770
|
-
console.log(` ${
|
|
8382
|
+
console.log(` ${chalk16.bold("Tool:")} ${chalk16.white(result.tool)}`);
|
|
7771
8383
|
if (argsRaw) {
|
|
7772
8384
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
7773
|
-
console.log(` ${
|
|
8385
|
+
console.log(` ${chalk16.bold("Input:")} ${chalk16.gray(preview)}`);
|
|
7774
8386
|
}
|
|
7775
8387
|
console.log("");
|
|
7776
|
-
console.log(
|
|
8388
|
+
console.log(chalk16.bold("Config Sources (Waterfall):"));
|
|
7777
8389
|
for (const tier of result.waterfall) {
|
|
7778
|
-
const num =
|
|
8390
|
+
const num = chalk16.gray(` ${tier.tier}.`);
|
|
7779
8391
|
const label = tier.label.padEnd(16);
|
|
7780
8392
|
let statusStr;
|
|
7781
8393
|
if (tier.tier === 1) {
|
|
7782
|
-
statusStr =
|
|
8394
|
+
statusStr = chalk16.gray(tier.note ?? "");
|
|
7783
8395
|
} else if (tier.status === "active") {
|
|
7784
|
-
const loc = tier.path ?
|
|
7785
|
-
const note = tier.note ?
|
|
7786
|
-
statusStr =
|
|
8396
|
+
const loc = tier.path ? chalk16.gray(tier.path) : "";
|
|
8397
|
+
const note = tier.note ? chalk16.gray(`(${tier.note})`) : "";
|
|
8398
|
+
statusStr = chalk16.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
7787
8399
|
} else {
|
|
7788
|
-
statusStr =
|
|
8400
|
+
statusStr = chalk16.gray("\u25CB " + (tier.note ?? "not found"));
|
|
7789
8401
|
}
|
|
7790
|
-
console.log(`${num} ${
|
|
8402
|
+
console.log(`${num} ${chalk16.white(label)} ${statusStr}`);
|
|
7791
8403
|
}
|
|
7792
8404
|
console.log("");
|
|
7793
|
-
console.log(
|
|
8405
|
+
console.log(chalk16.bold("Policy Evaluation:"));
|
|
7794
8406
|
for (const step of result.steps) {
|
|
7795
8407
|
const isFinal = step.isFinal;
|
|
7796
8408
|
let icon;
|
|
7797
|
-
if (step.outcome === "allow") icon =
|
|
7798
|
-
else if (step.outcome === "review") icon =
|
|
7799
|
-
else if (step.outcome === "skip") icon =
|
|
7800
|
-
else icon =
|
|
8409
|
+
if (step.outcome === "allow") icon = chalk16.green(" \u2705");
|
|
8410
|
+
else if (step.outcome === "review") icon = chalk16.red(" \u{1F534}");
|
|
8411
|
+
else if (step.outcome === "skip") icon = chalk16.gray(" \u2500 ");
|
|
8412
|
+
else icon = chalk16.gray(" \u25CB ");
|
|
7801
8413
|
const name = step.name.padEnd(18);
|
|
7802
|
-
const nameStr = isFinal ?
|
|
7803
|
-
const detail = isFinal ?
|
|
7804
|
-
const arrow = isFinal ?
|
|
8414
|
+
const nameStr = isFinal ? chalk16.white.bold(name) : chalk16.white(name);
|
|
8415
|
+
const detail = isFinal ? chalk16.white(step.detail) : chalk16.gray(step.detail);
|
|
8416
|
+
const arrow = isFinal ? chalk16.yellow(" \u2190 STOP") : "";
|
|
7805
8417
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
7806
8418
|
}
|
|
7807
8419
|
console.log("");
|
|
7808
8420
|
if (result.decision === "allow") {
|
|
7809
|
-
console.log(
|
|
8421
|
+
console.log(chalk16.green.bold(" Decision: \u2705 ALLOW") + chalk16.gray(" \u2014 no approval needed"));
|
|
7810
8422
|
} else {
|
|
7811
8423
|
console.log(
|
|
7812
|
-
|
|
8424
|
+
chalk16.red.bold(" Decision: \u{1F534} REVIEW") + chalk16.gray(" \u2014 human approval required")
|
|
7813
8425
|
);
|
|
7814
8426
|
if (result.blockedByLabel) {
|
|
7815
|
-
console.log(
|
|
8427
|
+
console.log(chalk16.gray(` Reason: ${result.blockedByLabel}`));
|
|
7816
8428
|
}
|
|
7817
8429
|
}
|
|
7818
8430
|
console.log("");
|
|
7819
8431
|
});
|
|
7820
8432
|
program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
|
|
7821
|
-
const configPath =
|
|
7822
|
-
if (
|
|
7823
|
-
console.log(
|
|
7824
|
-
console.log(
|
|
8433
|
+
const configPath = path24.join(os20.homedir(), ".node9", "config.json");
|
|
8434
|
+
if (fs22.existsSync(configPath) && !options.force) {
|
|
8435
|
+
console.log(chalk16.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
8436
|
+
console.log(chalk16.gray(` Run with --force to overwrite.`));
|
|
7825
8437
|
return;
|
|
7826
8438
|
}
|
|
7827
8439
|
const requestedMode = options.mode.toLowerCase();
|
|
@@ -7833,13 +8445,13 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
7833
8445
|
mode: safeMode
|
|
7834
8446
|
}
|
|
7835
8447
|
};
|
|
7836
|
-
const dir =
|
|
7837
|
-
if (!
|
|
7838
|
-
|
|
7839
|
-
console.log(
|
|
7840
|
-
console.log(
|
|
8448
|
+
const dir = path24.dirname(configPath);
|
|
8449
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
8450
|
+
fs22.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
8451
|
+
console.log(chalk16.green(`\u2705 Global config created: ${configPath}`));
|
|
8452
|
+
console.log(chalk16.cyan(` Mode set to: ${safeMode}`));
|
|
7841
8453
|
console.log(
|
|
7842
|
-
|
|
8454
|
+
chalk16.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
|
|
7843
8455
|
);
|
|
7844
8456
|
});
|
|
7845
8457
|
registerAuditCommand(program);
|
|
@@ -7850,7 +8462,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
7850
8462
|
try {
|
|
7851
8463
|
await startTail2(options);
|
|
7852
8464
|
} catch (err) {
|
|
7853
|
-
console.error(
|
|
8465
|
+
console.error(chalk16.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
7854
8466
|
process.exit(1);
|
|
7855
8467
|
}
|
|
7856
8468
|
});
|
|
@@ -7862,7 +8474,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
7862
8474
|
const ms = parseDuration(options.duration);
|
|
7863
8475
|
if (ms === null) {
|
|
7864
8476
|
console.error(
|
|
7865
|
-
|
|
8477
|
+
chalk16.red(`
|
|
7866
8478
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
7867
8479
|
`)
|
|
7868
8480
|
);
|
|
@@ -7870,20 +8482,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
7870
8482
|
}
|
|
7871
8483
|
pauseNode9(ms, options.duration);
|
|
7872
8484
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
7873
|
-
console.log(
|
|
8485
|
+
console.log(chalk16.yellow(`
|
|
7874
8486
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
7875
|
-
console.log(
|
|
7876
|
-
console.log(
|
|
8487
|
+
console.log(chalk16.gray(` All tool calls will be allowed without review.`));
|
|
8488
|
+
console.log(chalk16.gray(` Run "node9 resume" to re-enable early.
|
|
7877
8489
|
`));
|
|
7878
8490
|
});
|
|
7879
8491
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
7880
8492
|
const { paused } = checkPause();
|
|
7881
8493
|
if (!paused) {
|
|
7882
|
-
console.log(
|
|
8494
|
+
console.log(chalk16.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
7883
8495
|
return;
|
|
7884
8496
|
}
|
|
7885
8497
|
resumeNode9();
|
|
7886
|
-
console.log(
|
|
8498
|
+
console.log(chalk16.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
7887
8499
|
});
|
|
7888
8500
|
var HOOK_BASED_AGENTS = {
|
|
7889
8501
|
claude: "claude",
|
|
@@ -7896,15 +8508,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7896
8508
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
7897
8509
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
7898
8510
|
console.error(
|
|
7899
|
-
|
|
8511
|
+
chalk16.yellow(`
|
|
7900
8512
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
7901
8513
|
);
|
|
7902
|
-
console.error(
|
|
8514
|
+
console.error(chalk16.white(`
|
|
7903
8515
|
"${target}" uses its own hook system. Use:`));
|
|
7904
8516
|
console.error(
|
|
7905
|
-
|
|
8517
|
+
chalk16.green(` node9 addto ${target} `) + chalk16.gray("# one-time setup")
|
|
7906
8518
|
);
|
|
7907
|
-
console.error(
|
|
8519
|
+
console.error(chalk16.green(` ${target} `) + chalk16.gray("# run normally"));
|
|
7908
8520
|
process.exit(1);
|
|
7909
8521
|
}
|
|
7910
8522
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -7921,7 +8533,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7921
8533
|
}
|
|
7922
8534
|
);
|
|
7923
8535
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
7924
|
-
console.error(
|
|
8536
|
+
console.error(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
7925
8537
|
const daemonReady = await autoStartDaemonAndWait();
|
|
7926
8538
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
7927
8539
|
}
|
|
@@ -7934,12 +8546,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7934
8546
|
}
|
|
7935
8547
|
if (!result.approved) {
|
|
7936
8548
|
console.error(
|
|
7937
|
-
|
|
8549
|
+
chalk16.red(`
|
|
7938
8550
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
7939
8551
|
);
|
|
7940
8552
|
process.exit(1);
|
|
7941
8553
|
}
|
|
7942
|
-
console.error(
|
|
8554
|
+
console.error(chalk16.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
7943
8555
|
await runProxy(fullCommand);
|
|
7944
8556
|
} else {
|
|
7945
8557
|
program.help();
|
|
@@ -7948,14 +8560,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7948
8560
|
registerUndoCommand(program);
|
|
7949
8561
|
registerShieldCommand(program);
|
|
7950
8562
|
registerConfigShowCommand(program);
|
|
8563
|
+
registerTrustCommand(program);
|
|
7951
8564
|
if (process.argv[2] !== "daemon") {
|
|
7952
8565
|
process.on("unhandledRejection", (reason) => {
|
|
7953
8566
|
const isCheckHook = process.argv[2] === "check";
|
|
7954
8567
|
if (isCheckHook) {
|
|
7955
8568
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
7956
|
-
const logPath =
|
|
8569
|
+
const logPath = path24.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
7957
8570
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
7958
|
-
|
|
8571
|
+
fs22.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
7959
8572
|
`);
|
|
7960
8573
|
}
|
|
7961
8574
|
process.exit(0);
|