@node9/proxy 2.22.1 → 2.23.1

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.
@@ -741,9 +741,9 @@ function matchesPattern(text, patterns) {
741
741
  const withoutDotSlash = text.replace(/^\.\//, "");
742
742
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
743
743
  }
744
- function getNestedValue(obj, path15) {
744
+ function getNestedValue(obj, path17) {
745
745
  if (!obj || typeof obj !== "object") return null;
746
- const segments2 = path15.split(".");
746
+ const segments2 = path17.split(".");
747
747
  for (const seg of segments2) {
748
748
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
749
749
  }
@@ -945,6 +945,7 @@ function normalizeCommandForPolicyImpl(command) {
945
945
  const source = command.slice(s, e);
946
946
  if (resolved === source) continue;
947
947
  if (resolved === "" || /\s/.test(resolved)) continue;
948
+ if (/^[;&|()<>]+$/.test(resolved)) continue;
948
949
  rewrites.push([s, e, resolved]);
949
950
  const quoteOnly = source.replace(/['"]/g, "");
950
951
  if (quoteOnly !== source) quoteOnlyRewrites.push([s, e, quoteOnly]);
@@ -1138,23 +1139,147 @@ function extractLiteralArgs(callExpr) {
1138
1139
  const args = positionedArgs(words);
1139
1140
  return { name, flags, paths: args.map((a) => a.value), words, args };
1140
1141
  }
1142
+ function payloadKey(payload) {
1143
+ if (!assignmentTable || assignmentTable.size === 0) return payload;
1144
+ const bindings = [];
1145
+ for (const [name, rec] of assignmentTable) {
1146
+ if (rec.value !== null) bindings.push(`${name}=${rec.value}`);
1147
+ }
1148
+ return `${payload}\0${bindings.sort().join("")}`;
1149
+ }
1150
+ function claimPayload(payload) {
1151
+ if (!seenPayloads) return "ok";
1152
+ const key = payloadKey(payload);
1153
+ if (seenPayloads.has(key)) return "seen";
1154
+ if (payloadBudget <= 0) return "exhausted";
1155
+ payloadBudget--;
1156
+ seenPayloads.add(key);
1157
+ return "ok";
1158
+ }
1159
+ function recordTopLevelAssignments(f) {
1160
+ const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
1161
+ for (const stmt of stmts) recordTopLevelStmt(stmt);
1162
+ }
1163
+ function probeBinOp(src) {
1164
+ try {
1165
+ const cmd = syntax.NewParser().Parse(src, "probe")?.Stmts?.[0]?.Cmd;
1166
+ if (cmd && syntax.NodeType(cmd) === "BinaryCmd") return cmd.Op;
1167
+ } catch {
1168
+ }
1169
+ return null;
1170
+ }
1171
+ function recordTopLevelStmt(stmt) {
1172
+ if (!stmt || !stmt.Cmd) return false;
1173
+ const t = syntax.NodeType(stmt.Cmd);
1174
+ if (t === "BinaryCmd") {
1175
+ if (!AND_OR_OPS.has(stmt.Cmd.Op)) return false;
1176
+ if (!recordTopLevelStmt(stmt.Cmd.X)) return false;
1177
+ if (stmt.Cmd.Op === AND_OP) return recordTopLevelStmt(stmt.Cmd.Y);
1178
+ return true;
1179
+ }
1180
+ if (t !== "CallExpr" && t !== "DeclClause") return false;
1181
+ let at = 0;
1182
+ try {
1183
+ at = stmt.Pos().Offset();
1184
+ } catch {
1185
+ at = 0;
1186
+ }
1187
+ recordAssignments(stmt.Cmd, at);
1188
+ if (stmt.Negated) return false;
1189
+ if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
1190
+ return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
1191
+ }
1192
+ function recordAssignments(n, at) {
1193
+ if (!assignmentTable) return;
1194
+ const t = syntax.NodeType(n);
1195
+ let assigns = [];
1196
+ if (t === "CallExpr") {
1197
+ if ((n.Args || []).length > 0) return;
1198
+ assigns = n.Assigns || [];
1199
+ } else if (t === "DeclClause") {
1200
+ if (!ASSIGNMENT_HEADS.has(n.Variant?.Value ?? "")) return;
1201
+ assigns = (n.Args || []).filter((a) => syntax.NodeType(a) === "Assign");
1202
+ } else return;
1203
+ for (const a of assigns) {
1204
+ const name = a?.Name?.Value;
1205
+ if (!name || !a.Value || a.Append) continue;
1206
+ assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
1207
+ }
1208
+ }
1209
+ function resolveTrivialSubst(part) {
1210
+ if (syntax.NodeType(part) !== "CmdSubst") return void 0;
1211
+ const stmts = part.Stmts || [];
1212
+ if (stmts.length !== 1) return void 0;
1213
+ const st = stmts[0];
1214
+ if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
1215
+ const cmd = st.Cmd;
1216
+ if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return void 0;
1217
+ if ((cmd.Assigns || []).length > 0) return void 0;
1218
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
1219
+ if (words.length === 0 || words.some((w) => w === null)) return void 0;
1220
+ const head = baseWord(words[0]);
1221
+ const rest = words.slice(1);
1222
+ if (head === "echo") {
1223
+ let i = 0;
1224
+ while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
1225
+ return rest.slice(i).join(" ");
1226
+ }
1227
+ if (head === "printf") {
1228
+ if (rest.length !== 2) return void 0;
1229
+ if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
1230
+ return rest[1];
1231
+ }
1232
+ return void 0;
1233
+ }
1234
+ function recordedExpansion(name) {
1235
+ if (!assignmentTable || !name) return void 0;
1236
+ const rec = assignmentTable.get(name);
1237
+ if (rec === void 0 || rec.at >= currentStmtOffset) return void 0;
1238
+ return rec.value;
1239
+ }
1240
+ function isPlainParam(p) {
1241
+ if (syntax.NodeType(p) !== "ParamExp") return false;
1242
+ return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
1243
+ }
1244
+ function expandPlainParam(p) {
1245
+ if (!assignmentTable) return void 0;
1246
+ if (!isPlainParam(p)) return void 0;
1247
+ const recorded = recordedExpansion(p.Param?.Value);
1248
+ if (recorded !== void 0) return recorded;
1249
+ return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
1250
+ }
1141
1251
  function resolveWordLiteral(w) {
1142
1252
  const parts = w?.Parts || [];
1143
1253
  let s = "";
1144
1254
  for (const p of parts) {
1145
- const t = syntax.NodeType(p);
1146
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1147
- else if (t === "SglQuoted") s += p.Value ?? "";
1148
- else if (t === "DblQuoted") {
1149
- const inner = p.Parts || [];
1150
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1151
- s += inner.map((ip) => ip.Value ?? "").join("");
1152
- } else {
1153
- return null;
1154
- }
1255
+ const piece = resolvePart(p, false);
1256
+ if (piece === void 0 || piece === null) return null;
1257
+ s += piece;
1155
1258
  }
1156
1259
  return s;
1157
1260
  }
1261
+ function resolvePart(p, inQuotes) {
1262
+ const t = syntax.NodeType(p);
1263
+ if (t === "Lit") {
1264
+ const raw = p.Value ?? "";
1265
+ if (!inQuotes) return raw.replace(/\\(.)/g, "$1");
1266
+ return assignmentTable ? raw.replace(/\\([$`"\\])/g, "$1") : raw;
1267
+ }
1268
+ if (t === "SglQuoted") return p.Value ?? "";
1269
+ if (t === "ParamExp") return expandPlainParam(p);
1270
+ if (t === "CmdSubst") return assignmentTable ? resolveTrivialSubst(p) : void 0;
1271
+ if (t === "DblQuoted" && !inQuotes) {
1272
+ const inner = p.Parts || [];
1273
+ let out = "";
1274
+ for (const ip of inner) {
1275
+ const piece = resolvePart(ip, true);
1276
+ if (piece === void 0 || piece === null) return piece;
1277
+ out += piece;
1278
+ }
1279
+ return out;
1280
+ }
1281
+ return void 0;
1282
+ }
1158
1283
  function analyzeFsOperation(command) {
1159
1284
  const normalized = normalizeCommandForPolicy(command);
1160
1285
  if (!FS_OP_PRESCREEN_RE.test(normalized)) return null;
@@ -1190,17 +1315,35 @@ function analyzeFsOperationImpl(command, depth = 0) {
1190
1315
  const f = parseShared(command);
1191
1316
  if (f === PARSE_FAIL) return null;
1192
1317
  let result = null;
1318
+ const outerTable = assignmentTable;
1319
+ const outerOffset = currentStmtOffset;
1320
+ const outerSeen = seenPayloads;
1321
+ const outerBudget = payloadBudget;
1322
+ if (depth === 0) {
1323
+ seenPayloads = /* @__PURE__ */ new Set();
1324
+ payloadBudget = PAYLOAD_BUDGET;
1325
+ }
1326
+ assignmentTable = new Map(
1327
+ [...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
1328
+ );
1329
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
1193
1330
  try {
1331
+ recordTopLevelAssignments(f);
1194
1332
  syntax.Walk(f, (node) => {
1195
1333
  if (!node || result?.verdict === "block") return false;
1196
1334
  const n = node;
1197
1335
  const nodeType = syntax.NodeType(n);
1198
1336
  if (nodeType === "Stmt") {
1337
+ try {
1338
+ currentStmtOffset = n.Pos().Offset();
1339
+ } catch {
1340
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
1341
+ }
1199
1342
  result = stricter(result, jailedRedirectRead(n));
1200
1343
  return result?.verdict !== "block";
1201
1344
  }
1202
1345
  if (nodeType !== "CallExpr") return true;
1203
- const { name, flags, paths, words, args } = extractLiteralArgs(n);
1346
+ const { name, flags, paths, words } = extractLiteralArgs(n);
1204
1347
  if (!name) return true;
1205
1348
  if (name === "rm") {
1206
1349
  const flagStr = flags.join("").toLowerCase();
@@ -1229,9 +1372,30 @@ function analyzeFsOperationImpl(command, depth = 0) {
1229
1372
  }
1230
1373
  }
1231
1374
  }
1232
- if (depth < 1) {
1375
+ if (depth < 24 && name === "find") {
1376
+ for (const action of findActions(words, 0)) {
1377
+ const h = unwrapCommandHead(action);
1378
+ const inner = literalShellPayload(action.slice(h), baseWord(action[h]));
1379
+ if (inner === null) continue;
1380
+ const claim = claimPayload(inner);
1381
+ if (claim === "exhausted") {
1382
+ result = stricter(result, UNANALYSABLE_NESTING);
1383
+ continue;
1384
+ }
1385
+ if (claim === "seen") continue;
1386
+ const v = analyzeFsOperationImpl(inner, depth + 1);
1387
+ result = stricter(result, v);
1388
+ if (result?.verdict === "block") return false;
1389
+ }
1390
+ }
1391
+ if (depth < 24) {
1233
1392
  const payload = literalShellPayload(words, name);
1234
- if (payload !== null) {
1393
+ const claim = payload === null ? "seen" : claimPayload(payload);
1394
+ if (claim === "exhausted") {
1395
+ result = stricter(result, UNANALYSABLE_NESTING);
1396
+ return true;
1397
+ }
1398
+ if (payload !== null && claim === "ok") {
1235
1399
  const inner = analyzeFsOperationImpl(payload, depth + 1);
1236
1400
  if (inner) {
1237
1401
  result = inner;
@@ -1240,7 +1404,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1240
1404
  return true;
1241
1405
  }
1242
1406
  }
1243
- const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
1407
+ const readPaths = FS_READ_TOOLS.has(name) ? readerPaths(words, 0) : wrappedReadPaths(words, name);
1244
1408
  if (readPaths) {
1245
1409
  for (const p of readPaths) {
1246
1410
  result = stricter(result, matchSensitivePath2(p));
@@ -1255,6 +1419,11 @@ function analyzeFsOperationImpl(command, depth = 0) {
1255
1419
  return result;
1256
1420
  } catch {
1257
1421
  return null;
1422
+ } finally {
1423
+ assignmentTable = outerTable;
1424
+ currentStmtOffset = outerOffset;
1425
+ seenPayloads = outerSeen;
1426
+ if (depth === 0) payloadBudget = outerBudget;
1258
1427
  }
1259
1428
  }
1260
1429
  function stricter(a, b) {
@@ -1311,6 +1480,18 @@ function resolveCopyShape(words, h) {
1311
1480
  }
1312
1481
  return null;
1313
1482
  }
1483
+ function findAction(words, k) {
1484
+ const end = words.findIndex((w, i) => i > k && (w === ";" || w === "+"));
1485
+ return words.slice(k + 1, end < 0 ? words.length : end);
1486
+ }
1487
+ function findActions(words, h) {
1488
+ const out = [];
1489
+ for (let i = h + 1; i < words.length; i++) {
1490
+ const w = words[i];
1491
+ if (w !== null && FIND_EXEC_FLAGS.has(w)) out.push(findAction(words, i));
1492
+ }
1493
+ return out;
1494
+ }
1314
1495
  function findStartPoints(words, h) {
1315
1496
  const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
1316
1497
  if (k < 0) return { k, starts: [] };
@@ -1326,8 +1507,13 @@ function copySourcePaths(words) {
1326
1507
  if (fi >= 0) {
1327
1508
  const { k, starts } = findStartPoints(words, fi);
1328
1509
  if (k < 0) return [];
1329
- const action = unwrapCommandHead(words.slice(k + 1));
1330
- return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
1510
+ const out = [];
1511
+ for (const action of findActions(words, fi)) {
1512
+ const h2 = unwrapCommandHead(action);
1513
+ if (!resolveCopyShape(action, h2)) continue;
1514
+ out.push(...starts, ...copySourcePaths(action));
1515
+ }
1516
+ return out;
1331
1517
  }
1332
1518
  if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
1333
1519
  const r = resolveCopyShape(words, h);
@@ -1504,6 +1690,15 @@ function flagEffect(token, shape, known) {
1504
1690
  }
1505
1691
  return NONE;
1506
1692
  }
1693
+ function readerPaths(words, h) {
1694
+ const head = baseWord(words[h]);
1695
+ const from = h + 1;
1696
+ const flags = words.slice(from).filter((w) => w !== null && w.startsWith("-"));
1697
+ return [
1698
+ ...readTargets(head, positionedArgs(words, from), flags, words, from),
1699
+ ...flagOperandFiles(head, words, from)
1700
+ ];
1701
+ }
1507
1702
  function readTargets(verb, args, flags, words = [], from = 1) {
1508
1703
  const shape = PATTERN_VERBS[verb];
1509
1704
  if (!shape) return args.map((a) => a.value);
@@ -1585,18 +1780,21 @@ function flagOperandFiles(verb, words, from) {
1585
1780
  function wrappedReadPaths(words, name) {
1586
1781
  if (name === "find") {
1587
1782
  const { k, starts } = findStartPoints(words, 0);
1588
- return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
1783
+ if (k < 0) return null;
1784
+ const paths = [];
1785
+ let reads = false;
1786
+ for (const action of findActions(words, 0)) {
1787
+ const h2 = unwrapCommandHead(action);
1788
+ if (!isReaderWord(action[h2] ?? null)) continue;
1789
+ reads = true;
1790
+ paths.push(...readerPaths(action, h2));
1791
+ }
1792
+ return reads ? [...starts, ...paths] : paths;
1589
1793
  }
1590
1794
  if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1591
1795
  const h = unwrapCommandHead(words);
1592
1796
  if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
1593
- const head = baseWord(words[h]);
1594
- const rest = words.slice(h + 1);
1595
- const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
1596
- return [
1597
- ...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
1598
- ...flagOperandFiles(head, words, h + 1)
1599
- ];
1797
+ return readerPaths(words, h);
1600
1798
  }
1601
1799
  function literalShellPayload(words, name) {
1602
1800
  const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
@@ -1944,7 +2142,7 @@ function matchCanaryArgs(args, values) {
1944
2142
  return null;
1945
2143
  }
1946
2144
  }
1947
- var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, SOURCE_COMMANDS, TERMINAL_ESCAPE_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
2145
+ var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, SOURCE_COMMANDS, TERMINAL_ESCAPE_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
1948
2146
  var init_dist = __esm({
1949
2147
  "packages/policy-engine/dist/index.mjs"() {
1950
2148
  "use strict";
@@ -3154,6 +3352,22 @@ var init_dist = __esm({
3154
3352
  ]);
3155
3353
  WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
3156
3354
  FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
3355
+ HOME_VARIABLES = /* @__PURE__ */ new Set(["HOME", "USERPROFILE"]);
3356
+ assignmentTable = null;
3357
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
3358
+ PAYLOAD_BUDGET = 256;
3359
+ payloadBudget = 0;
3360
+ seenPayloads = null;
3361
+ UNANALYSABLE_NESTING = {
3362
+ ruleName: "review-unanalysable-nesting",
3363
+ verdict: "review",
3364
+ reason: "This command nests more wrapped shell payloads than the policy engine will unwrap, so some of what it runs was not read.",
3365
+ path: ""
3366
+ };
3367
+ ASSIGNMENT_HEADS = /* @__PURE__ */ new Set(["export", "declare", "local", "readonly", "typeset"]);
3368
+ AND_OP = probeBinOp("a && b");
3369
+ OR_OP = probeBinOp("a || b");
3370
+ AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
3157
3371
  FS_OP_CACHE_MAX = 5e3;
3158
3372
  fsOpCache = /* @__PURE__ */ new Map();
3159
3373
  REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
@@ -4170,8 +4384,8 @@ var init_daemon = __esm({
4170
4384
  import { z } from "zod";
4171
4385
  function formatIssues(issues) {
4172
4386
  const lines = issues.map((issue) => {
4173
- const path15 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
4174
- return ` \u2022 ${path15}: ${issue.message}`;
4387
+ const path17 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
4388
+ return ` \u2022 ${path17}: ${issue.message}`;
4175
4389
  });
4176
4390
  return `Invalid config:
4177
4391
  ${lines.join("\n")}`;
@@ -4184,9 +4398,9 @@ function prunePaths(root, paths) {
4184
4398
  const yi = y[y.length - 1];
4185
4399
  return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
4186
4400
  });
4187
- for (const path15 of ordered) {
4401
+ for (const path17 of ordered) {
4188
4402
  let cur = root;
4189
- for (const key of path15.slice(0, -1)) {
4403
+ for (const key of path17.slice(0, -1)) {
4190
4404
  if (cur === null || typeof cur !== "object") {
4191
4405
  cur = void 0;
4192
4406
  break;
@@ -4194,7 +4408,7 @@ function prunePaths(root, paths) {
4194
4408
  cur = cur[key];
4195
4409
  }
4196
4410
  if (cur === null || typeof cur !== "object") continue;
4197
- const last = path15[path15.length - 1];
4411
+ const last = path17[path17.length - 1];
4198
4412
  if (Array.isArray(cur)) {
4199
4413
  const i = Number(last);
4200
4414
  if (Number.isInteger(i) && i >= 0 && i < cur.length) {
@@ -4230,7 +4444,7 @@ function sanitizeConfig(raw) {
4230
4444
  return issue.keys.map((k) => [...at, k]);
4231
4445
  }
4232
4446
  return [at.slice(0, -level || void 0)];
4233
- }).filter((path15) => path15.length > 0);
4447
+ }).filter((path17) => path17.length > 0);
4234
4448
  if (paths.length === 0 || !prunePaths(working, paths)) break;
4235
4449
  }
4236
4450
  if (ConfigFileSchema.safeParse(working).success) break;
@@ -5233,13 +5447,13 @@ function getConfig(cwd) {
5233
5447
  }
5234
5448
  if (Array.isArray(mc.jailPaths)) {
5235
5449
  for (const jp of mc.jailPaths) {
5236
- const path15 = typeof jp?.path === "string" ? jp.path.trim() : "";
5237
- if (!path15) continue;
5450
+ const path17 = typeof jp?.path === "string" ? jp.path.trim() : "";
5451
+ if (!path17) continue;
5238
5452
  const verdict = jp?.verdict === "review" ? "review" : "block";
5239
- for (const r of pathRules(path15, verdict, "org-managed jail")) {
5453
+ for (const r of pathRules(path17, verdict, "org-managed jail")) {
5240
5454
  mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
5241
5455
  }
5242
- mergedPolicy.managedJailPaths.push({ path: path15, verdict });
5456
+ mergedPolicy.managedJailPaths.push({ path: path17, verdict });
5243
5457
  }
5244
5458
  }
5245
5459
  if (Array.isArray(mc.trustedHosts)) {
@@ -6693,19 +6907,129 @@ var init_scan_summary = __esm({
6693
6907
  }
6694
6908
  });
6695
6909
 
6696
- // src/utils/platform-shell.ts
6697
- var init_platform_shell = __esm({
6698
- "src/utils/platform-shell.ts"() {
6910
+ // src/agent-wiring.ts
6911
+ import fs10 from "fs";
6912
+ import path12 from "path";
6913
+ import * as yaml from "yaml";
6914
+ import { parse as parseToml } from "smol-toml";
6915
+ var exists, ck, lg, AGENT_SPECS;
6916
+ var init_agent_wiring = __esm({
6917
+ "src/agent-wiring.ts"() {
6699
6918
  "use strict";
6919
+ init_setup();
6920
+ exists = (p) => {
6921
+ try {
6922
+ return fs10.existsSync(p);
6923
+ } catch {
6924
+ return false;
6925
+ }
6926
+ };
6927
+ ck = (key) => ({ key, kind: "check" });
6928
+ lg = (key) => ({ key, kind: "log" });
6929
+ AGENT_SPECS = [
6930
+ {
6931
+ id: "claude",
6932
+ label: "Claude Code",
6933
+ setupCommand: "node9 agents add claude",
6934
+ hookFile: (h) => path12.join(h, ".claude", "settings.json"),
6935
+ hookFormat: "matcher",
6936
+ // UserPromptSubmit is prompt DLP. setup.ts has written it for Claude since
6937
+ // that shipped; the spec must name it too, or status/doctor never show the
6938
+ // row and heal (which repairs via setupAgent) has no signal it is missing.
6939
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
6940
+ mcpFile: (h) => path12.join(h, ".claude.json"),
6941
+ present: (h) => exists(path12.join(h, ".claude", "settings.json")) || exists(path12.join(h, ".claude.json"))
6942
+ },
6943
+ {
6944
+ id: "gemini",
6945
+ label: "Gemini CLI",
6946
+ setupCommand: "node9 agents add gemini",
6947
+ hookFile: (h) => path12.join(h, ".gemini", "settings.json"),
6948
+ hookFormat: "matcher",
6949
+ hookEvents: [ck("BeforeTool"), lg("AfterTool")],
6950
+ mcpFile: (h) => path12.join(h, ".gemini", "settings.json"),
6951
+ present: (h) => exists(path12.join(h, ".gemini", "settings.json"))
6952
+ },
6953
+ {
6954
+ id: "codex",
6955
+ label: "Codex",
6956
+ setupCommand: "node9 agents add codex",
6957
+ hookFile: (h) => path12.join(h, ".codex", "hooks.json"),
6958
+ hookFormat: "matcher",
6959
+ hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
6960
+ mcpFile: (h) => path12.join(h, ".codex", "config.toml"),
6961
+ mcpFormat: "toml",
6962
+ present: (h) => exists(path12.join(h, ".codex"))
6963
+ },
6964
+ {
6965
+ id: "antigravity",
6966
+ label: "Antigravity",
6967
+ setupCommand: "node9 agents add antigravity",
6968
+ hookFile: (h) => path12.join(h, ".gemini", "config", "hooks.json"),
6969
+ hookFormat: "matcher",
6970
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
6971
+ mcpFile: (h) => path12.join(h, ".gemini", "config", "mcp_config.json"),
6972
+ present: (h) => exists(path12.join(h, ".gemini", "config", "hooks.json")) || exists(path12.join(h, ".gemini", "antigravity-cli")) || exists(path12.join(h, ".gemini", "antigravity-ide"))
6973
+ },
6974
+ {
6975
+ id: "copilot",
6976
+ label: "GitHub Copilot",
6977
+ setupCommand: "node9 agents add copilot",
6978
+ hookFile: (h) => path12.join(h, ".copilot", "hooks", "node9.json"),
6979
+ hookFormat: "flat",
6980
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
6981
+ mcpFile: (h) => path12.join(h, ".copilot", "mcp-config.json"),
6982
+ present: (h) => exists(path12.join(h, ".copilot"))
6983
+ },
6984
+ {
6985
+ id: "cursor",
6986
+ label: "Cursor",
6987
+ setupCommand: "node9 agents add cursor",
6988
+ // MCP-only — no hook file (see note above).
6989
+ hookFormat: "flat",
6990
+ hookEvents: [],
6991
+ mcpFile: (h) => path12.join(h, ".cursor", "mcp.json"),
6992
+ present: (h) => exists(path12.join(h, ".cursor", "mcp.json"))
6993
+ },
6994
+ {
6995
+ id: "hermes",
6996
+ label: "Hermes Agent",
6997
+ setupCommand: "node9 agents add hermes",
6998
+ hookFile: (h) => hermesConfigPath(h),
6999
+ hookFormat: "yaml",
7000
+ hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
7001
+ labelPad: 14,
7002
+ // 'post_tool_call' is wider than the default
7003
+ present: (h) => exists(hermesConfigPath(h))
7004
+ },
7005
+ {
7006
+ // Plugin-shim agents — protected by a node9-authored plugin/extension file
7007
+ // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
7008
+ id: "opencode",
7009
+ label: "OpenCode",
7010
+ setupCommand: "node9 agents add opencode",
7011
+ hookFormat: "flat",
7012
+ hookEvents: [],
7013
+ shimFile: (h) => path12.join(opencodeConfigDir(h), "plugins", "node9.js"),
7014
+ present: (h) => exists(opencodeConfigDir(h)) || exists(path12.join(opencodeConfigDir(h), "plugins", "node9.js"))
7015
+ },
7016
+ {
7017
+ id: "pi",
7018
+ label: "Pi",
7019
+ setupCommand: "node9 agents add pi",
7020
+ hookFormat: "flat",
7021
+ hookEvents: [],
7022
+ shimFile: (h) => path12.join(h, ".pi", "agent", "extensions", "node9.js"),
7023
+ present: (h) => exists(path12.join(h, ".pi", "agent")) || exists(path12.join(h, ".pi", "agent", "extensions", "node9.js"))
7024
+ }
7025
+ ];
6700
7026
  }
6701
7027
  });
6702
7028
 
6703
- // src/codex-trust.ts
6704
- import { parse as parseToml } from "smol-toml";
6705
- var init_codex_trust = __esm({
6706
- "src/codex-trust.ts"() {
7029
+ // src/mcp-cmd.ts
7030
+ var init_mcp_cmd = __esm({
7031
+ "src/mcp-cmd.ts"() {
6707
7032
  "use strict";
6708
- init_platform_shell();
6709
7033
  }
6710
7034
  });
6711
7035
 
@@ -6716,15 +7040,52 @@ var init_mcp_pin = __esm({
6716
7040
  }
6717
7041
  });
6718
7042
 
7043
+ // src/mcp-wrap.ts
7044
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
7045
+ var init_mcp_wrap = __esm({
7046
+ "src/mcp-wrap.ts"() {
7047
+ "use strict";
7048
+ init_agent_wiring();
7049
+ init_mcp_cmd();
7050
+ init_mcp_pin();
7051
+ init_mcp_cmd();
7052
+ }
7053
+ });
7054
+
7055
+ // src/utils/platform-shell.ts
7056
+ var init_platform_shell = __esm({
7057
+ "src/utils/platform-shell.ts"() {
7058
+ "use strict";
7059
+ }
7060
+ });
7061
+
7062
+ // src/codex-app-server.ts
7063
+ import { parse as parseToml3 } from "smol-toml";
7064
+ var init_codex_app_server = __esm({
7065
+ "src/codex-app-server.ts"() {
7066
+ "use strict";
7067
+ }
7068
+ });
7069
+
7070
+ // src/codex-trust.ts
7071
+ import { parse as parseToml4 } from "smol-toml";
7072
+ var init_codex_trust = __esm({
7073
+ "src/codex-trust.ts"() {
7074
+ "use strict";
7075
+ init_platform_shell();
7076
+ init_codex_app_server();
7077
+ }
7078
+ });
7079
+
6719
7080
  // src/daemon/hook-baseline.ts
6720
- import path12 from "path";
7081
+ import path13 from "path";
6721
7082
  import os11 from "os";
6722
7083
  var BASELINE_FILE, NOTIFIED_FILE;
6723
7084
  var init_hook_baseline = __esm({
6724
7085
  "src/daemon/hook-baseline.ts"() {
6725
7086
  "use strict";
6726
- BASELINE_FILE = path12.join(os11.homedir(), ".node9", "hooks-baseline.json");
6727
- NOTIFIED_FILE = path12.join(os11.homedir(), ".node9", "hook-heal-notified.json");
7087
+ BASELINE_FILE = path13.join(os11.homedir(), ".node9", "hooks-baseline.json");
7088
+ NOTIFIED_FILE = path13.join(os11.homedir(), ".node9", "hook-heal-notified.json");
6728
7089
  }
6729
7090
  });
6730
7091
 
@@ -6750,19 +7111,38 @@ var init_atomic_write = __esm({
6750
7111
  });
6751
7112
 
6752
7113
  // src/setup.ts
7114
+ import path14 from "path";
7115
+ import os12 from "os";
6753
7116
  import chalk3 from "chalk";
6754
7117
  import { confirm as rawConfirm } from "@inquirer/prompts";
6755
- import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
6756
- import * as yaml from "yaml";
7118
+ import { parse as parseToml5, stringify as stringifyToml2 } from "smol-toml";
7119
+ import * as yaml2 from "yaml";
7120
+ function opencodeConfigDir(home = os12.homedir()) {
7121
+ const xdg = process.env.XDG_CONFIG_HOME;
7122
+ const base = xdg && path14.isAbsolute(xdg) ? xdg : path14.join(home, ".config");
7123
+ return path14.join(base, "opencode");
7124
+ }
7125
+ function hermesHomeDir(homeDir = os12.homedir()) {
7126
+ const env = process.env.HERMES_HOME?.trim();
7127
+ if (env && path14.isAbsolute(env)) return env;
7128
+ return path14.join(homeDir, ".hermes");
7129
+ }
7130
+ function hermesConfigPath(homeDir = os12.homedir()) {
7131
+ return path14.join(hermesHomeDir(homeDir), HERMES_CONFIG_FILENAME);
7132
+ }
7133
+ var HERMES_CONFIG_FILENAME;
6757
7134
  var init_setup = __esm({
6758
7135
  "src/setup.ts"() {
6759
7136
  "use strict";
7137
+ init_mcp_wrap();
6760
7138
  init_codex_trust();
7139
+ init_codex_app_server();
6761
7140
  init_mcp_pin();
6762
7141
  init_hook_baseline();
6763
7142
  init_setup_opencode_shim();
6764
7143
  init_setup_pi_shim();
6765
7144
  init_atomic_write();
7145
+ HERMES_CONFIG_FILENAME = "config.yaml";
6766
7146
  }
6767
7147
  });
6768
7148
 
@@ -6793,9 +7173,9 @@ var init_scan_history = __esm({
6793
7173
 
6794
7174
  // src/cli/commands/scan.ts
6795
7175
  import chalk5 from "chalk";
6796
- import fs10 from "fs";
6797
- import path13 from "path";
6798
- import os12 from "os";
7176
+ import fs11 from "fs";
7177
+ import path15 from "path";
7178
+ import os13 from "os";
6799
7179
  import stringWidth2 from "string-width";
6800
7180
  function claudeModelPrice2(model) {
6801
7181
  const t = pricingFor(model);
@@ -7003,7 +7383,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
7003
7383
  const session = { sessionId, costUSD: 0, toolCalls: 0 };
7004
7384
  let raw;
7005
7385
  try {
7006
- raw = fs10.readFileSync(path13.join(projPath, file), "utf-8");
7386
+ raw = fs11.readFileSync(path15.join(projPath, file), "utf-8");
7007
7387
  } catch {
7008
7388
  return;
7009
7389
  }
@@ -7066,7 +7446,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
7066
7446
  if (block.type !== "tool_result") continue;
7067
7447
  const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
7068
7448
  if (filePath) {
7069
- const ext = path13.extname(filePath).toLowerCase();
7449
+ const ext = path15.extname(filePath).toLowerCase();
7070
7450
  if (CODE_EXTENSIONS.has(ext)) continue;
7071
7451
  }
7072
7452
  const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
@@ -7136,7 +7516,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
7136
7516
  const rawCmd = String(input.command ?? "").trimStart();
7137
7517
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
7138
7518
  const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
7139
- const inputFileExt = inputFilePath ? path13.extname(inputFilePath).toLowerCase() : "";
7519
+ const inputFileExt = inputFilePath ? path15.extname(inputFilePath).toLowerCase() : "";
7140
7520
  const canaryInInput = recordCanaries(
7141
7521
  input,
7142
7522
  toolName,
@@ -7245,13 +7625,13 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
7245
7625
  result.perSession.push(session);
7246
7626
  }
7247
7627
  async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, canaryVals, onProgress, onLine) {
7248
- const projPath = path13.join(projectsDir, proj);
7628
+ const projPath = path15.join(projectsDir, proj);
7249
7629
  try {
7250
- if (!fs10.statSync(projPath).isDirectory()) return;
7630
+ if (!fs11.statSync(projPath).isDirectory()) return;
7251
7631
  } catch {
7252
7632
  return;
7253
7633
  }
7254
- const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os12.homedir(), "~")).slice(
7634
+ const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os13.homedir(), "~")).slice(
7255
7635
  0,
7256
7636
  40
7257
7637
  );
@@ -7298,12 +7678,12 @@ function emptyClaudeScan() {
7298
7678
  };
7299
7679
  }
7300
7680
  async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
7301
- const projectsDir = path13.join(os12.homedir(), ".claude", "projects");
7681
+ const projectsDir = path15.join(os13.homedir(), ".claude", "projects");
7302
7682
  const result = emptyClaudeScan();
7303
- if (!fs10.existsSync(projectsDir)) return result;
7683
+ if (!fs11.existsSync(projectsDir)) return result;
7304
7684
  let projDirs;
7305
7685
  try {
7306
- projDirs = fs10.readdirSync(projectsDir);
7686
+ projDirs = fs11.readdirSync(projectsDir);
7307
7687
  } catch {
7308
7688
  return result;
7309
7689
  }
@@ -7327,7 +7707,7 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
7327
7707
  }
7328
7708
  function scanGeminiHistory(startDate, onProgress, onLine) {
7329
7709
  const canaryVals = safeCanaryScanValues();
7330
- const tmpDir = path13.join(os12.homedir(), ".gemini", "tmp");
7710
+ const tmpDir = path15.join(os13.homedir(), ".gemini", "tmp");
7331
7711
  const result = {
7332
7712
  filesScanned: 0,
7333
7713
  sessions: 0,
@@ -7344,33 +7724,33 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
7344
7724
  perSession: []
7345
7725
  };
7346
7726
  const dedup = emptyScanDedup();
7347
- if (!fs10.existsSync(tmpDir)) return result;
7727
+ if (!fs11.existsSync(tmpDir)) return result;
7348
7728
  let slugDirs;
7349
7729
  try {
7350
- slugDirs = fs10.readdirSync(tmpDir);
7730
+ slugDirs = fs11.readdirSync(tmpDir);
7351
7731
  } catch {
7352
7732
  return result;
7353
7733
  }
7354
7734
  const ruleSources = buildRuleSources();
7355
7735
  for (const slug2 of slugDirs) {
7356
- const slugPath = path13.join(tmpDir, slug2);
7736
+ const slugPath = path15.join(tmpDir, slug2);
7357
7737
  try {
7358
- if (!fs10.statSync(slugPath).isDirectory()) continue;
7738
+ if (!fs11.statSync(slugPath).isDirectory()) continue;
7359
7739
  } catch {
7360
7740
  continue;
7361
7741
  }
7362
7742
  let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
7363
7743
  try {
7364
7744
  projLabel = stripTerminalEscapes(
7365
- fs10.readFileSync(path13.join(slugPath, ".project_root"), "utf-8").trim()
7366
- ).replace(os12.homedir(), "~").slice(0, 40);
7745
+ fs11.readFileSync(path15.join(slugPath, ".project_root"), "utf-8").trim()
7746
+ ).replace(os13.homedir(), "~").slice(0, 40);
7367
7747
  } catch {
7368
7748
  }
7369
- const chatsDir = path13.join(slugPath, "chats");
7370
- if (!fs10.existsSync(chatsDir)) continue;
7749
+ const chatsDir = path15.join(slugPath, "chats");
7750
+ if (!fs11.existsSync(chatsDir)) continue;
7371
7751
  let chatFiles;
7372
7752
  try {
7373
- chatFiles = fs10.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
7753
+ chatFiles = fs11.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
7374
7754
  } catch {
7375
7755
  continue;
7376
7756
  }
@@ -7383,7 +7763,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
7383
7763
  onProgress?.(result.filesScanned);
7384
7764
  let raw;
7385
7765
  try {
7386
- raw = fs10.readFileSync(path13.join(chatsDir, chatFile), "utf-8");
7766
+ raw = fs11.readFileSync(path15.join(chatsDir, chatFile), "utf-8");
7387
7767
  } catch {
7388
7768
  continue;
7389
7769
  }
@@ -7602,7 +7982,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
7602
7982
  onProgress?.(result.filesScanned);
7603
7983
  let lines;
7604
7984
  try {
7605
- lines = fs10.readFileSync(filePath, "utf-8").split("\n");
7985
+ lines = fs11.readFileSync(filePath, "utf-8").split("\n");
7606
7986
  } catch {
7607
7987
  continue;
7608
7988
  }
@@ -7625,7 +8005,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
7625
8005
  sessionId = String(payload["id"] ?? filePath);
7626
8006
  startTime = String(payload["timestamp"] ?? "");
7627
8007
  const cwd = String(payload["cwd"] ?? "");
7628
- projLabel = stripTerminalEscapes(cwd.replace(os12.homedir(), "~")).slice(0, 40);
8008
+ projLabel = stripTerminalEscapes(cwd.replace(os13.homedir(), "~")).slice(0, 40);
7629
8009
  continue;
7630
8010
  }
7631
8011
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
@@ -7870,23 +8250,23 @@ var init_scan = __esm({
7870
8250
  });
7871
8251
 
7872
8252
  // src/tui/dashboard/data.ts
7873
- import fs11 from "fs";
7874
- import os13 from "os";
7875
- import path14 from "path";
8253
+ import fs12 from "fs";
8254
+ import os14 from "os";
8255
+ import path16 from "path";
7876
8256
  import http from "http";
7877
8257
  function auditLogPath() {
7878
- return path14.join(os13.homedir(), ".node9", "audit.log");
8258
+ return path16.join(os14.homedir(), ".node9", "audit.log");
7879
8259
  }
7880
8260
  function readAuditEntriesAsync(chunkSize = 1e3, customPath) {
7881
8261
  return new Promise((resolve) => {
7882
8262
  const p = customPath ?? auditLogPath();
7883
- if (!fs11.existsSync(p)) {
8263
+ if (!fs12.existsSync(p)) {
7884
8264
  resolve([]);
7885
8265
  return;
7886
8266
  }
7887
8267
  let raw;
7888
8268
  try {
7889
- raw = fs11.readFileSync(p, "utf8");
8269
+ raw = fs12.readFileSync(p, "utf8");
7890
8270
  } catch {
7891
8271
  resolve([]);
7892
8272
  return;
@@ -8018,13 +8398,13 @@ function loadBlast() {
8018
8398
  }
8019
8399
  }
8020
8400
  function shortenPath(p) {
8021
- const home = os13.homedir();
8401
+ const home = os14.homedir();
8022
8402
  return p.startsWith(home) ? p.replace(home, "~") : p;
8023
8403
  }
8024
8404
  async function loadReportAuditAsync(period) {
8025
- const claudeProjectsDir = path14.join(os13.homedir(), ".claude", "projects");
8405
+ const claudeProjectsDir = path16.join(os14.homedir(), ".claude", "projects");
8026
8406
  const codexSessionsDir2 = codexSessionsDir();
8027
- const geminiTmpDir = path14.join(os13.homedir(), ".gemini", "tmp");
8407
+ const geminiTmpDir = path16.join(os14.homedir(), ".gemini", "tmp");
8028
8408
  const { start, end } = getDateRange(period, /* @__PURE__ */ new Date());
8029
8409
  const entries = await readAuditEntriesAsync();
8030
8410
  void ensurePricingLoaded();
@@ -9130,8 +9510,8 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-run
9130
9510
  function TopToolsProjects({ audit }) {
9131
9511
  const data = audit?.data;
9132
9512
  const tools = data ? [...data.toolMap.entries()].sort(([, a], [, b]) => b.calls - a.calls).slice(0, ROW_LIMIT) : [];
9133
- const projects = data ? [...data.cost.byProject.entries()].map(([path15, r]) => ({
9134
- name: basenameOf(path15),
9513
+ const projects = data ? [...data.cost.byProject.entries()].map(([path17, r]) => ({
9514
+ name: basenameOf(path17),
9135
9515
  cost: r.cost,
9136
9516
  tokens: r.inputTokens + r.outputTokens
9137
9517
  })).sort((a, b) => b.cost - a.cost).slice(0, ROW_LIMIT) : [];
@@ -9568,8 +9948,8 @@ function pickTopLoopFile(loops) {
9568
9948
  map.set(k, (map.get(k) ?? 0) + (l.count ?? 0));
9569
9949
  }
9570
9950
  if (map.size === 0) return void 0;
9571
- const [path15, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
9572
- return { path: path15, count };
9951
+ const [path17, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
9952
+ return { path: path17, count };
9573
9953
  }
9574
9954
  var EMPTY_FILTERED_SCAN;
9575
9955
  var init_derive = __esm({