@node9/proxy 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1157,9 +1157,7 @@ function unwrapCommandHead(words) {
1157
1157
  while (i < words.length) {
1158
1158
  const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
1159
1159
  if (head === "find") {
1160
- const x = words.findIndex(
1161
- (w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
1162
- );
1160
+ const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
1163
1161
  if (x < 0) break;
1164
1162
  i = x + 1;
1165
1163
  continue;
@@ -1181,7 +1179,11 @@ function unwrapCommandHead(words) {
1181
1179
  if (t.startsWith("-")) {
1182
1180
  i++;
1183
1181
  const nxt = words[i];
1184
- if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()))
1182
+ if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
1183
+ // `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
1184
+ // swallowed and the jail needed a looser fallback whose cost was a
1185
+ // false positive on `sudo echo cat X`.
1186
+ !FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
1185
1187
  i++;
1186
1188
  continue;
1187
1189
  }
@@ -1300,36 +1302,32 @@ function isProtectedHomePath(rawPath) {
1300
1302
  }
1301
1303
  return true;
1302
1304
  }
1303
- function extractLiteralArgs(callExpr) {
1304
- const args = callExpr.Args || [];
1305
- if (args.length === 0) return { name: "", flags: [], paths: [] };
1306
- const litFromWord = (w) => {
1307
- const parts = w?.Parts || [];
1308
- let s = "";
1309
- for (const p of parts) {
1310
- const t = syntax.NodeType(p);
1311
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1312
- else if (t === "SglQuoted") s += p.Value ?? "";
1313
- else if (t === "DblQuoted") {
1314
- const inner = p.Parts || [];
1315
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1316
- s += inner.map((ip) => ip.Value ?? "").join("");
1317
- } else {
1318
- return null;
1319
- }
1305
+ function positionedArgs(words, from = 1, to = words.length) {
1306
+ const out = [];
1307
+ let afterFlag = null;
1308
+ for (let i = from; i < to; i++) {
1309
+ const v = words[i];
1310
+ if (v === null) {
1311
+ afterFlag = null;
1312
+ continue;
1320
1313
  }
1321
- return s;
1322
- };
1323
- const name = (litFromWord(args[0]) || "").toLowerCase();
1324
- const flags = [];
1325
- const paths = [];
1326
- for (let i = 1; i < args.length; i++) {
1327
- const v = litFromWord(args[i]);
1328
- if (v === null) continue;
1329
- if (v.startsWith("-")) flags.push(v);
1330
- else paths.push(v);
1314
+ if (v.startsWith("-")) {
1315
+ afterFlag = v;
1316
+ continue;
1317
+ }
1318
+ out.push({ value: v, index: out.length, argv: i, afterFlag });
1319
+ afterFlag = null;
1331
1320
  }
1332
- return { name, flags, paths };
1321
+ return out;
1322
+ }
1323
+ function extractLiteralArgs(callExpr) {
1324
+ const rawArgs = callExpr.Args || [];
1325
+ if (rawArgs.length === 0) return { name: "", flags: [], paths: [], words: [], args: [] };
1326
+ const words = rawArgs.map((a) => resolveWordLiteral(a));
1327
+ const name = baseWord(words[0]);
1328
+ const flags = words.slice(1).filter((w) => w !== null && w.startsWith("-"));
1329
+ const args = positionedArgs(words);
1330
+ return { name, flags, paths: args.map((a) => a.value), words, args };
1333
1331
  }
1334
1332
  function resolveWordLiteral(w) {
1335
1333
  const parts = w?.Parts || [];
@@ -1587,16 +1585,21 @@ function isRmCreatedInCommandCleanup(command) {
1587
1585
  }
1588
1586
  return sawRm && ok2;
1589
1587
  }
1590
- function analyzeFsOperationImpl(command) {
1588
+ function analyzeFsOperationImpl(command, depth = 0) {
1591
1589
  const f = parseShared(command);
1592
1590
  if (f === PARSE_FAIL) return null;
1593
1591
  let result = null;
1594
1592
  try {
1595
1593
  syntax.Walk(f, (node) => {
1596
- if (!node || result) return false;
1594
+ if (!node || result?.verdict === "block") return false;
1597
1595
  const n = node;
1598
- if (syntax.NodeType(n) !== "CallExpr") return true;
1599
- const { name, flags, paths } = extractLiteralArgs(n);
1596
+ const nodeType = syntax.NodeType(n);
1597
+ if (nodeType === "Stmt") {
1598
+ result = stricter(result, jailedRedirectRead(n));
1599
+ return result?.verdict !== "block";
1600
+ }
1601
+ if (nodeType !== "CallExpr") return true;
1602
+ const { name, flags, paths, words } = extractLiteralArgs(n);
1600
1603
  if (!name) return true;
1601
1604
  if (name === "rm") {
1602
1605
  const flagStr = flags.join("").toLowerCase();
@@ -1625,21 +1628,27 @@ function analyzeFsOperationImpl(command) {
1625
1628
  }
1626
1629
  }
1627
1630
  }
1628
- if (FS_READ_TOOLS.has(name)) {
1629
- for (const p of paths) {
1630
- for (const sp of SENSITIVE_PATH_RULES) {
1631
- if (sp.match(p)) {
1632
- result = {
1633
- ruleName: sp.rule,
1634
- verdict: sp.verdict ?? "block",
1635
- reason: sp.reason,
1636
- path: p
1637
- };
1638
- return false;
1639
- }
1631
+ if (depth < 1) {
1632
+ const payload = literalShellPayload(words, name);
1633
+ if (payload !== null) {
1634
+ const inner = analyzeFsOperationImpl(payload, depth + 1);
1635
+ if (inner) {
1636
+ result = inner;
1637
+ return false;
1640
1638
  }
1639
+ return true;
1640
+ }
1641
+ }
1642
+ const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
1643
+ if (readPaths) {
1644
+ for (const p of readPaths) {
1645
+ result = stricter(result, matchSensitivePath2(p));
1646
+ if (result?.verdict === "block") return false;
1641
1647
  }
1642
1648
  }
1649
+ for (const p of copySourcePaths(words)) {
1650
+ result = stricter(result, copyVerdictOf(matchSensitivePath2(p)));
1651
+ }
1643
1652
  return true;
1644
1653
  });
1645
1654
  return result;
@@ -1647,6 +1656,183 @@ function analyzeFsOperationImpl(command) {
1647
1656
  return null;
1648
1657
  }
1649
1658
  }
1659
+ function stricter(a, b) {
1660
+ if (!a) return b;
1661
+ if (!b) return a;
1662
+ return b.verdict === "block" && a.verdict !== "block" ? b : a;
1663
+ }
1664
+ function flagInfo(w) {
1665
+ if (w.startsWith("--")) {
1666
+ const eq = w.indexOf("=");
1667
+ return eq < 0 ? { letter: null, long: w, attached: null } : { letter: null, long: w.slice(0, eq), attached: w.slice(eq + 1) };
1668
+ }
1669
+ const m = /^-([a-zA-Z]+)(.*)$/.exec(w);
1670
+ if (!m) return { letter: null, long: null, attached: null };
1671
+ return { letter: m[1][m[1].length - 1], long: null, attached: m[2] === "" ? null : m[2] };
1672
+ }
1673
+ function flagIs(w, names) {
1674
+ if (w === null) return false;
1675
+ const f = flagInfo(w);
1676
+ return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
1677
+ }
1678
+ function operandOf(a, names) {
1679
+ if (!names || a.afterFlag === null) return false;
1680
+ return flagIs(a.afterFlag, names) && flagInfo(a.afterFlag).attached === null;
1681
+ }
1682
+ function resolveCopyShape(words, h) {
1683
+ const verb = baseWord(words[h]);
1684
+ if (!verb) return null;
1685
+ const direct = COPY_VERBS[verb];
1686
+ if (direct) return { shape: direct, last: h };
1687
+ const slots = positionedArgs(words, h + 1);
1688
+ for (let i = 0; i < slots.length; i++) {
1689
+ for (let n = 3; n >= 1; n--) {
1690
+ const part = slots.slice(i, i + n);
1691
+ if (part.length < n) continue;
1692
+ const key = [verb, ...part.map((a) => a.value.toLowerCase())].join(" ");
1693
+ const shape = COPY_VERBS[key];
1694
+ if (shape) return { shape, last: part[n - 1].argv };
1695
+ }
1696
+ if (slots[i].afterFlag === null) return null;
1697
+ }
1698
+ return null;
1699
+ }
1700
+ function findStartPoints(words, h) {
1701
+ const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
1702
+ if (k < 0) return { k, starts: [] };
1703
+ const firstPredicate = words.findIndex(
1704
+ (w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
1705
+ );
1706
+ const end = firstPredicate > h ? firstPredicate : k;
1707
+ return { k, starts: positionalAfter(words, h + 1, end) };
1708
+ }
1709
+ function copySourcePaths(words) {
1710
+ const h = unwrapCommandHead(words);
1711
+ const fi = words.findIndex((w, i) => i <= h && baseWord(w) === "find");
1712
+ if (fi >= 0) {
1713
+ const { k, starts } = findStartPoints(words, fi);
1714
+ if (k < 0) return [];
1715
+ const action = unwrapCommandHead(words.slice(k + 1));
1716
+ return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
1717
+ }
1718
+ if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
1719
+ const r = resolveCopyShape(words, h);
1720
+ if (!r) return [];
1721
+ const { shape, last } = r;
1722
+ const args = positionedArgs(words, last + 1);
1723
+ const tail = words.slice(last + 1);
1724
+ const skipped = (a) => operandOf(a, shape.skipFlags);
1725
+ const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && flagIs(w, ["t", "--target-directory"]));
1726
+ const targetOperand = (a) => targetDir && operandOf(a, ["t", "--target-directory"]);
1727
+ const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
1728
+ const dynamicDest = lastOperand === null;
1729
+ const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
1730
+ if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
1731
+ return [];
1732
+ let src;
1733
+ switch (shape.source) {
1734
+ case "all":
1735
+ src = args;
1736
+ break;
1737
+ case "first":
1738
+ src = targetDir ? args : args.slice(0, 1);
1739
+ break;
1740
+ case "flagOperand": {
1741
+ const inline = tail.filter((w) => w !== null && w.startsWith("--")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && (shape.sourceFlags ?? []).includes(f.long ?? "")).map((f) => f.attached);
1742
+ return [
1743
+ ...args.filter(
1744
+ (a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
1745
+ ).map((a) => a.value),
1746
+ ...inline
1747
+ ];
1748
+ }
1749
+ case "archive":
1750
+ src = archiveInputs(shape.archive, args, tail);
1751
+ break;
1752
+ case "allButLast":
1753
+ src = targetDir || dynamicDest ? args : args.slice(0, -1);
1754
+ break;
1755
+ }
1756
+ return src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
1757
+ }
1758
+ function archiveInputs(kind, args, tail) {
1759
+ const first = args[0];
1760
+ const bareKey = first && first.afterFlag === null && TAR_MODE_WORD.test(first.value);
1761
+ if (kind === "tar") {
1762
+ const flagsText = tail.filter((w) => w !== null && w.startsWith("-")).join(" ");
1763
+ const mode = (bareKey ? first.value : "") + flagsText;
1764
+ const extracting = /x|t/.test(bareKey ? first.value.replace(/f/g, "") : "") || /(^|\s)-[a-zA-Z]*[xt]|--extract|--list|--get/.test(flagsText);
1765
+ const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
1766
+ if (extracting && !writing) return [];
1767
+ void mode;
1768
+ let i = 0;
1769
+ if (bareKey) {
1770
+ i = 1;
1771
+ const next = args[1];
1772
+ if (first.value.includes("f") && next && next.afterFlag === null) i = 2;
1773
+ }
1774
+ return args.slice(i);
1775
+ }
1776
+ if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
1777
+ if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
1778
+ return args.slice(2);
1779
+ }
1780
+ function copyVerdictOf(hit) {
1781
+ if (!hit) return null;
1782
+ const ruleName = COPY_RULE_OF[hit.ruleName];
1783
+ if (!ruleName) return null;
1784
+ return {
1785
+ ruleName,
1786
+ verdict: "review",
1787
+ reason: `Copying ${hit.path} moves a credential out of its jail (project-jail shield)`,
1788
+ path: hit.path
1789
+ };
1790
+ }
1791
+ function matchSensitivePath2(p) {
1792
+ for (const sp of SENSITIVE_PATH_RULES) {
1793
+ if (sp.match(p))
1794
+ return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
1795
+ }
1796
+ return null;
1797
+ }
1798
+ function baseWord(w) {
1799
+ return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
1800
+ }
1801
+ function wrappedReadPaths(words, name) {
1802
+ if (name === "find") {
1803
+ const { k, starts } = findStartPoints(words, 0);
1804
+ return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
1805
+ }
1806
+ if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1807
+ const h = unwrapCommandHead(words);
1808
+ return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
1809
+ }
1810
+ function literalShellPayload(words, name) {
1811
+ const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
1812
+ const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
1813
+ if (head === "eval") {
1814
+ const rest = words.slice(h + 1);
1815
+ if (rest.length === 0 || rest.some((w) => w === null)) return null;
1816
+ return rest.join(" ");
1817
+ }
1818
+ if (SHELL_INTERPRETERS.has(head)) {
1819
+ const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
1820
+ if (c < 0) return null;
1821
+ return words[c + 1] ?? null;
1822
+ }
1823
+ return null;
1824
+ }
1825
+ function jailedRedirectRead(stmt) {
1826
+ const redirs = stmt.Redirs || [];
1827
+ for (const r of redirs) {
1828
+ if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
1829
+ const p = resolveWordLiteral(r.Word);
1830
+ if (p === null || p === "") continue;
1831
+ const hit = matchSensitivePath2(p);
1832
+ if (hit) return hit;
1833
+ }
1834
+ return null;
1835
+ }
1650
1836
  function analyzeShellCommand(command) {
1651
1837
  const actions = [];
1652
1838
  const paths = [];
@@ -1782,8 +1968,8 @@ function splitOnPipe(cmd) {
1782
1968
  if (current.trim()) segments2.push(current.trim());
1783
1969
  return segments2.filter(Boolean);
1784
1970
  }
1785
- function positionalTokens(segment) {
1786
- return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1971
+ function positionalTokens(tokens) {
1972
+ return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1787
1973
  }
1788
1974
  function analyzePipeChain(command) {
1789
1975
  const segments2 = splitOnPipe(command);
@@ -1806,8 +1992,10 @@ function analyzePipeChain(command) {
1806
1992
  for (const segment of segments2) {
1807
1993
  const tokens = segment.split(/\s+/).filter(Boolean);
1808
1994
  if (tokens.length === 0) continue;
1809
- const binary = tokens[0].toLowerCase();
1810
- const args = positionalTokens(segment);
1995
+ const h = unwrapCommandHead(tokens);
1996
+ const head = h < tokens.length ? h : 0;
1997
+ const binary = tokens[head].toLowerCase();
1998
+ const args = positionalTokens(tokens.slice(head));
1811
1999
  if (SOURCE_COMMANDS.has(binary)) {
1812
2000
  sourceFiles.push(...args);
1813
2001
  if (args.some(isSensitivePath)) hasSensitiveSource = true;
@@ -2285,6 +2473,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2285
2473
  const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
2286
2474
  const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
2287
2475
  const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
2476
+ let pendingAstReview;
2288
2477
  if (bashCommand !== null) {
2289
2478
  const pipeVerdict = pipeChainVerdict(
2290
2479
  bashCommand,
@@ -2296,7 +2485,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2296
2485
  if (fsVerdict) {
2297
2486
  const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
2298
2487
  const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
2299
- return {
2488
+ const astVerdict = {
2300
2489
  decision: fsVerdict.verdict,
2301
2490
  blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
2302
2491
  reason: fsVerdict.reason,
@@ -2304,6 +2493,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2304
2493
  ruleName: fsVerdict.ruleName,
2305
2494
  ruleDescription: fsVerdict.reason
2306
2495
  };
2496
+ if (fsVerdict.verdict === "block") return astVerdict;
2497
+ pendingAstReview = astVerdict;
2307
2498
  }
2308
2499
  const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
2309
2500
  const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
@@ -2346,7 +2537,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2346
2537
  const matchedRule = resolvePinned(matches);
2347
2538
  if (matchedRule) {
2348
2539
  if (matchedRule.verdict === "allow")
2349
- return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
2540
+ return pendingAstReview ?? { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
2350
2541
  return {
2351
2542
  decision: matchedRule.verdict,
2352
2543
  blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
@@ -2373,6 +2564,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2373
2564
  allTokens = analyzed.allTokens;
2374
2565
  pathTokens = analyzed.paths;
2375
2566
  const candidates2 = [];
2567
+ if (pendingAstReview) candidates2.push(pendingAstReview);
2376
2568
  const evalVerdict = detectDangerousShellExec(shellCommand);
2377
2569
  if (evalVerdict === "block") {
2378
2570
  return {
@@ -2669,6 +2861,12 @@ function classifyRuleSeverity(name, verdict) {
2669
2861
  "read-ssh",
2670
2862
  "read-gcp",
2671
2863
  "read-cred",
2864
+ // Stage 4 (2026-09-11): a copy of a credential file scores like a READ of it --
2865
+ // read-ssh/aws/cred are critical, so copy-ssh/aws/cred are; read-env is high
2866
+ // (below), so copy-env joins the high list, not this one (/code-review).
2867
+ "copy-ssh",
2868
+ "copy-aws",
2869
+ "copy-cred",
2672
2870
  "delete-repo",
2673
2871
  "helm-uninstall",
2674
2872
  "drop-table",
@@ -2681,6 +2879,7 @@ function classifyRuleSeverity(name, verdict) {
2681
2879
  ];
2682
2880
  if (criticalPatterns.some((p) => n.includes(p))) return "critical";
2683
2881
  const highPatterns = [
2882
+ "copy-env",
2684
2883
  "force-push",
2685
2884
  "force_push",
2686
2885
  "git-destructive",
@@ -2698,6 +2897,11 @@ function narrativeRuleLabel(name) {
2698
2897
  const map = {
2699
2898
  "read-aws": "AWS credentials read",
2700
2899
  "read-ssh": "SSH private key read",
2900
+ // Stage 4 copy twins, so `scan --narrative` prints a label, not a raw slug.
2901
+ "copy-ssh": "SSH private key copied out",
2902
+ "copy-aws": "AWS credentials copied out",
2903
+ "copy-env": ".env file copied out",
2904
+ "copy-cred": "credential file copied out",
2701
2905
  "read-gcp": "GCP credentials read",
2702
2906
  "read-cred": "credential file read",
2703
2907
  "delete-repo": "GitHub repository deletion",
@@ -3287,7 +3491,7 @@ function* stringValues(obj, depth = 0) {
3287
3491
  }
3288
3492
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3289
3493
  }
3290
- var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, 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, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, 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, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
3494
+ var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, 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, SCP_VALUE_FLAGS, RSYNC_SKIP, 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, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, 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, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
3291
3495
  var init_dist = __esm({
3292
3496
  "packages/policy-engine/dist/index.mjs"() {
3293
3497
  "use strict";
@@ -3905,13 +4109,32 @@ var init_dist = __esm({
3905
4109
  })
3906
4110
  );
3907
4111
  SENSITIVE_PATH_PATTERNS = [
3908
- /[/\\]\.ssh[/\\]/i,
3909
- /[/\\]\.aws[/\\]/i,
4112
+ /[/\\]\.ssh([/\\]|$)/i,
4113
+ /[/\\]\.aws([/\\]|$)/i,
3910
4114
  /[/\\]\.config[/\\]gcloud[/\\]/i,
3911
4115
  /[/\\]\.azure[/\\]/i,
3912
4116
  /[/\\]\.kube[/\\]config$/i,
3913
- /[/\\]\.env($|\.)/i,
3914
- // .env, .env.local, .env.production — not .envoy
4117
+ // ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
4118
+ // (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
4119
+ // structural suffix chain rather than a hand-written list, `example|sample|
4120
+ // template` exempt because a fixture stays a fixture whatever follows, and
4121
+ // `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
4122
+ // committed template, `.env.test.local` is gitignored and holds real values.
4123
+ //
4124
+ // It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
4125
+ // blocked while `cat .env.example` allowed: the same file, opposite verdicts,
4126
+ // decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
4127
+ // which is the contract that now holds these copies in step, and stage 5 of
4128
+ // doc/credential-jail-architecture.md, which replaces them with one generated
4129
+ // source.
4130
+ // ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
4131
+ // fixture whatever follows it -- `.env.example.md` is documentation -- but
4132
+ // `.env.example.local` is gitignored by the `.env*.local` convention and holds
4133
+ // real values, exactly the reasoning that anchors `(?!\.test$)` rather than
4134
+ // using `\b`. Without this branch the fixture exemption also bought a two-step
4135
+ // bypass: `cp .env .env.sample`, then read the copy.
4136
+ /[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
4137
+ // .env + any suffix chain; fixtures exempt unless .local
3915
4138
  /[/\\]\.git-credentials$/i,
3916
4139
  /[/\\]\.npmrc$/i,
3917
4140
  /[/\\]\.docker[/\\]config\.json$/i,
@@ -3993,6 +4216,10 @@ var init_dist = __esm({
3993
4216
  "od",
3994
4217
  "xxd",
3995
4218
  "hexdump",
4219
+ // Emits the file's bytes, re-encoded, so it is a read by the set's own test
4220
+ // ("does it emit file contents"). Absent until 2026-09-10, which is why
4221
+ // `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
4222
+ "base64",
3996
4223
  "strings",
3997
4224
  "sort",
3998
4225
  "uniq",
@@ -4000,8 +4227,56 @@ var init_dist = __esm({
4000
4227
  "nl",
4001
4228
  "dd"
4002
4229
  ]);
4230
+ SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
4231
+ RSYNC_SKIP = [
4232
+ "e",
4233
+ "--rsh",
4234
+ "--exclude",
4235
+ "--exclude-from",
4236
+ "--include",
4237
+ "--include-from",
4238
+ "--files-from",
4239
+ "f",
4240
+ "--filter"
4241
+ ];
4242
+ COPY_VERBS = {
4243
+ cp: { source: "allButLast", targetDirFlag: true },
4244
+ mv: { source: "allButLast", targetDirFlag: true },
4245
+ install: { source: "allButLast", targetDirFlag: true },
4246
+ ln: { source: "first", targetDirFlag: true },
4247
+ scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
4248
+ rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
4249
+ tar: {
4250
+ source: "archive",
4251
+ archive: "tar",
4252
+ skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
4253
+ },
4254
+ zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
4255
+ ar: { source: "archive", archive: "ar" },
4256
+ "7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
4257
+ gzip: { source: "all" },
4258
+ bzip2: { source: "all" },
4259
+ xz: { source: "all" },
4260
+ "docker cp": { source: "allButLast" },
4261
+ "kubectl cp": { source: "allButLast" },
4262
+ "gsutil cp": { source: "allButLast" },
4263
+ "gsutil rsync": { source: "allButLast" },
4264
+ "rclone copy": { source: "allButLast" },
4265
+ "rclone sync": { source: "allButLast" },
4266
+ "aws s3 cp": { source: "allButLast" },
4267
+ "aws s3 mv": { source: "allButLast" },
4268
+ "aws s3 sync": { source: "allButLast" },
4269
+ "gcloud storage cp": { source: "allButLast" },
4270
+ "az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
4271
+ };
4272
+ TAR_MODE_WORD = /^[a-zA-Z]+$/;
4273
+ COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
4003
4274
  FS_OP_PRESCREEN_RE = new RegExp(
4004
- `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
4275
+ // A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
4276
+ // reader right after `"` / `'`, and without these two characters the
4277
+ // prescreen rejected every string-wrapped read before the parser ran.
4278
+ // Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
4279
+ `(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
4005
4280
  );
4006
4281
  HOME_CACHE_ALLOWLIST = [
4007
4282
  ".cache",
@@ -4022,12 +4297,12 @@ var init_dist = __esm({
4022
4297
  {
4023
4298
  rule: "shield:project-jail:block-read-ssh",
4024
4299
  reason: "Reading SSH private keys is blocked by project-jail shield",
4025
- match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
4300
+ match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
4026
4301
  },
4027
4302
  {
4028
4303
  rule: "shield:project-jail:block-read-aws",
4029
4304
  reason: "Reading AWS credentials is blocked by project-jail shield",
4030
- match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
4305
+ match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
4031
4306
  },
4032
4307
  {
4033
4308
  // Mirrors the JSON shield's `.env` pattern (project-jail.json's
@@ -4071,7 +4346,9 @@ var init_dist = __esm({
4071
4346
  // symmetry — silently exempts every `.env.test.*` file.
4072
4347
  //
4073
4348
  // shields.test.ts:983-995 is the canonical contract; keep both in step.
4074
- match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$/i.test(p)
4349
+ match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
4350
+ p
4351
+ )
4075
4352
  },
4076
4353
  {
4077
4354
  // verdict: 'review' (not 'block') is a deliberate design choice
@@ -4182,8 +4459,18 @@ var init_dist = __esm({
4182
4459
  _redirStdinOps = null;
4183
4460
  _listOps = null;
4184
4461
  WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
4462
+ FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
4185
4463
  INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
4186
- NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
4464
+ NET_BINARIES = /* @__PURE__ */ new Set([
4465
+ "curl",
4466
+ "wget",
4467
+ "scp",
4468
+ "ssh",
4469
+ "nc",
4470
+ "ncat",
4471
+ "netcat",
4472
+ "rsync"
4473
+ ]);
4187
4474
  VALUE_FLAGS = {
4188
4475
  curl: /* @__PURE__ */ new Set([
4189
4476
  "-d",
@@ -4272,10 +4559,22 @@ var init_dist = __esm({
4272
4559
  fsOpCache = /* @__PURE__ */ new Map();
4273
4560
  stripDotSlash = (p) => p.replace(/^\.\//, "");
4274
4561
  REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
4562
+ REDIR_FILE_IN_OPS = new Set(
4563
+ [deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
4564
+ );
4275
4565
  REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
4276
4566
  deriveRedirOp("cat <<X\nX"),
4277
4567
  deriveRedirOp("cat <<-X\nX")
4278
4568
  ]);
4569
+ FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
4570
+ COPY_RULE_OF = {
4571
+ "shield:project-jail:block-read-ssh": "shield:project-jail:review-copy-ssh",
4572
+ "shield:project-jail:block-read-aws": "shield:project-jail:review-copy-aws",
4573
+ "shield:project-jail:block-read-env": "shield:project-jail:review-copy-env",
4574
+ "shield:project-jail:review-read-credentials": "shield:project-jail:review-copy-credentials"
4575
+ };
4576
+ isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
4577
+ positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
4279
4578
  DEFAULT_EGRESS_ALLOWLIST = [
4280
4579
  // node9's own control plane (api, app, dev-api, staging and the apex).
4281
4580
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -4299,21 +4598,7 @@ var init_dist = __esm({
4299
4598
  "deb.debian.org",
4300
4599
  "*.ubuntu.com"
4301
4600
  ];
4302
- SOURCE_COMMANDS = /* @__PURE__ */ new Set([
4303
- "cat",
4304
- "head",
4305
- "tail",
4306
- "grep",
4307
- "awk",
4308
- "sed",
4309
- "cut",
4310
- "sort",
4311
- "tee",
4312
- "less",
4313
- "more",
4314
- "strings",
4315
- "xxd"
4316
- ]);
4601
+ SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
4317
4602
  SINK_COMMANDS = /* @__PURE__ */ new Set([
4318
4603
  "curl",
4319
4604
  "wget",
@@ -4344,16 +4629,25 @@ var init_dist = __esm({
4344
4629
  "node"
4345
4630
  ]);
4346
4631
  SENSITIVE_PATTERNS = [
4347
- /(?:^|\/)\.env(?:\.|$)/i,
4348
- // .env, .env.local, .env.production
4632
+ // Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
4633
+ /(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
4634
+ // .env chain; fixtures exempt unless .local
4349
4635
  /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
4350
4636
  // SSH private keys
4351
4637
  /\.pem$|\.key$|\.p12$|\.pfx$/i,
4352
4638
  // certificate files
4353
- /(?:^|\/)\.ssh\//i,
4354
- // ~/.ssh/ directory
4355
- /(?:^|\/)\.aws\/credentials/i,
4356
- // AWS credentials
4639
+ // The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
4640
+ // the directory counts wherever it appears, while the directory ITSELF counts
4641
+ // only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
4642
+ // `config/.ssh` is more likely a search pattern than a read. These are
4643
+ // extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
4644
+ // contract as the shell tier, so the same boundary is the right one.
4645
+ // Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
4646
+ // identical pipeline naming a file inside that directory.
4647
+ /(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
4648
+ // ~/.ssh/ and ~/.ssh
4649
+ /(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
4650
+ // AWS creds + dir
4357
4651
  /(?:^|\/)\.netrc$/i,
4358
4652
  // netrc (stores HTTP credentials)
4359
4653
  /(?:^|\/)(passwd|shadow|sudoers)$/i,
@@ -5021,7 +5315,7 @@ var init_dist = __esm({
5021
5315
  {
5022
5316
  field: "command",
5023
5317
  op: "matches",
5024
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
5318
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
5025
5319
  flags: "i"
5026
5320
  }
5027
5321
  ],
@@ -5035,7 +5329,7 @@ var init_dist = __esm({
5035
5329
  {
5036
5330
  field: "command",
5037
5331
  op: "matches",
5038
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
5332
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
5039
5333
  flags: "i"
5040
5334
  }
5041
5335
  ],
@@ -5049,7 +5343,7 @@ var init_dist = __esm({
5049
5343
  {
5050
5344
  field: "command",
5051
5345
  op: "matches",
5052
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
5346
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
5053
5347
  flags: "i"
5054
5348
  }
5055
5349
  ],
@@ -5063,7 +5357,7 @@ var init_dist = __esm({
5063
5357
  {
5064
5358
  field: "command",
5065
5359
  op: "matches",
5066
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
5360
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
5067
5361
  flags: "i"
5068
5362
  }
5069
5363
  ],
@@ -5077,7 +5371,7 @@ var init_dist = __esm({
5077
5371
  {
5078
5372
  field: "file_path",
5079
5373
  op: "matches",
5080
- value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
5374
+ value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
5081
5375
  flags: "i"
5082
5376
  }
5083
5377
  ],
@@ -5091,7 +5385,7 @@ var init_dist = __esm({
5091
5385
  {
5092
5386
  field: "file_path",
5093
5387
  op: "matches",
5094
- value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
5388
+ value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
5095
5389
  flags: "i"
5096
5390
  }
5097
5391
  ],
@@ -5105,7 +5399,7 @@ var init_dist = __esm({
5105
5399
  {
5106
5400
  field: "file_path",
5107
5401
  op: "matches",
5108
- value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
5402
+ value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
5109
5403
  flags: "i"
5110
5404
  }
5111
5405
  ],
@@ -5248,7 +5542,7 @@ var init_dist = __esm({
5248
5542
  };
5249
5543
  LOOP_THRESHOLD_FOR_WASTE = 3;
5250
5544
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
5251
- SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
5545
+ SENSITIVE_PATH_RE = /[\\/]\.aws(?:[\\/]|$)|^\.aws[\\/]|[\\/]\.ssh(?:[\\/]|$)|^\.ssh[\\/]|(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
5252
5546
  FILE_TOOLS = /* @__PURE__ */ new Set([
5253
5547
  "read",
5254
5548
  "read_file",
@@ -5325,7 +5619,7 @@ var init_dist = __esm({
5325
5619
  { view: "separators-stripped", decoder: "separators", stripped: true }
5326
5620
  ];
5327
5621
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5328
- CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
5622
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
5329
5623
  DEDUPE_PREVIEW_LEN = 120;
5330
5624
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5331
5625
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
@@ -8643,7 +8937,7 @@ function isNetworkTool(toolName, args) {
8643
8937
  if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
8644
8938
  const a = args;
8645
8939
  const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
8646
- return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
8940
+ return NETWORK_COMMAND_RE.test(cmd);
8647
8941
  }
8648
8942
  return false;
8649
8943
  }
@@ -9518,7 +9812,7 @@ function canaryRecordById(id) {
9518
9812
  return null;
9519
9813
  }
9520
9814
  }
9521
- var WRITE_TOOLS;
9815
+ var WRITE_TOOLS, NETWORK_COMMAND_RE;
9522
9816
  var init_orchestrator = __esm({
9523
9817
  "src/auth/orchestrator.ts"() {
9524
9818
  "use strict";
@@ -9548,6 +9842,7 @@ var init_orchestrator = __esm({
9548
9842
  "notebook_edit",
9549
9843
  "notebookedit"
9550
9844
  ]);
9845
+ NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
9551
9846
  }
9552
9847
  });
9553
9848
 
@@ -12613,9 +12908,10 @@ async function ensurePricingLoaded() {
12613
12908
  memCacheAt = Date.now();
12614
12909
  lookupCache.clear();
12615
12910
  }
12616
- function pricingFor(model) {
12911
+ function pricingFor(model, options = {}) {
12617
12912
  const norm = normalizeModel(model);
12618
- const cached = lookupCache.get(norm);
12913
+ const lookupKey = options.exact ? `exact:${norm}` : norm;
12914
+ const cached = lookupCache.get(lookupKey);
12619
12915
  if (cached !== void 0) return cached;
12620
12916
  if (memCache === null && !diskChecked) {
12621
12917
  diskChecked = true;
@@ -12635,6 +12931,7 @@ function pricingFor(model) {
12635
12931
  resolved = exact;
12636
12932
  break;
12637
12933
  }
12934
+ if (options.exact) continue;
12638
12935
  let best = null;
12639
12936
  for (const key of Object.keys(source)) {
12640
12937
  if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
@@ -12646,7 +12943,7 @@ function pricingFor(model) {
12646
12943
  break;
12647
12944
  }
12648
12945
  }
12649
- lookupCache.set(norm, resolved);
12946
+ lookupCache.set(lookupKey, resolved);
12650
12947
  return resolved;
12651
12948
  }
12652
12949
  var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
@@ -12680,6 +12977,18 @@ var init_litellm = __esm({
12680
12977
  "gpt-5": [125e-8, 1e-5, 0, 125e-9],
12681
12978
  "gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
12682
12979
  "gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
12980
+ // Codex offline rates checked against official OpenAI model pages, 2026-09-11.
12981
+ "gpt-5.1-codex": [125e-8, 1e-5, 0, 125e-9],
12982
+ "gpt-5.1-codex-max": [125e-8, 1e-5, 0, 125e-9],
12983
+ "gpt-5.1-codex-mini": [25e-8, 2e-6, 0, 25e-9],
12984
+ "gpt-5.2-codex": [175e-8, 14e-6, 0, 175e-9],
12985
+ "gpt-5.3-codex": [175e-8, 14e-6, 0, 175e-9],
12986
+ "gpt-5.4": [25e-7, 15e-6, 0, 25e-8],
12987
+ "gpt-5.4-mini": [75e-8, 45e-7, 0, 75e-9],
12988
+ "gpt-5.5": [5e-6, 3e-5, 0, 5e-7],
12989
+ "gpt-5.6-sol": [4e-6, 2e-5, 5e-6, 4e-7],
12990
+ "gpt-5.6-terra": [2e-6, 12e-6, 25e-7, 2e-7],
12991
+ "gpt-6-astra": [1e-5, 5e-5, 125e-7, 1e-6],
12683
12992
  o3: [2e-6, 8e-6, 0, 5e-7],
12684
12993
  "o4-mini": [11e-7, 44e-7, 0, 275e-9],
12685
12994
  // Google. Values copied from the live LiteLLM table (verified 2026-06-14)
@@ -12851,103 +13160,259 @@ import fs21 from "fs";
12851
13160
  import os20 from "os";
12852
13161
  import path23 from "path";
12853
13162
  function codexSessionsDir() {
12854
- return path23.join(os20.homedir(), ".codex", "sessions");
13163
+ return path23.join(process.env.CODEX_HOME?.trim() || path23.join(os20.homedir(), ".codex"), "sessions");
12855
13164
  }
12856
13165
  function codexPriceFor(model) {
12857
- return pricingFor(model) ?? CODEX_FALLBACK;
13166
+ return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
12858
13167
  }
12859
- function codexSessionCost(model, tokens) {
12860
- const nonCached = Math.max(0, tokens.input - tokens.cached);
12861
- const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
12862
- return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
13168
+ function codexModel(model) {
13169
+ return normalizeModel(model.replace(/^openai\//i, "").replace(/-\d{4}-\d{2}-\d{2}$/, ""));
12863
13170
  }
12864
- function listCodexSessionFiles(base) {
12865
- const out = [];
12866
- for (const y of safeReaddir2(base)) {
12867
- const yp = path23.join(base, y);
12868
- if (!isDir2(yp)) continue;
12869
- for (const m of safeReaddir2(yp)) {
12870
- const mp = path23.join(yp, m);
12871
- if (!isDir2(mp)) continue;
12872
- for (const d of safeReaddir2(mp)) {
12873
- const dp = path23.join(mp, d);
12874
- if (!isDir2(dp)) continue;
12875
- for (const f of safeReaddir2(dp)) {
12876
- if (f.endsWith(".jsonl")) out.push(path23.join(dp, f));
12877
- }
13171
+ function addTokens(previous, delta) {
13172
+ return {
13173
+ input: (previous?.input ?? 0) + delta.input,
13174
+ cached: (previous?.cached ?? 0) + delta.cached,
13175
+ output: (previous?.output ?? 0) + delta.output,
13176
+ cacheWrite: (previous?.cacheWrite ?? 0) + delta.cacheWrite
13177
+ };
13178
+ }
13179
+ function codexSessionCost(model, tokens, request2) {
13180
+ const input = tokenNumber(tokens.input);
13181
+ const cached = Math.min(input, tokenNumber(tokens.cached));
13182
+ const written = Math.min(input - cached, tokenNumber(tokens.cacheWrite));
13183
+ const [pin, pout, pcw, pcr] = codexPriceFor(model || "gpt-5");
13184
+ const longContext = request2 && request2.inputTokens > 272e3 && ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"].includes(
13185
+ codexModel(model)
13186
+ );
13187
+ const inputMultiplier = longContext ? 2 : 1;
13188
+ const outputMultiplier = longContext ? 1.5 : 1;
13189
+ const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
13190
+ return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
13191
+ }
13192
+ function statAndFirstLine(file) {
13193
+ const CAP = 4 * 1024 * 1024;
13194
+ const CHUNK = 64 * 1024;
13195
+ const fd = fs21.openSync(file, "r");
13196
+ try {
13197
+ const stat = fs21.fstatSync(fd);
13198
+ const limit = Math.min(stat.size, CAP);
13199
+ const parts = [];
13200
+ for (let pos = 0; pos < limit; pos += CHUNK) {
13201
+ const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
13202
+ const read2 = fs21.readSync(fd, buf, 0, buf.length, pos);
13203
+ if (read2 <= 0) break;
13204
+ const slice = buf.subarray(0, read2);
13205
+ const nl = slice.indexOf(10);
13206
+ parts.push(nl >= 0 ? slice.subarray(0, nl) : slice);
13207
+ if (nl >= 0) break;
13208
+ }
13209
+ return { stat, first: Buffer.concat(parts).toString("utf8") };
13210
+ } finally {
13211
+ fs21.closeSync(fd);
13212
+ }
13213
+ }
13214
+ function listCodexSessionFiles(base = codexSessionsDir()) {
13215
+ const files = [];
13216
+ const walk = (dir) => {
13217
+ try {
13218
+ for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
13219
+ const file = path23.join(dir, entry.name);
13220
+ if (entry.isDirectory()) walk(file);
13221
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
13222
+ }
13223
+ } catch {
13224
+ }
13225
+ };
13226
+ walk(base);
13227
+ if (path23.basename(base) === "sessions") walk(path23.join(path23.dirname(base), "archived_sessions"));
13228
+ const sessions = /* @__PURE__ */ new Map();
13229
+ for (const file of files.sort()) {
13230
+ try {
13231
+ const { stat, first: head } = statAndFirstLine(file);
13232
+ let id = "";
13233
+ try {
13234
+ const first = JSON.parse(head);
13235
+ if (first?.type === "session_meta" && typeof first.payload?.id === "string")
13236
+ id = first.payload.id;
13237
+ } catch {
12878
13238
  }
13239
+ const key = id ? `session:${id}` : `file:${file}`;
13240
+ const prior = sessions.get(key);
13241
+ if (!prior || stat.mtimeMs > prior.mtime || stat.mtimeMs === prior.mtime && stat.size > prior.size) {
13242
+ sessions.set(key, { file, mtime: stat.mtimeMs, size: stat.size });
13243
+ }
13244
+ } catch {
12879
13245
  }
12880
13246
  }
12881
- return out;
13247
+ return [...sessions.values()].map((s) => s.file);
12882
13248
  }
12883
- function safeReaddir2(dir) {
12884
- try {
12885
- return fs21.readdirSync(dir);
12886
- } catch {
12887
- return [];
12888
- }
13249
+ function record(value) {
13250
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
12889
13251
  }
12890
- function isDir2(p) {
12891
- try {
12892
- return fs21.statSync(p).isDirectory();
12893
- } catch {
12894
- return false;
13252
+ function tokenNumber(value) {
13253
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
13254
+ }
13255
+ function usage(value, fallback) {
13256
+ const u = record(value);
13257
+ if (!["input_tokens", "output_tokens"].some((k) => typeof u[k] === "number")) return null;
13258
+ for (const key of [
13259
+ "input_tokens",
13260
+ "cached_input_tokens",
13261
+ "cache_read_input_tokens",
13262
+ "output_tokens",
13263
+ "cache_write_input_tokens"
13264
+ ]) {
13265
+ if (u[key] !== void 0 && (typeof u[key] !== "number" || !Number.isFinite(u[key]) || u[key] < 0))
13266
+ return null;
12895
13267
  }
13268
+ return {
13269
+ input: tokenNumber(u.input_tokens ?? fallback?.input),
13270
+ cached: tokenNumber(u.cached_input_tokens ?? u.cache_read_input_tokens ?? fallback?.cached),
13271
+ output: tokenNumber(u.output_tokens ?? fallback?.output),
13272
+ cacheWrite: tokenNumber(u.cache_write_input_tokens ?? fallback?.cacheWrite)
13273
+ };
12896
13274
  }
12897
- function parseCodexSession(lines) {
12898
- let sessionStart = "";
12899
- let runId = "";
12900
- let cwd = "";
12901
- let model = "";
12902
- let input = 0;
12903
- let cached = 0;
12904
- let output = 0;
12905
- let sawUsage = false;
13275
+ function timestamp(value) {
13276
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : "";
13277
+ }
13278
+ function parseCodexUsage(lines) {
13279
+ const result = {
13280
+ events: [],
13281
+ sessionStart: "",
13282
+ runId: "",
13283
+ workingDir: "",
13284
+ legacyModels: []
13285
+ };
13286
+ let model = "gpt-5";
13287
+ let serviceTier;
13288
+ let previous = null;
13289
+ const legacyModels = /* @__PURE__ */ new Set();
13290
+ const seenStandalone = /* @__PURE__ */ new Set();
12906
13291
  for (const raw of lines) {
12907
- if (!raw.trim()) continue;
12908
13292
  let entry;
12909
13293
  try {
12910
- entry = JSON.parse(raw);
13294
+ entry = record(JSON.parse(raw));
12911
13295
  } catch {
12912
13296
  continue;
12913
13297
  }
12914
- const p = entry.payload ?? {};
13298
+ const p = record(entry.payload);
12915
13299
  if (entry.type === "session_meta") {
12916
- if (!sessionStart && typeof p["timestamp"] === "string") sessionStart = p["timestamp"];
12917
- if (!runId && typeof p["id"] === "string") runId = p["id"];
12918
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13300
+ result.sessionStart ||= timestamp(p.timestamp ?? entry.timestamp);
13301
+ if (!result.runId && typeof p.id === "string") result.runId = p.id;
13302
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
12919
13303
  continue;
12920
13304
  }
12921
13305
  if (entry.type === "turn_context") {
12922
- if (typeof p["model"] === "string") model = p["model"];
12923
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13306
+ if (typeof p.model === "string" && p.model) {
13307
+ model = p.model;
13308
+ legacyModels.add(normalizeModel(model));
13309
+ }
13310
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
13311
+ serviceTier = typeof p.service_tier === "string" ? p.service_tier : void 0;
12924
13312
  continue;
12925
13313
  }
12926
- if (entry.type === "event_msg" && p["type"] === "token_count") {
12927
- const info = p["info"] ?? {};
12928
- const usage = info["total_token_usage"] ?? {};
12929
- if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
12930
- if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
12931
- if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
12932
- sawUsage = true;
13314
+ if (entry.type !== "event_msg" || p.type !== "token_count") continue;
13315
+ const info = record(p.info);
13316
+ const total = usage(info.total_token_usage, previous);
13317
+ const last = usage(info.last_token_usage);
13318
+ if (!total && !last) continue;
13319
+ const eventModel = [info.model, info.model_name, p.model].find(
13320
+ (v) => typeof v === "string" && v
13321
+ );
13322
+ if (typeof eventModel === "string") model = eventModel;
13323
+ let delta;
13324
+ if (total) {
13325
+ if (previous && Object.keys(total).every(
13326
+ (k) => total[k] === previous[k]
13327
+ ))
13328
+ continue;
13329
+ const reset = previous && (total.input < previous.input || total.output < previous.output);
13330
+ delta = reset ? last ?? total : {
13331
+ input: Math.max(0, total.input - (previous?.input ?? 0)),
13332
+ cached: Math.max(0, total.cached - (previous?.cached ?? 0)),
13333
+ output: Math.max(0, total.output - (previous?.output ?? 0)),
13334
+ cacheWrite: Math.max(0, total.cacheWrite - (previous?.cacheWrite ?? 0))
13335
+ };
13336
+ previous = total;
13337
+ } else {
13338
+ delta = last;
13339
+ const key = JSON.stringify([entry.timestamp, model, delta]);
13340
+ if (entry.timestamp && seenStandalone.has(key)) continue;
13341
+ if (entry.timestamp) seenStandalone.add(key);
13342
+ previous = addTokens(previous, delta);
13343
+ }
13344
+ if (delta.input === 0 && delta.output === 0) continue;
13345
+ const ts = timestamp(entry.timestamp) || result.sessionStart;
13346
+ if (!ts) continue;
13347
+ const cached = Math.min(delta.input, delta.cached);
13348
+ const written = Math.min(delta.input - cached, delta.cacheWrite);
13349
+ result.events.push({
13350
+ timestamp: ts,
13351
+ date: ts.slice(0, 10),
13352
+ model: normalizeModel(model),
13353
+ workingDir: result.workingDir,
13354
+ runId: result.runId,
13355
+ costUSD: codexSessionCost(model, delta, {
13356
+ inputTokens: last?.input ?? delta.input,
13357
+ serviceTier: typeof info.service_tier === "string" ? info.service_tier : serviceTier
13358
+ }),
13359
+ inputTokens: delta.input - cached - written,
13360
+ outputTokens: delta.output,
13361
+ cacheReadTokens: cached,
13362
+ cacheWriteTokens: written
13363
+ });
13364
+ }
13365
+ result.legacyModels = [...legacyModels.size ? legacyModels : ["gpt-5"]];
13366
+ return result;
13367
+ }
13368
+ function codexUsageInWindow(usage2, start, end) {
13369
+ return usage2.events.filter(
13370
+ (e) => (!start || Date.parse(e.timestamp) >= start.getTime()) && (!end || Date.parse(e.timestamp) <= end.getTime())
13371
+ );
13372
+ }
13373
+ function parseCodexSession(lines) {
13374
+ const parsed = parseCodexUsage(lines);
13375
+ if (!parsed.events.length) return [];
13376
+ const rows = /* @__PURE__ */ new Map();
13377
+ if (parsed.sessionStart) {
13378
+ for (const model of parsed.legacyModels) {
13379
+ rows.set(`${parsed.sessionStart.slice(0, 10)}::${model}`, {
13380
+ date: parsed.sessionStart.slice(0, 10),
13381
+ model,
13382
+ workingDir: parsed.workingDir,
13383
+ runId: parsed.runId,
13384
+ costUSD: 0,
13385
+ inputTokens: 0,
13386
+ outputTokens: 0,
13387
+ cacheReadTokens: 0,
13388
+ cacheWriteTokens: 0
13389
+ });
13390
+ }
13391
+ }
13392
+ for (const event of parsed.events) {
13393
+ const e = {
13394
+ date: event.date,
13395
+ model: event.model,
13396
+ workingDir: event.workingDir,
13397
+ runId: event.runId,
13398
+ costUSD: event.costUSD,
13399
+ inputTokens: event.inputTokens,
13400
+ outputTokens: event.outputTokens,
13401
+ cacheReadTokens: event.cacheReadTokens,
13402
+ cacheWriteTokens: event.cacheWriteTokens
13403
+ };
13404
+ const key = `${e.date}::${e.model}`;
13405
+ const prev = rows.get(key);
13406
+ if (!prev) rows.set(key, { ...e });
13407
+ else {
13408
+ prev.costUSD += e.costUSD;
13409
+ prev.inputTokens += e.inputTokens;
13410
+ prev.outputTokens += e.outputTokens;
13411
+ prev.cacheReadTokens += e.cacheReadTokens;
13412
+ prev.cacheWriteTokens += e.cacheWriteTokens;
12933
13413
  }
12934
13414
  }
12935
- if (!sessionStart || !sawUsage) return null;
12936
- const nonCached = Math.max(0, input - cached);
12937
- if (nonCached === 0 && output === 0 && cached === 0) return null;
12938
- const norm = normalizeModel(model || "gpt-5");
12939
- const costUSD = codexSessionCost(model, { input, cached, output });
12940
- return {
12941
- date: sessionStart.slice(0, 10),
12942
- model: norm,
12943
- workingDir: cwd,
12944
- runId,
12945
- costUSD,
12946
- inputTokens: nonCached,
12947
- outputTokens: output,
12948
- cacheReadTokens: cached,
12949
- cacheWriteTokens: 0
12950
- };
13415
+ return [...rows.values()];
12951
13416
  }
12952
13417
  var CODEX_FALLBACK, codexSource;
12953
13418
  var init_cost_codex = __esm({
@@ -12957,43 +13422,17 @@ var init_cost_codex = __esm({
12957
13422
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
12958
13423
  codexSource = {
12959
13424
  id: "codex",
12960
- available() {
12961
- try {
12962
- return fs21.existsSync(codexSessionsDir());
12963
- } catch {
12964
- return false;
12965
- }
12966
- },
13425
+ available: () => fs21.existsSync(codexSessionsDir()) || fs21.existsSync(path23.join(path23.dirname(codexSessionsDir()), "archived_sessions")),
12967
13426
  collect(sinceMs) {
12968
- const base = codexSessionsDir();
12969
- const combined = /* @__PURE__ */ new Map();
12970
- for (const file of listCodexSessionFiles(base)) {
13427
+ const entries = [];
13428
+ for (const file of listCodexSessionFiles()) {
12971
13429
  try {
12972
13430
  if (sinceMs !== void 0 && fs21.statSync(file).mtimeMs < sinceMs) continue;
13431
+ entries.push(...parseCodexSession(fs21.readFileSync(file, "utf8").split("\n")));
12973
13432
  } catch {
12974
- continue;
12975
- }
12976
- let content;
12977
- try {
12978
- content = fs21.readFileSync(file, "utf8");
12979
- } catch {
12980
- continue;
12981
- }
12982
- const e = parseCodexSession(content.split("\n"));
12983
- if (!e) continue;
12984
- const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
12985
- const prev = combined.get(key);
12986
- if (prev) {
12987
- prev.costUSD += e.costUSD;
12988
- prev.inputTokens += e.inputTokens;
12989
- prev.outputTokens += e.outputTokens;
12990
- prev.cacheReadTokens += e.cacheReadTokens;
12991
- prev.cacheWriteTokens += e.cacheWriteTokens;
12992
- } else {
12993
- combined.set(key, { ...e });
12994
13433
  }
12995
13434
  }
12996
- return [...combined.values()];
13435
+ return entries;
12997
13436
  }
12998
13437
  };
12999
13438
  }
@@ -13006,7 +13445,7 @@ import path24 from "path";
13006
13445
  function copilotSessionsDir() {
13007
13446
  return path24.join(os21.homedir(), ".copilot", "session-state");
13008
13447
  }
13009
- function safeReaddir3(dir) {
13448
+ function safeReaddir2(dir) {
13010
13449
  try {
13011
13450
  return fs22.readdirSync(dir);
13012
13451
  } catch {
@@ -13092,7 +13531,7 @@ var init_cost_copilot = __esm({
13092
13531
  collect(sinceMs) {
13093
13532
  const base = copilotSessionsDir();
13094
13533
  const combined = /* @__PURE__ */ new Map();
13095
- for (const sid of safeReaddir3(base)) {
13534
+ for (const sid of safeReaddir2(base)) {
13096
13535
  const file = path24.join(base, sid, "events.jsonl");
13097
13536
  try {
13098
13537
  if (sinceMs !== void 0 && fs22.statSync(file).mtimeMs < sinceMs) continue;
@@ -13707,8 +14146,8 @@ function originForRule(ruleName, sections, enabled) {
13707
14146
  }
13708
14147
  return "";
13709
14148
  }
13710
- function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
13711
- const t = new Date(timestamp).getTime();
14149
+ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
14150
+ const t = new Date(timestamp2).getTime();
13712
14151
  if (Number.isNaN(t)) return "?";
13713
14152
  const days = Math.floor((now.getTime() - t) / 864e5);
13714
14153
  if (days < 1) return "today";
@@ -13819,7 +14258,7 @@ function readPreviousScan(opts = {}) {
13819
14258
  return null;
13820
14259
  }
13821
14260
  }
13822
- function appendScanHistory(record, opts = {}) {
14261
+ function appendScanHistory(record2, opts = {}) {
13823
14262
  const filePath = opts.path ?? defaultHistoryPath();
13824
14263
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
13825
14264
  try {
@@ -13834,7 +14273,7 @@ function appendScanHistory(record, opts = {}) {
13834
14273
  } catch {
13835
14274
  }
13836
14275
  }
13837
- history.push(record);
14276
+ history.push(record2);
13838
14277
  if (history.length > cap) {
13839
14278
  history = history.slice(history.length - cap);
13840
14279
  }
@@ -13895,17 +14334,17 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
13895
14334
  if (row["type"] !== "assistant") continue;
13896
14335
  const msg = row["message"];
13897
14336
  if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
13898
- const usage = msg["usage"];
14337
+ const usage2 = msg["usage"];
13899
14338
  const model = msg["model"];
13900
- const timestamp = row["timestamp"];
13901
- if (typeof timestamp !== "string" || timestamp.length < 10) continue;
13902
- const date = timestamp.slice(0, 10);
14339
+ const timestamp2 = row["timestamp"];
14340
+ if (typeof timestamp2 !== "string" || timestamp2.length < 10) continue;
14341
+ const date = timestamp2.slice(0, 10);
13903
14342
  const p = pricingFor(model);
13904
14343
  if (!p) continue;
13905
- const inp = Number(usage["input_tokens"] ?? 0);
13906
- const out = Number(usage["output_tokens"] ?? 0);
13907
- const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
13908
- const cr = Number(usage["cache_read_input_tokens"] ?? 0);
14344
+ const inp = Number(usage2["input_tokens"] ?? 0);
14345
+ const out = Number(usage2["output_tokens"] ?? 0);
14346
+ const cw = Number(usage2["cache_creation_input_tokens"] ?? 0);
14347
+ const cr = Number(usage2["cache_read_input_tokens"] ?? 0);
13909
14348
  const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
13910
14349
  const rowCwd = typeof row["cwd"] === "string" ? row["cwd"] : null;
13911
14350
  const workingDir = rowCwd && rowCwd.startsWith("/") ? rowCwd : fallbackWorkingDir;
@@ -14935,7 +15374,7 @@ function safeCanaryScanValues() {
14935
15374
  return [];
14936
15375
  }
14937
15376
  }
14938
- function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agent, result, dedup, values) {
15377
+ function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, agent, result, dedup, values) {
14939
15378
  if (values.length === 0) return [];
14940
15379
  let pool = [...values];
14941
15380
  const matched = [];
@@ -14949,8 +15388,8 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14949
15388
  const existing = dedup.canaryIndex.get(key);
14950
15389
  if (existing) {
14951
15390
  existing.count++;
14952
- if (timestamp && (!existing.timestamp || timestamp < existing.timestamp)) {
14953
- existing.timestamp = timestamp;
15391
+ if (timestamp2 && (!existing.timestamp || timestamp2 < existing.timestamp)) {
15392
+ existing.timestamp = timestamp2;
14954
15393
  }
14955
15394
  continue;
14956
15395
  }
@@ -14963,7 +15402,7 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14963
15402
  view: hit.view,
14964
15403
  retired: hit.retired,
14965
15404
  toolName,
14966
- timestamp,
15405
+ timestamp: timestamp2,
14967
15406
  project: projLabel,
14968
15407
  sessionId,
14969
15408
  agent,
@@ -14995,7 +15434,7 @@ function scrubDecoys(subject, values) {
14995
15434
  };
14996
15435
  return walk(subject, 0);
14997
15436
  }
14998
- function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
15437
+ function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
14999
15438
  const fsVerdict = analyzeFsOperation(command);
15000
15439
  if (!fsVerdict) return false;
15001
15440
  const synthRule = {
@@ -15025,7 +15464,7 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15025
15464
  source: synthSource,
15026
15465
  toolName,
15027
15466
  input,
15028
- timestamp,
15467
+ timestamp: timestamp2,
15029
15468
  project: projLabel,
15030
15469
  sessionId,
15031
15470
  agent
@@ -15033,9 +15472,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15033
15472
  }
15034
15473
  return true;
15035
15474
  }
15036
- function isStaleFinding(timestamp, now = Date.now()) {
15037
- if (!timestamp) return false;
15038
- const t = Date.parse(timestamp);
15475
+ function isStaleFinding(timestamp2, now = Date.now()) {
15476
+ if (!timestamp2) return false;
15477
+ const t = Date.parse(timestamp2);
15039
15478
  if (Number.isNaN(t)) return false;
15040
15479
  const ageDays = (now - t) / 864e5;
15041
15480
  return ageDays > STALE_AGE_DAYS;
@@ -15183,37 +15622,7 @@ function countScanFiles() {
15183
15622
  } catch {
15184
15623
  }
15185
15624
  }
15186
- const codexDir = path31.join(os27.homedir(), ".codex", "sessions");
15187
- if (fs29.existsSync(codexDir)) {
15188
- try {
15189
- for (const year of fs29.readdirSync(codexDir)) {
15190
- const yp = path31.join(codexDir, year);
15191
- try {
15192
- if (!fs29.statSync(yp).isDirectory()) continue;
15193
- for (const month of fs29.readdirSync(yp)) {
15194
- const mp = path31.join(yp, month);
15195
- try {
15196
- if (!fs29.statSync(mp).isDirectory()) continue;
15197
- for (const day of fs29.readdirSync(mp)) {
15198
- const dp = path31.join(mp, day);
15199
- try {
15200
- if (!fs29.statSync(dp).isDirectory()) continue;
15201
- total += listSessionFiles(dp).length;
15202
- } catch {
15203
- continue;
15204
- }
15205
- }
15206
- } catch {
15207
- continue;
15208
- }
15209
- }
15210
- } catch {
15211
- continue;
15212
- }
15213
- }
15214
- } catch {
15215
- }
15216
- }
15625
+ total += listCodexSessionFiles().length;
15217
15626
  return total;
15218
15627
  }
15219
15628
  function renderProgressBar(done, total, lines) {
@@ -15336,12 +15745,12 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
15336
15745
  }
15337
15746
  continue;
15338
15747
  }
15339
- const usage = entry.message?.usage;
15748
+ const usage2 = entry.message?.usage;
15340
15749
  const model = entry.message?.model;
15341
- if (usage && model) {
15750
+ if (usage2 && model) {
15342
15751
  const p = claudeModelPrice(model);
15343
15752
  if (p) {
15344
- const rowCost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
15753
+ const rowCost = (usage2.input_tokens ?? 0) * p.i + (usage2.output_tokens ?? 0) * p.o + (usage2.cache_creation_input_tokens ?? 0) * p.cw + (usage2.cache_read_input_tokens ?? 0) * p.cr;
15345
15754
  result.totalCostUSD += rowCost;
15346
15755
  session.costUSD += rowCost;
15347
15756
  }
@@ -15875,15 +16284,15 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15875
16284
  } catch {
15876
16285
  continue;
15877
16286
  }
15878
- const timestamp = step.created_at ?? "";
15879
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16287
+ const timestamp2 = step.created_at ?? "";
16288
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
15880
16289
  if (step.type === "USER_INPUT") {
15881
16290
  const text = typeof step.content === "string" ? step.content : "";
15882
16291
  if (text) {
15883
16292
  const decoysHere5 = recordCanaries(
15884
16293
  { text },
15885
16294
  "user-prompt",
15886
- timestamp,
16295
+ timestamp2,
15887
16296
  projLabel,
15888
16297
  sessionId,
15889
16298
  "antigravity",
@@ -15900,7 +16309,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15900
16309
  patternName: dlpMatch.patternName,
15901
16310
  redactedSample: dlpMatch.redactedSample,
15902
16311
  toolName: "user-prompt",
15903
- timestamp,
16312
+ timestamp: timestamp2,
15904
16313
  project: projLabel,
15905
16314
  sessionId,
15906
16315
  agent: "antigravity"
@@ -15911,16 +16320,16 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15911
16320
  continue;
15912
16321
  }
15913
16322
  if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
15914
- if (timestamp) {
15915
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
15916
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16323
+ if (timestamp2) {
16324
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16325
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
15917
16326
  }
15918
16327
  for (const tc of step.tool_calls) {
15919
16328
  result.totalToolCalls++;
15920
16329
  const toolName = tc.name ?? "";
15921
16330
  const toolNameLower = toolName.toLowerCase();
15922
16331
  const input = canonicalToolInput(toolName, tc.args ?? {});
15923
- sessionCalls.push({ toolName, input, timestamp });
16332
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
15924
16333
  const isShellTool = toolNameLower === "run_command";
15925
16334
  if (isShellTool) {
15926
16335
  result.bashCalls++;
@@ -15935,7 +16344,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15935
16344
  const decoysHere6 = recordCanaries(
15936
16345
  input,
15937
16346
  toolName,
15938
- timestamp,
16347
+ timestamp2,
15939
16348
  projLabel,
15940
16349
  sessionId,
15941
16350
  "antigravity",
@@ -15952,7 +16361,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15952
16361
  patternName: dlpMatch.patternName,
15953
16362
  redactedSample: dlpMatch.redactedSample,
15954
16363
  toolName,
15955
- timestamp,
16364
+ timestamp: timestamp2,
15956
16365
  project: projLabel,
15957
16366
  sessionId,
15958
16367
  agent: "antigravity"
@@ -15965,7 +16374,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15965
16374
  String(input.command ?? ""),
15966
16375
  toolName,
15967
16376
  input,
15968
- timestamp,
16377
+ timestamp2,
15969
16378
  projLabel,
15970
16379
  sessionId,
15971
16380
  "antigravity",
@@ -15989,7 +16398,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15989
16398
  source,
15990
16399
  toolName,
15991
16400
  input,
15992
- timestamp,
16401
+ timestamp: timestamp2,
15993
16402
  project: projLabel,
15994
16403
  sessionId,
15995
16404
  agent: "antigravity"
@@ -16021,7 +16430,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
16021
16430
  },
16022
16431
  toolName,
16023
16432
  input,
16024
- timestamp,
16433
+ timestamp: timestamp2,
16025
16434
  project: projLabel,
16026
16435
  sessionId,
16027
16436
  agent: "antigravity"
@@ -16091,7 +16500,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16091
16500
  } catch {
16092
16501
  continue;
16093
16502
  }
16094
- const timestamp = ev.timestamp ?? "";
16503
+ const timestamp2 = ev.timestamp ?? "";
16095
16504
  if (ev.type === "session.start") {
16096
16505
  const cwd = ev.data?.context?.cwd;
16097
16506
  if (typeof cwd === "string" && cwd) {
@@ -16099,14 +16508,14 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16099
16508
  }
16100
16509
  continue;
16101
16510
  }
16102
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16511
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
16103
16512
  if (ev.type === "user.message") {
16104
16513
  const text = ev.data?.content ?? ev.data?.text ?? "";
16105
16514
  if (typeof text === "string" && text) {
16106
16515
  const decoysHere7 = recordCanaries(
16107
16516
  { text },
16108
16517
  "user-prompt",
16109
- timestamp,
16518
+ timestamp2,
16110
16519
  projLabel,
16111
16520
  sessionId,
16112
16521
  "copilot",
@@ -16123,7 +16532,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16123
16532
  patternName: dlpMatch2.patternName,
16124
16533
  redactedSample: dlpMatch2.redactedSample,
16125
16534
  toolName: "user-prompt",
16126
- timestamp,
16535
+ timestamp: timestamp2,
16127
16536
  project: projLabel,
16128
16537
  sessionId,
16129
16538
  agent: "copilot"
@@ -16138,19 +16547,19 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16138
16547
  const toolNameLower = toolName.toLowerCase();
16139
16548
  const input = ev.data?.arguments ?? {};
16140
16549
  result.totalToolCalls++;
16141
- sessionCalls.push({ toolName, input, timestamp });
16550
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
16142
16551
  const isShellTool = isShellShapedTool(toolNameLower, toolInspectionMap);
16143
16552
  if (isShellTool) result.bashCalls++;
16144
- if (timestamp) {
16145
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
16146
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16553
+ if (timestamp2) {
16554
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16555
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
16147
16556
  }
16148
16557
  const rawCmd = String(input.command ?? "").trimStart();
16149
16558
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
16150
16559
  const decoysHere8 = recordCanaries(
16151
16560
  input,
16152
16561
  toolName,
16153
- timestamp,
16562
+ timestamp2,
16154
16563
  projLabel,
16155
16564
  sessionId,
16156
16565
  "copilot",
@@ -16167,7 +16576,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16167
16576
  patternName: dlpMatch.patternName,
16168
16577
  redactedSample: dlpMatch.redactedSample,
16169
16578
  toolName,
16170
- timestamp,
16579
+ timestamp: timestamp2,
16171
16580
  project: projLabel,
16172
16581
  sessionId,
16173
16582
  agent: "copilot"
@@ -16180,7 +16589,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16180
16589
  String(input.command ?? ""),
16181
16590
  toolName,
16182
16591
  input,
16183
- timestamp,
16592
+ timestamp2,
16184
16593
  projLabel,
16185
16594
  sessionId,
16186
16595
  "copilot",
@@ -16203,7 +16612,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16203
16612
  source,
16204
16613
  toolName,
16205
16614
  input,
16206
- timestamp,
16615
+ timestamp: timestamp2,
16207
16616
  project: projLabel,
16208
16617
  sessionId,
16209
16618
  agent: "copilot"
@@ -16235,7 +16644,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16235
16644
  },
16236
16645
  toolName,
16237
16646
  input,
16238
- timestamp,
16647
+ timestamp: timestamp2,
16239
16648
  project: projLabel,
16240
16649
  sessionId,
16241
16650
  agent: "copilot"
@@ -16250,7 +16659,6 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16250
16659
  }
16251
16660
  function scanCodexHistory(startDate, onProgress, onLine) {
16252
16661
  const canaryVals = safeCanaryScanValues();
16253
- const sessionsBase = path31.join(os27.homedir(), ".codex", "sessions");
16254
16662
  const result = {
16255
16663
  filesScanned: 0,
16256
16664
  sessions: 0,
@@ -16267,39 +16675,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16267
16675
  perSession: []
16268
16676
  };
16269
16677
  const dedup = emptyScanDedup();
16270
- if (!fs29.existsSync(sessionsBase)) return result;
16271
- const jsonlFiles = [];
16272
- try {
16273
- for (const year of fs29.readdirSync(sessionsBase)) {
16274
- const yearPath = path31.join(sessionsBase, year);
16275
- try {
16276
- if (!fs29.statSync(yearPath).isDirectory()) continue;
16277
- } catch {
16278
- continue;
16279
- }
16280
- for (const month of fs29.readdirSync(yearPath)) {
16281
- const monthPath = path31.join(yearPath, month);
16282
- try {
16283
- if (!fs29.statSync(monthPath).isDirectory()) continue;
16284
- } catch {
16285
- continue;
16286
- }
16287
- for (const day of fs29.readdirSync(monthPath)) {
16288
- const dayPath = path31.join(monthPath, day);
16289
- try {
16290
- if (!fs29.statSync(dayPath).isDirectory()) continue;
16291
- } catch {
16292
- continue;
16293
- }
16294
- for (const file of fs29.readdirSync(dayPath)) {
16295
- if (file.endsWith(".jsonl")) jsonlFiles.push(path31.join(dayPath, file));
16296
- }
16297
- }
16298
- }
16299
- }
16300
- } catch {
16301
- return result;
16302
- }
16678
+ const jsonlFiles = listCodexSessionFiles();
16303
16679
  const ruleSources = buildRuleSources();
16304
16680
  for (const filePath of jsonlFiles) {
16305
16681
  result.filesScanned++;
@@ -16315,10 +16691,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16315
16691
  let projLabel = "";
16316
16692
  result.sessions++;
16317
16693
  const sessionCalls = [];
16318
- let lastTotalInput = 0;
16319
- let lastTotalCached = 0;
16320
- let lastTotalOutput = 0;
16321
- let model = "";
16322
16694
  for (const line of lines) {
16323
16695
  if (!line.trim()) continue;
16324
16696
  onLine?.();
@@ -16336,18 +16708,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16336
16708
  projLabel = stripTerminalEscapes(cwd.replace(os27.homedir(), "~")).slice(0, 40);
16337
16709
  continue;
16338
16710
  }
16339
- if (entry.type === "turn_context" && typeof payload["model"] === "string") {
16340
- model = payload["model"];
16341
- continue;
16342
- }
16343
- if (entry.type === "event_msg" && payload["type"] === "token_count") {
16344
- const info = payload["info"];
16345
- const usage = info?.["total_token_usage"] ?? {};
16346
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
16347
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
16348
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
16349
- continue;
16350
- }
16351
16711
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
16352
16712
  const text = String(payload["message"] ?? "");
16353
16713
  if (text) {
@@ -16504,13 +16864,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16504
16864
  }
16505
16865
  }
16506
16866
  }
16507
- const withinWindow = !startDate || startTime !== "" && new Date(startTime) >= startDate;
16508
- if (withinWindow) {
16509
- result.totalCostUSD += codexSessionCost(model, {
16510
- input: lastTotalInput,
16511
- cached: lastTotalCached,
16512
- output: lastTotalOutput
16513
- });
16867
+ for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
16868
+ result.totalCostUSD += event.costUSD;
16514
16869
  }
16515
16870
  result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
16516
16871
  }
@@ -17942,13 +18297,13 @@ var init_taint_store = __esm({
17942
18297
  */
17943
18298
  check(filePath) {
17944
18299
  const resolved = this._resolve(filePath);
17945
- const record = this.records.get(resolved);
17946
- if (!record) return null;
17947
- if (Date.now() > record.expiresAt) {
18300
+ const record2 = this.records.get(resolved);
18301
+ if (!record2) return null;
18302
+ if (Date.now() > record2.expiresAt) {
17948
18303
  this.records.delete(resolved);
17949
18304
  return null;
17950
18305
  }
17951
- return record;
18306
+ return record2;
17952
18307
  }
17953
18308
  /**
17954
18309
  * Propagate taint from sourcePath to destPath (e.g. cp, mv).
@@ -17969,8 +18324,8 @@ var init_taint_store = __esm({
17969
18324
  /** Remove all expired records. Called periodically by the daemon. */
17970
18325
  prune() {
17971
18326
  const now = Date.now();
17972
- for (const [key, record] of this.records) {
17973
- if (now > record.expiresAt) this.records.delete(key);
18327
+ for (const [key, record2] of this.records) {
18328
+ if (now > record2.expiresAt) this.records.delete(key);
17974
18329
  }
17975
18330
  }
17976
18331
  /** Return all non-expired taint records (for audit/debug). */
@@ -18009,13 +18364,13 @@ var init_taint_store = __esm({
18009
18364
  * Expired records are pruned on access. */
18010
18365
  check(sessionId) {
18011
18366
  if (!sessionId) return null;
18012
- const record = this.records.get(sessionId);
18013
- if (!record) return null;
18014
- if (Date.now() > record.expiresAt) {
18367
+ const record2 = this.records.get(sessionId);
18368
+ if (!record2) return null;
18369
+ if (Date.now() > record2.expiresAt) {
18015
18370
  this.records.delete(sessionId);
18016
18371
  return null;
18017
18372
  }
18018
- return record;
18373
+ return record2;
18019
18374
  }
18020
18375
  /** Clear a session's taint (e.g. the user resolved it). Returns true if a
18021
18376
  * record was actually removed (false if the session wasn't tainted). */
@@ -18030,8 +18385,8 @@ var init_taint_store = __esm({
18030
18385
  /** Remove all expired records. Called periodically by the daemon. */
18031
18386
  prune() {
18032
18387
  const now = Date.now();
18033
- for (const [key, record] of this.records) {
18034
- if (now > record.expiresAt) this.records.delete(key);
18388
+ for (const [key, record2] of this.records) {
18389
+ if (now > record2.expiresAt) this.records.delete(key);
18035
18390
  }
18036
18391
  }
18037
18392
  /** Remove all records. Used by tests to reset state between runs. */
@@ -22765,10 +23120,10 @@ data: ${JSON.stringify(item.data)}
22765
23120
  return res.end(JSON.stringify({ error: "all paths must be strings" }));
22766
23121
  }
22767
23122
  for (const p of body.paths) {
22768
- const record = taintStore.check(p);
22769
- if (record) {
23123
+ const record2 = taintStore.check(p);
23124
+ if (record2) {
22770
23125
  res.writeHead(200, { "Content-Type": "application/json" });
22771
- return res.end(JSON.stringify({ tainted: true, record }));
23126
+ return res.end(JSON.stringify({ tainted: true, record: record2 }));
22772
23127
  }
22773
23128
  }
22774
23129
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -22817,9 +23172,9 @@ data: ${JSON.stringify(item.data)}
22817
23172
  res.writeHead(400, { "Content-Type": "application/json" });
22818
23173
  return res.end(JSON.stringify({ error: "sessionId must be a string" }));
22819
23174
  }
22820
- const record = sessionTaintStore.check(body.sessionId);
23175
+ const record2 = sessionTaintStore.check(body.sessionId);
22821
23176
  res.writeHead(200, { "Content-Type": "application/json" });
22822
- return res.end(JSON.stringify(record ? { tainted: true, record } : { tainted: false }));
23177
+ return res.end(JSON.stringify(record2 ? { tainted: true, record: record2 } : { tainted: false }));
22823
23178
  } catch {
22824
23179
  res.writeHead(400).end();
22825
23180
  return;
@@ -28765,8 +29120,8 @@ var require_util2 = __commonJS({
28765
29120
  request2.headersList.append("origin", serializedOrigin, true);
28766
29121
  }
28767
29122
  }
28768
- function coarsenTime(timestamp, crossOriginIsolatedCapability) {
28769
- return timestamp;
29123
+ function coarsenTime(timestamp2, crossOriginIsolatedCapability) {
29124
+ return timestamp2;
28770
29125
  }
28771
29126
  function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
28772
29127
  if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
@@ -38847,20 +39202,20 @@ var require_dns = __commonJS({
38847
39202
  return ip;
38848
39203
  }
38849
39204
  setRecords(origin, addresses) {
38850
- const timestamp = Date.now();
39205
+ const timestamp2 = Date.now();
38851
39206
  const records = { records: { 4: null, 6: null } };
38852
39207
  let minTTL = this.#maxTTL;
38853
- for (const record of addresses) {
38854
- record.timestamp = timestamp;
38855
- if (typeof record.ttl === "number") {
38856
- record.ttl = Math.min(record.ttl, this.#maxTTL);
38857
- minTTL = Math.min(minTTL, record.ttl);
39208
+ for (const record2 of addresses) {
39209
+ record2.timestamp = timestamp2;
39210
+ if (typeof record2.ttl === "number") {
39211
+ record2.ttl = Math.min(record2.ttl, this.#maxTTL);
39212
+ minTTL = Math.min(minTTL, record2.ttl);
38858
39213
  } else {
38859
- record.ttl = this.#maxTTL;
39214
+ record2.ttl = this.#maxTTL;
38860
39215
  }
38861
- const familyRecords = records.records[record.family] ?? { ips: [] };
38862
- familyRecords.ips.push(record);
38863
- records.records[record.family] = familyRecords;
39216
+ const familyRecords = records.records[record2.family] ?? { ips: [] };
39217
+ familyRecords.ips.push(record2);
39218
+ records.records[record2.family] = familyRecords;
38864
39219
  }
38865
39220
  this.storage.set(origin.hostname, records, { ttl: minTTL });
38866
39221
  }
@@ -53524,15 +53879,15 @@ import chalk16 from "chalk";
53524
53879
  import fs59 from "fs";
53525
53880
  import path57 from "path";
53526
53881
  import os53 from "os";
53527
- function formatRelativeTime(timestamp) {
53528
- const diff = Date.now() - new Date(timestamp).getTime();
53882
+ function formatRelativeTime(timestamp2) {
53883
+ const diff = Date.now() - new Date(timestamp2).getTime();
53529
53884
  const sec = Math.floor(diff / 1e3);
53530
53885
  if (sec < 60) return `${sec}s ago`;
53531
53886
  const min = Math.floor(sec / 60);
53532
53887
  if (min < 60) return `${min}m ago`;
53533
53888
  const hrs = Math.floor(min / 60);
53534
53889
  if (hrs < 24) return `${hrs}h ago`;
53535
- return new Date(timestamp).toLocaleDateString();
53890
+ return new Date(timestamp2).toLocaleDateString();
53536
53891
  }
53537
53892
  function registerAuditCommand(program2) {
53538
53893
  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) => {
@@ -53778,15 +54133,15 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
53778
54133
  if (!entry.timestamp) continue;
53779
54134
  const ts = new Date(entry.timestamp);
53780
54135
  if (ts < start || ts > end) continue;
53781
- const usage = entry.message?.usage;
54136
+ const usage2 = entry.message?.usage;
53782
54137
  const model = entry.message?.model;
53783
- if (!usage || !model) continue;
54138
+ if (!usage2 || !model) continue;
53784
54139
  const p = claudeModelPrice2(model);
53785
54140
  if (!p) continue;
53786
- const inp = usage.input_tokens ?? 0;
53787
- const out = usage.output_tokens ?? 0;
53788
- const cw = usage.cache_creation_input_tokens ?? 0;
53789
- const cr = usage.cache_read_input_tokens ?? 0;
54141
+ const inp = usage2.input_tokens ?? 0;
54142
+ const out = usage2.output_tokens ?? 0;
54143
+ const cw = usage2.cache_creation_input_tokens ?? 0;
54144
+ const cr = usage2.cache_read_input_tokens ?? 0;
53790
54145
  const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
53791
54146
  acc.total += cost;
53792
54147
  acc.inputTokens += inp;
@@ -53834,90 +54189,21 @@ function processCodexCostFile(filePath, start, end, acc) {
53834
54189
  } catch {
53835
54190
  return;
53836
54191
  }
53837
- let sessionStart = "";
53838
- let model = "";
53839
- let lastTotalInput = 0;
53840
- let lastTotalCached = 0;
53841
- let lastTotalOutput = 0;
53842
- let sessionToolCalls = 0;
54192
+ const parsed = parseCodexUsage(lines);
54193
+ for (const event of codexUsageInWindow(parsed, start, end)) {
54194
+ acc.total += event.costUSD;
54195
+ acc.byDay.set(event.date, (acc.byDay.get(event.date) ?? 0) + event.costUSD);
54196
+ acc.byModel.set(event.model, (acc.byModel.get(event.model) ?? 0) + event.costUSD);
54197
+ }
53843
54198
  for (const line of lines) {
53844
- if (!line.trim()) continue;
53845
- let entry;
53846
54199
  try {
53847
- entry = JSON.parse(line);
54200
+ const entry = JSON.parse(line);
54201
+ if (entry?.type !== "response_item" || entry.payload?.type !== "function_call") continue;
54202
+ const ts = new Date(entry.timestamp ?? parsed.sessionStart);
54203
+ if (ts >= start && ts <= end) acc.toolCalls++;
53848
54204
  } catch {
53849
- continue;
53850
- }
53851
- const p = entry.payload ?? {};
53852
- if (entry.type === "session_meta") {
53853
- sessionStart = String(p["timestamp"] ?? "");
53854
- continue;
53855
- }
53856
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
53857
- model = p["model"];
53858
- continue;
53859
- }
53860
- if (entry.type === "event_msg" && p["type"] === "token_count") {
53861
- const info = p["info"] ?? {};
53862
- const usage = info["total_token_usage"] ?? {};
53863
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
53864
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
53865
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
53866
- }
53867
- if (entry.type === "response_item" && p["type"] === "function_call") {
53868
- sessionToolCalls++;
53869
- }
53870
- }
53871
- if (!sessionStart) return;
53872
- const ts = new Date(sessionStart);
53873
- if (ts < start || ts > end) return;
53874
- const cost = codexSessionCost(model, {
53875
- input: lastTotalInput,
53876
- cached: lastTotalCached,
53877
- output: lastTotalOutput
53878
- });
53879
- acc.total += cost;
53880
- acc.toolCalls += sessionToolCalls;
53881
- const dateKey = sessionStart.slice(0, 10);
53882
- acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
53883
- const normModel = normalizeModel(model || "gpt-5");
53884
- acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
53885
- }
53886
- function listCodexSessionFiles2(sessionsBase) {
53887
- const jsonlFiles = [];
53888
- if (!fs60.existsSync(sessionsBase)) return jsonlFiles;
53889
- try {
53890
- for (const year of fs60.readdirSync(sessionsBase)) {
53891
- const yearPath = path58.join(sessionsBase, year);
53892
- try {
53893
- if (!fs60.statSync(yearPath).isDirectory()) continue;
53894
- } catch {
53895
- continue;
53896
- }
53897
- for (const month of fs60.readdirSync(yearPath)) {
53898
- const monthPath = path58.join(yearPath, month);
53899
- try {
53900
- if (!fs60.statSync(monthPath).isDirectory()) continue;
53901
- } catch {
53902
- continue;
53903
- }
53904
- for (const day of fs60.readdirSync(monthPath)) {
53905
- const dayPath = path58.join(monthPath, day);
53906
- try {
53907
- if (!fs60.statSync(dayPath).isDirectory()) continue;
53908
- } catch {
53909
- continue;
53910
- }
53911
- for (const file of fs60.readdirSync(dayPath)) {
53912
- if (file.endsWith(".jsonl")) jsonlFiles.push(path58.join(dayPath, file));
53913
- }
53914
- }
53915
- }
53916
54205
  }
53917
- } catch {
53918
- return [];
53919
54206
  }
53920
- return jsonlFiles;
53921
54207
  }
53922
54208
  function mergeByModel(...maps) {
53923
54209
  const out = /* @__PURE__ */ new Map();
@@ -53933,7 +54219,7 @@ function loadCodexCost(start, end, sessionsBase) {
53933
54219
  byDay: /* @__PURE__ */ new Map(),
53934
54220
  byModel: /* @__PURE__ */ new Map()
53935
54221
  };
53936
- const files = listCodexSessionFiles2(sessionsBase);
54222
+ const files = listCodexSessionFiles(sessionsBase);
53937
54223
  for (const filePath of files) {
53938
54224
  processCodexCostFile(filePath, start, end, acc);
53939
54225
  }
@@ -54073,7 +54359,7 @@ function aggregateReportFromAudit(period, opts = {}) {
54073
54359
  const now = opts.now ?? /* @__PURE__ */ new Date();
54074
54360
  const auditLogPath = opts.auditLogPath ?? path58.join(os54.homedir(), ".node9", "audit.log");
54075
54361
  const claudeProjectsDir = opts.claudeProjectsDir ?? path58.join(os54.homedir(), ".claude", "projects");
54076
- const codexSessionsDir2 = opts.codexSessionsDir ?? path58.join(os54.homedir(), ".codex", "sessions");
54362
+ const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
54077
54363
  const geminiTmpDir2 = opts.geminiTmpDir ?? path58.join(os54.homedir(), ".gemini", "tmp");
54078
54364
  const hasAuditFile = fs60.existsSync(auditLogPath);
54079
54365
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
@@ -59398,7 +59684,11 @@ var BUILTIN_JAIL = [
59398
59684
  "~/.ssh \u2014 SSH private keys",
59399
59685
  "~/.aws \u2014 AWS credentials",
59400
59686
  ".env files",
59401
- "credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
59687
+ "credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud",
59688
+ // Stage 4 (2026-09-11): reads are blocked; a COPY out of the jail (cp, tar,
59689
+ // scp, rsync, aws s3 cp, ...) is reviewed, because a backup and a theft are
59690
+ // the same command shape.
59691
+ "copies out of the jail (cp, tar, scp, rsync, cloud upload) \u2014 review"
59402
59692
  ];
59403
59693
  function registerJailCommand(program2) {
59404
59694
  const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
@@ -60272,12 +60562,12 @@ function parseSessionLines(lines) {
60272
60562
  continue;
60273
60563
  }
60274
60564
  if (entry.type !== "assistant") continue;
60275
- const usage = entry.message?.usage;
60565
+ const usage2 = entry.message?.usage;
60276
60566
  const model = entry.message?.model;
60277
- if (usage && model) {
60567
+ if (usage2 && model) {
60278
60568
  const p = modelPrice(model);
60279
60569
  if (p) {
60280
- costUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
60570
+ costUSD += (usage2.input_tokens ?? 0) * p.i + (usage2.output_tokens ?? 0) * p.o + (usage2.cache_creation_input_tokens ?? 0) * p.cw + (usage2.cache_read_input_tokens ?? 0) * p.cr;
60281
60571
  }
60282
60572
  }
60283
60573
  const content = entry.message?.content;
@@ -60457,46 +60747,13 @@ function buildGeminiSessions(days, allAuditEntries) {
60457
60747
  return summaries;
60458
60748
  }
60459
60749
  function buildCodexSessions(days, allAuditEntries) {
60460
- const sessionsBase = path69.join(os61.homedir(), ".codex", "sessions");
60461
- if (!fs74.existsSync(sessionsBase)) return [];
60462
60750
  const cutoff = days !== null ? (() => {
60463
60751
  const d = /* @__PURE__ */ new Date();
60464
60752
  d.setDate(d.getDate() - days);
60465
60753
  d.setHours(0, 0, 0, 0);
60466
60754
  return d;
60467
60755
  })() : null;
60468
- const jsonlFiles = [];
60469
- try {
60470
- for (const year of fs74.readdirSync(sessionsBase)) {
60471
- const yearPath = path69.join(sessionsBase, year);
60472
- try {
60473
- if (!fs74.statSync(yearPath).isDirectory()) continue;
60474
- } catch {
60475
- continue;
60476
- }
60477
- for (const month of fs74.readdirSync(yearPath)) {
60478
- const monthPath = path69.join(yearPath, month);
60479
- try {
60480
- if (!fs74.statSync(monthPath).isDirectory()) continue;
60481
- } catch {
60482
- continue;
60483
- }
60484
- for (const day of fs74.readdirSync(monthPath)) {
60485
- const dayPath = path69.join(monthPath, day);
60486
- try {
60487
- if (!fs74.statSync(dayPath).isDirectory()) continue;
60488
- } catch {
60489
- continue;
60490
- }
60491
- for (const file of fs74.readdirSync(dayPath)) {
60492
- if (file.endsWith(".jsonl")) jsonlFiles.push(path69.join(dayPath, file));
60493
- }
60494
- }
60495
- }
60496
- }
60497
- } catch {
60498
- return [];
60499
- }
60756
+ const jsonlFiles = listCodexSessionFiles();
60500
60757
  const summaries = [];
60501
60758
  for (const filePath of jsonlFiles) {
60502
60759
  let lines;
@@ -60511,10 +60768,6 @@ function buildCodexSessions(days, allAuditEntries) {
60511
60768
  let firstPrompt = "";
60512
60769
  const toolCalls = [];
60513
60770
  let lastToolTs = "";
60514
- let lastTotalInput = 0;
60515
- let lastTotalCached = 0;
60516
- let lastTotalOutput = 0;
60517
- let model = "";
60518
60771
  for (const line of lines) {
60519
60772
  if (!line.trim()) continue;
60520
60773
  let entry;
@@ -60530,22 +60783,10 @@ function buildCodexSessions(days, allAuditEntries) {
60530
60783
  cwd = String(p["cwd"] ?? "");
60531
60784
  continue;
60532
60785
  }
60533
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
60534
- model = p["model"];
60535
- continue;
60536
- }
60537
60786
  if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
60538
60787
  firstPrompt = String(p["message"] ?? "");
60539
60788
  continue;
60540
60789
  }
60541
- if (entry.type === "event_msg" && p["type"] === "token_count") {
60542
- const info = p["info"] ?? {};
60543
- const usage = info["total_token_usage"] ?? {};
60544
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
60545
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
60546
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
60547
- continue;
60548
- }
60549
60790
  if (entry.type === "response_item" && p["type"] === "function_call") {
60550
60791
  const tool = String(p["name"] ?? "");
60551
60792
  let input = {};
@@ -60559,12 +60800,13 @@ function buildCodexSessions(days, allAuditEntries) {
60559
60800
  }
60560
60801
  }
60561
60802
  if (!sessionId || !startTime) continue;
60562
- if (cutoff && new Date(startTime) < cutoff) continue;
60563
- const costUSD = codexSessionCost(model, {
60564
- input: lastTotalInput,
60565
- cached: lastTotalCached,
60566
- output: lastTotalOutput
60567
- });
60803
+ const parsedUsage = parseCodexUsage(lines);
60804
+ const usageEvents = codexUsageInWindow(parsedUsage, cutoff);
60805
+ if (cutoff && new Date(startTime) < cutoff && usageEvents.length === 0 && !toolCalls.some((call) => new Date(call.timestamp) >= cutoff))
60806
+ continue;
60807
+ const costUSD = usageEvents.reduce((sum, event) => sum + event.costUSD, 0);
60808
+ const lastUsageTs = parsedUsage.events.at(-1)?.timestamp ?? "";
60809
+ if (lastUsageTs > lastToolTs) lastToolTs = lastUsageTs;
60568
60810
  const windowEnd = new Date(
60569
60811
  Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
60570
60812
  ).toISOString();