@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.js
CHANGED
|
@@ -114,8 +114,8 @@ function sanitizeConfig(raw) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
const lines = result.error.issues.map((issue) => {
|
|
117
|
-
const
|
|
118
|
-
return ` \u2022 ${
|
|
117
|
+
const path25 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
118
|
+
return ` \u2022 ${path25}: ${issue.message}`;
|
|
119
119
|
});
|
|
120
120
|
return {
|
|
121
121
|
sanitized,
|
|
@@ -1178,8 +1178,497 @@ var init_dlp = __esm({
|
|
|
1178
1178
|
}
|
|
1179
1179
|
});
|
|
1180
1180
|
|
|
1181
|
+
// src/utils/provenance.ts
|
|
1182
|
+
function findInPath(cmd) {
|
|
1183
|
+
if (import_path5.default.posix.isAbsolute(cmd)) return cmd;
|
|
1184
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1185
|
+
for (const dir of pathEnv.split(import_path5.default.delimiter)) {
|
|
1186
|
+
if (!dir) continue;
|
|
1187
|
+
const full = import_path5.default.join(dir, cmd);
|
|
1188
|
+
try {
|
|
1189
|
+
import_fs5.default.accessSync(full, import_fs5.default.constants.X_OK);
|
|
1190
|
+
return full;
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
function _classifyPath(resolved, cwd) {
|
|
1197
|
+
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
1198
|
+
return { trustLevel: "user", reason: "binary in project directory" };
|
|
1199
|
+
}
|
|
1200
|
+
const osTmp = import_os4.default.tmpdir();
|
|
1201
|
+
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
1202
|
+
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1203
|
+
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
1204
|
+
}
|
|
1205
|
+
if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1206
|
+
return { trustLevel: "system", reason: "" };
|
|
1207
|
+
}
|
|
1208
|
+
if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1209
|
+
return { trustLevel: "managed", reason: "" };
|
|
1210
|
+
}
|
|
1211
|
+
if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1212
|
+
return { trustLevel: "user", reason: "" };
|
|
1213
|
+
}
|
|
1214
|
+
return { trustLevel: "unknown", reason: "binary in unrecognized location" };
|
|
1215
|
+
}
|
|
1216
|
+
function checkProvenance(cmd, cwd) {
|
|
1217
|
+
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
1218
|
+
if (import_path5.default.posix.isAbsolute(bare)) {
|
|
1219
|
+
const early = _classifyPath(bare, cwd);
|
|
1220
|
+
if (early.trustLevel === "suspect") {
|
|
1221
|
+
return { resolvedPath: bare, ...early };
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
let resolved;
|
|
1225
|
+
try {
|
|
1226
|
+
const found = findInPath(bare);
|
|
1227
|
+
if (!found) {
|
|
1228
|
+
return {
|
|
1229
|
+
resolvedPath: cmd,
|
|
1230
|
+
trustLevel: "unknown",
|
|
1231
|
+
reason: "binary not found in PATH"
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
resolved = import_fs5.default.realpathSync(found);
|
|
1235
|
+
} catch {
|
|
1236
|
+
return {
|
|
1237
|
+
resolvedPath: cmd,
|
|
1238
|
+
trustLevel: "unknown",
|
|
1239
|
+
reason: "binary not found in PATH"
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
const stat = import_fs5.default.statSync(resolved);
|
|
1244
|
+
if (stat.mode & 2) {
|
|
1245
|
+
return {
|
|
1246
|
+
resolvedPath: resolved,
|
|
1247
|
+
trustLevel: "suspect",
|
|
1248
|
+
reason: "binary is world-writable"
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
return {
|
|
1253
|
+
resolvedPath: resolved,
|
|
1254
|
+
trustLevel: "unknown",
|
|
1255
|
+
reason: "could not stat binary"
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
const classify = _classifyPath(resolved, cwd);
|
|
1259
|
+
return { resolvedPath: resolved, ...classify };
|
|
1260
|
+
}
|
|
1261
|
+
var import_fs5, import_path5, import_os4, SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
|
|
1262
|
+
var init_provenance = __esm({
|
|
1263
|
+
"src/utils/provenance.ts"() {
|
|
1264
|
+
"use strict";
|
|
1265
|
+
import_fs5 = __toESM(require("fs"));
|
|
1266
|
+
import_path5 = __toESM(require("path"));
|
|
1267
|
+
import_os4 = __toESM(require("os"));
|
|
1268
|
+
SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
1269
|
+
MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
1270
|
+
USER_PREFIXES = [
|
|
1271
|
+
import_path5.default.join(import_os4.default.homedir(), "bin"),
|
|
1272
|
+
import_path5.default.join(import_os4.default.homedir(), ".local", "bin"),
|
|
1273
|
+
import_path5.default.join(import_os4.default.homedir(), ".cargo", "bin"),
|
|
1274
|
+
import_path5.default.join(import_os4.default.homedir(), ".npm-global", "bin"),
|
|
1275
|
+
import_path5.default.join(import_os4.default.homedir(), ".volta", "bin")
|
|
1276
|
+
];
|
|
1277
|
+
SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// src/policy/pipe-chain.ts
|
|
1282
|
+
function isSensitivePath(p) {
|
|
1283
|
+
return SENSITIVE_PATTERNS.some((re) => re.test(p));
|
|
1284
|
+
}
|
|
1285
|
+
function splitOnPipe(cmd) {
|
|
1286
|
+
const segments = [];
|
|
1287
|
+
let current = "";
|
|
1288
|
+
let inSingle = false;
|
|
1289
|
+
let inDouble = false;
|
|
1290
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
1291
|
+
const ch = cmd[i];
|
|
1292
|
+
if (ch === "'" && !inDouble) {
|
|
1293
|
+
inSingle = !inSingle;
|
|
1294
|
+
current += ch;
|
|
1295
|
+
} else if (ch === '"' && !inSingle) {
|
|
1296
|
+
inDouble = !inDouble;
|
|
1297
|
+
current += ch;
|
|
1298
|
+
} else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
|
|
1299
|
+
segments.push(current.trim());
|
|
1300
|
+
current = "";
|
|
1301
|
+
} else {
|
|
1302
|
+
current += ch;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (current.trim()) segments.push(current.trim());
|
|
1306
|
+
return segments.filter(Boolean);
|
|
1307
|
+
}
|
|
1308
|
+
function positionalTokens(segment) {
|
|
1309
|
+
return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
1310
|
+
}
|
|
1311
|
+
function analyzePipeChain(command) {
|
|
1312
|
+
const segments = splitOnPipe(command);
|
|
1313
|
+
if (segments.length < 2) {
|
|
1314
|
+
return {
|
|
1315
|
+
isPipeline: false,
|
|
1316
|
+
hasSensitiveSource: false,
|
|
1317
|
+
hasExternalSink: false,
|
|
1318
|
+
hasObfuscation: false,
|
|
1319
|
+
sourceFiles: [],
|
|
1320
|
+
sinkTargets: [],
|
|
1321
|
+
risk: "none"
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
const sourceFiles = [];
|
|
1325
|
+
const sinkTargets = [];
|
|
1326
|
+
let hasSensitiveSource = false;
|
|
1327
|
+
let hasExternalSink = false;
|
|
1328
|
+
let hasObfuscation = false;
|
|
1329
|
+
for (const segment of segments) {
|
|
1330
|
+
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
1331
|
+
if (tokens.length === 0) continue;
|
|
1332
|
+
const binary = tokens[0].toLowerCase();
|
|
1333
|
+
const args = positionalTokens(segment);
|
|
1334
|
+
if (SOURCE_COMMANDS.has(binary)) {
|
|
1335
|
+
sourceFiles.push(...args);
|
|
1336
|
+
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
1337
|
+
}
|
|
1338
|
+
if (OBFUSCATORS.has(binary)) hasObfuscation = true;
|
|
1339
|
+
if (SINK_COMMANDS.has(binary)) {
|
|
1340
|
+
const targets = args.filter(
|
|
1341
|
+
(a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
|
|
1342
|
+
);
|
|
1343
|
+
sinkTargets.push(...targets);
|
|
1344
|
+
if (targets.length > 0) hasExternalSink = true;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const fullCmd = command.toLowerCase();
|
|
1348
|
+
if (!hasSensitiveSource) {
|
|
1349
|
+
const redirMatch = fullCmd.match(/<\s*(\S+)/);
|
|
1350
|
+
if (redirMatch && isSensitivePath(redirMatch[1])) {
|
|
1351
|
+
hasSensitiveSource = true;
|
|
1352
|
+
sourceFiles.push(redirMatch[1]);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
|
|
1356
|
+
return {
|
|
1357
|
+
isPipeline: true,
|
|
1358
|
+
hasSensitiveSource,
|
|
1359
|
+
hasExternalSink,
|
|
1360
|
+
hasObfuscation,
|
|
1361
|
+
sourceFiles,
|
|
1362
|
+
sinkTargets,
|
|
1363
|
+
risk
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
var SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS;
|
|
1367
|
+
var init_pipe_chain = __esm({
|
|
1368
|
+
"src/policy/pipe-chain.ts"() {
|
|
1369
|
+
"use strict";
|
|
1370
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1371
|
+
"cat",
|
|
1372
|
+
"head",
|
|
1373
|
+
"tail",
|
|
1374
|
+
"grep",
|
|
1375
|
+
"awk",
|
|
1376
|
+
"sed",
|
|
1377
|
+
"cut",
|
|
1378
|
+
"sort",
|
|
1379
|
+
"tee",
|
|
1380
|
+
"less",
|
|
1381
|
+
"more",
|
|
1382
|
+
"strings",
|
|
1383
|
+
"xxd"
|
|
1384
|
+
]);
|
|
1385
|
+
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
1386
|
+
"curl",
|
|
1387
|
+
"wget",
|
|
1388
|
+
"nc",
|
|
1389
|
+
"ncat",
|
|
1390
|
+
"netcat",
|
|
1391
|
+
"ssh",
|
|
1392
|
+
"scp",
|
|
1393
|
+
"rsync",
|
|
1394
|
+
"socat",
|
|
1395
|
+
"ftp",
|
|
1396
|
+
"sftp",
|
|
1397
|
+
"telnet"
|
|
1398
|
+
]);
|
|
1399
|
+
OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
1400
|
+
"base64",
|
|
1401
|
+
"gzip",
|
|
1402
|
+
"gunzip",
|
|
1403
|
+
"bzip2",
|
|
1404
|
+
"xz",
|
|
1405
|
+
"zstd",
|
|
1406
|
+
"openssl",
|
|
1407
|
+
"gpg",
|
|
1408
|
+
"python",
|
|
1409
|
+
"python3",
|
|
1410
|
+
"perl",
|
|
1411
|
+
"ruby",
|
|
1412
|
+
"node"
|
|
1413
|
+
]);
|
|
1414
|
+
SENSITIVE_PATTERNS = [
|
|
1415
|
+
/(?:^|\/)\.env(?:\.|$)/i,
|
|
1416
|
+
// .env, .env.local, .env.production
|
|
1417
|
+
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
1418
|
+
// SSH private keys
|
|
1419
|
+
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
1420
|
+
// certificate files
|
|
1421
|
+
/(?:^|\/)\.ssh\//i,
|
|
1422
|
+
// ~/.ssh/ directory
|
|
1423
|
+
/(?:^|\/)\.aws\/credentials/i,
|
|
1424
|
+
// AWS credentials
|
|
1425
|
+
/(?:^|\/)\.netrc$/i,
|
|
1426
|
+
// netrc (stores HTTP credentials)
|
|
1427
|
+
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
1428
|
+
// /etc/passwd, /etc/shadow
|
|
1429
|
+
/(?:^|\/)credentials(?:\.json)?$/i
|
|
1430
|
+
// generic credentials files
|
|
1431
|
+
];
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
// src/policy/flag-tables.ts
|
|
1436
|
+
function extractPositionalArgs(tokens, binary) {
|
|
1437
|
+
const binaryName = import_path6.default.basename(binary).replace(/\.exe$/i, "");
|
|
1438
|
+
const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
|
|
1439
|
+
const positional = [];
|
|
1440
|
+
let skipNext = false;
|
|
1441
|
+
for (const token of tokens) {
|
|
1442
|
+
if (skipNext) {
|
|
1443
|
+
skipNext = false;
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
if (token.startsWith("--") && token.includes("=")) continue;
|
|
1447
|
+
if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
|
|
1448
|
+
skipNext = true;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (token.startsWith("--") && flagsWithValues.has(token)) {
|
|
1452
|
+
skipNext = true;
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
const shortFlag = token.slice(0, 2);
|
|
1456
|
+
if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
|
|
1457
|
+
if (token.startsWith("-")) continue;
|
|
1458
|
+
if (token.startsWith("@")) continue;
|
|
1459
|
+
positional.push(token);
|
|
1460
|
+
}
|
|
1461
|
+
return positional;
|
|
1462
|
+
}
|
|
1463
|
+
function extractNetworkTargets(tokens, binary) {
|
|
1464
|
+
return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
|
|
1465
|
+
const colonIdx = t.indexOf(":");
|
|
1466
|
+
if (colonIdx === -1) return t;
|
|
1467
|
+
const afterColon = t.slice(colonIdx + 1);
|
|
1468
|
+
if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
|
|
1469
|
+
return t;
|
|
1470
|
+
}).filter(Boolean);
|
|
1471
|
+
}
|
|
1472
|
+
var import_path6, FLAGS_WITH_VALUES;
|
|
1473
|
+
var init_flag_tables = __esm({
|
|
1474
|
+
"src/policy/flag-tables.ts"() {
|
|
1475
|
+
"use strict";
|
|
1476
|
+
import_path6 = __toESM(require("path"));
|
|
1477
|
+
FLAGS_WITH_VALUES = {
|
|
1478
|
+
curl: /* @__PURE__ */ new Set([
|
|
1479
|
+
"-H",
|
|
1480
|
+
"--header",
|
|
1481
|
+
"-A",
|
|
1482
|
+
"--user-agent",
|
|
1483
|
+
"-e",
|
|
1484
|
+
"--referer",
|
|
1485
|
+
"-x",
|
|
1486
|
+
"--proxy",
|
|
1487
|
+
"-u",
|
|
1488
|
+
"--user",
|
|
1489
|
+
"-d",
|
|
1490
|
+
"--data",
|
|
1491
|
+
"--data-raw",
|
|
1492
|
+
"--data-binary",
|
|
1493
|
+
"-o",
|
|
1494
|
+
"--output",
|
|
1495
|
+
"-F",
|
|
1496
|
+
"--form",
|
|
1497
|
+
"--connect-to",
|
|
1498
|
+
"--resolve",
|
|
1499
|
+
"--cacert",
|
|
1500
|
+
"--cert",
|
|
1501
|
+
"--key",
|
|
1502
|
+
"-m",
|
|
1503
|
+
"--max-time"
|
|
1504
|
+
]),
|
|
1505
|
+
wget: /* @__PURE__ */ new Set([
|
|
1506
|
+
"-O",
|
|
1507
|
+
"--output-document",
|
|
1508
|
+
"-P",
|
|
1509
|
+
"--directory-prefix",
|
|
1510
|
+
"-U",
|
|
1511
|
+
"--user-agent",
|
|
1512
|
+
"-e",
|
|
1513
|
+
"--execute",
|
|
1514
|
+
"--proxy",
|
|
1515
|
+
"--ca-certificate"
|
|
1516
|
+
]),
|
|
1517
|
+
nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
|
|
1518
|
+
ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
|
|
1519
|
+
netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
|
|
1520
|
+
ssh: /* @__PURE__ */ new Set([
|
|
1521
|
+
"-i",
|
|
1522
|
+
"-l",
|
|
1523
|
+
"-p",
|
|
1524
|
+
"-o",
|
|
1525
|
+
"-E",
|
|
1526
|
+
"-F",
|
|
1527
|
+
"-J",
|
|
1528
|
+
"-L",
|
|
1529
|
+
"-R",
|
|
1530
|
+
"-W",
|
|
1531
|
+
"-b",
|
|
1532
|
+
"-c",
|
|
1533
|
+
"-D",
|
|
1534
|
+
"-e",
|
|
1535
|
+
"-I",
|
|
1536
|
+
"-S"
|
|
1537
|
+
]),
|
|
1538
|
+
scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
|
|
1539
|
+
rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
|
|
1540
|
+
socat: /* @__PURE__ */ new Set([])
|
|
1541
|
+
// socat uses address syntax, not flags — no value-flags
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
// src/policy/ssh-parser.ts
|
|
1547
|
+
function tokenize(cmd) {
|
|
1548
|
+
const tokens = [];
|
|
1549
|
+
let current = "";
|
|
1550
|
+
let inSingle = false;
|
|
1551
|
+
let inDouble = false;
|
|
1552
|
+
for (const ch of cmd) {
|
|
1553
|
+
if (ch === "'" && !inDouble) {
|
|
1554
|
+
inSingle = !inSingle;
|
|
1555
|
+
} else if (ch === '"' && !inSingle) {
|
|
1556
|
+
inDouble = !inDouble;
|
|
1557
|
+
} else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
|
|
1558
|
+
if (current) {
|
|
1559
|
+
tokens.push(current);
|
|
1560
|
+
current = "";
|
|
1561
|
+
}
|
|
1562
|
+
} else {
|
|
1563
|
+
current += ch;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (current) tokens.push(current);
|
|
1567
|
+
return tokens;
|
|
1568
|
+
}
|
|
1569
|
+
function parseHost(raw) {
|
|
1570
|
+
return raw.split("@").pop().split(":")[0];
|
|
1571
|
+
}
|
|
1572
|
+
function extractAllSshHosts(tokens) {
|
|
1573
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1574
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1575
|
+
const t = tokens[i];
|
|
1576
|
+
if (t === "-J" && tokens[i + 1]) {
|
|
1577
|
+
for (const hop of tokens[++i].split(",")) {
|
|
1578
|
+
const h = parseHost(hop);
|
|
1579
|
+
if (h) hosts.add(h);
|
|
1580
|
+
}
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
|
|
1584
|
+
const val = tokens[++i].split("=").slice(1).join("=");
|
|
1585
|
+
for (const hop of val.split(",")) {
|
|
1586
|
+
const h = parseHost(hop);
|
|
1587
|
+
if (h) hosts.add(h);
|
|
1588
|
+
}
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
|
|
1592
|
+
const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
|
|
1593
|
+
const subTokens = tokenize(raw);
|
|
1594
|
+
const binary = subTokens[0] ?? "";
|
|
1595
|
+
extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
|
|
1596
|
+
extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (!t.startsWith("-")) {
|
|
1600
|
+
const h = parseHost(t);
|
|
1601
|
+
if (h) hosts.add(h);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return [...hosts].filter(Boolean);
|
|
1605
|
+
}
|
|
1606
|
+
var init_ssh_parser = __esm({
|
|
1607
|
+
"src/policy/ssh-parser.ts"() {
|
|
1608
|
+
"use strict";
|
|
1609
|
+
init_flag_tables();
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
// src/auth/trusted-hosts.ts
|
|
1614
|
+
function getTrustedHostsPath() {
|
|
1615
|
+
return import_path7.default.join(import_os5.default.homedir(), ".node9", "trusted-hosts.json");
|
|
1616
|
+
}
|
|
1617
|
+
function readTrustedHosts() {
|
|
1618
|
+
try {
|
|
1619
|
+
const raw = import_fs6.default.readFileSync(getTrustedHostsPath(), "utf8");
|
|
1620
|
+
const parsed = JSON.parse(raw);
|
|
1621
|
+
return Array.isArray(parsed.hosts) ? parsed.hosts : [];
|
|
1622
|
+
} catch {
|
|
1623
|
+
return [];
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function writeTrustedHosts(hosts) {
|
|
1627
|
+
const filePath = getTrustedHostsPath();
|
|
1628
|
+
import_fs6.default.mkdirSync(import_path7.default.dirname(filePath), { recursive: true });
|
|
1629
|
+
const tmp = filePath + ".node9-tmp";
|
|
1630
|
+
import_fs6.default.writeFileSync(tmp, JSON.stringify({ hosts }, null, 2));
|
|
1631
|
+
import_fs6.default.renameSync(tmp, filePath);
|
|
1632
|
+
}
|
|
1633
|
+
function addTrustedHost(host) {
|
|
1634
|
+
const hosts = readTrustedHosts();
|
|
1635
|
+
if (hosts.some((h) => h.host === host)) return;
|
|
1636
|
+
hosts.push({ host, addedAt: Date.now(), addedBy: "user" });
|
|
1637
|
+
writeTrustedHosts(hosts);
|
|
1638
|
+
}
|
|
1639
|
+
function removeTrustedHost(host) {
|
|
1640
|
+
const hosts = readTrustedHosts();
|
|
1641
|
+
const filtered = hosts.filter((h) => h.host !== host);
|
|
1642
|
+
if (filtered.length === hosts.length) return false;
|
|
1643
|
+
writeTrustedHosts(filtered);
|
|
1644
|
+
return true;
|
|
1645
|
+
}
|
|
1646
|
+
function normalizeHost(raw) {
|
|
1647
|
+
return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
|
|
1648
|
+
}
|
|
1649
|
+
function isTrustedHost(host) {
|
|
1650
|
+
const normalized = normalizeHost(host);
|
|
1651
|
+
return readTrustedHosts().some((entry) => {
|
|
1652
|
+
const entryHost = entry.host.toLowerCase();
|
|
1653
|
+
if (entryHost.startsWith("*.")) {
|
|
1654
|
+
const domain = entryHost.slice(2);
|
|
1655
|
+
return normalized === domain || normalized.endsWith("." + domain);
|
|
1656
|
+
}
|
|
1657
|
+
return normalized === entryHost;
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
var import_fs6, import_path7, import_os5;
|
|
1661
|
+
var init_trusted_hosts = __esm({
|
|
1662
|
+
"src/auth/trusted-hosts.ts"() {
|
|
1663
|
+
"use strict";
|
|
1664
|
+
import_fs6 = __toESM(require("fs"));
|
|
1665
|
+
import_path7 = __toESM(require("path"));
|
|
1666
|
+
import_os5 = __toESM(require("os"));
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1181
1670
|
// src/policy/index.ts
|
|
1182
|
-
function
|
|
1671
|
+
function tokenize2(toolName) {
|
|
1183
1672
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
1184
1673
|
}
|
|
1185
1674
|
function matchesPattern(text, patterns) {
|
|
@@ -1192,9 +1681,9 @@ function matchesPattern(text, patterns) {
|
|
|
1192
1681
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1193
1682
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1194
1683
|
}
|
|
1195
|
-
function getNestedValue(obj,
|
|
1684
|
+
function getNestedValue(obj, path25) {
|
|
1196
1685
|
if (!obj || typeof obj !== "object") return null;
|
|
1197
|
-
return
|
|
1686
|
+
return path25.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1198
1687
|
}
|
|
1199
1688
|
function shouldSnapshot(toolName, args, config) {
|
|
1200
1689
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1329,7 +1818,7 @@ async function analyzeShellCommand(command) {
|
|
|
1329
1818
|
}
|
|
1330
1819
|
return { actions, paths, allTokens };
|
|
1331
1820
|
}
|
|
1332
|
-
async function evaluatePolicy(toolName, args, agent) {
|
|
1821
|
+
async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
1333
1822
|
const config = getConfig();
|
|
1334
1823
|
if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
|
|
1335
1824
|
if (config.policy.smartRules.length > 0) {
|
|
@@ -1359,11 +1848,66 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1359
1848
|
if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
|
|
1360
1849
|
return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
|
|
1361
1850
|
}
|
|
1851
|
+
const pipeAnalysis = analyzePipeChain(shellCommand);
|
|
1852
|
+
if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
|
|
1853
|
+
const sinks = pipeAnalysis.sinkTargets;
|
|
1854
|
+
const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
|
|
1855
|
+
if (pipeAnalysis.risk === "critical") {
|
|
1856
|
+
if (allTrusted) {
|
|
1857
|
+
return {
|
|
1858
|
+
decision: "review",
|
|
1859
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1860
|
+
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1861
|
+
tier: 3
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
return {
|
|
1865
|
+
decision: "block",
|
|
1866
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1867
|
+
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1868
|
+
tier: 3
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (allTrusted) {
|
|
1872
|
+
return { decision: "allow" };
|
|
1873
|
+
}
|
|
1874
|
+
return {
|
|
1875
|
+
decision: "review",
|
|
1876
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1877
|
+
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1878
|
+
tier: 3
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
const firstToken = analyzed.actions[0] ?? "";
|
|
1882
|
+
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1883
|
+
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
1884
|
+
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
1885
|
+
allTokens.push(...sshHosts);
|
|
1886
|
+
}
|
|
1887
|
+
if (firstToken && import_path8.default.posix.isAbsolute(firstToken)) {
|
|
1888
|
+
const prov = checkProvenance(firstToken, cwd);
|
|
1889
|
+
if (prov.trustLevel === "suspect") {
|
|
1890
|
+
return {
|
|
1891
|
+
decision: config.settings.mode === "strict" ? "block" : "review",
|
|
1892
|
+
blockedByLabel: "Node9: Suspect Binary",
|
|
1893
|
+
reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
|
|
1894
|
+
tier: 3
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
|
|
1898
|
+
return {
|
|
1899
|
+
decision: "review",
|
|
1900
|
+
blockedByLabel: "Node9: Unknown Binary (strict mode)",
|
|
1901
|
+
reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
|
|
1902
|
+
tier: 3
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1362
1906
|
if (isSqlTool(toolName, config.policy.toolInspection)) {
|
|
1363
1907
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
1364
1908
|
}
|
|
1365
1909
|
} else {
|
|
1366
|
-
allTokens =
|
|
1910
|
+
allTokens = tokenize2(toolName);
|
|
1367
1911
|
if (args && typeof args === "object") {
|
|
1368
1912
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
1369
1913
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
@@ -1437,9 +1981,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1437
1981
|
}
|
|
1438
1982
|
async function explainPolicy(toolName, args) {
|
|
1439
1983
|
const steps = [];
|
|
1440
|
-
const globalPath =
|
|
1441
|
-
const projectPath =
|
|
1442
|
-
const credsPath =
|
|
1984
|
+
const globalPath = import_path8.default.join(import_os6.default.homedir(), ".node9", "config.json");
|
|
1985
|
+
const projectPath = import_path8.default.join(process.cwd(), "node9.config.json");
|
|
1986
|
+
const credsPath = import_path8.default.join(import_os6.default.homedir(), ".node9", "credentials.json");
|
|
1443
1987
|
const waterfall = [
|
|
1444
1988
|
{
|
|
1445
1989
|
tier: 1,
|
|
@@ -1450,19 +1994,19 @@ async function explainPolicy(toolName, args) {
|
|
|
1450
1994
|
{
|
|
1451
1995
|
tier: 2,
|
|
1452
1996
|
label: "Cloud policy",
|
|
1453
|
-
status:
|
|
1454
|
-
note:
|
|
1997
|
+
status: import_fs7.default.existsSync(credsPath) ? "active" : "missing",
|
|
1998
|
+
note: import_fs7.default.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
1455
1999
|
},
|
|
1456
2000
|
{
|
|
1457
2001
|
tier: 3,
|
|
1458
2002
|
label: "Project config",
|
|
1459
|
-
status:
|
|
2003
|
+
status: import_fs7.default.existsSync(projectPath) ? "active" : "missing",
|
|
1460
2004
|
path: projectPath
|
|
1461
2005
|
},
|
|
1462
2006
|
{
|
|
1463
2007
|
tier: 4,
|
|
1464
2008
|
label: "Global config",
|
|
1465
|
-
status:
|
|
2009
|
+
status: import_fs7.default.existsSync(globalPath) ? "active" : "missing",
|
|
1466
2010
|
path: globalPath
|
|
1467
2011
|
},
|
|
1468
2012
|
{
|
|
@@ -1594,7 +2138,7 @@ async function explainPolicy(toolName, args) {
|
|
|
1594
2138
|
});
|
|
1595
2139
|
}
|
|
1596
2140
|
} else {
|
|
1597
|
-
allTokens =
|
|
2141
|
+
allTokens = tokenize2(toolName);
|
|
1598
2142
|
let detail = `No toolInspection match for "${toolName}" \u2014 tokens: [${allTokens.join(", ")}]`;
|
|
1599
2143
|
if (args && typeof args === "object") {
|
|
1600
2144
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
@@ -1699,18 +2243,22 @@ function isIgnoredTool(toolName) {
|
|
|
1699
2243
|
const config = getConfig();
|
|
1700
2244
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
1701
2245
|
}
|
|
1702
|
-
var
|
|
2246
|
+
var import_fs7, import_path8, import_os6, import_picomatch, import_sh_syntax, SQL_DML_KEYWORDS;
|
|
1703
2247
|
var init_policy = __esm({
|
|
1704
2248
|
"src/policy/index.ts"() {
|
|
1705
2249
|
"use strict";
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
2250
|
+
import_fs7 = __toESM(require("fs"));
|
|
2251
|
+
import_path8 = __toESM(require("path"));
|
|
2252
|
+
import_os6 = __toESM(require("os"));
|
|
1709
2253
|
import_picomatch = __toESM(require("picomatch"));
|
|
1710
2254
|
import_sh_syntax = require("sh-syntax");
|
|
1711
2255
|
init_dlp();
|
|
1712
2256
|
init_config();
|
|
1713
2257
|
init_regex();
|
|
2258
|
+
init_provenance();
|
|
2259
|
+
init_pipe_chain();
|
|
2260
|
+
init_ssh_parser();
|
|
2261
|
+
init_trusted_hosts();
|
|
1714
2262
|
SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
1715
2263
|
}
|
|
1716
2264
|
});
|
|
@@ -1718,11 +2266,11 @@ var init_policy = __esm({
|
|
|
1718
2266
|
// src/auth/state.ts
|
|
1719
2267
|
function checkPause() {
|
|
1720
2268
|
try {
|
|
1721
|
-
if (!
|
|
1722
|
-
const state = JSON.parse(
|
|
2269
|
+
if (!import_fs8.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
2270
|
+
const state = JSON.parse(import_fs8.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
1723
2271
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
1724
2272
|
try {
|
|
1725
|
-
|
|
2273
|
+
import_fs8.default.unlinkSync(PAUSED_FILE);
|
|
1726
2274
|
} catch {
|
|
1727
2275
|
}
|
|
1728
2276
|
return { paused: false };
|
|
@@ -1733,11 +2281,11 @@ function checkPause() {
|
|
|
1733
2281
|
}
|
|
1734
2282
|
}
|
|
1735
2283
|
function atomicWriteSync(filePath, data, options) {
|
|
1736
|
-
const dir =
|
|
1737
|
-
if (!
|
|
1738
|
-
const tmpPath = `${filePath}.${
|
|
1739
|
-
|
|
1740
|
-
|
|
2284
|
+
const dir = import_path9.default.dirname(filePath);
|
|
2285
|
+
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
2286
|
+
const tmpPath = `${filePath}.${import_os7.default.hostname()}.${process.pid}.tmp`;
|
|
2287
|
+
import_fs8.default.writeFileSync(tmpPath, data, options);
|
|
2288
|
+
import_fs8.default.renameSync(tmpPath, filePath);
|
|
1741
2289
|
}
|
|
1742
2290
|
function pauseNode9(durationMs, durationStr) {
|
|
1743
2291
|
const state = { expiry: Date.now() + durationMs, duration: durationStr };
|
|
@@ -1745,18 +2293,18 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
1745
2293
|
}
|
|
1746
2294
|
function resumeNode9() {
|
|
1747
2295
|
try {
|
|
1748
|
-
if (
|
|
2296
|
+
if (import_fs8.default.existsSync(PAUSED_FILE)) import_fs8.default.unlinkSync(PAUSED_FILE);
|
|
1749
2297
|
} catch {
|
|
1750
2298
|
}
|
|
1751
2299
|
}
|
|
1752
2300
|
function getActiveTrustSession(toolName) {
|
|
1753
2301
|
try {
|
|
1754
|
-
if (!
|
|
1755
|
-
const trust = JSON.parse(
|
|
2302
|
+
if (!import_fs8.default.existsSync(TRUST_FILE)) return false;
|
|
2303
|
+
const trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
1756
2304
|
const now = Date.now();
|
|
1757
2305
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
1758
2306
|
if (active.length !== trust.entries.length) {
|
|
1759
|
-
|
|
2307
|
+
import_fs8.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
1760
2308
|
}
|
|
1761
2309
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
1762
2310
|
} catch {
|
|
@@ -1767,8 +2315,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1767
2315
|
try {
|
|
1768
2316
|
let trust = { entries: [] };
|
|
1769
2317
|
try {
|
|
1770
|
-
if (
|
|
1771
|
-
trust = JSON.parse(
|
|
2318
|
+
if (import_fs8.default.existsSync(TRUST_FILE)) {
|
|
2319
|
+
trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
1772
2320
|
}
|
|
1773
2321
|
} catch {
|
|
1774
2322
|
}
|
|
@@ -1784,34 +2332,34 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1784
2332
|
}
|
|
1785
2333
|
function getPersistentDecision(toolName) {
|
|
1786
2334
|
try {
|
|
1787
|
-
const file =
|
|
1788
|
-
if (!
|
|
1789
|
-
const decisions = JSON.parse(
|
|
2335
|
+
const file = import_path9.default.join(import_os7.default.homedir(), ".node9", "decisions.json");
|
|
2336
|
+
if (!import_fs8.default.existsSync(file)) return null;
|
|
2337
|
+
const decisions = JSON.parse(import_fs8.default.readFileSync(file, "utf-8"));
|
|
1790
2338
|
const d = decisions[toolName];
|
|
1791
2339
|
if (d === "allow" || d === "deny") return d;
|
|
1792
2340
|
} catch {
|
|
1793
2341
|
}
|
|
1794
2342
|
return null;
|
|
1795
2343
|
}
|
|
1796
|
-
var
|
|
2344
|
+
var import_fs8, import_path9, import_os7, PAUSED_FILE, TRUST_FILE;
|
|
1797
2345
|
var init_state = __esm({
|
|
1798
2346
|
"src/auth/state.ts"() {
|
|
1799
2347
|
"use strict";
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2348
|
+
import_fs8 = __toESM(require("fs"));
|
|
2349
|
+
import_path9 = __toESM(require("path"));
|
|
2350
|
+
import_os7 = __toESM(require("os"));
|
|
1803
2351
|
init_policy();
|
|
1804
|
-
PAUSED_FILE =
|
|
1805
|
-
TRUST_FILE =
|
|
2352
|
+
PAUSED_FILE = import_path9.default.join(import_os7.default.homedir(), ".node9", "PAUSED");
|
|
2353
|
+
TRUST_FILE = import_path9.default.join(import_os7.default.homedir(), ".node9", "trust.json");
|
|
1806
2354
|
}
|
|
1807
2355
|
});
|
|
1808
2356
|
|
|
1809
2357
|
// src/auth/daemon.ts
|
|
1810
2358
|
function getInternalToken() {
|
|
1811
2359
|
try {
|
|
1812
|
-
const pidFile =
|
|
1813
|
-
if (!
|
|
1814
|
-
const data = JSON.parse(
|
|
2360
|
+
const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
|
|
2361
|
+
if (!import_fs9.default.existsSync(pidFile)) return null;
|
|
2362
|
+
const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
1815
2363
|
process.kill(data.pid, 0);
|
|
1816
2364
|
return data.internalToken ?? null;
|
|
1817
2365
|
} catch {
|
|
@@ -1819,10 +2367,10 @@ function getInternalToken() {
|
|
|
1819
2367
|
}
|
|
1820
2368
|
}
|
|
1821
2369
|
function isDaemonRunning() {
|
|
1822
|
-
const pidFile =
|
|
1823
|
-
if (
|
|
2370
|
+
const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
|
|
2371
|
+
if (import_fs9.default.existsSync(pidFile)) {
|
|
1824
2372
|
try {
|
|
1825
|
-
const { pid, port } = JSON.parse(
|
|
2373
|
+
const { pid, port } = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
1826
2374
|
if (port !== DAEMON_PORT) return false;
|
|
1827
2375
|
process.kill(pid, 0);
|
|
1828
2376
|
return true;
|
|
@@ -1915,13 +2463,13 @@ async function resolveViaDaemon(id, decision, internalToken) {
|
|
|
1915
2463
|
signal: AbortSignal.timeout(3e3)
|
|
1916
2464
|
});
|
|
1917
2465
|
}
|
|
1918
|
-
var
|
|
2466
|
+
var import_fs9, import_path10, import_os8, import_child_process, DAEMON_PORT, DAEMON_HOST;
|
|
1919
2467
|
var init_daemon = __esm({
|
|
1920
2468
|
"src/auth/daemon.ts"() {
|
|
1921
2469
|
"use strict";
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2470
|
+
import_fs9 = __toESM(require("fs"));
|
|
2471
|
+
import_path10 = __toESM(require("path"));
|
|
2472
|
+
import_os8 = __toESM(require("os"));
|
|
1925
2473
|
import_child_process = require("child_process");
|
|
1926
2474
|
DAEMON_PORT = 7391;
|
|
1927
2475
|
DAEMON_HOST = "127.0.0.1";
|
|
@@ -1980,7 +2528,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
1980
2528
|
intent = "EDIT";
|
|
1981
2529
|
if (obj.file_path) {
|
|
1982
2530
|
editFilePath = String(obj.file_path);
|
|
1983
|
-
editFileName =
|
|
2531
|
+
editFileName = import_path11.default.basename(editFilePath);
|
|
1984
2532
|
}
|
|
1985
2533
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
1986
2534
|
contextSnippet = result.snippet;
|
|
@@ -2012,11 +2560,11 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
2012
2560
|
...ruleName && { ruleName }
|
|
2013
2561
|
};
|
|
2014
2562
|
}
|
|
2015
|
-
var
|
|
2563
|
+
var import_path11, CODE_KEYS;
|
|
2016
2564
|
var init_context_sniper = __esm({
|
|
2017
2565
|
"src/context-sniper.ts"() {
|
|
2018
2566
|
"use strict";
|
|
2019
|
-
|
|
2567
|
+
import_path11 = __toESM(require("path"));
|
|
2020
2568
|
CODE_KEYS = [
|
|
2021
2569
|
"command",
|
|
2022
2570
|
"cmd",
|
|
@@ -2055,7 +2603,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
2055
2603
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2056
2604
|
const obj = parsed;
|
|
2057
2605
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
2058
|
-
const file = obj.file_path ?
|
|
2606
|
+
const file = obj.file_path ? import_path12.default.basename(String(obj.file_path)) : "file";
|
|
2059
2607
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
2060
2608
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
2061
2609
|
return {
|
|
@@ -2228,12 +2776,12 @@ end run`;
|
|
|
2228
2776
|
}
|
|
2229
2777
|
});
|
|
2230
2778
|
}
|
|
2231
|
-
var import_child_process2,
|
|
2779
|
+
var import_child_process2, import_path12, isTestEnv;
|
|
2232
2780
|
var init_native = __esm({
|
|
2233
2781
|
"src/ui/native.ts"() {
|
|
2234
2782
|
"use strict";
|
|
2235
2783
|
import_child_process2 = require("child_process");
|
|
2236
|
-
|
|
2784
|
+
import_path12 = __toESM(require("path"));
|
|
2237
2785
|
init_context_sniper();
|
|
2238
2786
|
isTestEnv = () => {
|
|
2239
2787
|
return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
|
|
@@ -2253,9 +2801,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2253
2801
|
context: {
|
|
2254
2802
|
agent: meta?.agent,
|
|
2255
2803
|
mcpServer: meta?.mcpServer,
|
|
2256
|
-
hostname:
|
|
2804
|
+
hostname: import_os9.default.hostname(),
|
|
2257
2805
|
cwd: process.cwd(),
|
|
2258
|
-
platform:
|
|
2806
|
+
platform: import_os9.default.platform()
|
|
2259
2807
|
}
|
|
2260
2808
|
}),
|
|
2261
2809
|
signal: AbortSignal.timeout(5e3)
|
|
@@ -2276,9 +2824,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2276
2824
|
context: {
|
|
2277
2825
|
agent: meta?.agent,
|
|
2278
2826
|
mcpServer: meta?.mcpServer,
|
|
2279
|
-
hostname:
|
|
2827
|
+
hostname: import_os9.default.hostname(),
|
|
2280
2828
|
cwd: process.cwd(),
|
|
2281
|
-
platform:
|
|
2829
|
+
platform: import_os9.default.platform()
|
|
2282
2830
|
},
|
|
2283
2831
|
...riskMetadata && { riskMetadata }
|
|
2284
2832
|
}),
|
|
@@ -2334,26 +2882,26 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
2334
2882
|
});
|
|
2335
2883
|
clearTimeout(timer);
|
|
2336
2884
|
if (!res.ok) {
|
|
2337
|
-
|
|
2885
|
+
import_fs10.default.appendFileSync(
|
|
2338
2886
|
HOOK_DEBUG_LOG,
|
|
2339
2887
|
`[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
|
|
2340
2888
|
`
|
|
2341
2889
|
);
|
|
2342
2890
|
}
|
|
2343
2891
|
} catch (err) {
|
|
2344
|
-
|
|
2892
|
+
import_fs10.default.appendFileSync(
|
|
2345
2893
|
HOOK_DEBUG_LOG,
|
|
2346
2894
|
`[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
|
|
2347
2895
|
`
|
|
2348
2896
|
);
|
|
2349
2897
|
}
|
|
2350
2898
|
}
|
|
2351
|
-
var
|
|
2899
|
+
var import_fs10, import_os9;
|
|
2352
2900
|
var init_cloud = __esm({
|
|
2353
2901
|
"src/auth/cloud.ts"() {
|
|
2354
2902
|
"use strict";
|
|
2355
|
-
|
|
2356
|
-
|
|
2903
|
+
import_fs10 = __toESM(require("fs"));
|
|
2904
|
+
import_os9 = __toESM(require("os"));
|
|
2357
2905
|
init_audit();
|
|
2358
2906
|
}
|
|
2359
2907
|
});
|
|
@@ -2440,7 +2988,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2440
2988
|
}
|
|
2441
2989
|
if (config.settings.mode === "audit") {
|
|
2442
2990
|
if (!isIgnoredTool(toolName)) {
|
|
2443
|
-
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
2991
|
+
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
2444
2992
|
if (policyResult.decision === "review") {
|
|
2445
2993
|
appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
|
|
2446
2994
|
if (approvers.cloud && creds?.apiKey) {
|
|
@@ -2705,13 +3253,13 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
2705
3253
|
}
|
|
2706
3254
|
return finalResult;
|
|
2707
3255
|
}
|
|
2708
|
-
var import_net,
|
|
3256
|
+
var import_net, import_path13, import_os10, import_crypto2, ACTIVITY_SOCKET_PATH;
|
|
2709
3257
|
var init_orchestrator = __esm({
|
|
2710
3258
|
"src/auth/orchestrator.ts"() {
|
|
2711
3259
|
"use strict";
|
|
2712
3260
|
import_net = __toESM(require("net"));
|
|
2713
|
-
|
|
2714
|
-
|
|
3261
|
+
import_path13 = __toESM(require("path"));
|
|
3262
|
+
import_os10 = __toESM(require("os"));
|
|
2715
3263
|
import_crypto2 = require("crypto");
|
|
2716
3264
|
init_native();
|
|
2717
3265
|
init_context_sniper();
|
|
@@ -2722,7 +3270,7 @@ var init_orchestrator = __esm({
|
|
|
2722
3270
|
init_state();
|
|
2723
3271
|
init_daemon();
|
|
2724
3272
|
init_cloud();
|
|
2725
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
3273
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
|
|
2726
3274
|
}
|
|
2727
3275
|
});
|
|
2728
3276
|
|
|
@@ -4215,11 +4763,11 @@ function markRejectionHandlerRegistered() {
|
|
|
4215
4763
|
daemonRejectionHandlerRegistered = true;
|
|
4216
4764
|
}
|
|
4217
4765
|
function atomicWriteSync2(filePath, data, options) {
|
|
4218
|
-
const dir =
|
|
4219
|
-
if (!
|
|
4766
|
+
const dir = import_path15.default.dirname(filePath);
|
|
4767
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
4220
4768
|
const tmpPath = `${filePath}.${(0, import_crypto3.randomUUID)()}.tmp`;
|
|
4221
|
-
|
|
4222
|
-
|
|
4769
|
+
import_fs12.default.writeFileSync(tmpPath, data, options);
|
|
4770
|
+
import_fs12.default.renameSync(tmpPath, filePath);
|
|
4223
4771
|
}
|
|
4224
4772
|
function redactArgs(value) {
|
|
4225
4773
|
if (!value || typeof value !== "object") return value;
|
|
@@ -4239,16 +4787,16 @@ function appendAuditLog(data) {
|
|
|
4239
4787
|
decision: data.decision,
|
|
4240
4788
|
source: "daemon"
|
|
4241
4789
|
};
|
|
4242
|
-
const dir =
|
|
4243
|
-
if (!
|
|
4244
|
-
|
|
4790
|
+
const dir = import_path15.default.dirname(AUDIT_LOG_FILE);
|
|
4791
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
4792
|
+
import_fs12.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
4245
4793
|
} catch {
|
|
4246
4794
|
}
|
|
4247
4795
|
}
|
|
4248
4796
|
function getAuditHistory(limit = 20) {
|
|
4249
4797
|
try {
|
|
4250
|
-
if (!
|
|
4251
|
-
const lines =
|
|
4798
|
+
if (!import_fs12.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
4799
|
+
const lines = import_fs12.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
4252
4800
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4253
4801
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4254
4802
|
} catch {
|
|
@@ -4257,19 +4805,19 @@ function getAuditHistory(limit = 20) {
|
|
|
4257
4805
|
}
|
|
4258
4806
|
function getOrgName() {
|
|
4259
4807
|
try {
|
|
4260
|
-
if (
|
|
4808
|
+
if (import_fs12.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
4261
4809
|
} catch {
|
|
4262
4810
|
}
|
|
4263
4811
|
return null;
|
|
4264
4812
|
}
|
|
4265
4813
|
function hasStoredSlackKey() {
|
|
4266
|
-
return
|
|
4814
|
+
return import_fs12.default.existsSync(CREDENTIALS_FILE);
|
|
4267
4815
|
}
|
|
4268
4816
|
function writeGlobalSetting(key, value) {
|
|
4269
4817
|
let config = {};
|
|
4270
4818
|
try {
|
|
4271
|
-
if (
|
|
4272
|
-
config = JSON.parse(
|
|
4819
|
+
if (import_fs12.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
4820
|
+
config = JSON.parse(import_fs12.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
4273
4821
|
}
|
|
4274
4822
|
} catch {
|
|
4275
4823
|
}
|
|
@@ -4281,8 +4829,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4281
4829
|
try {
|
|
4282
4830
|
let trust = { entries: [] };
|
|
4283
4831
|
try {
|
|
4284
|
-
if (
|
|
4285
|
-
trust = JSON.parse(
|
|
4832
|
+
if (import_fs12.default.existsSync(TRUST_FILE2))
|
|
4833
|
+
trust = JSON.parse(import_fs12.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
4286
4834
|
} catch {
|
|
4287
4835
|
}
|
|
4288
4836
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -4293,8 +4841,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4293
4841
|
}
|
|
4294
4842
|
function readPersistentDecisions() {
|
|
4295
4843
|
try {
|
|
4296
|
-
if (
|
|
4297
|
-
return JSON.parse(
|
|
4844
|
+
if (import_fs12.default.existsSync(DECISIONS_FILE)) {
|
|
4845
|
+
return JSON.parse(import_fs12.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4298
4846
|
}
|
|
4299
4847
|
} catch {
|
|
4300
4848
|
}
|
|
@@ -4359,7 +4907,7 @@ function abandonPending() {
|
|
|
4359
4907
|
});
|
|
4360
4908
|
if (autoStarted) {
|
|
4361
4909
|
try {
|
|
4362
|
-
|
|
4910
|
+
import_fs12.default.unlinkSync(DAEMON_PID_FILE);
|
|
4363
4911
|
} catch {
|
|
4364
4912
|
}
|
|
4365
4913
|
setTimeout(() => {
|
|
@@ -4370,7 +4918,7 @@ function abandonPending() {
|
|
|
4370
4918
|
}
|
|
4371
4919
|
function startActivitySocket() {
|
|
4372
4920
|
try {
|
|
4373
|
-
|
|
4921
|
+
import_fs12.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4374
4922
|
} catch {
|
|
4375
4923
|
}
|
|
4376
4924
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4412,29 +4960,29 @@ function startActivitySocket() {
|
|
|
4412
4960
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4413
4961
|
process.on("exit", () => {
|
|
4414
4962
|
try {
|
|
4415
|
-
|
|
4963
|
+
import_fs12.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4416
4964
|
} catch {
|
|
4417
4965
|
}
|
|
4418
4966
|
});
|
|
4419
4967
|
}
|
|
4420
|
-
var import_net2,
|
|
4968
|
+
var import_net2, import_fs12, import_path15, import_os12, import_child_process3, import_crypto3, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, pending, sseClients, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE;
|
|
4421
4969
|
var init_state2 = __esm({
|
|
4422
4970
|
"src/daemon/state.ts"() {
|
|
4423
4971
|
"use strict";
|
|
4424
4972
|
import_net2 = __toESM(require("net"));
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4973
|
+
import_fs12 = __toESM(require("fs"));
|
|
4974
|
+
import_path15 = __toESM(require("path"));
|
|
4975
|
+
import_os12 = __toESM(require("os"));
|
|
4428
4976
|
import_child_process3 = require("child_process");
|
|
4429
4977
|
import_crypto3 = require("crypto");
|
|
4430
4978
|
init_daemon();
|
|
4431
|
-
homeDir =
|
|
4432
|
-
DAEMON_PID_FILE =
|
|
4433
|
-
DECISIONS_FILE =
|
|
4434
|
-
AUDIT_LOG_FILE =
|
|
4435
|
-
TRUST_FILE2 =
|
|
4436
|
-
GLOBAL_CONFIG_FILE =
|
|
4437
|
-
CREDENTIALS_FILE =
|
|
4979
|
+
homeDir = import_os12.default.homedir();
|
|
4980
|
+
DAEMON_PID_FILE = import_path15.default.join(homeDir, ".node9", "daemon.pid");
|
|
4981
|
+
DECISIONS_FILE = import_path15.default.join(homeDir, ".node9", "decisions.json");
|
|
4982
|
+
AUDIT_LOG_FILE = import_path15.default.join(homeDir, ".node9", "audit.log");
|
|
4983
|
+
TRUST_FILE2 = import_path15.default.join(homeDir, ".node9", "trust.json");
|
|
4984
|
+
GLOBAL_CONFIG_FILE = import_path15.default.join(homeDir, ".node9", "config.json");
|
|
4985
|
+
CREDENTIALS_FILE = import_path15.default.join(homeDir, ".node9", "credentials.json");
|
|
4438
4986
|
pending = /* @__PURE__ */ new Map();
|
|
4439
4987
|
sseClients = /* @__PURE__ */ new Set();
|
|
4440
4988
|
_abandonTimer = null;
|
|
@@ -4448,7 +4996,7 @@ var init_state2 = __esm({
|
|
|
4448
4996
|
"2h": 2 * 60 * 6e4
|
|
4449
4997
|
};
|
|
4450
4998
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
4451
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
4999
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path15.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
|
|
4452
5000
|
ACTIVITY_RING_SIZE = 100;
|
|
4453
5001
|
activityRing = [];
|
|
4454
5002
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -4471,7 +5019,7 @@ function startDaemon() {
|
|
|
4471
5019
|
idleTimer = setTimeout(() => {
|
|
4472
5020
|
if (autoStarted) {
|
|
4473
5021
|
try {
|
|
4474
|
-
|
|
5022
|
+
import_fs13.default.unlinkSync(DAEMON_PID_FILE);
|
|
4475
5023
|
} catch {
|
|
4476
5024
|
}
|
|
4477
5025
|
}
|
|
@@ -4613,7 +5161,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4613
5161
|
status: "pending"
|
|
4614
5162
|
});
|
|
4615
5163
|
}
|
|
4616
|
-
const projectCwd = typeof cwd === "string" &&
|
|
5164
|
+
const projectCwd = typeof cwd === "string" && import_path16.default.isAbsolute(cwd) ? cwd : void 0;
|
|
4617
5165
|
const projectConfig = getConfig(projectCwd);
|
|
4618
5166
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
4619
5167
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -4917,14 +5465,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4917
5465
|
server.on("error", (e) => {
|
|
4918
5466
|
if (e.code === "EADDRINUSE") {
|
|
4919
5467
|
try {
|
|
4920
|
-
if (
|
|
4921
|
-
const { pid } = JSON.parse(
|
|
5468
|
+
if (import_fs13.default.existsSync(DAEMON_PID_FILE)) {
|
|
5469
|
+
const { pid } = JSON.parse(import_fs13.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4922
5470
|
process.kill(pid, 0);
|
|
4923
5471
|
return process.exit(0);
|
|
4924
5472
|
}
|
|
4925
5473
|
} catch {
|
|
4926
5474
|
try {
|
|
4927
|
-
|
|
5475
|
+
import_fs13.default.unlinkSync(DAEMON_PID_FILE);
|
|
4928
5476
|
} catch {
|
|
4929
5477
|
}
|
|
4930
5478
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -4983,13 +5531,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
4983
5531
|
}
|
|
4984
5532
|
startActivitySocket();
|
|
4985
5533
|
}
|
|
4986
|
-
var import_http,
|
|
5534
|
+
var import_http, import_fs13, import_path16, import_crypto4, import_child_process4, import_chalk2;
|
|
4987
5535
|
var init_server = __esm({
|
|
4988
5536
|
"src/daemon/server.ts"() {
|
|
4989
5537
|
"use strict";
|
|
4990
5538
|
import_http = __toESM(require("http"));
|
|
4991
|
-
|
|
4992
|
-
|
|
5539
|
+
import_fs13 = __toESM(require("fs"));
|
|
5540
|
+
import_path16 = __toESM(require("path"));
|
|
4993
5541
|
import_crypto4 = require("crypto");
|
|
4994
5542
|
import_child_process4 = require("child_process");
|
|
4995
5543
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -5002,24 +5550,24 @@ var init_server = __esm({
|
|
|
5002
5550
|
|
|
5003
5551
|
// src/daemon/index.ts
|
|
5004
5552
|
function stopDaemon() {
|
|
5005
|
-
if (!
|
|
5553
|
+
if (!import_fs14.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
5006
5554
|
try {
|
|
5007
|
-
const { pid } = JSON.parse(
|
|
5555
|
+
const { pid } = JSON.parse(import_fs14.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
5008
5556
|
process.kill(pid, "SIGTERM");
|
|
5009
5557
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
5010
5558
|
} catch {
|
|
5011
5559
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
5012
5560
|
} finally {
|
|
5013
5561
|
try {
|
|
5014
|
-
|
|
5562
|
+
import_fs14.default.unlinkSync(DAEMON_PID_FILE);
|
|
5015
5563
|
} catch {
|
|
5016
5564
|
}
|
|
5017
5565
|
}
|
|
5018
5566
|
}
|
|
5019
5567
|
function daemonStatus() {
|
|
5020
|
-
if (
|
|
5568
|
+
if (import_fs14.default.existsSync(DAEMON_PID_FILE)) {
|
|
5021
5569
|
try {
|
|
5022
|
-
const { pid } = JSON.parse(
|
|
5570
|
+
const { pid } = JSON.parse(import_fs14.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
5023
5571
|
process.kill(pid, 0);
|
|
5024
5572
|
console.log(import_chalk3.default.green("Node9 daemon: running"));
|
|
5025
5573
|
return;
|
|
@@ -5038,11 +5586,11 @@ function daemonStatus() {
|
|
|
5038
5586
|
console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
|
|
5039
5587
|
}
|
|
5040
5588
|
}
|
|
5041
|
-
var
|
|
5589
|
+
var import_fs14, import_chalk3, import_child_process5;
|
|
5042
5590
|
var init_daemon2 = __esm({
|
|
5043
5591
|
"src/daemon/index.ts"() {
|
|
5044
5592
|
"use strict";
|
|
5045
|
-
|
|
5593
|
+
import_fs14 = __toESM(require("fs"));
|
|
5046
5594
|
import_chalk3 = __toESM(require("chalk"));
|
|
5047
5595
|
import_child_process5 = require("child_process");
|
|
5048
5596
|
init_server();
|
|
@@ -5069,17 +5617,17 @@ function formatBase(activity) {
|
|
|
5069
5617
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
5070
5618
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
5071
5619
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
5072
|
-
return `${
|
|
5620
|
+
return `${import_chalk15.default.gray(time)} ${icon} ${import_chalk15.default.white.bold(toolName)} ${import_chalk15.default.dim(argsPreview)}`;
|
|
5073
5621
|
}
|
|
5074
5622
|
function renderResult(activity, result) {
|
|
5075
5623
|
const base = formatBase(activity);
|
|
5076
5624
|
let status;
|
|
5077
5625
|
if (result.status === "allow") {
|
|
5078
|
-
status =
|
|
5626
|
+
status = import_chalk15.default.green("\u2713 ALLOW");
|
|
5079
5627
|
} else if (result.status === "dlp") {
|
|
5080
|
-
status =
|
|
5628
|
+
status = import_chalk15.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
5081
5629
|
} else {
|
|
5082
|
-
status =
|
|
5630
|
+
status = import_chalk15.default.red("\u2717 BLOCK");
|
|
5083
5631
|
}
|
|
5084
5632
|
if (process.stdout.isTTY) {
|
|
5085
5633
|
import_readline3.default.clearLine(process.stdout, 0);
|
|
@@ -5089,16 +5637,16 @@ function renderResult(activity, result) {
|
|
|
5089
5637
|
}
|
|
5090
5638
|
function renderPending(activity) {
|
|
5091
5639
|
if (!process.stdout.isTTY) return;
|
|
5092
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
5640
|
+
process.stdout.write(`${formatBase(activity)} ${import_chalk15.default.yellow("\u25CF \u2026")}\r`);
|
|
5093
5641
|
}
|
|
5094
5642
|
async function ensureDaemon() {
|
|
5095
5643
|
let pidPort = null;
|
|
5096
|
-
if (
|
|
5644
|
+
if (import_fs21.default.existsSync(PID_FILE)) {
|
|
5097
5645
|
try {
|
|
5098
|
-
const { port } = JSON.parse(
|
|
5646
|
+
const { port } = JSON.parse(import_fs21.default.readFileSync(PID_FILE, "utf-8"));
|
|
5099
5647
|
pidPort = port;
|
|
5100
5648
|
} catch {
|
|
5101
|
-
console.error(
|
|
5649
|
+
console.error(import_chalk15.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
5102
5650
|
}
|
|
5103
5651
|
}
|
|
5104
5652
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -5109,7 +5657,7 @@ async function ensureDaemon() {
|
|
|
5109
5657
|
if (res.ok) return checkPort;
|
|
5110
5658
|
} catch {
|
|
5111
5659
|
}
|
|
5112
|
-
console.log(
|
|
5660
|
+
console.log(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
5113
5661
|
const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
5114
5662
|
detached: true,
|
|
5115
5663
|
stdio: "ignore",
|
|
@@ -5126,7 +5674,7 @@ async function ensureDaemon() {
|
|
|
5126
5674
|
} catch {
|
|
5127
5675
|
}
|
|
5128
5676
|
}
|
|
5129
|
-
console.error(
|
|
5677
|
+
console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
5130
5678
|
process.exit(1);
|
|
5131
5679
|
}
|
|
5132
5680
|
function postDecisionHttp(id, decision, csrfToken, port) {
|
|
@@ -5197,7 +5745,7 @@ async function startTail(options = {}) {
|
|
|
5197
5745
|
req2.end();
|
|
5198
5746
|
});
|
|
5199
5747
|
if (result.ok) {
|
|
5200
|
-
console.log(
|
|
5748
|
+
console.log(import_chalk15.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
5201
5749
|
} else if (result.code === "ECONNREFUSED") {
|
|
5202
5750
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
5203
5751
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -5260,16 +5808,16 @@ async function startTail(options = {}) {
|
|
|
5260
5808
|
process.stdout.write(SHOW_CURSOR);
|
|
5261
5809
|
postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
|
|
5262
5810
|
try {
|
|
5263
|
-
|
|
5264
|
-
|
|
5811
|
+
import_fs21.default.appendFileSync(
|
|
5812
|
+
import_path23.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log"),
|
|
5265
5813
|
`[tail] POST /decision failed: ${String(err)}
|
|
5266
5814
|
`
|
|
5267
5815
|
);
|
|
5268
5816
|
} catch {
|
|
5269
5817
|
}
|
|
5270
5818
|
});
|
|
5271
|
-
const decisionLabel = decision === "allow" ?
|
|
5272
|
-
console.log(`${
|
|
5819
|
+
const decisionLabel = decision === "allow" ? import_chalk15.default.green("\u2713 ALLOWED (terminal)") : import_chalk15.default.red("\u2717 DENIED (terminal)");
|
|
5820
|
+
console.log(`${import_chalk15.default.cyan("\u25C6")} ${import_chalk15.default.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
|
|
5273
5821
|
approvalQueue.shift();
|
|
5274
5822
|
cardActive = false;
|
|
5275
5823
|
showNextCard();
|
|
@@ -5306,16 +5854,16 @@ async function startTail(options = {}) {
|
|
|
5306
5854
|
}
|
|
5307
5855
|
} catch {
|
|
5308
5856
|
}
|
|
5309
|
-
console.log(
|
|
5310
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
5857
|
+
console.log(import_chalk15.default.cyan.bold(`
|
|
5858
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk15.default.dim(`\u2192 ${dashboardUrl}`));
|
|
5311
5859
|
if (canApprove) {
|
|
5312
|
-
console.log(
|
|
5860
|
+
console.log(import_chalk15.default.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
|
|
5313
5861
|
}
|
|
5314
5862
|
if (options.history) {
|
|
5315
|
-
console.log(
|
|
5863
|
+
console.log(import_chalk15.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
5316
5864
|
} else {
|
|
5317
5865
|
console.log(
|
|
5318
|
-
|
|
5866
|
+
import_chalk15.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
5319
5867
|
);
|
|
5320
5868
|
}
|
|
5321
5869
|
process.on("SIGINT", () => {
|
|
@@ -5325,13 +5873,13 @@ async function startTail(options = {}) {
|
|
|
5325
5873
|
import_readline3.default.clearLine(process.stdout, 0);
|
|
5326
5874
|
import_readline3.default.cursorTo(process.stdout, 0);
|
|
5327
5875
|
}
|
|
5328
|
-
console.log(
|
|
5876
|
+
console.log(import_chalk15.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
5329
5877
|
process.exit(0);
|
|
5330
5878
|
});
|
|
5331
5879
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
5332
5880
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
5333
5881
|
if (res.statusCode !== 200) {
|
|
5334
|
-
console.error(
|
|
5882
|
+
console.error(import_chalk15.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
5335
5883
|
process.exit(1);
|
|
5336
5884
|
}
|
|
5337
5885
|
let currentEvent = "";
|
|
@@ -5361,7 +5909,7 @@ async function startTail(options = {}) {
|
|
|
5361
5909
|
import_readline3.default.clearLine(process.stdout, 0);
|
|
5362
5910
|
import_readline3.default.cursorTo(process.stdout, 0);
|
|
5363
5911
|
}
|
|
5364
|
-
console.log(
|
|
5912
|
+
console.log(import_chalk15.default.red("\n\u274C Daemon disconnected."));
|
|
5365
5913
|
process.exit(1);
|
|
5366
5914
|
});
|
|
5367
5915
|
});
|
|
@@ -5441,25 +5989,25 @@ async function startTail(options = {}) {
|
|
|
5441
5989
|
}
|
|
5442
5990
|
req.on("error", (err) => {
|
|
5443
5991
|
const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
|
|
5444
|
-
console.error(
|
|
5992
|
+
console.error(import_chalk15.default.red(`
|
|
5445
5993
|
\u274C ${msg}`));
|
|
5446
5994
|
process.exit(1);
|
|
5447
5995
|
});
|
|
5448
5996
|
}
|
|
5449
|
-
var import_http2,
|
|
5997
|
+
var import_http2, import_chalk15, import_fs21, import_os19, import_path23, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
|
|
5450
5998
|
var init_tail = __esm({
|
|
5451
5999
|
"src/tui/tail.ts"() {
|
|
5452
6000
|
"use strict";
|
|
5453
6001
|
import_http2 = __toESM(require("http"));
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
6002
|
+
import_chalk15 = __toESM(require("chalk"));
|
|
6003
|
+
import_fs21 = __toESM(require("fs"));
|
|
6004
|
+
import_os19 = __toESM(require("os"));
|
|
6005
|
+
import_path23 = __toESM(require("path"));
|
|
5458
6006
|
import_readline3 = __toESM(require("readline"));
|
|
5459
6007
|
import_child_process13 = require("child_process");
|
|
5460
6008
|
init_daemon2();
|
|
5461
6009
|
init_core();
|
|
5462
|
-
PID_FILE =
|
|
6010
|
+
PID_FILE = import_path23.default.join(import_os19.default.homedir(), ".node9", "daemon.pid");
|
|
5463
6011
|
ICONS = {
|
|
5464
6012
|
bash: "\u{1F4BB}",
|
|
5465
6013
|
shell: "\u{1F4BB}",
|
|
@@ -5497,9 +6045,9 @@ var import_commander = require("commander");
|
|
|
5497
6045
|
init_core();
|
|
5498
6046
|
|
|
5499
6047
|
// src/setup.ts
|
|
5500
|
-
var
|
|
5501
|
-
var
|
|
5502
|
-
var
|
|
6048
|
+
var import_fs11 = __toESM(require("fs"));
|
|
6049
|
+
var import_path14 = __toESM(require("path"));
|
|
6050
|
+
var import_os11 = __toESM(require("os"));
|
|
5503
6051
|
var import_chalk = __toESM(require("chalk"));
|
|
5504
6052
|
var import_prompts = require("@inquirer/prompts");
|
|
5505
6053
|
function printDaemonTip() {
|
|
@@ -5516,26 +6064,26 @@ function fullPathCommand(subcommand) {
|
|
|
5516
6064
|
}
|
|
5517
6065
|
function readJson(filePath) {
|
|
5518
6066
|
try {
|
|
5519
|
-
if (
|
|
5520
|
-
return JSON.parse(
|
|
6067
|
+
if (import_fs11.default.existsSync(filePath)) {
|
|
6068
|
+
return JSON.parse(import_fs11.default.readFileSync(filePath, "utf-8"));
|
|
5521
6069
|
}
|
|
5522
6070
|
} catch {
|
|
5523
6071
|
}
|
|
5524
6072
|
return null;
|
|
5525
6073
|
}
|
|
5526
6074
|
function writeJson(filePath, data) {
|
|
5527
|
-
const dir =
|
|
5528
|
-
if (!
|
|
5529
|
-
|
|
6075
|
+
const dir = import_path14.default.dirname(filePath);
|
|
6076
|
+
if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
6077
|
+
import_fs11.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
5530
6078
|
}
|
|
5531
6079
|
function isNode9Hook(cmd) {
|
|
5532
6080
|
if (!cmd) return false;
|
|
5533
6081
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
5534
6082
|
}
|
|
5535
6083
|
function teardownClaude() {
|
|
5536
|
-
const homeDir2 =
|
|
5537
|
-
const hooksPath =
|
|
5538
|
-
const mcpPath =
|
|
6084
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6085
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
6086
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
5539
6087
|
let changed = false;
|
|
5540
6088
|
const settings = readJson(hooksPath);
|
|
5541
6089
|
if (settings?.hooks) {
|
|
@@ -5583,8 +6131,8 @@ function teardownClaude() {
|
|
|
5583
6131
|
}
|
|
5584
6132
|
}
|
|
5585
6133
|
function teardownGemini() {
|
|
5586
|
-
const homeDir2 =
|
|
5587
|
-
const settingsPath =
|
|
6134
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6135
|
+
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
5588
6136
|
const settings = readJson(settingsPath);
|
|
5589
6137
|
if (!settings) {
|
|
5590
6138
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5622,8 +6170,8 @@ function teardownGemini() {
|
|
|
5622
6170
|
}
|
|
5623
6171
|
}
|
|
5624
6172
|
function teardownCursor() {
|
|
5625
|
-
const homeDir2 =
|
|
5626
|
-
const mcpPath =
|
|
6173
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6174
|
+
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5627
6175
|
const mcpConfig = readJson(mcpPath);
|
|
5628
6176
|
if (!mcpConfig?.mcpServers) {
|
|
5629
6177
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5649,9 +6197,9 @@ function teardownCursor() {
|
|
|
5649
6197
|
}
|
|
5650
6198
|
}
|
|
5651
6199
|
async function setupClaude() {
|
|
5652
|
-
const homeDir2 =
|
|
5653
|
-
const mcpPath =
|
|
5654
|
-
const hooksPath =
|
|
6200
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6201
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
6202
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
5655
6203
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5656
6204
|
const settings = readJson(hooksPath) ?? {};
|
|
5657
6205
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5725,8 +6273,8 @@ async function setupClaude() {
|
|
|
5725
6273
|
}
|
|
5726
6274
|
}
|
|
5727
6275
|
async function setupGemini() {
|
|
5728
|
-
const homeDir2 =
|
|
5729
|
-
const settingsPath =
|
|
6276
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6277
|
+
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
5730
6278
|
const settings = readJson(settingsPath) ?? {};
|
|
5731
6279
|
const servers = settings.mcpServers ?? {};
|
|
5732
6280
|
let anythingChanged = false;
|
|
@@ -5808,8 +6356,8 @@ async function setupGemini() {
|
|
|
5808
6356
|
}
|
|
5809
6357
|
}
|
|
5810
6358
|
async function setupCursor() {
|
|
5811
|
-
const homeDir2 =
|
|
5812
|
-
const mcpPath =
|
|
6359
|
+
const homeDir2 = import_os11.default.homedir();
|
|
6360
|
+
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5813
6361
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5814
6362
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5815
6363
|
let anythingChanged = false;
|
|
@@ -5865,10 +6413,10 @@ async function setupCursor() {
|
|
|
5865
6413
|
|
|
5866
6414
|
// src/cli.ts
|
|
5867
6415
|
init_daemon2();
|
|
5868
|
-
var
|
|
5869
|
-
var
|
|
5870
|
-
var
|
|
5871
|
-
var
|
|
6416
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
6417
|
+
var import_fs22 = __toESM(require("fs"));
|
|
6418
|
+
var import_path24 = __toESM(require("path"));
|
|
6419
|
+
var import_os20 = __toESM(require("os"));
|
|
5872
6420
|
var import_prompts3 = require("@inquirer/prompts");
|
|
5873
6421
|
|
|
5874
6422
|
// src/utils/duration.ts
|
|
@@ -6089,9 +6637,9 @@ async function autoStartDaemonAndWait() {
|
|
|
6089
6637
|
|
|
6090
6638
|
// src/cli/commands/check.ts
|
|
6091
6639
|
var import_chalk5 = __toESM(require("chalk"));
|
|
6092
|
-
var
|
|
6093
|
-
var
|
|
6094
|
-
var
|
|
6640
|
+
var import_fs16 = __toESM(require("fs"));
|
|
6641
|
+
var import_path18 = __toESM(require("path"));
|
|
6642
|
+
var import_os14 = __toESM(require("os"));
|
|
6095
6643
|
init_orchestrator();
|
|
6096
6644
|
init_daemon();
|
|
6097
6645
|
init_config();
|
|
@@ -6100,25 +6648,25 @@ init_policy();
|
|
|
6100
6648
|
// src/undo.ts
|
|
6101
6649
|
var import_child_process8 = require("child_process");
|
|
6102
6650
|
var import_crypto5 = __toESM(require("crypto"));
|
|
6103
|
-
var
|
|
6104
|
-
var
|
|
6105
|
-
var
|
|
6106
|
-
var SNAPSHOT_STACK_PATH =
|
|
6107
|
-
var UNDO_LATEST_PATH =
|
|
6651
|
+
var import_fs15 = __toESM(require("fs"));
|
|
6652
|
+
var import_path17 = __toESM(require("path"));
|
|
6653
|
+
var import_os13 = __toESM(require("os"));
|
|
6654
|
+
var SNAPSHOT_STACK_PATH = import_path17.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
|
|
6655
|
+
var UNDO_LATEST_PATH = import_path17.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
|
|
6108
6656
|
var MAX_SNAPSHOTS = 10;
|
|
6109
6657
|
var GIT_TIMEOUT = 15e3;
|
|
6110
6658
|
function readStack() {
|
|
6111
6659
|
try {
|
|
6112
|
-
if (
|
|
6113
|
-
return JSON.parse(
|
|
6660
|
+
if (import_fs15.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
6661
|
+
return JSON.parse(import_fs15.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
6114
6662
|
} catch {
|
|
6115
6663
|
}
|
|
6116
6664
|
return [];
|
|
6117
6665
|
}
|
|
6118
6666
|
function writeStack(stack) {
|
|
6119
|
-
const dir =
|
|
6120
|
-
if (!
|
|
6121
|
-
|
|
6667
|
+
const dir = import_path17.default.dirname(SNAPSHOT_STACK_PATH);
|
|
6668
|
+
if (!import_fs15.default.existsSync(dir)) import_fs15.default.mkdirSync(dir, { recursive: true });
|
|
6669
|
+
import_fs15.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
6122
6670
|
}
|
|
6123
6671
|
function buildArgsSummary(tool, args) {
|
|
6124
6672
|
if (!args || typeof args !== "object") return "";
|
|
@@ -6134,7 +6682,7 @@ function buildArgsSummary(tool, args) {
|
|
|
6134
6682
|
function normalizeCwdForHash(cwd) {
|
|
6135
6683
|
let normalized;
|
|
6136
6684
|
try {
|
|
6137
|
-
normalized =
|
|
6685
|
+
normalized = import_fs15.default.realpathSync(cwd);
|
|
6138
6686
|
} catch {
|
|
6139
6687
|
normalized = cwd;
|
|
6140
6688
|
}
|
|
@@ -6144,16 +6692,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
6144
6692
|
}
|
|
6145
6693
|
function getShadowRepoDir(cwd) {
|
|
6146
6694
|
const hash = import_crypto5.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
6147
|
-
return
|
|
6695
|
+
return import_path17.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
|
|
6148
6696
|
}
|
|
6149
6697
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
6150
6698
|
try {
|
|
6151
6699
|
const cutoff = Date.now() - 6e4;
|
|
6152
|
-
for (const f of
|
|
6700
|
+
for (const f of import_fs15.default.readdirSync(shadowDir)) {
|
|
6153
6701
|
if (f.startsWith("index_")) {
|
|
6154
|
-
const fp =
|
|
6702
|
+
const fp = import_path17.default.join(shadowDir, f);
|
|
6155
6703
|
try {
|
|
6156
|
-
if (
|
|
6704
|
+
if (import_fs15.default.statSync(fp).mtimeMs < cutoff) import_fs15.default.unlinkSync(fp);
|
|
6157
6705
|
} catch {
|
|
6158
6706
|
}
|
|
6159
6707
|
}
|
|
@@ -6165,7 +6713,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
6165
6713
|
const hardcoded = [".git", ".node9"];
|
|
6166
6714
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
6167
6715
|
try {
|
|
6168
|
-
|
|
6716
|
+
import_fs15.default.writeFileSync(import_path17.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
6169
6717
|
} catch {
|
|
6170
6718
|
}
|
|
6171
6719
|
}
|
|
@@ -6178,25 +6726,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6178
6726
|
timeout: 3e3
|
|
6179
6727
|
});
|
|
6180
6728
|
if (check.status === 0) {
|
|
6181
|
-
const ptPath =
|
|
6729
|
+
const ptPath = import_path17.default.join(shadowDir, "project-path.txt");
|
|
6182
6730
|
try {
|
|
6183
|
-
const stored =
|
|
6731
|
+
const stored = import_fs15.default.readFileSync(ptPath, "utf8").trim();
|
|
6184
6732
|
if (stored === normalizedCwd) return true;
|
|
6185
6733
|
if (process.env.NODE9_DEBUG === "1")
|
|
6186
6734
|
console.error(
|
|
6187
6735
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
6188
6736
|
);
|
|
6189
|
-
|
|
6737
|
+
import_fs15.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
6190
6738
|
} catch {
|
|
6191
6739
|
try {
|
|
6192
|
-
|
|
6740
|
+
import_fs15.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
6193
6741
|
} catch {
|
|
6194
6742
|
}
|
|
6195
6743
|
return true;
|
|
6196
6744
|
}
|
|
6197
6745
|
}
|
|
6198
6746
|
try {
|
|
6199
|
-
|
|
6747
|
+
import_fs15.default.mkdirSync(shadowDir, { recursive: true });
|
|
6200
6748
|
} catch {
|
|
6201
6749
|
}
|
|
6202
6750
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -6205,7 +6753,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6205
6753
|
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
6206
6754
|
return false;
|
|
6207
6755
|
}
|
|
6208
|
-
const configFile =
|
|
6756
|
+
const configFile = import_path17.default.join(shadowDir, "config");
|
|
6209
6757
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
6210
6758
|
timeout: 3e3
|
|
6211
6759
|
});
|
|
@@ -6213,7 +6761,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6213
6761
|
timeout: 3e3
|
|
6214
6762
|
});
|
|
6215
6763
|
try {
|
|
6216
|
-
|
|
6764
|
+
import_fs15.default.writeFileSync(import_path17.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
6217
6765
|
} catch {
|
|
6218
6766
|
}
|
|
6219
6767
|
return true;
|
|
@@ -6236,7 +6784,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6236
6784
|
const shadowDir = getShadowRepoDir(cwd);
|
|
6237
6785
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
6238
6786
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
6239
|
-
indexFile =
|
|
6787
|
+
indexFile = import_path17.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
6240
6788
|
const shadowEnv = {
|
|
6241
6789
|
...process.env,
|
|
6242
6790
|
GIT_DIR: shadowDir,
|
|
@@ -6265,7 +6813,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6265
6813
|
const shouldGc = stack.length % 5 === 0;
|
|
6266
6814
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
6267
6815
|
writeStack(stack);
|
|
6268
|
-
|
|
6816
|
+
import_fs15.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
6269
6817
|
if (shouldGc) {
|
|
6270
6818
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
6271
6819
|
}
|
|
@@ -6276,7 +6824,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6276
6824
|
} finally {
|
|
6277
6825
|
if (indexFile) {
|
|
6278
6826
|
try {
|
|
6279
|
-
|
|
6827
|
+
import_fs15.default.unlinkSync(indexFile);
|
|
6280
6828
|
} catch {
|
|
6281
6829
|
}
|
|
6282
6830
|
}
|
|
@@ -6345,9 +6893,9 @@ function applyUndo(hash, cwd) {
|
|
|
6345
6893
|
timeout: GIT_TIMEOUT
|
|
6346
6894
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
6347
6895
|
for (const file of [...tracked, ...untracked]) {
|
|
6348
|
-
const fullPath =
|
|
6349
|
-
if (!snapshotFiles.has(file) &&
|
|
6350
|
-
|
|
6896
|
+
const fullPath = import_path17.default.join(dir, file);
|
|
6897
|
+
if (!snapshotFiles.has(file) && import_fs15.default.existsSync(fullPath)) {
|
|
6898
|
+
import_fs15.default.unlinkSync(fullPath);
|
|
6351
6899
|
}
|
|
6352
6900
|
}
|
|
6353
6901
|
return true;
|
|
@@ -6371,9 +6919,9 @@ function registerCheckCommand(program2) {
|
|
|
6371
6919
|
} catch (err) {
|
|
6372
6920
|
const tempConfig = getConfig();
|
|
6373
6921
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6374
|
-
const logPath =
|
|
6922
|
+
const logPath = import_path18.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
6375
6923
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6376
|
-
|
|
6924
|
+
import_fs16.default.appendFileSync(
|
|
6377
6925
|
logPath,
|
|
6378
6926
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6379
6927
|
RAW: ${raw}
|
|
@@ -6384,10 +6932,10 @@ RAW: ${raw}
|
|
|
6384
6932
|
}
|
|
6385
6933
|
const config = getConfig(payload.cwd || void 0);
|
|
6386
6934
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6387
|
-
const logPath =
|
|
6388
|
-
if (!
|
|
6389
|
-
|
|
6390
|
-
|
|
6935
|
+
const logPath = import_path18.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
6936
|
+
if (!import_fs16.default.existsSync(import_path18.default.dirname(logPath)))
|
|
6937
|
+
import_fs16.default.mkdirSync(import_path18.default.dirname(logPath), { recursive: true });
|
|
6938
|
+
import_fs16.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
6391
6939
|
`);
|
|
6392
6940
|
}
|
|
6393
6941
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6400,8 +6948,8 @@ RAW: ${raw}
|
|
|
6400
6948
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
6401
6949
|
let ttyFd = null;
|
|
6402
6950
|
try {
|
|
6403
|
-
ttyFd =
|
|
6404
|
-
const writeTty = (line) =>
|
|
6951
|
+
ttyFd = import_fs16.default.openSync("/dev/tty", "w");
|
|
6952
|
+
const writeTty = (line) => import_fs16.default.writeSync(ttyFd, line + "\n");
|
|
6405
6953
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
6406
6954
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
6407
6955
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -6417,7 +6965,7 @@ RAW: ${raw}
|
|
|
6417
6965
|
} finally {
|
|
6418
6966
|
if (ttyFd !== null)
|
|
6419
6967
|
try {
|
|
6420
|
-
|
|
6968
|
+
import_fs16.default.closeSync(ttyFd);
|
|
6421
6969
|
} catch {
|
|
6422
6970
|
}
|
|
6423
6971
|
}
|
|
@@ -6448,7 +6996,7 @@ RAW: ${raw}
|
|
|
6448
6996
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6449
6997
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6450
6998
|
}
|
|
6451
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
6999
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path18.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6452
7000
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
6453
7001
|
cwd: safeCwdForAuth
|
|
6454
7002
|
});
|
|
@@ -6460,12 +7008,12 @@ RAW: ${raw}
|
|
|
6460
7008
|
}
|
|
6461
7009
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
6462
7010
|
try {
|
|
6463
|
-
const tty =
|
|
6464
|
-
|
|
7011
|
+
const tty = import_fs16.default.openSync("/dev/tty", "w");
|
|
7012
|
+
import_fs16.default.writeSync(
|
|
6465
7013
|
tty,
|
|
6466
7014
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
6467
7015
|
);
|
|
6468
|
-
|
|
7016
|
+
import_fs16.default.closeSync(tty);
|
|
6469
7017
|
} catch {
|
|
6470
7018
|
}
|
|
6471
7019
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -6492,9 +7040,9 @@ RAW: ${raw}
|
|
|
6492
7040
|
});
|
|
6493
7041
|
} catch (err) {
|
|
6494
7042
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6495
|
-
const logPath =
|
|
7043
|
+
const logPath = import_path18.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
6496
7044
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6497
|
-
|
|
7045
|
+
import_fs16.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6498
7046
|
`);
|
|
6499
7047
|
}
|
|
6500
7048
|
process.exit(0);
|
|
@@ -6528,9 +7076,9 @@ RAW: ${raw}
|
|
|
6528
7076
|
}
|
|
6529
7077
|
|
|
6530
7078
|
// src/cli/commands/log.ts
|
|
6531
|
-
var
|
|
6532
|
-
var
|
|
6533
|
-
var
|
|
7079
|
+
var import_fs17 = __toESM(require("fs"));
|
|
7080
|
+
var import_path19 = __toESM(require("path"));
|
|
7081
|
+
var import_os15 = __toESM(require("os"));
|
|
6534
7082
|
init_audit();
|
|
6535
7083
|
init_config();
|
|
6536
7084
|
init_policy();
|
|
@@ -6552,11 +7100,11 @@ function registerLogCommand(program2) {
|
|
|
6552
7100
|
decision: "allowed",
|
|
6553
7101
|
source: "post-hook"
|
|
6554
7102
|
};
|
|
6555
|
-
const logPath =
|
|
6556
|
-
if (!
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
7103
|
+
const logPath = import_path19.default.join(import_os15.default.homedir(), ".node9", "audit.log");
|
|
7104
|
+
if (!import_fs17.default.existsSync(import_path19.default.dirname(logPath)))
|
|
7105
|
+
import_fs17.default.mkdirSync(import_path19.default.dirname(logPath), { recursive: true });
|
|
7106
|
+
import_fs17.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
7107
|
+
const safeCwd = typeof payload.cwd === "string" && import_path19.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6560
7108
|
const config = getConfig(safeCwd);
|
|
6561
7109
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6562
7110
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -6565,9 +7113,9 @@ function registerLogCommand(program2) {
|
|
|
6565
7113
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6566
7114
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
6567
7115
|
`);
|
|
6568
|
-
const debugPath =
|
|
7116
|
+
const debugPath = import_path19.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
6569
7117
|
try {
|
|
6570
|
-
|
|
7118
|
+
import_fs17.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
6571
7119
|
`);
|
|
6572
7120
|
} catch {
|
|
6573
7121
|
}
|
|
@@ -6871,14 +7419,14 @@ function registerConfigShowCommand(program2) {
|
|
|
6871
7419
|
|
|
6872
7420
|
// src/cli/commands/doctor.ts
|
|
6873
7421
|
var import_chalk7 = __toESM(require("chalk"));
|
|
6874
|
-
var
|
|
6875
|
-
var
|
|
6876
|
-
var
|
|
7422
|
+
var import_fs18 = __toESM(require("fs"));
|
|
7423
|
+
var import_path20 = __toESM(require("path"));
|
|
7424
|
+
var import_os16 = __toESM(require("os"));
|
|
6877
7425
|
var import_child_process9 = require("child_process");
|
|
6878
7426
|
init_daemon();
|
|
6879
7427
|
function registerDoctorCommand(program2, version2) {
|
|
6880
7428
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
6881
|
-
const homeDir2 =
|
|
7429
|
+
const homeDir2 = import_os16.default.homedir();
|
|
6882
7430
|
let failures = 0;
|
|
6883
7431
|
function pass(msg) {
|
|
6884
7432
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -6927,10 +7475,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6927
7475
|
);
|
|
6928
7476
|
}
|
|
6929
7477
|
section("Configuration");
|
|
6930
|
-
const globalConfigPath =
|
|
6931
|
-
if (
|
|
7478
|
+
const globalConfigPath = import_path20.default.join(homeDir2, ".node9", "config.json");
|
|
7479
|
+
if (import_fs18.default.existsSync(globalConfigPath)) {
|
|
6932
7480
|
try {
|
|
6933
|
-
JSON.parse(
|
|
7481
|
+
JSON.parse(import_fs18.default.readFileSync(globalConfigPath, "utf-8"));
|
|
6934
7482
|
pass("~/.node9/config.json found and valid");
|
|
6935
7483
|
} catch {
|
|
6936
7484
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -6938,10 +7486,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6938
7486
|
} else {
|
|
6939
7487
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
6940
7488
|
}
|
|
6941
|
-
const projectConfigPath =
|
|
6942
|
-
if (
|
|
7489
|
+
const projectConfigPath = import_path20.default.join(process.cwd(), "node9.config.json");
|
|
7490
|
+
if (import_fs18.default.existsSync(projectConfigPath)) {
|
|
6943
7491
|
try {
|
|
6944
|
-
JSON.parse(
|
|
7492
|
+
JSON.parse(import_fs18.default.readFileSync(projectConfigPath, "utf-8"));
|
|
6945
7493
|
pass("node9.config.json found and valid (project)");
|
|
6946
7494
|
} catch {
|
|
6947
7495
|
fail(
|
|
@@ -6950,8 +7498,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6950
7498
|
);
|
|
6951
7499
|
}
|
|
6952
7500
|
}
|
|
6953
|
-
const credsPath =
|
|
6954
|
-
if (
|
|
7501
|
+
const credsPath = import_path20.default.join(homeDir2, ".node9", "credentials.json");
|
|
7502
|
+
if (import_fs18.default.existsSync(credsPath)) {
|
|
6955
7503
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
6956
7504
|
} else {
|
|
6957
7505
|
warn(
|
|
@@ -6960,10 +7508,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6960
7508
|
);
|
|
6961
7509
|
}
|
|
6962
7510
|
section("Agent Hooks");
|
|
6963
|
-
const claudeSettingsPath =
|
|
6964
|
-
if (
|
|
7511
|
+
const claudeSettingsPath = import_path20.default.join(homeDir2, ".claude", "settings.json");
|
|
7512
|
+
if (import_fs18.default.existsSync(claudeSettingsPath)) {
|
|
6965
7513
|
try {
|
|
6966
|
-
const cs = JSON.parse(
|
|
7514
|
+
const cs = JSON.parse(import_fs18.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
6967
7515
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
6968
7516
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6969
7517
|
);
|
|
@@ -6979,10 +7527,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6979
7527
|
} else {
|
|
6980
7528
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
6981
7529
|
}
|
|
6982
|
-
const geminiSettingsPath =
|
|
6983
|
-
if (
|
|
7530
|
+
const geminiSettingsPath = import_path20.default.join(homeDir2, ".gemini", "settings.json");
|
|
7531
|
+
if (import_fs18.default.existsSync(geminiSettingsPath)) {
|
|
6984
7532
|
try {
|
|
6985
|
-
const gs = JSON.parse(
|
|
7533
|
+
const gs = JSON.parse(import_fs18.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
6986
7534
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
6987
7535
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6988
7536
|
);
|
|
@@ -6998,10 +7546,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6998
7546
|
} else {
|
|
6999
7547
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
7000
7548
|
}
|
|
7001
|
-
const cursorHooksPath =
|
|
7002
|
-
if (
|
|
7549
|
+
const cursorHooksPath = import_path20.default.join(homeDir2, ".cursor", "hooks.json");
|
|
7550
|
+
if (import_fs18.default.existsSync(cursorHooksPath)) {
|
|
7003
7551
|
try {
|
|
7004
|
-
const cur = JSON.parse(
|
|
7552
|
+
const cur = JSON.parse(import_fs18.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
7005
7553
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
7006
7554
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
7007
7555
|
);
|
|
@@ -7039,9 +7587,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
7039
7587
|
|
|
7040
7588
|
// src/cli/commands/audit.ts
|
|
7041
7589
|
var import_chalk8 = __toESM(require("chalk"));
|
|
7042
|
-
var
|
|
7043
|
-
var
|
|
7044
|
-
var
|
|
7590
|
+
var import_fs19 = __toESM(require("fs"));
|
|
7591
|
+
var import_path21 = __toESM(require("path"));
|
|
7592
|
+
var import_os17 = __toESM(require("os"));
|
|
7045
7593
|
function formatRelativeTime(timestamp) {
|
|
7046
7594
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
7047
7595
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -7054,14 +7602,14 @@ function formatRelativeTime(timestamp) {
|
|
|
7054
7602
|
}
|
|
7055
7603
|
function registerAuditCommand(program2) {
|
|
7056
7604
|
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) => {
|
|
7057
|
-
const logPath =
|
|
7058
|
-
if (!
|
|
7605
|
+
const logPath = import_path21.default.join(import_os17.default.homedir(), ".node9", "audit.log");
|
|
7606
|
+
if (!import_fs19.default.existsSync(logPath)) {
|
|
7059
7607
|
console.log(
|
|
7060
7608
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
7061
7609
|
);
|
|
7062
7610
|
return;
|
|
7063
7611
|
}
|
|
7064
|
-
const raw =
|
|
7612
|
+
const raw = import_fs19.default.readFileSync(logPath, "utf-8");
|
|
7065
7613
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
7066
7614
|
let entries = lines.flatMap((line) => {
|
|
7067
7615
|
try {
|
|
@@ -7179,9 +7727,9 @@ function registerDaemonCommand(program2) {
|
|
|
7179
7727
|
|
|
7180
7728
|
// src/cli/commands/status.ts
|
|
7181
7729
|
var import_chalk10 = __toESM(require("chalk"));
|
|
7182
|
-
var
|
|
7183
|
-
var
|
|
7184
|
-
var
|
|
7730
|
+
var import_fs20 = __toESM(require("fs"));
|
|
7731
|
+
var import_path22 = __toESM(require("path"));
|
|
7732
|
+
var import_os18 = __toESM(require("os"));
|
|
7185
7733
|
init_core();
|
|
7186
7734
|
init_daemon();
|
|
7187
7735
|
function registerStatusCommand(program2) {
|
|
@@ -7218,13 +7766,13 @@ function registerStatusCommand(program2) {
|
|
|
7218
7766
|
console.log("");
|
|
7219
7767
|
const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
|
|
7220
7768
|
console.log(` Mode: ${modeLabel}`);
|
|
7221
|
-
const projectConfig =
|
|
7222
|
-
const globalConfig =
|
|
7769
|
+
const projectConfig = import_path22.default.join(process.cwd(), "node9.config.json");
|
|
7770
|
+
const globalConfig = import_path22.default.join(import_os18.default.homedir(), ".node9", "config.json");
|
|
7223
7771
|
console.log(
|
|
7224
|
-
` Local: ${
|
|
7772
|
+
` Local: ${import_fs20.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
|
|
7225
7773
|
);
|
|
7226
7774
|
console.log(
|
|
7227
|
-
` Global: ${
|
|
7775
|
+
` Global: ${import_fs20.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
|
|
7228
7776
|
);
|
|
7229
7777
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
7230
7778
|
console.log(
|
|
@@ -7403,6 +7951,7 @@ var import_chalk13 = __toESM(require("chalk"));
|
|
|
7403
7951
|
var import_child_process12 = require("child_process");
|
|
7404
7952
|
var import_execa3 = require("execa");
|
|
7405
7953
|
init_orchestrator();
|
|
7954
|
+
init_provenance();
|
|
7406
7955
|
function sanitize4(value) {
|
|
7407
7956
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
7408
7957
|
}
|
|
@@ -7415,7 +7964,7 @@ function extractMcpServer(toolName) {
|
|
|
7415
7964
|
const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
7416
7965
|
return match?.[1];
|
|
7417
7966
|
}
|
|
7418
|
-
function
|
|
7967
|
+
function tokenize3(cmd) {
|
|
7419
7968
|
const tokens = [];
|
|
7420
7969
|
let current = "";
|
|
7421
7970
|
let inDouble = false;
|
|
@@ -7450,7 +7999,7 @@ function tokenize2(cmd) {
|
|
|
7450
7999
|
return tokens;
|
|
7451
8000
|
}
|
|
7452
8001
|
async function runMcpGateway(upstreamCommand) {
|
|
7453
|
-
const commandParts =
|
|
8002
|
+
const commandParts = tokenize3(upstreamCommand);
|
|
7454
8003
|
const cmd = commandParts[0];
|
|
7455
8004
|
const cmdArgs = commandParts.slice(1);
|
|
7456
8005
|
let executable = cmd;
|
|
@@ -7459,6 +8008,15 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
7459
8008
|
if (stdout) executable = stdout.trim();
|
|
7460
8009
|
} catch {
|
|
7461
8010
|
}
|
|
8011
|
+
const prov = checkProvenance(executable);
|
|
8012
|
+
if (prov.trustLevel === "suspect") {
|
|
8013
|
+
console.error(
|
|
8014
|
+
import_chalk13.default.red(
|
|
8015
|
+
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
8016
|
+
)
|
|
8017
|
+
);
|
|
8018
|
+
console.error(import_chalk13.default.red(" Verify this binary is trusted before proceeding."));
|
|
8019
|
+
}
|
|
7462
8020
|
console.error(import_chalk13.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
7463
8021
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
7464
8022
|
"NODE_OPTIONS",
|
|
@@ -7598,22 +8156,77 @@ function registerMcpGatewayCommand(program2) {
|
|
|
7598
8156
|
});
|
|
7599
8157
|
}
|
|
7600
8158
|
|
|
8159
|
+
// src/cli/commands/trust.ts
|
|
8160
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
8161
|
+
init_trusted_hosts();
|
|
8162
|
+
function isValidHost(host) {
|
|
8163
|
+
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
8164
|
+
}
|
|
8165
|
+
function registerTrustCommand(program2) {
|
|
8166
|
+
const trustCmd = program2.command("trust").description("Manage trusted network hosts (reduces approval friction for known destinations)");
|
|
8167
|
+
trustCmd.command("add <host>").description("Add a trusted host \u2014 pipe-chain blocks targeting this host are downgraded").action((host) => {
|
|
8168
|
+
const normalized = normalizeHost(host.trim());
|
|
8169
|
+
if (!isValidHost(normalized)) {
|
|
8170
|
+
console.error(
|
|
8171
|
+
import_chalk14.default.red(`
|
|
8172
|
+
\u274C Invalid host: "${host}"
|
|
8173
|
+
`) + import_chalk14.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
8174
|
+
);
|
|
8175
|
+
process.exit(1);
|
|
8176
|
+
}
|
|
8177
|
+
addTrustedHost(normalized);
|
|
8178
|
+
console.log(import_chalk14.default.green(`
|
|
8179
|
+
\u2705 ${normalized} added to trusted hosts.`));
|
|
8180
|
+
console.log(
|
|
8181
|
+
import_chalk14.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
8182
|
+
);
|
|
8183
|
+
});
|
|
8184
|
+
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
8185
|
+
const normalized = normalizeHost(host.trim());
|
|
8186
|
+
const removed = removeTrustedHost(normalized);
|
|
8187
|
+
if (!removed) {
|
|
8188
|
+
console.error(import_chalk14.default.yellow(`
|
|
8189
|
+
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
8190
|
+
`));
|
|
8191
|
+
process.exit(1);
|
|
8192
|
+
}
|
|
8193
|
+
console.log(import_chalk14.default.green(`
|
|
8194
|
+
\u2705 ${normalized} removed from trusted hosts.
|
|
8195
|
+
`));
|
|
8196
|
+
});
|
|
8197
|
+
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
8198
|
+
const hosts = readTrustedHosts();
|
|
8199
|
+
if (hosts.length === 0) {
|
|
8200
|
+
console.log(import_chalk14.default.gray("\n No trusted hosts configured.\n"));
|
|
8201
|
+
console.log(` Add one: ${import_chalk14.default.cyan("node9 trust add api.mycompany.com")}
|
|
8202
|
+
`);
|
|
8203
|
+
return;
|
|
8204
|
+
}
|
|
8205
|
+
console.log(import_chalk14.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
8206
|
+
for (const entry of hosts) {
|
|
8207
|
+
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
8208
|
+
console.log(` ${import_chalk14.default.cyan(entry.host.padEnd(40))} ${import_chalk14.default.gray(`added ${date}`)}`);
|
|
8209
|
+
}
|
|
8210
|
+
console.log("");
|
|
8211
|
+
});
|
|
8212
|
+
}
|
|
8213
|
+
|
|
7601
8214
|
// src/cli.ts
|
|
7602
8215
|
var { version } = JSON.parse(
|
|
7603
|
-
|
|
8216
|
+
import_fs22.default.readFileSync(import_path24.default.join(__dirname, "../package.json"), "utf-8")
|
|
7604
8217
|
);
|
|
7605
8218
|
var program = new import_commander.Command();
|
|
7606
8219
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
7607
8220
|
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) => {
|
|
7608
8221
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
7609
|
-
const credPath =
|
|
7610
|
-
if (!
|
|
7611
|
-
|
|
8222
|
+
const credPath = import_path24.default.join(import_os20.default.homedir(), ".node9", "credentials.json");
|
|
8223
|
+
if (!import_fs22.default.existsSync(import_path24.default.dirname(credPath)))
|
|
8224
|
+
import_fs22.default.mkdirSync(import_path24.default.dirname(credPath), { recursive: true });
|
|
7612
8225
|
const profileName = options.profile || "default";
|
|
7613
8226
|
let existingCreds = {};
|
|
7614
8227
|
try {
|
|
7615
|
-
if (
|
|
7616
|
-
const raw = JSON.parse(
|
|
8228
|
+
if (import_fs22.default.existsSync(credPath)) {
|
|
8229
|
+
const raw = JSON.parse(import_fs22.default.readFileSync(credPath, "utf-8"));
|
|
7617
8230
|
if (raw.apiKey) {
|
|
7618
8231
|
existingCreds = {
|
|
7619
8232
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -7625,13 +8238,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7625
8238
|
} catch {
|
|
7626
8239
|
}
|
|
7627
8240
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
7628
|
-
|
|
8241
|
+
import_fs22.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
7629
8242
|
if (profileName === "default") {
|
|
7630
|
-
const configPath =
|
|
8243
|
+
const configPath = import_path24.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
7631
8244
|
let config = {};
|
|
7632
8245
|
try {
|
|
7633
|
-
if (
|
|
7634
|
-
config = JSON.parse(
|
|
8246
|
+
if (import_fs22.default.existsSync(configPath))
|
|
8247
|
+
config = JSON.parse(import_fs22.default.readFileSync(configPath, "utf-8"));
|
|
7635
8248
|
} catch {
|
|
7636
8249
|
}
|
|
7637
8250
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -7646,36 +8259,36 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7646
8259
|
approvers.cloud = false;
|
|
7647
8260
|
}
|
|
7648
8261
|
s.approvers = approvers;
|
|
7649
|
-
if (!
|
|
7650
|
-
|
|
7651
|
-
|
|
8262
|
+
if (!import_fs22.default.existsSync(import_path24.default.dirname(configPath)))
|
|
8263
|
+
import_fs22.default.mkdirSync(import_path24.default.dirname(configPath), { recursive: true });
|
|
8264
|
+
import_fs22.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
7652
8265
|
}
|
|
7653
8266
|
if (options.profile && profileName !== "default") {
|
|
7654
|
-
console.log(
|
|
7655
|
-
console.log(
|
|
8267
|
+
console.log(import_chalk16.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
8268
|
+
console.log(import_chalk16.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
7656
8269
|
} else if (options.local) {
|
|
7657
|
-
console.log(
|
|
7658
|
-
console.log(
|
|
8270
|
+
console.log(import_chalk16.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
8271
|
+
console.log(import_chalk16.default.gray(` All decisions stay on this machine.`));
|
|
7659
8272
|
} else {
|
|
7660
|
-
console.log(
|
|
7661
|
-
console.log(
|
|
8273
|
+
console.log(import_chalk16.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
8274
|
+
console.log(import_chalk16.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
7662
8275
|
}
|
|
7663
8276
|
});
|
|
7664
8277
|
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) => {
|
|
7665
8278
|
if (target === "gemini") return await setupGemini();
|
|
7666
8279
|
if (target === "claude") return await setupClaude();
|
|
7667
8280
|
if (target === "cursor") return await setupCursor();
|
|
7668
|
-
console.error(
|
|
8281
|
+
console.error(import_chalk16.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7669
8282
|
process.exit(1);
|
|
7670
8283
|
});
|
|
7671
8284
|
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) => {
|
|
7672
8285
|
if (!target) {
|
|
7673
|
-
console.log(
|
|
7674
|
-
console.log(" Usage: " +
|
|
8286
|
+
console.log(import_chalk16.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
8287
|
+
console.log(" Usage: " + import_chalk16.default.white("node9 setup <target>") + "\n");
|
|
7675
8288
|
console.log(" Targets:");
|
|
7676
|
-
console.log(" " +
|
|
7677
|
-
console.log(" " +
|
|
7678
|
-
console.log(" " +
|
|
8289
|
+
console.log(" " + import_chalk16.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
8290
|
+
console.log(" " + import_chalk16.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
8291
|
+
console.log(" " + import_chalk16.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
7679
8292
|
console.log("");
|
|
7680
8293
|
return;
|
|
7681
8294
|
}
|
|
@@ -7683,7 +8296,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
7683
8296
|
if (t === "gemini") return await setupGemini();
|
|
7684
8297
|
if (t === "claude") return await setupClaude();
|
|
7685
8298
|
if (t === "cursor") return await setupCursor();
|
|
7686
|
-
console.error(
|
|
8299
|
+
console.error(import_chalk16.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7687
8300
|
process.exit(1);
|
|
7688
8301
|
});
|
|
7689
8302
|
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) => {
|
|
@@ -7692,30 +8305,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
7692
8305
|
else if (target === "gemini") fn = teardownGemini;
|
|
7693
8306
|
else if (target === "cursor") fn = teardownCursor;
|
|
7694
8307
|
else {
|
|
7695
|
-
console.error(
|
|
8308
|
+
console.error(import_chalk16.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
7696
8309
|
process.exit(1);
|
|
7697
8310
|
}
|
|
7698
|
-
console.log(
|
|
8311
|
+
console.log(import_chalk16.default.cyan(`
|
|
7699
8312
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
7700
8313
|
`));
|
|
7701
8314
|
try {
|
|
7702
8315
|
fn();
|
|
7703
8316
|
} catch (err) {
|
|
7704
|
-
console.error(
|
|
8317
|
+
console.error(import_chalk16.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
7705
8318
|
process.exit(1);
|
|
7706
8319
|
}
|
|
7707
|
-
console.log(
|
|
8320
|
+
console.log(import_chalk16.default.gray("\n Restart the agent for changes to take effect."));
|
|
7708
8321
|
});
|
|
7709
8322
|
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) => {
|
|
7710
|
-
console.log(
|
|
7711
|
-
console.log(
|
|
8323
|
+
console.log(import_chalk16.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
8324
|
+
console.log(import_chalk16.default.bold("Stopping daemon..."));
|
|
7712
8325
|
try {
|
|
7713
8326
|
stopDaemon();
|
|
7714
|
-
console.log(
|
|
8327
|
+
console.log(import_chalk16.default.green(" \u2705 Daemon stopped"));
|
|
7715
8328
|
} catch {
|
|
7716
|
-
console.log(
|
|
8329
|
+
console.log(import_chalk16.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
7717
8330
|
}
|
|
7718
|
-
console.log(
|
|
8331
|
+
console.log(import_chalk16.default.bold("\nRemoving hooks..."));
|
|
7719
8332
|
let teardownFailed = false;
|
|
7720
8333
|
for (const [label, fn] of [
|
|
7721
8334
|
["Claude", teardownClaude],
|
|
@@ -7727,45 +8340,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
7727
8340
|
} catch (err) {
|
|
7728
8341
|
teardownFailed = true;
|
|
7729
8342
|
console.error(
|
|
7730
|
-
|
|
8343
|
+
import_chalk16.default.red(
|
|
7731
8344
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
7732
8345
|
)
|
|
7733
8346
|
);
|
|
7734
8347
|
}
|
|
7735
8348
|
}
|
|
7736
8349
|
if (options.purge) {
|
|
7737
|
-
const node9Dir =
|
|
7738
|
-
if (
|
|
8350
|
+
const node9Dir = import_path24.default.join(import_os20.default.homedir(), ".node9");
|
|
8351
|
+
if (import_fs22.default.existsSync(node9Dir)) {
|
|
7739
8352
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
7740
8353
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
7741
8354
|
default: false
|
|
7742
8355
|
});
|
|
7743
8356
|
if (confirmed) {
|
|
7744
|
-
|
|
7745
|
-
if (
|
|
8357
|
+
import_fs22.default.rmSync(node9Dir, { recursive: true });
|
|
8358
|
+
if (import_fs22.default.existsSync(node9Dir)) {
|
|
7746
8359
|
console.error(
|
|
7747
|
-
|
|
8360
|
+
import_chalk16.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
7748
8361
|
);
|
|
7749
8362
|
} else {
|
|
7750
|
-
console.log(
|
|
8363
|
+
console.log(import_chalk16.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
7751
8364
|
}
|
|
7752
8365
|
} else {
|
|
7753
|
-
console.log(
|
|
8366
|
+
console.log(import_chalk16.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
7754
8367
|
}
|
|
7755
8368
|
} else {
|
|
7756
|
-
console.log(
|
|
8369
|
+
console.log(import_chalk16.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
7757
8370
|
}
|
|
7758
8371
|
} else {
|
|
7759
8372
|
console.log(
|
|
7760
|
-
|
|
8373
|
+
import_chalk16.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
7761
8374
|
);
|
|
7762
8375
|
}
|
|
7763
8376
|
if (teardownFailed) {
|
|
7764
|
-
console.error(
|
|
8377
|
+
console.error(import_chalk16.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
7765
8378
|
process.exit(1);
|
|
7766
8379
|
}
|
|
7767
|
-
console.log(
|
|
7768
|
-
console.log(
|
|
8380
|
+
console.log(import_chalk16.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
8381
|
+
console.log(import_chalk16.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
7769
8382
|
});
|
|
7770
8383
|
registerDoctorCommand(program, version);
|
|
7771
8384
|
program.command("explain").description(
|
|
@@ -7778,7 +8391,7 @@ program.command("explain").description(
|
|
|
7778
8391
|
try {
|
|
7779
8392
|
args = JSON.parse(trimmed);
|
|
7780
8393
|
} catch {
|
|
7781
|
-
console.error(
|
|
8394
|
+
console.error(import_chalk16.default.red(`
|
|
7782
8395
|
\u274C Invalid JSON: ${trimmed}
|
|
7783
8396
|
`));
|
|
7784
8397
|
process.exit(1);
|
|
@@ -7789,63 +8402,63 @@ program.command("explain").description(
|
|
|
7789
8402
|
}
|
|
7790
8403
|
const result = await explainPolicy(tool, args);
|
|
7791
8404
|
console.log("");
|
|
7792
|
-
console.log(
|
|
8405
|
+
console.log(import_chalk16.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
7793
8406
|
console.log("");
|
|
7794
|
-
console.log(` ${
|
|
8407
|
+
console.log(` ${import_chalk16.default.bold("Tool:")} ${import_chalk16.default.white(result.tool)}`);
|
|
7795
8408
|
if (argsRaw) {
|
|
7796
8409
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
7797
|
-
console.log(` ${
|
|
8410
|
+
console.log(` ${import_chalk16.default.bold("Input:")} ${import_chalk16.default.gray(preview)}`);
|
|
7798
8411
|
}
|
|
7799
8412
|
console.log("");
|
|
7800
|
-
console.log(
|
|
8413
|
+
console.log(import_chalk16.default.bold("Config Sources (Waterfall):"));
|
|
7801
8414
|
for (const tier of result.waterfall) {
|
|
7802
|
-
const num =
|
|
8415
|
+
const num = import_chalk16.default.gray(` ${tier.tier}.`);
|
|
7803
8416
|
const label = tier.label.padEnd(16);
|
|
7804
8417
|
let statusStr;
|
|
7805
8418
|
if (tier.tier === 1) {
|
|
7806
|
-
statusStr =
|
|
8419
|
+
statusStr = import_chalk16.default.gray(tier.note ?? "");
|
|
7807
8420
|
} else if (tier.status === "active") {
|
|
7808
|
-
const loc = tier.path ?
|
|
7809
|
-
const note = tier.note ?
|
|
7810
|
-
statusStr =
|
|
8421
|
+
const loc = tier.path ? import_chalk16.default.gray(tier.path) : "";
|
|
8422
|
+
const note = tier.note ? import_chalk16.default.gray(`(${tier.note})`) : "";
|
|
8423
|
+
statusStr = import_chalk16.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
7811
8424
|
} else {
|
|
7812
|
-
statusStr =
|
|
8425
|
+
statusStr = import_chalk16.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
7813
8426
|
}
|
|
7814
|
-
console.log(`${num} ${
|
|
8427
|
+
console.log(`${num} ${import_chalk16.default.white(label)} ${statusStr}`);
|
|
7815
8428
|
}
|
|
7816
8429
|
console.log("");
|
|
7817
|
-
console.log(
|
|
8430
|
+
console.log(import_chalk16.default.bold("Policy Evaluation:"));
|
|
7818
8431
|
for (const step of result.steps) {
|
|
7819
8432
|
const isFinal = step.isFinal;
|
|
7820
8433
|
let icon;
|
|
7821
|
-
if (step.outcome === "allow") icon =
|
|
7822
|
-
else if (step.outcome === "review") icon =
|
|
7823
|
-
else if (step.outcome === "skip") icon =
|
|
7824
|
-
else icon =
|
|
8434
|
+
if (step.outcome === "allow") icon = import_chalk16.default.green(" \u2705");
|
|
8435
|
+
else if (step.outcome === "review") icon = import_chalk16.default.red(" \u{1F534}");
|
|
8436
|
+
else if (step.outcome === "skip") icon = import_chalk16.default.gray(" \u2500 ");
|
|
8437
|
+
else icon = import_chalk16.default.gray(" \u25CB ");
|
|
7825
8438
|
const name = step.name.padEnd(18);
|
|
7826
|
-
const nameStr = isFinal ?
|
|
7827
|
-
const detail = isFinal ?
|
|
7828
|
-
const arrow = isFinal ?
|
|
8439
|
+
const nameStr = isFinal ? import_chalk16.default.white.bold(name) : import_chalk16.default.white(name);
|
|
8440
|
+
const detail = isFinal ? import_chalk16.default.white(step.detail) : import_chalk16.default.gray(step.detail);
|
|
8441
|
+
const arrow = isFinal ? import_chalk16.default.yellow(" \u2190 STOP") : "";
|
|
7829
8442
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
7830
8443
|
}
|
|
7831
8444
|
console.log("");
|
|
7832
8445
|
if (result.decision === "allow") {
|
|
7833
|
-
console.log(
|
|
8446
|
+
console.log(import_chalk16.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk16.default.gray(" \u2014 no approval needed"));
|
|
7834
8447
|
} else {
|
|
7835
8448
|
console.log(
|
|
7836
|
-
|
|
8449
|
+
import_chalk16.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk16.default.gray(" \u2014 human approval required")
|
|
7837
8450
|
);
|
|
7838
8451
|
if (result.blockedByLabel) {
|
|
7839
|
-
console.log(
|
|
8452
|
+
console.log(import_chalk16.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
7840
8453
|
}
|
|
7841
8454
|
}
|
|
7842
8455
|
console.log("");
|
|
7843
8456
|
});
|
|
7844
8457
|
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) => {
|
|
7845
|
-
const configPath =
|
|
7846
|
-
if (
|
|
7847
|
-
console.log(
|
|
7848
|
-
console.log(
|
|
8458
|
+
const configPath = import_path24.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
8459
|
+
if (import_fs22.default.existsSync(configPath) && !options.force) {
|
|
8460
|
+
console.log(import_chalk16.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
8461
|
+
console.log(import_chalk16.default.gray(` Run with --force to overwrite.`));
|
|
7849
8462
|
return;
|
|
7850
8463
|
}
|
|
7851
8464
|
const requestedMode = options.mode.toLowerCase();
|
|
@@ -7857,13 +8470,13 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
7857
8470
|
mode: safeMode
|
|
7858
8471
|
}
|
|
7859
8472
|
};
|
|
7860
|
-
const dir =
|
|
7861
|
-
if (!
|
|
7862
|
-
|
|
7863
|
-
console.log(
|
|
7864
|
-
console.log(
|
|
8473
|
+
const dir = import_path24.default.dirname(configPath);
|
|
8474
|
+
if (!import_fs22.default.existsSync(dir)) import_fs22.default.mkdirSync(dir, { recursive: true });
|
|
8475
|
+
import_fs22.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
8476
|
+
console.log(import_chalk16.default.green(`\u2705 Global config created: ${configPath}`));
|
|
8477
|
+
console.log(import_chalk16.default.cyan(` Mode set to: ${safeMode}`));
|
|
7865
8478
|
console.log(
|
|
7866
|
-
|
|
8479
|
+
import_chalk16.default.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
|
|
7867
8480
|
);
|
|
7868
8481
|
});
|
|
7869
8482
|
registerAuditCommand(program);
|
|
@@ -7874,7 +8487,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
7874
8487
|
try {
|
|
7875
8488
|
await startTail2(options);
|
|
7876
8489
|
} catch (err) {
|
|
7877
|
-
console.error(
|
|
8490
|
+
console.error(import_chalk16.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
7878
8491
|
process.exit(1);
|
|
7879
8492
|
}
|
|
7880
8493
|
});
|
|
@@ -7886,7 +8499,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
7886
8499
|
const ms = parseDuration(options.duration);
|
|
7887
8500
|
if (ms === null) {
|
|
7888
8501
|
console.error(
|
|
7889
|
-
|
|
8502
|
+
import_chalk16.default.red(`
|
|
7890
8503
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
7891
8504
|
`)
|
|
7892
8505
|
);
|
|
@@ -7894,20 +8507,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
7894
8507
|
}
|
|
7895
8508
|
pauseNode9(ms, options.duration);
|
|
7896
8509
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
7897
|
-
console.log(
|
|
8510
|
+
console.log(import_chalk16.default.yellow(`
|
|
7898
8511
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
7899
|
-
console.log(
|
|
7900
|
-
console.log(
|
|
8512
|
+
console.log(import_chalk16.default.gray(` All tool calls will be allowed without review.`));
|
|
8513
|
+
console.log(import_chalk16.default.gray(` Run "node9 resume" to re-enable early.
|
|
7901
8514
|
`));
|
|
7902
8515
|
});
|
|
7903
8516
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
7904
8517
|
const { paused } = checkPause();
|
|
7905
8518
|
if (!paused) {
|
|
7906
|
-
console.log(
|
|
8519
|
+
console.log(import_chalk16.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
7907
8520
|
return;
|
|
7908
8521
|
}
|
|
7909
8522
|
resumeNode9();
|
|
7910
|
-
console.log(
|
|
8523
|
+
console.log(import_chalk16.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
7911
8524
|
});
|
|
7912
8525
|
var HOOK_BASED_AGENTS = {
|
|
7913
8526
|
claude: "claude",
|
|
@@ -7920,15 +8533,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7920
8533
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
7921
8534
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
7922
8535
|
console.error(
|
|
7923
|
-
|
|
8536
|
+
import_chalk16.default.yellow(`
|
|
7924
8537
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
7925
8538
|
);
|
|
7926
|
-
console.error(
|
|
8539
|
+
console.error(import_chalk16.default.white(`
|
|
7927
8540
|
"${target}" uses its own hook system. Use:`));
|
|
7928
8541
|
console.error(
|
|
7929
|
-
|
|
8542
|
+
import_chalk16.default.green(` node9 addto ${target} `) + import_chalk16.default.gray("# one-time setup")
|
|
7930
8543
|
);
|
|
7931
|
-
console.error(
|
|
8544
|
+
console.error(import_chalk16.default.green(` ${target} `) + import_chalk16.default.gray("# run normally"));
|
|
7932
8545
|
process.exit(1);
|
|
7933
8546
|
}
|
|
7934
8547
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -7945,7 +8558,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7945
8558
|
}
|
|
7946
8559
|
);
|
|
7947
8560
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
7948
|
-
console.error(
|
|
8561
|
+
console.error(import_chalk16.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
7949
8562
|
const daemonReady = await autoStartDaemonAndWait();
|
|
7950
8563
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
7951
8564
|
}
|
|
@@ -7958,12 +8571,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7958
8571
|
}
|
|
7959
8572
|
if (!result.approved) {
|
|
7960
8573
|
console.error(
|
|
7961
|
-
|
|
8574
|
+
import_chalk16.default.red(`
|
|
7962
8575
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
7963
8576
|
);
|
|
7964
8577
|
process.exit(1);
|
|
7965
8578
|
}
|
|
7966
|
-
console.error(
|
|
8579
|
+
console.error(import_chalk16.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
7967
8580
|
await runProxy(fullCommand);
|
|
7968
8581
|
} else {
|
|
7969
8582
|
program.help();
|
|
@@ -7972,14 +8585,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
7972
8585
|
registerUndoCommand(program);
|
|
7973
8586
|
registerShieldCommand(program);
|
|
7974
8587
|
registerConfigShowCommand(program);
|
|
8588
|
+
registerTrustCommand(program);
|
|
7975
8589
|
if (process.argv[2] !== "daemon") {
|
|
7976
8590
|
process.on("unhandledRejection", (reason) => {
|
|
7977
8591
|
const isCheckHook = process.argv[2] === "check";
|
|
7978
8592
|
if (isCheckHook) {
|
|
7979
8593
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
7980
|
-
const logPath =
|
|
8594
|
+
const logPath = import_path24.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
7981
8595
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
7982
|
-
|
|
8596
|
+
import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
7983
8597
|
`);
|
|
7984
8598
|
}
|
|
7985
8599
|
process.exit(0);
|