@node9/proxy 1.3.0 → 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/index.mjs CHANGED
@@ -155,8 +155,8 @@ function sanitizeConfig(raw) {
155
155
  }
156
156
  }
157
157
  const lines = result.error.issues.map((issue) => {
158
- const path10 = issue.path.length > 0 ? issue.path.join(".") : "root";
159
- return ` \u2022 ${path10}: ${issue.message}`;
158
+ const path13 = issue.path.length > 0 ? issue.path.join(".") : "root";
159
+ return ` \u2022 ${path13}: ${issue.message}`;
160
160
  });
161
161
  return {
162
162
  sanitized,
@@ -886,6 +886,7 @@ function getCompiledRegex(pattern, flags = "") {
886
886
  }
887
887
 
888
888
  // src/policy/index.ts
889
+ import path7 from "path";
889
890
  import pm from "picomatch";
890
891
  import { parse } from "sh-syntax";
891
892
 
@@ -1031,8 +1032,416 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1031
1032
  return null;
1032
1033
  }
1033
1034
 
1035
+ // src/utils/provenance.ts
1036
+ import fs5 from "fs";
1037
+ import path5 from "path";
1038
+ import os4 from "os";
1039
+ var SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
1040
+ var MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
1041
+ var USER_PREFIXES = [
1042
+ path5.join(os4.homedir(), "bin"),
1043
+ path5.join(os4.homedir(), ".local", "bin"),
1044
+ path5.join(os4.homedir(), ".cargo", "bin"),
1045
+ path5.join(os4.homedir(), ".npm-global", "bin"),
1046
+ path5.join(os4.homedir(), ".volta", "bin")
1047
+ ];
1048
+ var SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
1049
+ function findInPath(cmd) {
1050
+ if (path5.posix.isAbsolute(cmd)) return cmd;
1051
+ const pathEnv = process.env.PATH ?? "";
1052
+ for (const dir of pathEnv.split(path5.delimiter)) {
1053
+ if (!dir) continue;
1054
+ const full = path5.join(dir, cmd);
1055
+ try {
1056
+ fs5.accessSync(full, fs5.constants.X_OK);
1057
+ return full;
1058
+ } catch {
1059
+ }
1060
+ }
1061
+ return null;
1062
+ }
1063
+ function _classifyPath(resolved, cwd) {
1064
+ if (cwd && resolved.startsWith(cwd + "/")) {
1065
+ return { trustLevel: "user", reason: "binary in project directory" };
1066
+ }
1067
+ const osTmp = os4.tmpdir();
1068
+ const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
1069
+ if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1070
+ return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
1071
+ }
1072
+ if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1073
+ return { trustLevel: "system", reason: "" };
1074
+ }
1075
+ if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1076
+ return { trustLevel: "managed", reason: "" };
1077
+ }
1078
+ if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1079
+ return { trustLevel: "user", reason: "" };
1080
+ }
1081
+ return { trustLevel: "unknown", reason: "binary in unrecognized location" };
1082
+ }
1083
+ function checkProvenance(cmd, cwd) {
1084
+ const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
1085
+ if (path5.posix.isAbsolute(bare)) {
1086
+ const early = _classifyPath(bare, cwd);
1087
+ if (early.trustLevel === "suspect") {
1088
+ return { resolvedPath: bare, ...early };
1089
+ }
1090
+ }
1091
+ let resolved;
1092
+ try {
1093
+ const found = findInPath(bare);
1094
+ if (!found) {
1095
+ return {
1096
+ resolvedPath: cmd,
1097
+ trustLevel: "unknown",
1098
+ reason: "binary not found in PATH"
1099
+ };
1100
+ }
1101
+ resolved = fs5.realpathSync(found);
1102
+ } catch {
1103
+ return {
1104
+ resolvedPath: cmd,
1105
+ trustLevel: "unknown",
1106
+ reason: "binary not found in PATH"
1107
+ };
1108
+ }
1109
+ try {
1110
+ const stat = fs5.statSync(resolved);
1111
+ if (stat.mode & 2) {
1112
+ return {
1113
+ resolvedPath: resolved,
1114
+ trustLevel: "suspect",
1115
+ reason: "binary is world-writable"
1116
+ };
1117
+ }
1118
+ } catch {
1119
+ return {
1120
+ resolvedPath: resolved,
1121
+ trustLevel: "unknown",
1122
+ reason: "could not stat binary"
1123
+ };
1124
+ }
1125
+ const classify = _classifyPath(resolved, cwd);
1126
+ return { resolvedPath: resolved, ...classify };
1127
+ }
1128
+
1129
+ // src/policy/pipe-chain.ts
1130
+ var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
1131
+ "cat",
1132
+ "head",
1133
+ "tail",
1134
+ "grep",
1135
+ "awk",
1136
+ "sed",
1137
+ "cut",
1138
+ "sort",
1139
+ "tee",
1140
+ "less",
1141
+ "more",
1142
+ "strings",
1143
+ "xxd"
1144
+ ]);
1145
+ var SINK_COMMANDS = /* @__PURE__ */ new Set([
1146
+ "curl",
1147
+ "wget",
1148
+ "nc",
1149
+ "ncat",
1150
+ "netcat",
1151
+ "ssh",
1152
+ "scp",
1153
+ "rsync",
1154
+ "socat",
1155
+ "ftp",
1156
+ "sftp",
1157
+ "telnet"
1158
+ ]);
1159
+ var OBFUSCATORS = /* @__PURE__ */ new Set([
1160
+ "base64",
1161
+ "gzip",
1162
+ "gunzip",
1163
+ "bzip2",
1164
+ "xz",
1165
+ "zstd",
1166
+ "openssl",
1167
+ "gpg",
1168
+ "python",
1169
+ "python3",
1170
+ "perl",
1171
+ "ruby",
1172
+ "node"
1173
+ ]);
1174
+ var SENSITIVE_PATTERNS = [
1175
+ /(?:^|\/)\.env(?:\.|$)/i,
1176
+ // .env, .env.local, .env.production
1177
+ /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
1178
+ // SSH private keys
1179
+ /\.pem$|\.key$|\.p12$|\.pfx$/i,
1180
+ // certificate files
1181
+ /(?:^|\/)\.ssh\//i,
1182
+ // ~/.ssh/ directory
1183
+ /(?:^|\/)\.aws\/credentials/i,
1184
+ // AWS credentials
1185
+ /(?:^|\/)\.netrc$/i,
1186
+ // netrc (stores HTTP credentials)
1187
+ /(?:^|\/)(passwd|shadow|sudoers)$/i,
1188
+ // /etc/passwd, /etc/shadow
1189
+ /(?:^|\/)credentials(?:\.json)?$/i
1190
+ // generic credentials files
1191
+ ];
1192
+ function isSensitivePath(p) {
1193
+ return SENSITIVE_PATTERNS.some((re) => re.test(p));
1194
+ }
1195
+ function splitOnPipe(cmd) {
1196
+ const segments = [];
1197
+ let current = "";
1198
+ let inSingle = false;
1199
+ let inDouble = false;
1200
+ for (let i = 0; i < cmd.length; i++) {
1201
+ const ch = cmd[i];
1202
+ if (ch === "'" && !inDouble) {
1203
+ inSingle = !inSingle;
1204
+ current += ch;
1205
+ } else if (ch === '"' && !inSingle) {
1206
+ inDouble = !inDouble;
1207
+ current += ch;
1208
+ } else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
1209
+ segments.push(current.trim());
1210
+ current = "";
1211
+ } else {
1212
+ current += ch;
1213
+ }
1214
+ }
1215
+ if (current.trim()) segments.push(current.trim());
1216
+ return segments.filter(Boolean);
1217
+ }
1218
+ function positionalTokens(segment) {
1219
+ return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1220
+ }
1221
+ function analyzePipeChain(command) {
1222
+ const segments = splitOnPipe(command);
1223
+ if (segments.length < 2) {
1224
+ return {
1225
+ isPipeline: false,
1226
+ hasSensitiveSource: false,
1227
+ hasExternalSink: false,
1228
+ hasObfuscation: false,
1229
+ sourceFiles: [],
1230
+ sinkTargets: [],
1231
+ risk: "none"
1232
+ };
1233
+ }
1234
+ const sourceFiles = [];
1235
+ const sinkTargets = [];
1236
+ let hasSensitiveSource = false;
1237
+ let hasExternalSink = false;
1238
+ let hasObfuscation = false;
1239
+ for (const segment of segments) {
1240
+ const tokens = segment.split(/\s+/).filter(Boolean);
1241
+ if (tokens.length === 0) continue;
1242
+ const binary = tokens[0].toLowerCase();
1243
+ const args = positionalTokens(segment);
1244
+ if (SOURCE_COMMANDS.has(binary)) {
1245
+ sourceFiles.push(...args);
1246
+ if (args.some(isSensitivePath)) hasSensitiveSource = true;
1247
+ }
1248
+ if (OBFUSCATORS.has(binary)) hasObfuscation = true;
1249
+ if (SINK_COMMANDS.has(binary)) {
1250
+ const targets = args.filter(
1251
+ (a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
1252
+ );
1253
+ sinkTargets.push(...targets);
1254
+ if (targets.length > 0) hasExternalSink = true;
1255
+ }
1256
+ }
1257
+ const fullCmd = command.toLowerCase();
1258
+ if (!hasSensitiveSource) {
1259
+ const redirMatch = fullCmd.match(/<\s*(\S+)/);
1260
+ if (redirMatch && isSensitivePath(redirMatch[1])) {
1261
+ hasSensitiveSource = true;
1262
+ sourceFiles.push(redirMatch[1]);
1263
+ }
1264
+ }
1265
+ const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
1266
+ return {
1267
+ isPipeline: true,
1268
+ hasSensitiveSource,
1269
+ hasExternalSink,
1270
+ hasObfuscation,
1271
+ sourceFiles,
1272
+ sinkTargets,
1273
+ risk
1274
+ };
1275
+ }
1276
+
1277
+ // src/policy/flag-tables.ts
1278
+ import path6 from "path";
1279
+ var FLAGS_WITH_VALUES = {
1280
+ curl: /* @__PURE__ */ new Set([
1281
+ "-H",
1282
+ "--header",
1283
+ "-A",
1284
+ "--user-agent",
1285
+ "-e",
1286
+ "--referer",
1287
+ "-x",
1288
+ "--proxy",
1289
+ "-u",
1290
+ "--user",
1291
+ "-d",
1292
+ "--data",
1293
+ "--data-raw",
1294
+ "--data-binary",
1295
+ "-o",
1296
+ "--output",
1297
+ "-F",
1298
+ "--form",
1299
+ "--connect-to",
1300
+ "--resolve",
1301
+ "--cacert",
1302
+ "--cert",
1303
+ "--key",
1304
+ "-m",
1305
+ "--max-time"
1306
+ ]),
1307
+ wget: /* @__PURE__ */ new Set([
1308
+ "-O",
1309
+ "--output-document",
1310
+ "-P",
1311
+ "--directory-prefix",
1312
+ "-U",
1313
+ "--user-agent",
1314
+ "-e",
1315
+ "--execute",
1316
+ "--proxy",
1317
+ "--ca-certificate"
1318
+ ]),
1319
+ nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
1320
+ ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
1321
+ netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
1322
+ ssh: /* @__PURE__ */ new Set([
1323
+ "-i",
1324
+ "-l",
1325
+ "-p",
1326
+ "-o",
1327
+ "-E",
1328
+ "-F",
1329
+ "-J",
1330
+ "-L",
1331
+ "-R",
1332
+ "-W",
1333
+ "-b",
1334
+ "-c",
1335
+ "-D",
1336
+ "-e",
1337
+ "-I",
1338
+ "-S"
1339
+ ]),
1340
+ scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
1341
+ rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
1342
+ socat: /* @__PURE__ */ new Set([])
1343
+ // socat uses address syntax, not flags — no value-flags
1344
+ };
1345
+ function extractPositionalArgs(tokens, binary) {
1346
+ const binaryName = path6.basename(binary).replace(/\.exe$/i, "");
1347
+ const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
1348
+ const positional = [];
1349
+ let skipNext = false;
1350
+ for (const token of tokens) {
1351
+ if (skipNext) {
1352
+ skipNext = false;
1353
+ continue;
1354
+ }
1355
+ if (token.startsWith("--") && token.includes("=")) continue;
1356
+ if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
1357
+ skipNext = true;
1358
+ continue;
1359
+ }
1360
+ if (token.startsWith("--") && flagsWithValues.has(token)) {
1361
+ skipNext = true;
1362
+ continue;
1363
+ }
1364
+ const shortFlag = token.slice(0, 2);
1365
+ if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
1366
+ if (token.startsWith("-")) continue;
1367
+ if (token.startsWith("@")) continue;
1368
+ positional.push(token);
1369
+ }
1370
+ return positional;
1371
+ }
1372
+ function extractNetworkTargets(tokens, binary) {
1373
+ return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
1374
+ const colonIdx = t.indexOf(":");
1375
+ if (colonIdx === -1) return t;
1376
+ const afterColon = t.slice(colonIdx + 1);
1377
+ if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
1378
+ return t;
1379
+ }).filter(Boolean);
1380
+ }
1381
+
1382
+ // src/policy/ssh-parser.ts
1383
+ function tokenize(cmd) {
1384
+ const tokens = [];
1385
+ let current = "";
1386
+ let inSingle = false;
1387
+ let inDouble = false;
1388
+ for (const ch of cmd) {
1389
+ if (ch === "'" && !inDouble) {
1390
+ inSingle = !inSingle;
1391
+ } else if (ch === '"' && !inSingle) {
1392
+ inDouble = !inDouble;
1393
+ } else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
1394
+ if (current) {
1395
+ tokens.push(current);
1396
+ current = "";
1397
+ }
1398
+ } else {
1399
+ current += ch;
1400
+ }
1401
+ }
1402
+ if (current) tokens.push(current);
1403
+ return tokens;
1404
+ }
1405
+ function parseHost(raw) {
1406
+ return raw.split("@").pop().split(":")[0];
1407
+ }
1408
+ function extractAllSshHosts(tokens) {
1409
+ const hosts = /* @__PURE__ */ new Set();
1410
+ for (let i = 0; i < tokens.length; i++) {
1411
+ const t = tokens[i];
1412
+ if (t === "-J" && tokens[i + 1]) {
1413
+ for (const hop of tokens[++i].split(",")) {
1414
+ const h = parseHost(hop);
1415
+ if (h) hosts.add(h);
1416
+ }
1417
+ continue;
1418
+ }
1419
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
1420
+ const val = tokens[++i].split("=").slice(1).join("=");
1421
+ for (const hop of val.split(",")) {
1422
+ const h = parseHost(hop);
1423
+ if (h) hosts.add(h);
1424
+ }
1425
+ continue;
1426
+ }
1427
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
1428
+ const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
1429
+ const subTokens = tokenize(raw);
1430
+ const binary = subTokens[0] ?? "";
1431
+ extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
1432
+ extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
1433
+ continue;
1434
+ }
1435
+ if (!t.startsWith("-")) {
1436
+ const h = parseHost(t);
1437
+ if (h) hosts.add(h);
1438
+ }
1439
+ }
1440
+ return [...hosts].filter(Boolean);
1441
+ }
1442
+
1034
1443
  // src/policy/index.ts
1035
- function tokenize(toolName) {
1444
+ function tokenize2(toolName) {
1036
1445
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
1037
1446
  }
1038
1447
  function matchesPattern(text, patterns) {
@@ -1045,9 +1454,9 @@ function matchesPattern(text, patterns) {
1045
1454
  const withoutDotSlash = text.replace(/^\.\//, "");
1046
1455
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1047
1456
  }
1048
- function getNestedValue(obj, path10) {
1457
+ function getNestedValue(obj, path13) {
1049
1458
  if (!obj || typeof obj !== "object") return null;
1050
- return path10.split(".").reduce((prev, curr) => prev?.[curr], obj);
1459
+ return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1051
1460
  }
1052
1461
  function evaluateSmartConditions(args, rule) {
1053
1462
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -1171,7 +1580,7 @@ async function analyzeShellCommand(command) {
1171
1580
  }
1172
1581
  return { actions, paths, allTokens };
1173
1582
  }
1174
- async function evaluatePolicy(toolName, args, agent) {
1583
+ async function evaluatePolicy(toolName, args, agent, cwd) {
1175
1584
  const config = getConfig();
1176
1585
  if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
1177
1586
  if (config.policy.smartRules.length > 0) {
@@ -1201,11 +1610,55 @@ async function evaluatePolicy(toolName, args, agent) {
1201
1610
  if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
1202
1611
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1203
1612
  }
1613
+ const pipeAnalysis = analyzePipeChain(shellCommand);
1614
+ if (pipeAnalysis.isPipeline) {
1615
+ if (pipeAnalysis.risk === "critical") {
1616
+ return {
1617
+ decision: "block",
1618
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1619
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1620
+ tier: 3
1621
+ };
1622
+ }
1623
+ if (pipeAnalysis.risk === "high") {
1624
+ return {
1625
+ decision: "review",
1626
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1627
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1628
+ tier: 3
1629
+ };
1630
+ }
1631
+ }
1632
+ const firstToken = analyzed.actions[0] ?? "";
1633
+ if (["ssh", "scp", "rsync"].includes(firstToken)) {
1634
+ const rawTokens = shellCommand.trim().split(/\s+/);
1635
+ const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1636
+ allTokens.push(...sshHosts);
1637
+ }
1638
+ if (firstToken && path7.posix.isAbsolute(firstToken)) {
1639
+ const prov = checkProvenance(firstToken, cwd);
1640
+ if (prov.trustLevel === "suspect") {
1641
+ return {
1642
+ decision: config.settings.mode === "strict" ? "block" : "review",
1643
+ blockedByLabel: "Node9: Suspect Binary",
1644
+ reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
1645
+ tier: 3
1646
+ };
1647
+ }
1648
+ if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
1649
+ return {
1650
+ decision: "review",
1651
+ blockedByLabel: "Node9: Unknown Binary (strict mode)",
1652
+ reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
1653
+ tier: 3
1654
+ };
1655
+ }
1656
+ }
1204
1657
  if (isSqlTool(toolName, config.policy.toolInspection)) {
1205
1658
  allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
1206
1659
  }
1207
1660
  } else {
1208
- allTokens = tokenize(toolName);
1661
+ allTokens = tokenize2(toolName);
1209
1662
  if (args && typeof args === "object") {
1210
1663
  const flattenedArgs = JSON.stringify(args).toLowerCase();
1211
1664
  const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
@@ -1283,18 +1736,18 @@ function isIgnoredTool(toolName) {
1283
1736
  }
1284
1737
 
1285
1738
  // src/auth/state.ts
1286
- import fs5 from "fs";
1287
- import path5 from "path";
1288
- import os4 from "os";
1289
- var PAUSED_FILE = path5.join(os4.homedir(), ".node9", "PAUSED");
1290
- var TRUST_FILE = path5.join(os4.homedir(), ".node9", "trust.json");
1739
+ import fs6 from "fs";
1740
+ import path8 from "path";
1741
+ import os5 from "os";
1742
+ var PAUSED_FILE = path8.join(os5.homedir(), ".node9", "PAUSED");
1743
+ var TRUST_FILE = path8.join(os5.homedir(), ".node9", "trust.json");
1291
1744
  function checkPause() {
1292
1745
  try {
1293
- if (!fs5.existsSync(PAUSED_FILE)) return { paused: false };
1294
- const state = JSON.parse(fs5.readFileSync(PAUSED_FILE, "utf-8"));
1746
+ if (!fs6.existsSync(PAUSED_FILE)) return { paused: false };
1747
+ const state = JSON.parse(fs6.readFileSync(PAUSED_FILE, "utf-8"));
1295
1748
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1296
1749
  try {
1297
- fs5.unlinkSync(PAUSED_FILE);
1750
+ fs6.unlinkSync(PAUSED_FILE);
1298
1751
  } catch {
1299
1752
  }
1300
1753
  return { paused: false };
@@ -1305,20 +1758,20 @@ function checkPause() {
1305
1758
  }
1306
1759
  }
1307
1760
  function atomicWriteSync(filePath, data, options) {
1308
- const dir = path5.dirname(filePath);
1309
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
1310
- const tmpPath = `${filePath}.${os4.hostname()}.${process.pid}.tmp`;
1311
- fs5.writeFileSync(tmpPath, data, options);
1312
- fs5.renameSync(tmpPath, filePath);
1761
+ const dir = path8.dirname(filePath);
1762
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
1763
+ const tmpPath = `${filePath}.${os5.hostname()}.${process.pid}.tmp`;
1764
+ fs6.writeFileSync(tmpPath, data, options);
1765
+ fs6.renameSync(tmpPath, filePath);
1313
1766
  }
1314
1767
  function getActiveTrustSession(toolName) {
1315
1768
  try {
1316
- if (!fs5.existsSync(TRUST_FILE)) return false;
1317
- const trust = JSON.parse(fs5.readFileSync(TRUST_FILE, "utf-8"));
1769
+ if (!fs6.existsSync(TRUST_FILE)) return false;
1770
+ const trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
1318
1771
  const now = Date.now();
1319
1772
  const active = trust.entries.filter((e) => e.expiry > now);
1320
1773
  if (active.length !== trust.entries.length) {
1321
- fs5.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1774
+ fs6.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1322
1775
  }
1323
1776
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1324
1777
  } catch {
@@ -1329,8 +1782,8 @@ function writeTrustSession(toolName, durationMs) {
1329
1782
  try {
1330
1783
  let trust = { entries: [] };
1331
1784
  try {
1332
- if (fs5.existsSync(TRUST_FILE)) {
1333
- trust = JSON.parse(fs5.readFileSync(TRUST_FILE, "utf-8"));
1785
+ if (fs6.existsSync(TRUST_FILE)) {
1786
+ trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
1334
1787
  }
1335
1788
  } catch {
1336
1789
  }
@@ -1346,9 +1799,9 @@ function writeTrustSession(toolName, durationMs) {
1346
1799
  }
1347
1800
  function getPersistentDecision(toolName) {
1348
1801
  try {
1349
- const file = path5.join(os4.homedir(), ".node9", "decisions.json");
1350
- if (!fs5.existsSync(file)) return null;
1351
- const decisions = JSON.parse(fs5.readFileSync(file, "utf-8"));
1802
+ const file = path8.join(os5.homedir(), ".node9", "decisions.json");
1803
+ if (!fs6.existsSync(file)) return null;
1804
+ const decisions = JSON.parse(fs6.readFileSync(file, "utf-8"));
1352
1805
  const d = decisions[toolName];
1353
1806
  if (d === "allow" || d === "deny") return d;
1354
1807
  } catch {
@@ -1357,17 +1810,17 @@ function getPersistentDecision(toolName) {
1357
1810
  }
1358
1811
 
1359
1812
  // src/auth/daemon.ts
1360
- import fs6 from "fs";
1361
- import path6 from "path";
1362
- import os5 from "os";
1813
+ import fs7 from "fs";
1814
+ import path9 from "path";
1815
+ import os6 from "os";
1363
1816
  import { spawnSync } from "child_process";
1364
1817
  var DAEMON_PORT = 7391;
1365
1818
  var DAEMON_HOST = "127.0.0.1";
1366
1819
  function getInternalToken() {
1367
1820
  try {
1368
- const pidFile = path6.join(os5.homedir(), ".node9", "daemon.pid");
1369
- if (!fs6.existsSync(pidFile)) return null;
1370
- const data = JSON.parse(fs6.readFileSync(pidFile, "utf-8"));
1821
+ const pidFile = path9.join(os6.homedir(), ".node9", "daemon.pid");
1822
+ if (!fs7.existsSync(pidFile)) return null;
1823
+ const data = JSON.parse(fs7.readFileSync(pidFile, "utf-8"));
1371
1824
  process.kill(data.pid, 0);
1372
1825
  return data.internalToken ?? null;
1373
1826
  } catch {
@@ -1375,10 +1828,10 @@ function getInternalToken() {
1375
1828
  }
1376
1829
  }
1377
1830
  function isDaemonRunning() {
1378
- const pidFile = path6.join(os5.homedir(), ".node9", "daemon.pid");
1379
- if (fs6.existsSync(pidFile)) {
1831
+ const pidFile = path9.join(os6.homedir(), ".node9", "daemon.pid");
1832
+ if (fs7.existsSync(pidFile)) {
1380
1833
  try {
1381
- const { pid, port } = JSON.parse(fs6.readFileSync(pidFile, "utf-8"));
1834
+ const { pid, port } = JSON.parse(fs7.readFileSync(pidFile, "utf-8"));
1382
1835
  if (port !== DAEMON_PORT) return false;
1383
1836
  process.kill(pid, 0);
1384
1837
  return true;
@@ -1474,16 +1927,16 @@ async function resolveViaDaemon(id, decision, internalToken) {
1474
1927
 
1475
1928
  // src/auth/orchestrator.ts
1476
1929
  import net from "net";
1477
- import path9 from "path";
1478
- import os7 from "os";
1930
+ import path12 from "path";
1931
+ import os8 from "os";
1479
1932
  import { randomUUID } from "crypto";
1480
1933
 
1481
1934
  // src/ui/native.ts
1482
1935
  import { spawn } from "child_process";
1483
- import path8 from "path";
1936
+ import path11 from "path";
1484
1937
 
1485
1938
  // src/context-sniper.ts
1486
- import path7 from "path";
1939
+ import path10 from "path";
1487
1940
  function smartTruncate(str, maxLen = 500) {
1488
1941
  if (str.length <= maxLen) return str;
1489
1942
  const edge = Math.floor(maxLen / 2) - 3;
@@ -1551,7 +2004,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
1551
2004
  intent = "EDIT";
1552
2005
  if (obj.file_path) {
1553
2006
  editFilePath = String(obj.file_path);
1554
- editFileName = path7.basename(editFilePath);
2007
+ editFileName = path10.basename(editFilePath);
1555
2008
  }
1556
2009
  const result = extractContext(String(obj.new_string), matchedWord);
1557
2010
  contextSnippet = result.snippet;
@@ -1606,7 +2059,7 @@ function formatArgs(args, matchedField, matchedWord) {
1606
2059
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
1607
2060
  const obj = parsed;
1608
2061
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
1609
- const file = obj.file_path ? path8.basename(String(obj.file_path)) : "file";
2062
+ const file = obj.file_path ? path11.basename(String(obj.file_path)) : "file";
1610
2063
  const oldPreview = smartTruncate(String(obj.old_string), 120);
1611
2064
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
1612
2065
  return {
@@ -1781,8 +2234,8 @@ end run`;
1781
2234
  }
1782
2235
 
1783
2236
  // src/auth/cloud.ts
1784
- import fs7 from "fs";
1785
- import os6 from "os";
2237
+ import fs8 from "fs";
2238
+ import os7 from "os";
1786
2239
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
1787
2240
  return fetch(`${creds.apiUrl}/audit`, {
1788
2241
  method: "POST",
@@ -1794,9 +2247,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
1794
2247
  context: {
1795
2248
  agent: meta?.agent,
1796
2249
  mcpServer: meta?.mcpServer,
1797
- hostname: os6.hostname(),
2250
+ hostname: os7.hostname(),
1798
2251
  cwd: process.cwd(),
1799
- platform: os6.platform()
2252
+ platform: os7.platform()
1800
2253
  }
1801
2254
  }),
1802
2255
  signal: AbortSignal.timeout(5e3)
@@ -1817,9 +2270,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
1817
2270
  context: {
1818
2271
  agent: meta?.agent,
1819
2272
  mcpServer: meta?.mcpServer,
1820
- hostname: os6.hostname(),
2273
+ hostname: os7.hostname(),
1821
2274
  cwd: process.cwd(),
1822
- platform: os6.platform()
2275
+ platform: os7.platform()
1823
2276
  },
1824
2277
  ...riskMetadata && { riskMetadata }
1825
2278
  }),
@@ -1875,14 +2328,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
1875
2328
  });
1876
2329
  clearTimeout(timer);
1877
2330
  if (!res.ok) {
1878
- fs7.appendFileSync(
2331
+ fs8.appendFileSync(
1879
2332
  HOOK_DEBUG_LOG,
1880
2333
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
1881
2334
  `
1882
2335
  );
1883
2336
  }
1884
2337
  } catch (err) {
1885
- fs7.appendFileSync(
2338
+ fs8.appendFileSync(
1886
2339
  HOOK_DEBUG_LOG,
1887
2340
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
1888
2341
  `
@@ -1891,7 +2344,7 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
1891
2344
  }
1892
2345
 
1893
2346
  // src/auth/orchestrator.ts
1894
- var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path9.join(os7.tmpdir(), "node9-activity.sock");
2347
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os8.tmpdir(), "node9-activity.sock");
1895
2348
  function notifyActivity(data) {
1896
2349
  return new Promise((resolve) => {
1897
2350
  try {
@@ -1973,7 +2426,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
1973
2426
  }
1974
2427
  if (config.settings.mode === "audit") {
1975
2428
  if (!isIgnoredTool(toolName)) {
1976
- const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2429
+ const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
1977
2430
  if (policyResult.decision === "review") {
1978
2431
  appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
1979
2432
  if (approvers.cloud && creds?.apiKey) {