@node9/proxy 1.3.1 → 1.3.2
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 +759 -270
- package/dist/cli.mjs +746 -257
- package/dist/index.js +506 -53
- package/dist/index.mjs +506 -53
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -94,8 +94,8 @@ function sanitizeConfig(raw) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
const lines = result.error.issues.map((issue) => {
|
|
97
|
-
const
|
|
98
|
-
return ` \u2022 ${
|
|
97
|
+
const path24 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
98
|
+
return ` \u2022 ${path24}: ${issue.message}`;
|
|
99
99
|
});
|
|
100
100
|
return {
|
|
101
101
|
sanitized,
|
|
@@ -1157,13 +1157,445 @@ var init_dlp = __esm({
|
|
|
1157
1157
|
}
|
|
1158
1158
|
});
|
|
1159
1159
|
|
|
1160
|
-
// src/
|
|
1160
|
+
// src/utils/provenance.ts
|
|
1161
1161
|
import fs5 from "fs";
|
|
1162
1162
|
import path5 from "path";
|
|
1163
1163
|
import os4 from "os";
|
|
1164
|
+
function findInPath(cmd) {
|
|
1165
|
+
if (path5.posix.isAbsolute(cmd)) return cmd;
|
|
1166
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1167
|
+
for (const dir of pathEnv.split(path5.delimiter)) {
|
|
1168
|
+
if (!dir) continue;
|
|
1169
|
+
const full = path5.join(dir, cmd);
|
|
1170
|
+
try {
|
|
1171
|
+
fs5.accessSync(full, fs5.constants.X_OK);
|
|
1172
|
+
return full;
|
|
1173
|
+
} catch {
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
function _classifyPath(resolved, cwd) {
|
|
1179
|
+
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
1180
|
+
return { trustLevel: "user", reason: "binary in project directory" };
|
|
1181
|
+
}
|
|
1182
|
+
const osTmp = os4.tmpdir();
|
|
1183
|
+
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
1184
|
+
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1185
|
+
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
1186
|
+
}
|
|
1187
|
+
if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1188
|
+
return { trustLevel: "system", reason: "" };
|
|
1189
|
+
}
|
|
1190
|
+
if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1191
|
+
return { trustLevel: "managed", reason: "" };
|
|
1192
|
+
}
|
|
1193
|
+
if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
1194
|
+
return { trustLevel: "user", reason: "" };
|
|
1195
|
+
}
|
|
1196
|
+
return { trustLevel: "unknown", reason: "binary in unrecognized location" };
|
|
1197
|
+
}
|
|
1198
|
+
function checkProvenance(cmd, cwd) {
|
|
1199
|
+
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
1200
|
+
if (path5.posix.isAbsolute(bare)) {
|
|
1201
|
+
const early = _classifyPath(bare, cwd);
|
|
1202
|
+
if (early.trustLevel === "suspect") {
|
|
1203
|
+
return { resolvedPath: bare, ...early };
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
let resolved;
|
|
1207
|
+
try {
|
|
1208
|
+
const found = findInPath(bare);
|
|
1209
|
+
if (!found) {
|
|
1210
|
+
return {
|
|
1211
|
+
resolvedPath: cmd,
|
|
1212
|
+
trustLevel: "unknown",
|
|
1213
|
+
reason: "binary not found in PATH"
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
resolved = fs5.realpathSync(found);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return {
|
|
1219
|
+
resolvedPath: cmd,
|
|
1220
|
+
trustLevel: "unknown",
|
|
1221
|
+
reason: "binary not found in PATH"
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
try {
|
|
1225
|
+
const stat = fs5.statSync(resolved);
|
|
1226
|
+
if (stat.mode & 2) {
|
|
1227
|
+
return {
|
|
1228
|
+
resolvedPath: resolved,
|
|
1229
|
+
trustLevel: "suspect",
|
|
1230
|
+
reason: "binary is world-writable"
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
return {
|
|
1235
|
+
resolvedPath: resolved,
|
|
1236
|
+
trustLevel: "unknown",
|
|
1237
|
+
reason: "could not stat binary"
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
const classify = _classifyPath(resolved, cwd);
|
|
1241
|
+
return { resolvedPath: resolved, ...classify };
|
|
1242
|
+
}
|
|
1243
|
+
var SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
|
|
1244
|
+
var init_provenance = __esm({
|
|
1245
|
+
"src/utils/provenance.ts"() {
|
|
1246
|
+
"use strict";
|
|
1247
|
+
SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
1248
|
+
MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
1249
|
+
USER_PREFIXES = [
|
|
1250
|
+
path5.join(os4.homedir(), "bin"),
|
|
1251
|
+
path5.join(os4.homedir(), ".local", "bin"),
|
|
1252
|
+
path5.join(os4.homedir(), ".cargo", "bin"),
|
|
1253
|
+
path5.join(os4.homedir(), ".npm-global", "bin"),
|
|
1254
|
+
path5.join(os4.homedir(), ".volta", "bin")
|
|
1255
|
+
];
|
|
1256
|
+
SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
// src/policy/pipe-chain.ts
|
|
1261
|
+
function isSensitivePath(p) {
|
|
1262
|
+
return SENSITIVE_PATTERNS.some((re) => re.test(p));
|
|
1263
|
+
}
|
|
1264
|
+
function splitOnPipe(cmd) {
|
|
1265
|
+
const segments = [];
|
|
1266
|
+
let current = "";
|
|
1267
|
+
let inSingle = false;
|
|
1268
|
+
let inDouble = false;
|
|
1269
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
1270
|
+
const ch = cmd[i];
|
|
1271
|
+
if (ch === "'" && !inDouble) {
|
|
1272
|
+
inSingle = !inSingle;
|
|
1273
|
+
current += ch;
|
|
1274
|
+
} else if (ch === '"' && !inSingle) {
|
|
1275
|
+
inDouble = !inDouble;
|
|
1276
|
+
current += ch;
|
|
1277
|
+
} else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
|
|
1278
|
+
segments.push(current.trim());
|
|
1279
|
+
current = "";
|
|
1280
|
+
} else {
|
|
1281
|
+
current += ch;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (current.trim()) segments.push(current.trim());
|
|
1285
|
+
return segments.filter(Boolean);
|
|
1286
|
+
}
|
|
1287
|
+
function positionalTokens(segment) {
|
|
1288
|
+
return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
1289
|
+
}
|
|
1290
|
+
function analyzePipeChain(command) {
|
|
1291
|
+
const segments = splitOnPipe(command);
|
|
1292
|
+
if (segments.length < 2) {
|
|
1293
|
+
return {
|
|
1294
|
+
isPipeline: false,
|
|
1295
|
+
hasSensitiveSource: false,
|
|
1296
|
+
hasExternalSink: false,
|
|
1297
|
+
hasObfuscation: false,
|
|
1298
|
+
sourceFiles: [],
|
|
1299
|
+
sinkTargets: [],
|
|
1300
|
+
risk: "none"
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
const sourceFiles = [];
|
|
1304
|
+
const sinkTargets = [];
|
|
1305
|
+
let hasSensitiveSource = false;
|
|
1306
|
+
let hasExternalSink = false;
|
|
1307
|
+
let hasObfuscation = false;
|
|
1308
|
+
for (const segment of segments) {
|
|
1309
|
+
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
1310
|
+
if (tokens.length === 0) continue;
|
|
1311
|
+
const binary = tokens[0].toLowerCase();
|
|
1312
|
+
const args = positionalTokens(segment);
|
|
1313
|
+
if (SOURCE_COMMANDS.has(binary)) {
|
|
1314
|
+
sourceFiles.push(...args);
|
|
1315
|
+
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
1316
|
+
}
|
|
1317
|
+
if (OBFUSCATORS.has(binary)) hasObfuscation = true;
|
|
1318
|
+
if (SINK_COMMANDS.has(binary)) {
|
|
1319
|
+
const targets = args.filter(
|
|
1320
|
+
(a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
|
|
1321
|
+
);
|
|
1322
|
+
sinkTargets.push(...targets);
|
|
1323
|
+
if (targets.length > 0) hasExternalSink = true;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const fullCmd = command.toLowerCase();
|
|
1327
|
+
if (!hasSensitiveSource) {
|
|
1328
|
+
const redirMatch = fullCmd.match(/<\s*(\S+)/);
|
|
1329
|
+
if (redirMatch && isSensitivePath(redirMatch[1])) {
|
|
1330
|
+
hasSensitiveSource = true;
|
|
1331
|
+
sourceFiles.push(redirMatch[1]);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
|
|
1335
|
+
return {
|
|
1336
|
+
isPipeline: true,
|
|
1337
|
+
hasSensitiveSource,
|
|
1338
|
+
hasExternalSink,
|
|
1339
|
+
hasObfuscation,
|
|
1340
|
+
sourceFiles,
|
|
1341
|
+
sinkTargets,
|
|
1342
|
+
risk
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
var SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS;
|
|
1346
|
+
var init_pipe_chain = __esm({
|
|
1347
|
+
"src/policy/pipe-chain.ts"() {
|
|
1348
|
+
"use strict";
|
|
1349
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1350
|
+
"cat",
|
|
1351
|
+
"head",
|
|
1352
|
+
"tail",
|
|
1353
|
+
"grep",
|
|
1354
|
+
"awk",
|
|
1355
|
+
"sed",
|
|
1356
|
+
"cut",
|
|
1357
|
+
"sort",
|
|
1358
|
+
"tee",
|
|
1359
|
+
"less",
|
|
1360
|
+
"more",
|
|
1361
|
+
"strings",
|
|
1362
|
+
"xxd"
|
|
1363
|
+
]);
|
|
1364
|
+
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
1365
|
+
"curl",
|
|
1366
|
+
"wget",
|
|
1367
|
+
"nc",
|
|
1368
|
+
"ncat",
|
|
1369
|
+
"netcat",
|
|
1370
|
+
"ssh",
|
|
1371
|
+
"scp",
|
|
1372
|
+
"rsync",
|
|
1373
|
+
"socat",
|
|
1374
|
+
"ftp",
|
|
1375
|
+
"sftp",
|
|
1376
|
+
"telnet"
|
|
1377
|
+
]);
|
|
1378
|
+
OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
1379
|
+
"base64",
|
|
1380
|
+
"gzip",
|
|
1381
|
+
"gunzip",
|
|
1382
|
+
"bzip2",
|
|
1383
|
+
"xz",
|
|
1384
|
+
"zstd",
|
|
1385
|
+
"openssl",
|
|
1386
|
+
"gpg",
|
|
1387
|
+
"python",
|
|
1388
|
+
"python3",
|
|
1389
|
+
"perl",
|
|
1390
|
+
"ruby",
|
|
1391
|
+
"node"
|
|
1392
|
+
]);
|
|
1393
|
+
SENSITIVE_PATTERNS = [
|
|
1394
|
+
/(?:^|\/)\.env(?:\.|$)/i,
|
|
1395
|
+
// .env, .env.local, .env.production
|
|
1396
|
+
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
1397
|
+
// SSH private keys
|
|
1398
|
+
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
1399
|
+
// certificate files
|
|
1400
|
+
/(?:^|\/)\.ssh\//i,
|
|
1401
|
+
// ~/.ssh/ directory
|
|
1402
|
+
/(?:^|\/)\.aws\/credentials/i,
|
|
1403
|
+
// AWS credentials
|
|
1404
|
+
/(?:^|\/)\.netrc$/i,
|
|
1405
|
+
// netrc (stores HTTP credentials)
|
|
1406
|
+
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
1407
|
+
// /etc/passwd, /etc/shadow
|
|
1408
|
+
/(?:^|\/)credentials(?:\.json)?$/i
|
|
1409
|
+
// generic credentials files
|
|
1410
|
+
];
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
// src/policy/flag-tables.ts
|
|
1415
|
+
import path6 from "path";
|
|
1416
|
+
function extractPositionalArgs(tokens, binary) {
|
|
1417
|
+
const binaryName = path6.basename(binary).replace(/\.exe$/i, "");
|
|
1418
|
+
const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
|
|
1419
|
+
const positional = [];
|
|
1420
|
+
let skipNext = false;
|
|
1421
|
+
for (const token of tokens) {
|
|
1422
|
+
if (skipNext) {
|
|
1423
|
+
skipNext = false;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (token.startsWith("--") && token.includes("=")) continue;
|
|
1427
|
+
if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
|
|
1428
|
+
skipNext = true;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (token.startsWith("--") && flagsWithValues.has(token)) {
|
|
1432
|
+
skipNext = true;
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
const shortFlag = token.slice(0, 2);
|
|
1436
|
+
if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
|
|
1437
|
+
if (token.startsWith("-")) continue;
|
|
1438
|
+
if (token.startsWith("@")) continue;
|
|
1439
|
+
positional.push(token);
|
|
1440
|
+
}
|
|
1441
|
+
return positional;
|
|
1442
|
+
}
|
|
1443
|
+
function extractNetworkTargets(tokens, binary) {
|
|
1444
|
+
return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
|
|
1445
|
+
const colonIdx = t.indexOf(":");
|
|
1446
|
+
if (colonIdx === -1) return t;
|
|
1447
|
+
const afterColon = t.slice(colonIdx + 1);
|
|
1448
|
+
if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
|
|
1449
|
+
return t;
|
|
1450
|
+
}).filter(Boolean);
|
|
1451
|
+
}
|
|
1452
|
+
var FLAGS_WITH_VALUES;
|
|
1453
|
+
var init_flag_tables = __esm({
|
|
1454
|
+
"src/policy/flag-tables.ts"() {
|
|
1455
|
+
"use strict";
|
|
1456
|
+
FLAGS_WITH_VALUES = {
|
|
1457
|
+
curl: /* @__PURE__ */ new Set([
|
|
1458
|
+
"-H",
|
|
1459
|
+
"--header",
|
|
1460
|
+
"-A",
|
|
1461
|
+
"--user-agent",
|
|
1462
|
+
"-e",
|
|
1463
|
+
"--referer",
|
|
1464
|
+
"-x",
|
|
1465
|
+
"--proxy",
|
|
1466
|
+
"-u",
|
|
1467
|
+
"--user",
|
|
1468
|
+
"-d",
|
|
1469
|
+
"--data",
|
|
1470
|
+
"--data-raw",
|
|
1471
|
+
"--data-binary",
|
|
1472
|
+
"-o",
|
|
1473
|
+
"--output",
|
|
1474
|
+
"-F",
|
|
1475
|
+
"--form",
|
|
1476
|
+
"--connect-to",
|
|
1477
|
+
"--resolve",
|
|
1478
|
+
"--cacert",
|
|
1479
|
+
"--cert",
|
|
1480
|
+
"--key",
|
|
1481
|
+
"-m",
|
|
1482
|
+
"--max-time"
|
|
1483
|
+
]),
|
|
1484
|
+
wget: /* @__PURE__ */ new Set([
|
|
1485
|
+
"-O",
|
|
1486
|
+
"--output-document",
|
|
1487
|
+
"-P",
|
|
1488
|
+
"--directory-prefix",
|
|
1489
|
+
"-U",
|
|
1490
|
+
"--user-agent",
|
|
1491
|
+
"-e",
|
|
1492
|
+
"--execute",
|
|
1493
|
+
"--proxy",
|
|
1494
|
+
"--ca-certificate"
|
|
1495
|
+
]),
|
|
1496
|
+
nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
|
|
1497
|
+
ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
|
|
1498
|
+
netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
|
|
1499
|
+
ssh: /* @__PURE__ */ new Set([
|
|
1500
|
+
"-i",
|
|
1501
|
+
"-l",
|
|
1502
|
+
"-p",
|
|
1503
|
+
"-o",
|
|
1504
|
+
"-E",
|
|
1505
|
+
"-F",
|
|
1506
|
+
"-J",
|
|
1507
|
+
"-L",
|
|
1508
|
+
"-R",
|
|
1509
|
+
"-W",
|
|
1510
|
+
"-b",
|
|
1511
|
+
"-c",
|
|
1512
|
+
"-D",
|
|
1513
|
+
"-e",
|
|
1514
|
+
"-I",
|
|
1515
|
+
"-S"
|
|
1516
|
+
]),
|
|
1517
|
+
scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
|
|
1518
|
+
rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
|
|
1519
|
+
socat: /* @__PURE__ */ new Set([])
|
|
1520
|
+
// socat uses address syntax, not flags — no value-flags
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
// src/policy/ssh-parser.ts
|
|
1526
|
+
function tokenize(cmd) {
|
|
1527
|
+
const tokens = [];
|
|
1528
|
+
let current = "";
|
|
1529
|
+
let inSingle = false;
|
|
1530
|
+
let inDouble = false;
|
|
1531
|
+
for (const ch of cmd) {
|
|
1532
|
+
if (ch === "'" && !inDouble) {
|
|
1533
|
+
inSingle = !inSingle;
|
|
1534
|
+
} else if (ch === '"' && !inSingle) {
|
|
1535
|
+
inDouble = !inDouble;
|
|
1536
|
+
} else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
|
|
1537
|
+
if (current) {
|
|
1538
|
+
tokens.push(current);
|
|
1539
|
+
current = "";
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
current += ch;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (current) tokens.push(current);
|
|
1546
|
+
return tokens;
|
|
1547
|
+
}
|
|
1548
|
+
function parseHost(raw) {
|
|
1549
|
+
return raw.split("@").pop().split(":")[0];
|
|
1550
|
+
}
|
|
1551
|
+
function extractAllSshHosts(tokens) {
|
|
1552
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1553
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1554
|
+
const t = tokens[i];
|
|
1555
|
+
if (t === "-J" && tokens[i + 1]) {
|
|
1556
|
+
for (const hop of tokens[++i].split(",")) {
|
|
1557
|
+
const h = parseHost(hop);
|
|
1558
|
+
if (h) hosts.add(h);
|
|
1559
|
+
}
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
|
|
1563
|
+
const val = tokens[++i].split("=").slice(1).join("=");
|
|
1564
|
+
for (const hop of val.split(",")) {
|
|
1565
|
+
const h = parseHost(hop);
|
|
1566
|
+
if (h) hosts.add(h);
|
|
1567
|
+
}
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
|
|
1571
|
+
const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
|
|
1572
|
+
const subTokens = tokenize(raw);
|
|
1573
|
+
const binary = subTokens[0] ?? "";
|
|
1574
|
+
extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
|
|
1575
|
+
extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
if (!t.startsWith("-")) {
|
|
1579
|
+
const h = parseHost(t);
|
|
1580
|
+
if (h) hosts.add(h);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return [...hosts].filter(Boolean);
|
|
1584
|
+
}
|
|
1585
|
+
var init_ssh_parser = __esm({
|
|
1586
|
+
"src/policy/ssh-parser.ts"() {
|
|
1587
|
+
"use strict";
|
|
1588
|
+
init_flag_tables();
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
// src/policy/index.ts
|
|
1593
|
+
import fs6 from "fs";
|
|
1594
|
+
import path7 from "path";
|
|
1595
|
+
import os5 from "os";
|
|
1164
1596
|
import pm from "picomatch";
|
|
1165
1597
|
import { parse } from "sh-syntax";
|
|
1166
|
-
function
|
|
1598
|
+
function tokenize2(toolName) {
|
|
1167
1599
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
1168
1600
|
}
|
|
1169
1601
|
function matchesPattern(text, patterns) {
|
|
@@ -1176,9 +1608,9 @@ function matchesPattern(text, patterns) {
|
|
|
1176
1608
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1177
1609
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1178
1610
|
}
|
|
1179
|
-
function getNestedValue(obj,
|
|
1611
|
+
function getNestedValue(obj, path24) {
|
|
1180
1612
|
if (!obj || typeof obj !== "object") return null;
|
|
1181
|
-
return
|
|
1613
|
+
return path24.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1182
1614
|
}
|
|
1183
1615
|
function shouldSnapshot(toolName, args, config) {
|
|
1184
1616
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1313,7 +1745,7 @@ async function analyzeShellCommand(command) {
|
|
|
1313
1745
|
}
|
|
1314
1746
|
return { actions, paths, allTokens };
|
|
1315
1747
|
}
|
|
1316
|
-
async function evaluatePolicy(toolName, args, agent) {
|
|
1748
|
+
async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
1317
1749
|
const config = getConfig();
|
|
1318
1750
|
if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
|
|
1319
1751
|
if (config.policy.smartRules.length > 0) {
|
|
@@ -1343,11 +1775,55 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1343
1775
|
if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
|
|
1344
1776
|
return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
|
|
1345
1777
|
}
|
|
1778
|
+
const pipeAnalysis = analyzePipeChain(shellCommand);
|
|
1779
|
+
if (pipeAnalysis.isPipeline) {
|
|
1780
|
+
if (pipeAnalysis.risk === "critical") {
|
|
1781
|
+
return {
|
|
1782
|
+
decision: "block",
|
|
1783
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1784
|
+
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
|
|
1785
|
+
tier: 3
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
if (pipeAnalysis.risk === "high") {
|
|
1789
|
+
return {
|
|
1790
|
+
decision: "review",
|
|
1791
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1792
|
+
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
|
|
1793
|
+
tier: 3
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const firstToken = analyzed.actions[0] ?? "";
|
|
1798
|
+
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1799
|
+
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
1800
|
+
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
1801
|
+
allTokens.push(...sshHosts);
|
|
1802
|
+
}
|
|
1803
|
+
if (firstToken && path7.posix.isAbsolute(firstToken)) {
|
|
1804
|
+
const prov = checkProvenance(firstToken, cwd);
|
|
1805
|
+
if (prov.trustLevel === "suspect") {
|
|
1806
|
+
return {
|
|
1807
|
+
decision: config.settings.mode === "strict" ? "block" : "review",
|
|
1808
|
+
blockedByLabel: "Node9: Suspect Binary",
|
|
1809
|
+
reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
|
|
1810
|
+
tier: 3
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
|
|
1814
|
+
return {
|
|
1815
|
+
decision: "review",
|
|
1816
|
+
blockedByLabel: "Node9: Unknown Binary (strict mode)",
|
|
1817
|
+
reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
|
|
1818
|
+
tier: 3
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1346
1822
|
if (isSqlTool(toolName, config.policy.toolInspection)) {
|
|
1347
1823
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
1348
1824
|
}
|
|
1349
1825
|
} else {
|
|
1350
|
-
allTokens =
|
|
1826
|
+
allTokens = tokenize2(toolName);
|
|
1351
1827
|
if (args && typeof args === "object") {
|
|
1352
1828
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
1353
1829
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
@@ -1421,9 +1897,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1421
1897
|
}
|
|
1422
1898
|
async function explainPolicy(toolName, args) {
|
|
1423
1899
|
const steps = [];
|
|
1424
|
-
const globalPath =
|
|
1425
|
-
const projectPath =
|
|
1426
|
-
const credsPath =
|
|
1900
|
+
const globalPath = path7.join(os5.homedir(), ".node9", "config.json");
|
|
1901
|
+
const projectPath = path7.join(process.cwd(), "node9.config.json");
|
|
1902
|
+
const credsPath = path7.join(os5.homedir(), ".node9", "credentials.json");
|
|
1427
1903
|
const waterfall = [
|
|
1428
1904
|
{
|
|
1429
1905
|
tier: 1,
|
|
@@ -1434,19 +1910,19 @@ async function explainPolicy(toolName, args) {
|
|
|
1434
1910
|
{
|
|
1435
1911
|
tier: 2,
|
|
1436
1912
|
label: "Cloud policy",
|
|
1437
|
-
status:
|
|
1438
|
-
note:
|
|
1913
|
+
status: fs6.existsSync(credsPath) ? "active" : "missing",
|
|
1914
|
+
note: fs6.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
1439
1915
|
},
|
|
1440
1916
|
{
|
|
1441
1917
|
tier: 3,
|
|
1442
1918
|
label: "Project config",
|
|
1443
|
-
status:
|
|
1919
|
+
status: fs6.existsSync(projectPath) ? "active" : "missing",
|
|
1444
1920
|
path: projectPath
|
|
1445
1921
|
},
|
|
1446
1922
|
{
|
|
1447
1923
|
tier: 4,
|
|
1448
1924
|
label: "Global config",
|
|
1449
|
-
status:
|
|
1925
|
+
status: fs6.existsSync(globalPath) ? "active" : "missing",
|
|
1450
1926
|
path: globalPath
|
|
1451
1927
|
},
|
|
1452
1928
|
{
|
|
@@ -1578,7 +2054,7 @@ async function explainPolicy(toolName, args) {
|
|
|
1578
2054
|
});
|
|
1579
2055
|
}
|
|
1580
2056
|
} else {
|
|
1581
|
-
allTokens =
|
|
2057
|
+
allTokens = tokenize2(toolName);
|
|
1582
2058
|
let detail = `No toolInspection match for "${toolName}" \u2014 tokens: [${allTokens.join(", ")}]`;
|
|
1583
2059
|
if (args && typeof args === "object") {
|
|
1584
2060
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
@@ -1690,21 +2166,24 @@ var init_policy = __esm({
|
|
|
1690
2166
|
init_dlp();
|
|
1691
2167
|
init_config();
|
|
1692
2168
|
init_regex();
|
|
2169
|
+
init_provenance();
|
|
2170
|
+
init_pipe_chain();
|
|
2171
|
+
init_ssh_parser();
|
|
1693
2172
|
SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
1694
2173
|
}
|
|
1695
2174
|
});
|
|
1696
2175
|
|
|
1697
2176
|
// src/auth/state.ts
|
|
1698
|
-
import
|
|
1699
|
-
import
|
|
1700
|
-
import
|
|
2177
|
+
import fs7 from "fs";
|
|
2178
|
+
import path8 from "path";
|
|
2179
|
+
import os6 from "os";
|
|
1701
2180
|
function checkPause() {
|
|
1702
2181
|
try {
|
|
1703
|
-
if (!
|
|
1704
|
-
const state = JSON.parse(
|
|
2182
|
+
if (!fs7.existsSync(PAUSED_FILE)) return { paused: false };
|
|
2183
|
+
const state = JSON.parse(fs7.readFileSync(PAUSED_FILE, "utf-8"));
|
|
1705
2184
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
1706
2185
|
try {
|
|
1707
|
-
|
|
2186
|
+
fs7.unlinkSync(PAUSED_FILE);
|
|
1708
2187
|
} catch {
|
|
1709
2188
|
}
|
|
1710
2189
|
return { paused: false };
|
|
@@ -1715,11 +2194,11 @@ function checkPause() {
|
|
|
1715
2194
|
}
|
|
1716
2195
|
}
|
|
1717
2196
|
function atomicWriteSync(filePath, data, options) {
|
|
1718
|
-
const dir =
|
|
1719
|
-
if (!
|
|
1720
|
-
const tmpPath = `${filePath}.${
|
|
1721
|
-
|
|
1722
|
-
|
|
2197
|
+
const dir = path8.dirname(filePath);
|
|
2198
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2199
|
+
const tmpPath = `${filePath}.${os6.hostname()}.${process.pid}.tmp`;
|
|
2200
|
+
fs7.writeFileSync(tmpPath, data, options);
|
|
2201
|
+
fs7.renameSync(tmpPath, filePath);
|
|
1723
2202
|
}
|
|
1724
2203
|
function pauseNode9(durationMs, durationStr) {
|
|
1725
2204
|
const state = { expiry: Date.now() + durationMs, duration: durationStr };
|
|
@@ -1727,18 +2206,18 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
1727
2206
|
}
|
|
1728
2207
|
function resumeNode9() {
|
|
1729
2208
|
try {
|
|
1730
|
-
if (
|
|
2209
|
+
if (fs7.existsSync(PAUSED_FILE)) fs7.unlinkSync(PAUSED_FILE);
|
|
1731
2210
|
} catch {
|
|
1732
2211
|
}
|
|
1733
2212
|
}
|
|
1734
2213
|
function getActiveTrustSession(toolName) {
|
|
1735
2214
|
try {
|
|
1736
|
-
if (!
|
|
1737
|
-
const trust = JSON.parse(
|
|
2215
|
+
if (!fs7.existsSync(TRUST_FILE)) return false;
|
|
2216
|
+
const trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
|
|
1738
2217
|
const now = Date.now();
|
|
1739
2218
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
1740
2219
|
if (active.length !== trust.entries.length) {
|
|
1741
|
-
|
|
2220
|
+
fs7.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
1742
2221
|
}
|
|
1743
2222
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
1744
2223
|
} catch {
|
|
@@ -1749,8 +2228,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1749
2228
|
try {
|
|
1750
2229
|
let trust = { entries: [] };
|
|
1751
2230
|
try {
|
|
1752
|
-
if (
|
|
1753
|
-
trust = JSON.parse(
|
|
2231
|
+
if (fs7.existsSync(TRUST_FILE)) {
|
|
2232
|
+
trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
|
|
1754
2233
|
}
|
|
1755
2234
|
} catch {
|
|
1756
2235
|
}
|
|
@@ -1766,9 +2245,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1766
2245
|
}
|
|
1767
2246
|
function getPersistentDecision(toolName) {
|
|
1768
2247
|
try {
|
|
1769
|
-
const file =
|
|
1770
|
-
if (!
|
|
1771
|
-
const decisions = JSON.parse(
|
|
2248
|
+
const file = path8.join(os6.homedir(), ".node9", "decisions.json");
|
|
2249
|
+
if (!fs7.existsSync(file)) return null;
|
|
2250
|
+
const decisions = JSON.parse(fs7.readFileSync(file, "utf-8"));
|
|
1772
2251
|
const d = decisions[toolName];
|
|
1773
2252
|
if (d === "allow" || d === "deny") return d;
|
|
1774
2253
|
} catch {
|
|
@@ -1780,21 +2259,21 @@ var init_state = __esm({
|
|
|
1780
2259
|
"src/auth/state.ts"() {
|
|
1781
2260
|
"use strict";
|
|
1782
2261
|
init_policy();
|
|
1783
|
-
PAUSED_FILE =
|
|
1784
|
-
TRUST_FILE =
|
|
2262
|
+
PAUSED_FILE = path8.join(os6.homedir(), ".node9", "PAUSED");
|
|
2263
|
+
TRUST_FILE = path8.join(os6.homedir(), ".node9", "trust.json");
|
|
1785
2264
|
}
|
|
1786
2265
|
});
|
|
1787
2266
|
|
|
1788
2267
|
// src/auth/daemon.ts
|
|
1789
|
-
import
|
|
1790
|
-
import
|
|
1791
|
-
import
|
|
2268
|
+
import fs8 from "fs";
|
|
2269
|
+
import path9 from "path";
|
|
2270
|
+
import os7 from "os";
|
|
1792
2271
|
import { spawnSync } from "child_process";
|
|
1793
2272
|
function getInternalToken() {
|
|
1794
2273
|
try {
|
|
1795
|
-
const pidFile =
|
|
1796
|
-
if (!
|
|
1797
|
-
const data = JSON.parse(
|
|
2274
|
+
const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
|
|
2275
|
+
if (!fs8.existsSync(pidFile)) return null;
|
|
2276
|
+
const data = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
|
|
1798
2277
|
process.kill(data.pid, 0);
|
|
1799
2278
|
return data.internalToken ?? null;
|
|
1800
2279
|
} catch {
|
|
@@ -1802,10 +2281,10 @@ function getInternalToken() {
|
|
|
1802
2281
|
}
|
|
1803
2282
|
}
|
|
1804
2283
|
function isDaemonRunning() {
|
|
1805
|
-
const pidFile =
|
|
1806
|
-
if (
|
|
2284
|
+
const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
|
|
2285
|
+
if (fs8.existsSync(pidFile)) {
|
|
1807
2286
|
try {
|
|
1808
|
-
const { pid, port } = JSON.parse(
|
|
2287
|
+
const { pid, port } = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
|
|
1809
2288
|
if (port !== DAEMON_PORT) return false;
|
|
1810
2289
|
process.kill(pid, 0);
|
|
1811
2290
|
return true;
|
|
@@ -1908,7 +2387,7 @@ var init_daemon = __esm({
|
|
|
1908
2387
|
});
|
|
1909
2388
|
|
|
1910
2389
|
// src/context-sniper.ts
|
|
1911
|
-
import
|
|
2390
|
+
import path10 from "path";
|
|
1912
2391
|
function smartTruncate(str, maxLen = 500) {
|
|
1913
2392
|
if (str.length <= maxLen) return str;
|
|
1914
2393
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -1960,7 +2439,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
1960
2439
|
intent = "EDIT";
|
|
1961
2440
|
if (obj.file_path) {
|
|
1962
2441
|
editFilePath = String(obj.file_path);
|
|
1963
|
-
editFileName =
|
|
2442
|
+
editFileName = path10.basename(editFilePath);
|
|
1964
2443
|
}
|
|
1965
2444
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
1966
2445
|
contextSnippet = result.snippet;
|
|
@@ -2017,7 +2496,7 @@ var init_context_sniper = __esm({
|
|
|
2017
2496
|
|
|
2018
2497
|
// src/ui/native.ts
|
|
2019
2498
|
import { spawn } from "child_process";
|
|
2020
|
-
import
|
|
2499
|
+
import path11 from "path";
|
|
2021
2500
|
function formatArgs(args, matchedField, matchedWord) {
|
|
2022
2501
|
if (args === null || args === void 0) return { message: "(none)", intent: "EXEC" };
|
|
2023
2502
|
let parsed = args;
|
|
@@ -2036,7 +2515,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
2036
2515
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2037
2516
|
const obj = parsed;
|
|
2038
2517
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
2039
|
-
const file = obj.file_path ?
|
|
2518
|
+
const file = obj.file_path ? path11.basename(String(obj.file_path)) : "file";
|
|
2040
2519
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
2041
2520
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
2042
2521
|
return {
|
|
@@ -2221,8 +2700,8 @@ var init_native = __esm({
|
|
|
2221
2700
|
});
|
|
2222
2701
|
|
|
2223
2702
|
// src/auth/cloud.ts
|
|
2224
|
-
import
|
|
2225
|
-
import
|
|
2703
|
+
import fs9 from "fs";
|
|
2704
|
+
import os8 from "os";
|
|
2226
2705
|
function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
2227
2706
|
return fetch(`${creds.apiUrl}/audit`, {
|
|
2228
2707
|
method: "POST",
|
|
@@ -2234,9 +2713,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2234
2713
|
context: {
|
|
2235
2714
|
agent: meta?.agent,
|
|
2236
2715
|
mcpServer: meta?.mcpServer,
|
|
2237
|
-
hostname:
|
|
2716
|
+
hostname: os8.hostname(),
|
|
2238
2717
|
cwd: process.cwd(),
|
|
2239
|
-
platform:
|
|
2718
|
+
platform: os8.platform()
|
|
2240
2719
|
}
|
|
2241
2720
|
}),
|
|
2242
2721
|
signal: AbortSignal.timeout(5e3)
|
|
@@ -2257,9 +2736,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2257
2736
|
context: {
|
|
2258
2737
|
agent: meta?.agent,
|
|
2259
2738
|
mcpServer: meta?.mcpServer,
|
|
2260
|
-
hostname:
|
|
2739
|
+
hostname: os8.hostname(),
|
|
2261
2740
|
cwd: process.cwd(),
|
|
2262
|
-
platform:
|
|
2741
|
+
platform: os8.platform()
|
|
2263
2742
|
},
|
|
2264
2743
|
...riskMetadata && { riskMetadata }
|
|
2265
2744
|
}),
|
|
@@ -2315,14 +2794,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
2315
2794
|
});
|
|
2316
2795
|
clearTimeout(timer);
|
|
2317
2796
|
if (!res.ok) {
|
|
2318
|
-
|
|
2797
|
+
fs9.appendFileSync(
|
|
2319
2798
|
HOOK_DEBUG_LOG,
|
|
2320
2799
|
`[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
|
|
2321
2800
|
`
|
|
2322
2801
|
);
|
|
2323
2802
|
}
|
|
2324
2803
|
} catch (err) {
|
|
2325
|
-
|
|
2804
|
+
fs9.appendFileSync(
|
|
2326
2805
|
HOOK_DEBUG_LOG,
|
|
2327
2806
|
`[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
|
|
2328
2807
|
`
|
|
@@ -2338,8 +2817,8 @@ var init_cloud = __esm({
|
|
|
2338
2817
|
|
|
2339
2818
|
// src/auth/orchestrator.ts
|
|
2340
2819
|
import net from "net";
|
|
2341
|
-
import
|
|
2342
|
-
import
|
|
2820
|
+
import path12 from "path";
|
|
2821
|
+
import os9 from "os";
|
|
2343
2822
|
import { randomUUID } from "crypto";
|
|
2344
2823
|
function notifyActivity(data) {
|
|
2345
2824
|
return new Promise((resolve) => {
|
|
@@ -2422,7 +2901,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2422
2901
|
}
|
|
2423
2902
|
if (config.settings.mode === "audit") {
|
|
2424
2903
|
if (!isIgnoredTool(toolName)) {
|
|
2425
|
-
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
2904
|
+
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
2426
2905
|
if (policyResult.decision === "review") {
|
|
2427
2906
|
appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
|
|
2428
2907
|
if (approvers.cloud && creds?.apiKey) {
|
|
@@ -2700,7 +3179,7 @@ var init_orchestrator = __esm({
|
|
|
2700
3179
|
init_state();
|
|
2701
3180
|
init_daemon();
|
|
2702
3181
|
init_cloud();
|
|
2703
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
3182
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os9.tmpdir(), "node9-activity.sock");
|
|
2704
3183
|
}
|
|
2705
3184
|
});
|
|
2706
3185
|
|
|
@@ -4172,9 +4651,9 @@ var init_ui2 = __esm({
|
|
|
4172
4651
|
|
|
4173
4652
|
// src/daemon/state.ts
|
|
4174
4653
|
import net2 from "net";
|
|
4175
|
-
import
|
|
4176
|
-
import
|
|
4177
|
-
import
|
|
4654
|
+
import fs11 from "fs";
|
|
4655
|
+
import path14 from "path";
|
|
4656
|
+
import os11 from "os";
|
|
4178
4657
|
import { spawn as spawn2 } from "child_process";
|
|
4179
4658
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4180
4659
|
function getAbandonTimer() {
|
|
@@ -4199,11 +4678,11 @@ function markRejectionHandlerRegistered() {
|
|
|
4199
4678
|
daemonRejectionHandlerRegistered = true;
|
|
4200
4679
|
}
|
|
4201
4680
|
function atomicWriteSync2(filePath, data, options) {
|
|
4202
|
-
const dir =
|
|
4203
|
-
if (!
|
|
4681
|
+
const dir = path14.dirname(filePath);
|
|
4682
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
4204
4683
|
const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
|
|
4205
|
-
|
|
4206
|
-
|
|
4684
|
+
fs11.writeFileSync(tmpPath, data, options);
|
|
4685
|
+
fs11.renameSync(tmpPath, filePath);
|
|
4207
4686
|
}
|
|
4208
4687
|
function redactArgs(value) {
|
|
4209
4688
|
if (!value || typeof value !== "object") return value;
|
|
@@ -4223,16 +4702,16 @@ function appendAuditLog(data) {
|
|
|
4223
4702
|
decision: data.decision,
|
|
4224
4703
|
source: "daemon"
|
|
4225
4704
|
};
|
|
4226
|
-
const dir =
|
|
4227
|
-
if (!
|
|
4228
|
-
|
|
4705
|
+
const dir = path14.dirname(AUDIT_LOG_FILE);
|
|
4706
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
4707
|
+
fs11.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
4229
4708
|
} catch {
|
|
4230
4709
|
}
|
|
4231
4710
|
}
|
|
4232
4711
|
function getAuditHistory(limit = 20) {
|
|
4233
4712
|
try {
|
|
4234
|
-
if (!
|
|
4235
|
-
const lines =
|
|
4713
|
+
if (!fs11.existsSync(AUDIT_LOG_FILE)) return [];
|
|
4714
|
+
const lines = fs11.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
4236
4715
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4237
4716
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4238
4717
|
} catch {
|
|
@@ -4241,19 +4720,19 @@ function getAuditHistory(limit = 20) {
|
|
|
4241
4720
|
}
|
|
4242
4721
|
function getOrgName() {
|
|
4243
4722
|
try {
|
|
4244
|
-
if (
|
|
4723
|
+
if (fs11.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
4245
4724
|
} catch {
|
|
4246
4725
|
}
|
|
4247
4726
|
return null;
|
|
4248
4727
|
}
|
|
4249
4728
|
function hasStoredSlackKey() {
|
|
4250
|
-
return
|
|
4729
|
+
return fs11.existsSync(CREDENTIALS_FILE);
|
|
4251
4730
|
}
|
|
4252
4731
|
function writeGlobalSetting(key, value) {
|
|
4253
4732
|
let config = {};
|
|
4254
4733
|
try {
|
|
4255
|
-
if (
|
|
4256
|
-
config = JSON.parse(
|
|
4734
|
+
if (fs11.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
4735
|
+
config = JSON.parse(fs11.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
4257
4736
|
}
|
|
4258
4737
|
} catch {
|
|
4259
4738
|
}
|
|
@@ -4265,8 +4744,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4265
4744
|
try {
|
|
4266
4745
|
let trust = { entries: [] };
|
|
4267
4746
|
try {
|
|
4268
|
-
if (
|
|
4269
|
-
trust = JSON.parse(
|
|
4747
|
+
if (fs11.existsSync(TRUST_FILE2))
|
|
4748
|
+
trust = JSON.parse(fs11.readFileSync(TRUST_FILE2, "utf-8"));
|
|
4270
4749
|
} catch {
|
|
4271
4750
|
}
|
|
4272
4751
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -4277,8 +4756,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
4277
4756
|
}
|
|
4278
4757
|
function readPersistentDecisions() {
|
|
4279
4758
|
try {
|
|
4280
|
-
if (
|
|
4281
|
-
return JSON.parse(
|
|
4759
|
+
if (fs11.existsSync(DECISIONS_FILE)) {
|
|
4760
|
+
return JSON.parse(fs11.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4282
4761
|
}
|
|
4283
4762
|
} catch {
|
|
4284
4763
|
}
|
|
@@ -4343,7 +4822,7 @@ function abandonPending() {
|
|
|
4343
4822
|
});
|
|
4344
4823
|
if (autoStarted) {
|
|
4345
4824
|
try {
|
|
4346
|
-
|
|
4825
|
+
fs11.unlinkSync(DAEMON_PID_FILE);
|
|
4347
4826
|
} catch {
|
|
4348
4827
|
}
|
|
4349
4828
|
setTimeout(() => {
|
|
@@ -4354,7 +4833,7 @@ function abandonPending() {
|
|
|
4354
4833
|
}
|
|
4355
4834
|
function startActivitySocket() {
|
|
4356
4835
|
try {
|
|
4357
|
-
|
|
4836
|
+
fs11.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4358
4837
|
} catch {
|
|
4359
4838
|
}
|
|
4360
4839
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4396,7 +4875,7 @@ function startActivitySocket() {
|
|
|
4396
4875
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4397
4876
|
process.on("exit", () => {
|
|
4398
4877
|
try {
|
|
4399
|
-
|
|
4878
|
+
fs11.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4400
4879
|
} catch {
|
|
4401
4880
|
}
|
|
4402
4881
|
});
|
|
@@ -4406,13 +4885,13 @@ var init_state2 = __esm({
|
|
|
4406
4885
|
"src/daemon/state.ts"() {
|
|
4407
4886
|
"use strict";
|
|
4408
4887
|
init_daemon();
|
|
4409
|
-
homeDir =
|
|
4410
|
-
DAEMON_PID_FILE =
|
|
4411
|
-
DECISIONS_FILE =
|
|
4412
|
-
AUDIT_LOG_FILE =
|
|
4413
|
-
TRUST_FILE2 =
|
|
4414
|
-
GLOBAL_CONFIG_FILE =
|
|
4415
|
-
CREDENTIALS_FILE =
|
|
4888
|
+
homeDir = os11.homedir();
|
|
4889
|
+
DAEMON_PID_FILE = path14.join(homeDir, ".node9", "daemon.pid");
|
|
4890
|
+
DECISIONS_FILE = path14.join(homeDir, ".node9", "decisions.json");
|
|
4891
|
+
AUDIT_LOG_FILE = path14.join(homeDir, ".node9", "audit.log");
|
|
4892
|
+
TRUST_FILE2 = path14.join(homeDir, ".node9", "trust.json");
|
|
4893
|
+
GLOBAL_CONFIG_FILE = path14.join(homeDir, ".node9", "config.json");
|
|
4894
|
+
CREDENTIALS_FILE = path14.join(homeDir, ".node9", "credentials.json");
|
|
4416
4895
|
pending = /* @__PURE__ */ new Map();
|
|
4417
4896
|
sseClients = /* @__PURE__ */ new Set();
|
|
4418
4897
|
_abandonTimer = null;
|
|
@@ -4426,7 +4905,7 @@ var init_state2 = __esm({
|
|
|
4426
4905
|
"2h": 2 * 60 * 6e4
|
|
4427
4906
|
};
|
|
4428
4907
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
4429
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
4908
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path14.join(os11.tmpdir(), "node9-activity.sock");
|
|
4430
4909
|
ACTIVITY_RING_SIZE = 100;
|
|
4431
4910
|
activityRing = [];
|
|
4432
4911
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -4435,8 +4914,8 @@ var init_state2 = __esm({
|
|
|
4435
4914
|
|
|
4436
4915
|
// src/daemon/server.ts
|
|
4437
4916
|
import http from "http";
|
|
4438
|
-
import
|
|
4439
|
-
import
|
|
4917
|
+
import fs12 from "fs";
|
|
4918
|
+
import path15 from "path";
|
|
4440
4919
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4441
4920
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4442
4921
|
import chalk2 from "chalk";
|
|
@@ -4455,7 +4934,7 @@ function startDaemon() {
|
|
|
4455
4934
|
idleTimer = setTimeout(() => {
|
|
4456
4935
|
if (autoStarted) {
|
|
4457
4936
|
try {
|
|
4458
|
-
|
|
4937
|
+
fs12.unlinkSync(DAEMON_PID_FILE);
|
|
4459
4938
|
} catch {
|
|
4460
4939
|
}
|
|
4461
4940
|
}
|
|
@@ -4597,7 +5076,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4597
5076
|
status: "pending"
|
|
4598
5077
|
});
|
|
4599
5078
|
}
|
|
4600
|
-
const projectCwd = typeof cwd === "string" &&
|
|
5079
|
+
const projectCwd = typeof cwd === "string" && path15.isAbsolute(cwd) ? cwd : void 0;
|
|
4601
5080
|
const projectConfig = getConfig(projectCwd);
|
|
4602
5081
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
4603
5082
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -4901,14 +5380,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4901
5380
|
server.on("error", (e) => {
|
|
4902
5381
|
if (e.code === "EADDRINUSE") {
|
|
4903
5382
|
try {
|
|
4904
|
-
if (
|
|
4905
|
-
const { pid } = JSON.parse(
|
|
5383
|
+
if (fs12.existsSync(DAEMON_PID_FILE)) {
|
|
5384
|
+
const { pid } = JSON.parse(fs12.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4906
5385
|
process.kill(pid, 0);
|
|
4907
5386
|
return process.exit(0);
|
|
4908
5387
|
}
|
|
4909
5388
|
} catch {
|
|
4910
5389
|
try {
|
|
4911
|
-
|
|
5390
|
+
fs12.unlinkSync(DAEMON_PID_FILE);
|
|
4912
5391
|
} catch {
|
|
4913
5392
|
}
|
|
4914
5393
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -4978,28 +5457,28 @@ var init_server = __esm({
|
|
|
4978
5457
|
});
|
|
4979
5458
|
|
|
4980
5459
|
// src/daemon/index.ts
|
|
4981
|
-
import
|
|
5460
|
+
import fs13 from "fs";
|
|
4982
5461
|
import chalk3 from "chalk";
|
|
4983
5462
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4984
5463
|
function stopDaemon() {
|
|
4985
|
-
if (!
|
|
5464
|
+
if (!fs13.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
|
|
4986
5465
|
try {
|
|
4987
|
-
const { pid } = JSON.parse(
|
|
5466
|
+
const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4988
5467
|
process.kill(pid, "SIGTERM");
|
|
4989
5468
|
console.log(chalk3.green("\u2705 Stopped."));
|
|
4990
5469
|
} catch {
|
|
4991
5470
|
console.log(chalk3.gray("Cleaned up stale PID file."));
|
|
4992
5471
|
} finally {
|
|
4993
5472
|
try {
|
|
4994
|
-
|
|
5473
|
+
fs13.unlinkSync(DAEMON_PID_FILE);
|
|
4995
5474
|
} catch {
|
|
4996
5475
|
}
|
|
4997
5476
|
}
|
|
4998
5477
|
}
|
|
4999
5478
|
function daemonStatus() {
|
|
5000
|
-
if (
|
|
5479
|
+
if (fs13.existsSync(DAEMON_PID_FILE)) {
|
|
5001
5480
|
try {
|
|
5002
|
-
const { pid } = JSON.parse(
|
|
5481
|
+
const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
5003
5482
|
process.kill(pid, 0);
|
|
5004
5483
|
console.log(chalk3.green("Node9 daemon: running"));
|
|
5005
5484
|
return;
|
|
@@ -5034,9 +5513,9 @@ __export(tail_exports, {
|
|
|
5034
5513
|
});
|
|
5035
5514
|
import http2 from "http";
|
|
5036
5515
|
import chalk14 from "chalk";
|
|
5037
|
-
import
|
|
5038
|
-
import
|
|
5039
|
-
import
|
|
5516
|
+
import fs20 from "fs";
|
|
5517
|
+
import os18 from "os";
|
|
5518
|
+
import path22 from "path";
|
|
5040
5519
|
import readline3 from "readline";
|
|
5041
5520
|
import { spawn as spawn9, execSync as execSync3 } from "child_process";
|
|
5042
5521
|
function getIcon(tool) {
|
|
@@ -5076,9 +5555,9 @@ function renderPending(activity) {
|
|
|
5076
5555
|
}
|
|
5077
5556
|
async function ensureDaemon() {
|
|
5078
5557
|
let pidPort = null;
|
|
5079
|
-
if (
|
|
5558
|
+
if (fs20.existsSync(PID_FILE)) {
|
|
5080
5559
|
try {
|
|
5081
|
-
const { port } = JSON.parse(
|
|
5560
|
+
const { port } = JSON.parse(fs20.readFileSync(PID_FILE, "utf-8"));
|
|
5082
5561
|
pidPort = port;
|
|
5083
5562
|
} catch {
|
|
5084
5563
|
console.error(chalk14.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -5243,8 +5722,8 @@ async function startTail(options = {}) {
|
|
|
5243
5722
|
process.stdout.write(SHOW_CURSOR);
|
|
5244
5723
|
postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
|
|
5245
5724
|
try {
|
|
5246
|
-
|
|
5247
|
-
|
|
5725
|
+
fs20.appendFileSync(
|
|
5726
|
+
path22.join(os18.homedir(), ".node9", "hook-debug.log"),
|
|
5248
5727
|
`[tail] POST /decision failed: ${String(err)}
|
|
5249
5728
|
`
|
|
5250
5729
|
);
|
|
@@ -5435,7 +5914,7 @@ var init_tail = __esm({
|
|
|
5435
5914
|
"use strict";
|
|
5436
5915
|
init_daemon2();
|
|
5437
5916
|
init_core();
|
|
5438
|
-
PID_FILE =
|
|
5917
|
+
PID_FILE = path22.join(os18.homedir(), ".node9", "daemon.pid");
|
|
5439
5918
|
ICONS = {
|
|
5440
5919
|
bash: "\u{1F4BB}",
|
|
5441
5920
|
shell: "\u{1F4BB}",
|
|
@@ -5473,9 +5952,9 @@ init_core();
|
|
|
5473
5952
|
import { Command } from "commander";
|
|
5474
5953
|
|
|
5475
5954
|
// src/setup.ts
|
|
5476
|
-
import
|
|
5477
|
-
import
|
|
5478
|
-
import
|
|
5955
|
+
import fs10 from "fs";
|
|
5956
|
+
import path13 from "path";
|
|
5957
|
+
import os10 from "os";
|
|
5479
5958
|
import chalk from "chalk";
|
|
5480
5959
|
import { confirm } from "@inquirer/prompts";
|
|
5481
5960
|
function printDaemonTip() {
|
|
@@ -5492,26 +5971,26 @@ function fullPathCommand(subcommand) {
|
|
|
5492
5971
|
}
|
|
5493
5972
|
function readJson(filePath) {
|
|
5494
5973
|
try {
|
|
5495
|
-
if (
|
|
5496
|
-
return JSON.parse(
|
|
5974
|
+
if (fs10.existsSync(filePath)) {
|
|
5975
|
+
return JSON.parse(fs10.readFileSync(filePath, "utf-8"));
|
|
5497
5976
|
}
|
|
5498
5977
|
} catch {
|
|
5499
5978
|
}
|
|
5500
5979
|
return null;
|
|
5501
5980
|
}
|
|
5502
5981
|
function writeJson(filePath, data) {
|
|
5503
|
-
const dir =
|
|
5504
|
-
if (!
|
|
5505
|
-
|
|
5982
|
+
const dir = path13.dirname(filePath);
|
|
5983
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
5984
|
+
fs10.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
5506
5985
|
}
|
|
5507
5986
|
function isNode9Hook(cmd) {
|
|
5508
5987
|
if (!cmd) return false;
|
|
5509
5988
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
5510
5989
|
}
|
|
5511
5990
|
function teardownClaude() {
|
|
5512
|
-
const homeDir2 =
|
|
5513
|
-
const hooksPath =
|
|
5514
|
-
const mcpPath =
|
|
5991
|
+
const homeDir2 = os10.homedir();
|
|
5992
|
+
const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
|
|
5993
|
+
const mcpPath = path13.join(homeDir2, ".claude.json");
|
|
5515
5994
|
let changed = false;
|
|
5516
5995
|
const settings = readJson(hooksPath);
|
|
5517
5996
|
if (settings?.hooks) {
|
|
@@ -5559,8 +6038,8 @@ function teardownClaude() {
|
|
|
5559
6038
|
}
|
|
5560
6039
|
}
|
|
5561
6040
|
function teardownGemini() {
|
|
5562
|
-
const homeDir2 =
|
|
5563
|
-
const settingsPath =
|
|
6041
|
+
const homeDir2 = os10.homedir();
|
|
6042
|
+
const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
|
|
5564
6043
|
const settings = readJson(settingsPath);
|
|
5565
6044
|
if (!settings) {
|
|
5566
6045
|
console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5598,8 +6077,8 @@ function teardownGemini() {
|
|
|
5598
6077
|
}
|
|
5599
6078
|
}
|
|
5600
6079
|
function teardownCursor() {
|
|
5601
|
-
const homeDir2 =
|
|
5602
|
-
const mcpPath =
|
|
6080
|
+
const homeDir2 = os10.homedir();
|
|
6081
|
+
const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
|
|
5603
6082
|
const mcpConfig = readJson(mcpPath);
|
|
5604
6083
|
if (!mcpConfig?.mcpServers) {
|
|
5605
6084
|
console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5625,9 +6104,9 @@ function teardownCursor() {
|
|
|
5625
6104
|
}
|
|
5626
6105
|
}
|
|
5627
6106
|
async function setupClaude() {
|
|
5628
|
-
const homeDir2 =
|
|
5629
|
-
const mcpPath =
|
|
5630
|
-
const hooksPath =
|
|
6107
|
+
const homeDir2 = os10.homedir();
|
|
6108
|
+
const mcpPath = path13.join(homeDir2, ".claude.json");
|
|
6109
|
+
const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
|
|
5631
6110
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5632
6111
|
const settings = readJson(hooksPath) ?? {};
|
|
5633
6112
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5701,8 +6180,8 @@ async function setupClaude() {
|
|
|
5701
6180
|
}
|
|
5702
6181
|
}
|
|
5703
6182
|
async function setupGemini() {
|
|
5704
|
-
const homeDir2 =
|
|
5705
|
-
const settingsPath =
|
|
6183
|
+
const homeDir2 = os10.homedir();
|
|
6184
|
+
const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
|
|
5706
6185
|
const settings = readJson(settingsPath) ?? {};
|
|
5707
6186
|
const servers = settings.mcpServers ?? {};
|
|
5708
6187
|
let anythingChanged = false;
|
|
@@ -5784,8 +6263,8 @@ async function setupGemini() {
|
|
|
5784
6263
|
}
|
|
5785
6264
|
}
|
|
5786
6265
|
async function setupCursor() {
|
|
5787
|
-
const homeDir2 =
|
|
5788
|
-
const mcpPath =
|
|
6266
|
+
const homeDir2 = os10.homedir();
|
|
6267
|
+
const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
|
|
5789
6268
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5790
6269
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5791
6270
|
let anythingChanged = false;
|
|
@@ -5842,9 +6321,9 @@ async function setupCursor() {
|
|
|
5842
6321
|
// src/cli.ts
|
|
5843
6322
|
init_daemon2();
|
|
5844
6323
|
import chalk15 from "chalk";
|
|
5845
|
-
import
|
|
5846
|
-
import
|
|
5847
|
-
import
|
|
6324
|
+
import fs21 from "fs";
|
|
6325
|
+
import path23 from "path";
|
|
6326
|
+
import os19 from "os";
|
|
5848
6327
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
5849
6328
|
|
|
5850
6329
|
// src/utils/duration.ts
|
|
@@ -6069,32 +6548,32 @@ init_daemon();
|
|
|
6069
6548
|
init_config();
|
|
6070
6549
|
init_policy();
|
|
6071
6550
|
import chalk5 from "chalk";
|
|
6072
|
-
import
|
|
6073
|
-
import
|
|
6074
|
-
import
|
|
6551
|
+
import fs15 from "fs";
|
|
6552
|
+
import path17 from "path";
|
|
6553
|
+
import os13 from "os";
|
|
6075
6554
|
|
|
6076
6555
|
// src/undo.ts
|
|
6077
6556
|
import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
|
|
6078
6557
|
import crypto2 from "crypto";
|
|
6079
|
-
import
|
|
6080
|
-
import
|
|
6081
|
-
import
|
|
6082
|
-
var SNAPSHOT_STACK_PATH =
|
|
6083
|
-
var UNDO_LATEST_PATH =
|
|
6558
|
+
import fs14 from "fs";
|
|
6559
|
+
import path16 from "path";
|
|
6560
|
+
import os12 from "os";
|
|
6561
|
+
var SNAPSHOT_STACK_PATH = path16.join(os12.homedir(), ".node9", "snapshots.json");
|
|
6562
|
+
var UNDO_LATEST_PATH = path16.join(os12.homedir(), ".node9", "undo_latest.txt");
|
|
6084
6563
|
var MAX_SNAPSHOTS = 10;
|
|
6085
6564
|
var GIT_TIMEOUT = 15e3;
|
|
6086
6565
|
function readStack() {
|
|
6087
6566
|
try {
|
|
6088
|
-
if (
|
|
6089
|
-
return JSON.parse(
|
|
6567
|
+
if (fs14.existsSync(SNAPSHOT_STACK_PATH))
|
|
6568
|
+
return JSON.parse(fs14.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
6090
6569
|
} catch {
|
|
6091
6570
|
}
|
|
6092
6571
|
return [];
|
|
6093
6572
|
}
|
|
6094
6573
|
function writeStack(stack) {
|
|
6095
|
-
const dir =
|
|
6096
|
-
if (!
|
|
6097
|
-
|
|
6574
|
+
const dir = path16.dirname(SNAPSHOT_STACK_PATH);
|
|
6575
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
6576
|
+
fs14.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
6098
6577
|
}
|
|
6099
6578
|
function buildArgsSummary(tool, args) {
|
|
6100
6579
|
if (!args || typeof args !== "object") return "";
|
|
@@ -6110,7 +6589,7 @@ function buildArgsSummary(tool, args) {
|
|
|
6110
6589
|
function normalizeCwdForHash(cwd) {
|
|
6111
6590
|
let normalized;
|
|
6112
6591
|
try {
|
|
6113
|
-
normalized =
|
|
6592
|
+
normalized = fs14.realpathSync(cwd);
|
|
6114
6593
|
} catch {
|
|
6115
6594
|
normalized = cwd;
|
|
6116
6595
|
}
|
|
@@ -6120,16 +6599,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
6120
6599
|
}
|
|
6121
6600
|
function getShadowRepoDir(cwd) {
|
|
6122
6601
|
const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
6123
|
-
return
|
|
6602
|
+
return path16.join(os12.homedir(), ".node9", "snapshots", hash);
|
|
6124
6603
|
}
|
|
6125
6604
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
6126
6605
|
try {
|
|
6127
6606
|
const cutoff = Date.now() - 6e4;
|
|
6128
|
-
for (const f of
|
|
6607
|
+
for (const f of fs14.readdirSync(shadowDir)) {
|
|
6129
6608
|
if (f.startsWith("index_")) {
|
|
6130
|
-
const fp =
|
|
6609
|
+
const fp = path16.join(shadowDir, f);
|
|
6131
6610
|
try {
|
|
6132
|
-
if (
|
|
6611
|
+
if (fs14.statSync(fp).mtimeMs < cutoff) fs14.unlinkSync(fp);
|
|
6133
6612
|
} catch {
|
|
6134
6613
|
}
|
|
6135
6614
|
}
|
|
@@ -6141,7 +6620,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
6141
6620
|
const hardcoded = [".git", ".node9"];
|
|
6142
6621
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
6143
6622
|
try {
|
|
6144
|
-
|
|
6623
|
+
fs14.writeFileSync(path16.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
6145
6624
|
} catch {
|
|
6146
6625
|
}
|
|
6147
6626
|
}
|
|
@@ -6154,25 +6633,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6154
6633
|
timeout: 3e3
|
|
6155
6634
|
});
|
|
6156
6635
|
if (check.status === 0) {
|
|
6157
|
-
const ptPath =
|
|
6636
|
+
const ptPath = path16.join(shadowDir, "project-path.txt");
|
|
6158
6637
|
try {
|
|
6159
|
-
const stored =
|
|
6638
|
+
const stored = fs14.readFileSync(ptPath, "utf8").trim();
|
|
6160
6639
|
if (stored === normalizedCwd) return true;
|
|
6161
6640
|
if (process.env.NODE9_DEBUG === "1")
|
|
6162
6641
|
console.error(
|
|
6163
6642
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
6164
6643
|
);
|
|
6165
|
-
|
|
6644
|
+
fs14.rmSync(shadowDir, { recursive: true, force: true });
|
|
6166
6645
|
} catch {
|
|
6167
6646
|
try {
|
|
6168
|
-
|
|
6647
|
+
fs14.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
6169
6648
|
} catch {
|
|
6170
6649
|
}
|
|
6171
6650
|
return true;
|
|
6172
6651
|
}
|
|
6173
6652
|
}
|
|
6174
6653
|
try {
|
|
6175
|
-
|
|
6654
|
+
fs14.mkdirSync(shadowDir, { recursive: true });
|
|
6176
6655
|
} catch {
|
|
6177
6656
|
}
|
|
6178
6657
|
const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -6181,7 +6660,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6181
6660
|
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
6182
6661
|
return false;
|
|
6183
6662
|
}
|
|
6184
|
-
const configFile =
|
|
6663
|
+
const configFile = path16.join(shadowDir, "config");
|
|
6185
6664
|
spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
6186
6665
|
timeout: 3e3
|
|
6187
6666
|
});
|
|
@@ -6189,7 +6668,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
6189
6668
|
timeout: 3e3
|
|
6190
6669
|
});
|
|
6191
6670
|
try {
|
|
6192
|
-
|
|
6671
|
+
fs14.writeFileSync(path16.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
6193
6672
|
} catch {
|
|
6194
6673
|
}
|
|
6195
6674
|
return true;
|
|
@@ -6212,7 +6691,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6212
6691
|
const shadowDir = getShadowRepoDir(cwd);
|
|
6213
6692
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
6214
6693
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
6215
|
-
indexFile =
|
|
6694
|
+
indexFile = path16.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
6216
6695
|
const shadowEnv = {
|
|
6217
6696
|
...process.env,
|
|
6218
6697
|
GIT_DIR: shadowDir,
|
|
@@ -6241,7 +6720,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6241
6720
|
const shouldGc = stack.length % 5 === 0;
|
|
6242
6721
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
6243
6722
|
writeStack(stack);
|
|
6244
|
-
|
|
6723
|
+
fs14.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
6245
6724
|
if (shouldGc) {
|
|
6246
6725
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
6247
6726
|
}
|
|
@@ -6252,7 +6731,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
6252
6731
|
} finally {
|
|
6253
6732
|
if (indexFile) {
|
|
6254
6733
|
try {
|
|
6255
|
-
|
|
6734
|
+
fs14.unlinkSync(indexFile);
|
|
6256
6735
|
} catch {
|
|
6257
6736
|
}
|
|
6258
6737
|
}
|
|
@@ -6321,9 +6800,9 @@ function applyUndo(hash, cwd) {
|
|
|
6321
6800
|
timeout: GIT_TIMEOUT
|
|
6322
6801
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
6323
6802
|
for (const file of [...tracked, ...untracked]) {
|
|
6324
|
-
const fullPath =
|
|
6325
|
-
if (!snapshotFiles.has(file) &&
|
|
6326
|
-
|
|
6803
|
+
const fullPath = path16.join(dir, file);
|
|
6804
|
+
if (!snapshotFiles.has(file) && fs14.existsSync(fullPath)) {
|
|
6805
|
+
fs14.unlinkSync(fullPath);
|
|
6327
6806
|
}
|
|
6328
6807
|
}
|
|
6329
6808
|
return true;
|
|
@@ -6347,9 +6826,9 @@ function registerCheckCommand(program2) {
|
|
|
6347
6826
|
} catch (err) {
|
|
6348
6827
|
const tempConfig = getConfig();
|
|
6349
6828
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6350
|
-
const logPath =
|
|
6829
|
+
const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
|
|
6351
6830
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6352
|
-
|
|
6831
|
+
fs15.appendFileSync(
|
|
6353
6832
|
logPath,
|
|
6354
6833
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6355
6834
|
RAW: ${raw}
|
|
@@ -6360,10 +6839,10 @@ RAW: ${raw}
|
|
|
6360
6839
|
}
|
|
6361
6840
|
const config = getConfig(payload.cwd || void 0);
|
|
6362
6841
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6363
|
-
const logPath =
|
|
6364
|
-
if (!
|
|
6365
|
-
|
|
6366
|
-
|
|
6842
|
+
const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
|
|
6843
|
+
if (!fs15.existsSync(path17.dirname(logPath)))
|
|
6844
|
+
fs15.mkdirSync(path17.dirname(logPath), { recursive: true });
|
|
6845
|
+
fs15.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
6367
6846
|
`);
|
|
6368
6847
|
}
|
|
6369
6848
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6376,8 +6855,8 @@ RAW: ${raw}
|
|
|
6376
6855
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
6377
6856
|
let ttyFd = null;
|
|
6378
6857
|
try {
|
|
6379
|
-
ttyFd =
|
|
6380
|
-
const writeTty = (line) =>
|
|
6858
|
+
ttyFd = fs15.openSync("/dev/tty", "w");
|
|
6859
|
+
const writeTty = (line) => fs15.writeSync(ttyFd, line + "\n");
|
|
6381
6860
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
6382
6861
|
writeTty(chalk5.bgRed.white.bold(`
|
|
6383
6862
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -6393,7 +6872,7 @@ RAW: ${raw}
|
|
|
6393
6872
|
} finally {
|
|
6394
6873
|
if (ttyFd !== null)
|
|
6395
6874
|
try {
|
|
6396
|
-
|
|
6875
|
+
fs15.closeSync(ttyFd);
|
|
6397
6876
|
} catch {
|
|
6398
6877
|
}
|
|
6399
6878
|
}
|
|
@@ -6424,7 +6903,7 @@ RAW: ${raw}
|
|
|
6424
6903
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6425
6904
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6426
6905
|
}
|
|
6427
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
6906
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path17.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6428
6907
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
6429
6908
|
cwd: safeCwdForAuth
|
|
6430
6909
|
});
|
|
@@ -6436,12 +6915,12 @@ RAW: ${raw}
|
|
|
6436
6915
|
}
|
|
6437
6916
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
6438
6917
|
try {
|
|
6439
|
-
const tty =
|
|
6440
|
-
|
|
6918
|
+
const tty = fs15.openSync("/dev/tty", "w");
|
|
6919
|
+
fs15.writeSync(
|
|
6441
6920
|
tty,
|
|
6442
6921
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
6443
6922
|
);
|
|
6444
|
-
|
|
6923
|
+
fs15.closeSync(tty);
|
|
6445
6924
|
} catch {
|
|
6446
6925
|
}
|
|
6447
6926
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -6468,9 +6947,9 @@ RAW: ${raw}
|
|
|
6468
6947
|
});
|
|
6469
6948
|
} catch (err) {
|
|
6470
6949
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6471
|
-
const logPath =
|
|
6950
|
+
const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
|
|
6472
6951
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6473
|
-
|
|
6952
|
+
fs15.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6474
6953
|
`);
|
|
6475
6954
|
}
|
|
6476
6955
|
process.exit(0);
|
|
@@ -6507,9 +6986,9 @@ RAW: ${raw}
|
|
|
6507
6986
|
init_audit();
|
|
6508
6987
|
init_config();
|
|
6509
6988
|
init_policy();
|
|
6510
|
-
import
|
|
6511
|
-
import
|
|
6512
|
-
import
|
|
6989
|
+
import fs16 from "fs";
|
|
6990
|
+
import path18 from "path";
|
|
6991
|
+
import os14 from "os";
|
|
6513
6992
|
function sanitize3(value) {
|
|
6514
6993
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
6515
6994
|
}
|
|
@@ -6528,11 +7007,11 @@ function registerLogCommand(program2) {
|
|
|
6528
7007
|
decision: "allowed",
|
|
6529
7008
|
source: "post-hook"
|
|
6530
7009
|
};
|
|
6531
|
-
const logPath =
|
|
6532
|
-
if (!
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
7010
|
+
const logPath = path18.join(os14.homedir(), ".node9", "audit.log");
|
|
7011
|
+
if (!fs16.existsSync(path18.dirname(logPath)))
|
|
7012
|
+
fs16.mkdirSync(path18.dirname(logPath), { recursive: true });
|
|
7013
|
+
fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
7014
|
+
const safeCwd = typeof payload.cwd === "string" && path18.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6536
7015
|
const config = getConfig(safeCwd);
|
|
6537
7016
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6538
7017
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -6541,9 +7020,9 @@ function registerLogCommand(program2) {
|
|
|
6541
7020
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6542
7021
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
6543
7022
|
`);
|
|
6544
|
-
const debugPath =
|
|
7023
|
+
const debugPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
|
|
6545
7024
|
try {
|
|
6546
|
-
|
|
7025
|
+
fs16.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
6547
7026
|
`);
|
|
6548
7027
|
} catch {
|
|
6549
7028
|
}
|
|
@@ -6848,13 +7327,13 @@ function registerConfigShowCommand(program2) {
|
|
|
6848
7327
|
// src/cli/commands/doctor.ts
|
|
6849
7328
|
init_daemon();
|
|
6850
7329
|
import chalk7 from "chalk";
|
|
6851
|
-
import
|
|
6852
|
-
import
|
|
6853
|
-
import
|
|
7330
|
+
import fs17 from "fs";
|
|
7331
|
+
import path19 from "path";
|
|
7332
|
+
import os15 from "os";
|
|
6854
7333
|
import { execSync as execSync2 } from "child_process";
|
|
6855
7334
|
function registerDoctorCommand(program2, version2) {
|
|
6856
7335
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
6857
|
-
const homeDir2 =
|
|
7336
|
+
const homeDir2 = os15.homedir();
|
|
6858
7337
|
let failures = 0;
|
|
6859
7338
|
function pass(msg) {
|
|
6860
7339
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -6903,10 +7382,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6903
7382
|
);
|
|
6904
7383
|
}
|
|
6905
7384
|
section("Configuration");
|
|
6906
|
-
const globalConfigPath =
|
|
6907
|
-
if (
|
|
7385
|
+
const globalConfigPath = path19.join(homeDir2, ".node9", "config.json");
|
|
7386
|
+
if (fs17.existsSync(globalConfigPath)) {
|
|
6908
7387
|
try {
|
|
6909
|
-
JSON.parse(
|
|
7388
|
+
JSON.parse(fs17.readFileSync(globalConfigPath, "utf-8"));
|
|
6910
7389
|
pass("~/.node9/config.json found and valid");
|
|
6911
7390
|
} catch {
|
|
6912
7391
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -6914,10 +7393,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6914
7393
|
} else {
|
|
6915
7394
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
6916
7395
|
}
|
|
6917
|
-
const projectConfigPath =
|
|
6918
|
-
if (
|
|
7396
|
+
const projectConfigPath = path19.join(process.cwd(), "node9.config.json");
|
|
7397
|
+
if (fs17.existsSync(projectConfigPath)) {
|
|
6919
7398
|
try {
|
|
6920
|
-
JSON.parse(
|
|
7399
|
+
JSON.parse(fs17.readFileSync(projectConfigPath, "utf-8"));
|
|
6921
7400
|
pass("node9.config.json found and valid (project)");
|
|
6922
7401
|
} catch {
|
|
6923
7402
|
fail(
|
|
@@ -6926,8 +7405,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6926
7405
|
);
|
|
6927
7406
|
}
|
|
6928
7407
|
}
|
|
6929
|
-
const credsPath =
|
|
6930
|
-
if (
|
|
7408
|
+
const credsPath = path19.join(homeDir2, ".node9", "credentials.json");
|
|
7409
|
+
if (fs17.existsSync(credsPath)) {
|
|
6931
7410
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
6932
7411
|
} else {
|
|
6933
7412
|
warn(
|
|
@@ -6936,10 +7415,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6936
7415
|
);
|
|
6937
7416
|
}
|
|
6938
7417
|
section("Agent Hooks");
|
|
6939
|
-
const claudeSettingsPath =
|
|
6940
|
-
if (
|
|
7418
|
+
const claudeSettingsPath = path19.join(homeDir2, ".claude", "settings.json");
|
|
7419
|
+
if (fs17.existsSync(claudeSettingsPath)) {
|
|
6941
7420
|
try {
|
|
6942
|
-
const cs = JSON.parse(
|
|
7421
|
+
const cs = JSON.parse(fs17.readFileSync(claudeSettingsPath, "utf-8"));
|
|
6943
7422
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
6944
7423
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6945
7424
|
);
|
|
@@ -6955,10 +7434,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6955
7434
|
} else {
|
|
6956
7435
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
6957
7436
|
}
|
|
6958
|
-
const geminiSettingsPath =
|
|
6959
|
-
if (
|
|
7437
|
+
const geminiSettingsPath = path19.join(homeDir2, ".gemini", "settings.json");
|
|
7438
|
+
if (fs17.existsSync(geminiSettingsPath)) {
|
|
6960
7439
|
try {
|
|
6961
|
-
const gs = JSON.parse(
|
|
7440
|
+
const gs = JSON.parse(fs17.readFileSync(geminiSettingsPath, "utf-8"));
|
|
6962
7441
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
6963
7442
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
6964
7443
|
);
|
|
@@ -6974,10 +7453,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
6974
7453
|
} else {
|
|
6975
7454
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
6976
7455
|
}
|
|
6977
|
-
const cursorHooksPath =
|
|
6978
|
-
if (
|
|
7456
|
+
const cursorHooksPath = path19.join(homeDir2, ".cursor", "hooks.json");
|
|
7457
|
+
if (fs17.existsSync(cursorHooksPath)) {
|
|
6979
7458
|
try {
|
|
6980
|
-
const cur = JSON.parse(
|
|
7459
|
+
const cur = JSON.parse(fs17.readFileSync(cursorHooksPath, "utf-8"));
|
|
6981
7460
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
6982
7461
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
6983
7462
|
);
|
|
@@ -7015,9 +7494,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
7015
7494
|
|
|
7016
7495
|
// src/cli/commands/audit.ts
|
|
7017
7496
|
import chalk8 from "chalk";
|
|
7018
|
-
import
|
|
7019
|
-
import
|
|
7020
|
-
import
|
|
7497
|
+
import fs18 from "fs";
|
|
7498
|
+
import path20 from "path";
|
|
7499
|
+
import os16 from "os";
|
|
7021
7500
|
function formatRelativeTime(timestamp) {
|
|
7022
7501
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
7023
7502
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -7030,14 +7509,14 @@ function formatRelativeTime(timestamp) {
|
|
|
7030
7509
|
}
|
|
7031
7510
|
function registerAuditCommand(program2) {
|
|
7032
7511
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
7033
|
-
const logPath =
|
|
7034
|
-
if (!
|
|
7512
|
+
const logPath = path20.join(os16.homedir(), ".node9", "audit.log");
|
|
7513
|
+
if (!fs18.existsSync(logPath)) {
|
|
7035
7514
|
console.log(
|
|
7036
7515
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
7037
7516
|
);
|
|
7038
7517
|
return;
|
|
7039
7518
|
}
|
|
7040
|
-
const raw =
|
|
7519
|
+
const raw = fs18.readFileSync(logPath, "utf-8");
|
|
7041
7520
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
7042
7521
|
let entries = lines.flatMap((line) => {
|
|
7043
7522
|
try {
|
|
@@ -7157,9 +7636,9 @@ function registerDaemonCommand(program2) {
|
|
|
7157
7636
|
init_core();
|
|
7158
7637
|
init_daemon();
|
|
7159
7638
|
import chalk10 from "chalk";
|
|
7160
|
-
import
|
|
7161
|
-
import
|
|
7162
|
-
import
|
|
7639
|
+
import fs19 from "fs";
|
|
7640
|
+
import path21 from "path";
|
|
7641
|
+
import os17 from "os";
|
|
7163
7642
|
function registerStatusCommand(program2) {
|
|
7164
7643
|
program2.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
|
|
7165
7644
|
const creds = getCredentials();
|
|
@@ -7194,13 +7673,13 @@ function registerStatusCommand(program2) {
|
|
|
7194
7673
|
console.log("");
|
|
7195
7674
|
const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
|
|
7196
7675
|
console.log(` Mode: ${modeLabel}`);
|
|
7197
|
-
const projectConfig =
|
|
7198
|
-
const globalConfig =
|
|
7676
|
+
const projectConfig = path21.join(process.cwd(), "node9.config.json");
|
|
7677
|
+
const globalConfig = path21.join(os17.homedir(), ".node9", "config.json");
|
|
7199
7678
|
console.log(
|
|
7200
|
-
` Local: ${
|
|
7679
|
+
` Local: ${fs19.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
|
|
7201
7680
|
);
|
|
7202
7681
|
console.log(
|
|
7203
|
-
` Global: ${
|
|
7682
|
+
` Global: ${fs19.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
|
|
7204
7683
|
);
|
|
7205
7684
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
7206
7685
|
console.log(
|
|
@@ -7379,6 +7858,7 @@ import readline2 from "readline";
|
|
|
7379
7858
|
import chalk13 from "chalk";
|
|
7380
7859
|
import { spawn as spawn8 } from "child_process";
|
|
7381
7860
|
import { execa as execa2 } from "execa";
|
|
7861
|
+
init_provenance();
|
|
7382
7862
|
function sanitize4(value) {
|
|
7383
7863
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
7384
7864
|
}
|
|
@@ -7391,7 +7871,7 @@ function extractMcpServer(toolName) {
|
|
|
7391
7871
|
const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
7392
7872
|
return match?.[1];
|
|
7393
7873
|
}
|
|
7394
|
-
function
|
|
7874
|
+
function tokenize3(cmd) {
|
|
7395
7875
|
const tokens = [];
|
|
7396
7876
|
let current = "";
|
|
7397
7877
|
let inDouble = false;
|
|
@@ -7426,7 +7906,7 @@ function tokenize2(cmd) {
|
|
|
7426
7906
|
return tokens;
|
|
7427
7907
|
}
|
|
7428
7908
|
async function runMcpGateway(upstreamCommand) {
|
|
7429
|
-
const commandParts =
|
|
7909
|
+
const commandParts = tokenize3(upstreamCommand);
|
|
7430
7910
|
const cmd = commandParts[0];
|
|
7431
7911
|
const cmdArgs = commandParts.slice(1);
|
|
7432
7912
|
let executable = cmd;
|
|
@@ -7435,6 +7915,15 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
7435
7915
|
if (stdout) executable = stdout.trim();
|
|
7436
7916
|
} catch {
|
|
7437
7917
|
}
|
|
7918
|
+
const prov = checkProvenance(executable);
|
|
7919
|
+
if (prov.trustLevel === "suspect") {
|
|
7920
|
+
console.error(
|
|
7921
|
+
chalk13.red(
|
|
7922
|
+
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
7923
|
+
)
|
|
7924
|
+
);
|
|
7925
|
+
console.error(chalk13.red(" Verify this binary is trusted before proceeding."));
|
|
7926
|
+
}
|
|
7438
7927
|
console.error(chalk13.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
7439
7928
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
7440
7929
|
"NODE_OPTIONS",
|
|
@@ -7576,20 +8065,20 @@ function registerMcpGatewayCommand(program2) {
|
|
|
7576
8065
|
|
|
7577
8066
|
// src/cli.ts
|
|
7578
8067
|
var { version } = JSON.parse(
|
|
7579
|
-
|
|
8068
|
+
fs21.readFileSync(path23.join(__dirname, "../package.json"), "utf-8")
|
|
7580
8069
|
);
|
|
7581
8070
|
var program = new Command();
|
|
7582
8071
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
7583
8072
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
7584
8073
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
7585
|
-
const credPath =
|
|
7586
|
-
if (!
|
|
7587
|
-
|
|
8074
|
+
const credPath = path23.join(os19.homedir(), ".node9", "credentials.json");
|
|
8075
|
+
if (!fs21.existsSync(path23.dirname(credPath)))
|
|
8076
|
+
fs21.mkdirSync(path23.dirname(credPath), { recursive: true });
|
|
7588
8077
|
const profileName = options.profile || "default";
|
|
7589
8078
|
let existingCreds = {};
|
|
7590
8079
|
try {
|
|
7591
|
-
if (
|
|
7592
|
-
const raw = JSON.parse(
|
|
8080
|
+
if (fs21.existsSync(credPath)) {
|
|
8081
|
+
const raw = JSON.parse(fs21.readFileSync(credPath, "utf-8"));
|
|
7593
8082
|
if (raw.apiKey) {
|
|
7594
8083
|
existingCreds = {
|
|
7595
8084
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -7601,13 +8090,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7601
8090
|
} catch {
|
|
7602
8091
|
}
|
|
7603
8092
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
7604
|
-
|
|
8093
|
+
fs21.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
7605
8094
|
if (profileName === "default") {
|
|
7606
|
-
const configPath =
|
|
8095
|
+
const configPath = path23.join(os19.homedir(), ".node9", "config.json");
|
|
7607
8096
|
let config = {};
|
|
7608
8097
|
try {
|
|
7609
|
-
if (
|
|
7610
|
-
config = JSON.parse(
|
|
8098
|
+
if (fs21.existsSync(configPath))
|
|
8099
|
+
config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
7611
8100
|
} catch {
|
|
7612
8101
|
}
|
|
7613
8102
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -7622,9 +8111,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
7622
8111
|
approvers.cloud = false;
|
|
7623
8112
|
}
|
|
7624
8113
|
s.approvers = approvers;
|
|
7625
|
-
if (!
|
|
7626
|
-
|
|
7627
|
-
|
|
8114
|
+
if (!fs21.existsSync(path23.dirname(configPath)))
|
|
8115
|
+
fs21.mkdirSync(path23.dirname(configPath), { recursive: true });
|
|
8116
|
+
fs21.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
7628
8117
|
}
|
|
7629
8118
|
if (options.profile && profileName !== "default") {
|
|
7630
8119
|
console.log(chalk15.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -7710,15 +8199,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
7710
8199
|
}
|
|
7711
8200
|
}
|
|
7712
8201
|
if (options.purge) {
|
|
7713
|
-
const node9Dir =
|
|
7714
|
-
if (
|
|
8202
|
+
const node9Dir = path23.join(os19.homedir(), ".node9");
|
|
8203
|
+
if (fs21.existsSync(node9Dir)) {
|
|
7715
8204
|
const confirmed = await confirm3({
|
|
7716
8205
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
7717
8206
|
default: false
|
|
7718
8207
|
});
|
|
7719
8208
|
if (confirmed) {
|
|
7720
|
-
|
|
7721
|
-
if (
|
|
8209
|
+
fs21.rmSync(node9Dir, { recursive: true });
|
|
8210
|
+
if (fs21.existsSync(node9Dir)) {
|
|
7722
8211
|
console.error(
|
|
7723
8212
|
chalk15.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
7724
8213
|
);
|
|
@@ -7818,8 +8307,8 @@ program.command("explain").description(
|
|
|
7818
8307
|
console.log("");
|
|
7819
8308
|
});
|
|
7820
8309
|
program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
|
|
7821
|
-
const configPath =
|
|
7822
|
-
if (
|
|
8310
|
+
const configPath = path23.join(os19.homedir(), ".node9", "config.json");
|
|
8311
|
+
if (fs21.existsSync(configPath) && !options.force) {
|
|
7823
8312
|
console.log(chalk15.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
7824
8313
|
console.log(chalk15.gray(` Run with --force to overwrite.`));
|
|
7825
8314
|
return;
|
|
@@ -7833,9 +8322,9 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
7833
8322
|
mode: safeMode
|
|
7834
8323
|
}
|
|
7835
8324
|
};
|
|
7836
|
-
const dir =
|
|
7837
|
-
if (!
|
|
7838
|
-
|
|
8325
|
+
const dir = path23.dirname(configPath);
|
|
8326
|
+
if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
|
|
8327
|
+
fs21.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
7839
8328
|
console.log(chalk15.green(`\u2705 Global config created: ${configPath}`));
|
|
7840
8329
|
console.log(chalk15.cyan(` Mode set to: ${safeMode}`));
|
|
7841
8330
|
console.log(
|
|
@@ -7953,9 +8442,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
7953
8442
|
const isCheckHook = process.argv[2] === "check";
|
|
7954
8443
|
if (isCheckHook) {
|
|
7955
8444
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
7956
|
-
const logPath =
|
|
8445
|
+
const logPath = path23.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
7957
8446
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
7958
|
-
|
|
8447
|
+
fs21.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
7959
8448
|
`);
|
|
7960
8449
|
}
|
|
7961
8450
|
process.exit(0);
|