@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/README.md +1 -1
- package/dist/cli.js +763 -521
- package/dist/cli.mjs +763 -521
- package/dist/dashboard.mjs +2806 -2331
- package/dist/index.js +375 -92
- package/dist/index.mjs +375 -92
- package/dist/scan-ink.mjs +86 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1146,9 +1146,7 @@ function unwrapCommandHead(words) {
|
|
|
1146
1146
|
while (i < words.length) {
|
|
1147
1147
|
const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
|
|
1148
1148
|
if (head === "find") {
|
|
1149
|
-
const x = words.findIndex(
|
|
1150
|
-
(w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
|
|
1151
|
-
);
|
|
1149
|
+
const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1152
1150
|
if (x < 0) break;
|
|
1153
1151
|
i = x + 1;
|
|
1154
1152
|
continue;
|
|
@@ -1170,7 +1168,11 @@ function unwrapCommandHead(words) {
|
|
|
1170
1168
|
if (t.startsWith("-")) {
|
|
1171
1169
|
i++;
|
|
1172
1170
|
const nxt = words[i];
|
|
1173
|
-
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase())
|
|
1171
|
+
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`,
|
|
1172
|
+
// `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
|
|
1173
|
+
// swallowed and the jail needed a looser fallback whose cost was a
|
|
1174
|
+
// false positive on `sudo echo cat X`.
|
|
1175
|
+
!FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
|
|
1174
1176
|
i++;
|
|
1175
1177
|
continue;
|
|
1176
1178
|
}
|
|
@@ -1289,36 +1291,32 @@ function isProtectedHomePath(rawPath) {
|
|
|
1289
1291
|
}
|
|
1290
1292
|
return true;
|
|
1291
1293
|
}
|
|
1292
|
-
function
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1301
|
-
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1302
|
-
else if (t === "DblQuoted") {
|
|
1303
|
-
const inner = p.Parts || [];
|
|
1304
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1305
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1306
|
-
} else {
|
|
1307
|
-
return null;
|
|
1308
|
-
}
|
|
1294
|
+
function positionedArgs(words, from = 1, to = words.length) {
|
|
1295
|
+
const out = [];
|
|
1296
|
+
let afterFlag = null;
|
|
1297
|
+
for (let i = from; i < to; i++) {
|
|
1298
|
+
const v = words[i];
|
|
1299
|
+
if (v === null) {
|
|
1300
|
+
afterFlag = null;
|
|
1301
|
+
continue;
|
|
1309
1302
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const v = litFromWord(args[i]);
|
|
1317
|
-
if (v === null) continue;
|
|
1318
|
-
if (v.startsWith("-")) flags.push(v);
|
|
1319
|
-
else paths.push(v);
|
|
1303
|
+
if (v.startsWith("-")) {
|
|
1304
|
+
afterFlag = v;
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
out.push({ value: v, index: out.length, argv: i, afterFlag });
|
|
1308
|
+
afterFlag = null;
|
|
1320
1309
|
}
|
|
1321
|
-
return
|
|
1310
|
+
return out;
|
|
1311
|
+
}
|
|
1312
|
+
function extractLiteralArgs(callExpr) {
|
|
1313
|
+
const rawArgs = callExpr.Args || [];
|
|
1314
|
+
if (rawArgs.length === 0) return { name: "", flags: [], paths: [], words: [], args: [] };
|
|
1315
|
+
const words = rawArgs.map((a) => resolveWordLiteral(a));
|
|
1316
|
+
const name = baseWord(words[0]);
|
|
1317
|
+
const flags = words.slice(1).filter((w) => w !== null && w.startsWith("-"));
|
|
1318
|
+
const args = positionedArgs(words);
|
|
1319
|
+
return { name, flags, paths: args.map((a) => a.value), words, args };
|
|
1322
1320
|
}
|
|
1323
1321
|
function resolveWordLiteral(w) {
|
|
1324
1322
|
const parts = w?.Parts || [];
|
|
@@ -1576,16 +1574,21 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
1576
1574
|
}
|
|
1577
1575
|
return sawRm && ok2;
|
|
1578
1576
|
}
|
|
1579
|
-
function analyzeFsOperationImpl(command) {
|
|
1577
|
+
function analyzeFsOperationImpl(command, depth = 0) {
|
|
1580
1578
|
const f = parseShared(command);
|
|
1581
1579
|
if (f === PARSE_FAIL) return null;
|
|
1582
1580
|
let result = null;
|
|
1583
1581
|
try {
|
|
1584
1582
|
syntax.Walk(f, (node) => {
|
|
1585
|
-
if (!node || result) return false;
|
|
1583
|
+
if (!node || result?.verdict === "block") return false;
|
|
1586
1584
|
const n = node;
|
|
1587
|
-
|
|
1588
|
-
|
|
1585
|
+
const nodeType = syntax.NodeType(n);
|
|
1586
|
+
if (nodeType === "Stmt") {
|
|
1587
|
+
result = stricter(result, jailedRedirectRead(n));
|
|
1588
|
+
return result?.verdict !== "block";
|
|
1589
|
+
}
|
|
1590
|
+
if (nodeType !== "CallExpr") return true;
|
|
1591
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
1589
1592
|
if (!name) return true;
|
|
1590
1593
|
if (name === "rm") {
|
|
1591
1594
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -1614,21 +1617,27 @@ function analyzeFsOperationImpl(command) {
|
|
|
1614
1617
|
}
|
|
1615
1618
|
}
|
|
1616
1619
|
}
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
reason: sp.reason,
|
|
1625
|
-
path: p
|
|
1626
|
-
};
|
|
1627
|
-
return false;
|
|
1628
|
-
}
|
|
1620
|
+
if (depth < 1) {
|
|
1621
|
+
const payload = literalShellPayload(words, name);
|
|
1622
|
+
if (payload !== null) {
|
|
1623
|
+
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
1624
|
+
if (inner) {
|
|
1625
|
+
result = inner;
|
|
1626
|
+
return false;
|
|
1629
1627
|
}
|
|
1628
|
+
return true;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
|
|
1632
|
+
if (readPaths) {
|
|
1633
|
+
for (const p of readPaths) {
|
|
1634
|
+
result = stricter(result, matchSensitivePath2(p));
|
|
1635
|
+
if (result?.verdict === "block") return false;
|
|
1630
1636
|
}
|
|
1631
1637
|
}
|
|
1638
|
+
for (const p of copySourcePaths(words)) {
|
|
1639
|
+
result = stricter(result, copyVerdictOf(matchSensitivePath2(p)));
|
|
1640
|
+
}
|
|
1632
1641
|
return true;
|
|
1633
1642
|
});
|
|
1634
1643
|
return result;
|
|
@@ -1636,6 +1645,183 @@ function analyzeFsOperationImpl(command) {
|
|
|
1636
1645
|
return null;
|
|
1637
1646
|
}
|
|
1638
1647
|
}
|
|
1648
|
+
function stricter(a, b) {
|
|
1649
|
+
if (!a) return b;
|
|
1650
|
+
if (!b) return a;
|
|
1651
|
+
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
1652
|
+
}
|
|
1653
|
+
function flagInfo(w) {
|
|
1654
|
+
if (w.startsWith("--")) {
|
|
1655
|
+
const eq = w.indexOf("=");
|
|
1656
|
+
return eq < 0 ? { letter: null, long: w, attached: null } : { letter: null, long: w.slice(0, eq), attached: w.slice(eq + 1) };
|
|
1657
|
+
}
|
|
1658
|
+
const m = /^-([a-zA-Z]+)(.*)$/.exec(w);
|
|
1659
|
+
if (!m) return { letter: null, long: null, attached: null };
|
|
1660
|
+
return { letter: m[1][m[1].length - 1], long: null, attached: m[2] === "" ? null : m[2] };
|
|
1661
|
+
}
|
|
1662
|
+
function flagIs(w, names) {
|
|
1663
|
+
if (w === null) return false;
|
|
1664
|
+
const f = flagInfo(w);
|
|
1665
|
+
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
1666
|
+
}
|
|
1667
|
+
function operandOf(a, names) {
|
|
1668
|
+
if (!names || a.afterFlag === null) return false;
|
|
1669
|
+
return flagIs(a.afterFlag, names) && flagInfo(a.afterFlag).attached === null;
|
|
1670
|
+
}
|
|
1671
|
+
function resolveCopyShape(words, h) {
|
|
1672
|
+
const verb = baseWord(words[h]);
|
|
1673
|
+
if (!verb) return null;
|
|
1674
|
+
const direct = COPY_VERBS[verb];
|
|
1675
|
+
if (direct) return { shape: direct, last: h };
|
|
1676
|
+
const slots = positionedArgs(words, h + 1);
|
|
1677
|
+
for (let i = 0; i < slots.length; i++) {
|
|
1678
|
+
for (let n = 3; n >= 1; n--) {
|
|
1679
|
+
const part = slots.slice(i, i + n);
|
|
1680
|
+
if (part.length < n) continue;
|
|
1681
|
+
const key = [verb, ...part.map((a) => a.value.toLowerCase())].join(" ");
|
|
1682
|
+
const shape = COPY_VERBS[key];
|
|
1683
|
+
if (shape) return { shape, last: part[n - 1].argv };
|
|
1684
|
+
}
|
|
1685
|
+
if (slots[i].afterFlag === null) return null;
|
|
1686
|
+
}
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
function findStartPoints(words, h) {
|
|
1690
|
+
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1691
|
+
if (k < 0) return { k, starts: [] };
|
|
1692
|
+
const firstPredicate = words.findIndex(
|
|
1693
|
+
(w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
1694
|
+
);
|
|
1695
|
+
const end = firstPredicate > h ? firstPredicate : k;
|
|
1696
|
+
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
1697
|
+
}
|
|
1698
|
+
function copySourcePaths(words) {
|
|
1699
|
+
const h = unwrapCommandHead(words);
|
|
1700
|
+
const fi = words.findIndex((w, i) => i <= h && baseWord(w) === "find");
|
|
1701
|
+
if (fi >= 0) {
|
|
1702
|
+
const { k, starts } = findStartPoints(words, fi);
|
|
1703
|
+
if (k < 0) return [];
|
|
1704
|
+
const action = unwrapCommandHead(words.slice(k + 1));
|
|
1705
|
+
return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
|
|
1706
|
+
}
|
|
1707
|
+
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
1708
|
+
const r = resolveCopyShape(words, h);
|
|
1709
|
+
if (!r) return [];
|
|
1710
|
+
const { shape, last } = r;
|
|
1711
|
+
const args = positionedArgs(words, last + 1);
|
|
1712
|
+
const tail = words.slice(last + 1);
|
|
1713
|
+
const skipped = (a) => operandOf(a, shape.skipFlags);
|
|
1714
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && flagIs(w, ["t", "--target-directory"]));
|
|
1715
|
+
const targetOperand = (a) => targetDir && operandOf(a, ["t", "--target-directory"]);
|
|
1716
|
+
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
1717
|
+
const dynamicDest = lastOperand === null;
|
|
1718
|
+
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
1719
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
|
|
1720
|
+
return [];
|
|
1721
|
+
let src;
|
|
1722
|
+
switch (shape.source) {
|
|
1723
|
+
case "all":
|
|
1724
|
+
src = args;
|
|
1725
|
+
break;
|
|
1726
|
+
case "first":
|
|
1727
|
+
src = targetDir ? args : args.slice(0, 1);
|
|
1728
|
+
break;
|
|
1729
|
+
case "flagOperand": {
|
|
1730
|
+
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);
|
|
1731
|
+
return [
|
|
1732
|
+
...args.filter(
|
|
1733
|
+
(a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
|
|
1734
|
+
).map((a) => a.value),
|
|
1735
|
+
...inline
|
|
1736
|
+
];
|
|
1737
|
+
}
|
|
1738
|
+
case "archive":
|
|
1739
|
+
src = archiveInputs(shape.archive, args, tail);
|
|
1740
|
+
break;
|
|
1741
|
+
case "allButLast":
|
|
1742
|
+
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
1743
|
+
break;
|
|
1744
|
+
}
|
|
1745
|
+
return src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
1746
|
+
}
|
|
1747
|
+
function archiveInputs(kind, args, tail) {
|
|
1748
|
+
const first = args[0];
|
|
1749
|
+
const bareKey = first && first.afterFlag === null && TAR_MODE_WORD.test(first.value);
|
|
1750
|
+
if (kind === "tar") {
|
|
1751
|
+
const flagsText = tail.filter((w) => w !== null && w.startsWith("-")).join(" ");
|
|
1752
|
+
const mode = (bareKey ? first.value : "") + flagsText;
|
|
1753
|
+
const extracting = /x|t/.test(bareKey ? first.value.replace(/f/g, "") : "") || /(^|\s)-[a-zA-Z]*[xt]|--extract|--list|--get/.test(flagsText);
|
|
1754
|
+
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
1755
|
+
if (extracting && !writing) return [];
|
|
1756
|
+
void mode;
|
|
1757
|
+
let i = 0;
|
|
1758
|
+
if (bareKey) {
|
|
1759
|
+
i = 1;
|
|
1760
|
+
const next = args[1];
|
|
1761
|
+
if (first.value.includes("f") && next && next.afterFlag === null) i = 2;
|
|
1762
|
+
}
|
|
1763
|
+
return args.slice(i);
|
|
1764
|
+
}
|
|
1765
|
+
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
1766
|
+
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
1767
|
+
return args.slice(2);
|
|
1768
|
+
}
|
|
1769
|
+
function copyVerdictOf(hit) {
|
|
1770
|
+
if (!hit) return null;
|
|
1771
|
+
const ruleName = COPY_RULE_OF[hit.ruleName];
|
|
1772
|
+
if (!ruleName) return null;
|
|
1773
|
+
return {
|
|
1774
|
+
ruleName,
|
|
1775
|
+
verdict: "review",
|
|
1776
|
+
reason: `Copying ${hit.path} moves a credential out of its jail (project-jail shield)`,
|
|
1777
|
+
path: hit.path
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function matchSensitivePath2(p) {
|
|
1781
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1782
|
+
if (sp.match(p))
|
|
1783
|
+
return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
|
|
1784
|
+
}
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
function baseWord(w) {
|
|
1788
|
+
return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1789
|
+
}
|
|
1790
|
+
function wrappedReadPaths(words, name) {
|
|
1791
|
+
if (name === "find") {
|
|
1792
|
+
const { k, starts } = findStartPoints(words, 0);
|
|
1793
|
+
return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
|
|
1794
|
+
}
|
|
1795
|
+
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
1796
|
+
const h = unwrapCommandHead(words);
|
|
1797
|
+
return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
|
|
1798
|
+
}
|
|
1799
|
+
function literalShellPayload(words, name) {
|
|
1800
|
+
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
1801
|
+
const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1802
|
+
if (head === "eval") {
|
|
1803
|
+
const rest = words.slice(h + 1);
|
|
1804
|
+
if (rest.length === 0 || rest.some((w) => w === null)) return null;
|
|
1805
|
+
return rest.join(" ");
|
|
1806
|
+
}
|
|
1807
|
+
if (SHELL_INTERPRETERS.has(head)) {
|
|
1808
|
+
const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
|
|
1809
|
+
if (c < 0) return null;
|
|
1810
|
+
return words[c + 1] ?? null;
|
|
1811
|
+
}
|
|
1812
|
+
return null;
|
|
1813
|
+
}
|
|
1814
|
+
function jailedRedirectRead(stmt) {
|
|
1815
|
+
const redirs = stmt.Redirs || [];
|
|
1816
|
+
for (const r of redirs) {
|
|
1817
|
+
if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
|
|
1818
|
+
const p = resolveWordLiteral(r.Word);
|
|
1819
|
+
if (p === null || p === "") continue;
|
|
1820
|
+
const hit = matchSensitivePath2(p);
|
|
1821
|
+
if (hit) return hit;
|
|
1822
|
+
}
|
|
1823
|
+
return null;
|
|
1824
|
+
}
|
|
1639
1825
|
function analyzeShellCommand(command) {
|
|
1640
1826
|
const actions = [];
|
|
1641
1827
|
const paths = [];
|
|
@@ -1771,8 +1957,8 @@ function splitOnPipe(cmd) {
|
|
|
1771
1957
|
if (current.trim()) segments2.push(current.trim());
|
|
1772
1958
|
return segments2.filter(Boolean);
|
|
1773
1959
|
}
|
|
1774
|
-
function positionalTokens(
|
|
1775
|
-
return
|
|
1960
|
+
function positionalTokens(tokens) {
|
|
1961
|
+
return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
1776
1962
|
}
|
|
1777
1963
|
function analyzePipeChain(command) {
|
|
1778
1964
|
const segments2 = splitOnPipe(command);
|
|
@@ -1795,8 +1981,10 @@ function analyzePipeChain(command) {
|
|
|
1795
1981
|
for (const segment of segments2) {
|
|
1796
1982
|
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
1797
1983
|
if (tokens.length === 0) continue;
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
1984
|
+
const h = unwrapCommandHead(tokens);
|
|
1985
|
+
const head = h < tokens.length ? h : 0;
|
|
1986
|
+
const binary = tokens[head].toLowerCase();
|
|
1987
|
+
const args = positionalTokens(tokens.slice(head));
|
|
1800
1988
|
if (SOURCE_COMMANDS.has(binary)) {
|
|
1801
1989
|
sourceFiles.push(...args);
|
|
1802
1990
|
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
@@ -2274,6 +2462,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2274
2462
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
2275
2463
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
2276
2464
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
2465
|
+
let pendingAstReview;
|
|
2277
2466
|
if (bashCommand !== null) {
|
|
2278
2467
|
const pipeVerdict = pipeChainVerdict(
|
|
2279
2468
|
bashCommand,
|
|
@@ -2285,7 +2474,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2285
2474
|
if (fsVerdict) {
|
|
2286
2475
|
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
2287
2476
|
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
2288
|
-
|
|
2477
|
+
const astVerdict = {
|
|
2289
2478
|
decision: fsVerdict.verdict,
|
|
2290
2479
|
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
2291
2480
|
reason: fsVerdict.reason,
|
|
@@ -2293,6 +2482,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2293
2482
|
ruleName: fsVerdict.ruleName,
|
|
2294
2483
|
ruleDescription: fsVerdict.reason
|
|
2295
2484
|
};
|
|
2485
|
+
if (fsVerdict.verdict === "block") return astVerdict;
|
|
2486
|
+
pendingAstReview = astVerdict;
|
|
2296
2487
|
}
|
|
2297
2488
|
const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
|
|
2298
2489
|
const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
|
|
@@ -2335,7 +2526,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2335
2526
|
const matchedRule = resolvePinned(matches);
|
|
2336
2527
|
if (matchedRule) {
|
|
2337
2528
|
if (matchedRule.verdict === "allow")
|
|
2338
|
-
return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2529
|
+
return pendingAstReview ?? { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2339
2530
|
return {
|
|
2340
2531
|
decision: matchedRule.verdict,
|
|
2341
2532
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
@@ -2362,6 +2553,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2362
2553
|
allTokens = analyzed.allTokens;
|
|
2363
2554
|
pathTokens = analyzed.paths;
|
|
2364
2555
|
const candidates2 = [];
|
|
2556
|
+
if (pendingAstReview) candidates2.push(pendingAstReview);
|
|
2365
2557
|
const evalVerdict = detectDangerousShellExec(shellCommand);
|
|
2366
2558
|
if (evalVerdict === "block") {
|
|
2367
2559
|
return {
|
|
@@ -2658,6 +2850,12 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2658
2850
|
"read-ssh",
|
|
2659
2851
|
"read-gcp",
|
|
2660
2852
|
"read-cred",
|
|
2853
|
+
// Stage 4 (2026-09-11): a copy of a credential file scores like a READ of it --
|
|
2854
|
+
// read-ssh/aws/cred are critical, so copy-ssh/aws/cred are; read-env is high
|
|
2855
|
+
// (below), so copy-env joins the high list, not this one (/code-review).
|
|
2856
|
+
"copy-ssh",
|
|
2857
|
+
"copy-aws",
|
|
2858
|
+
"copy-cred",
|
|
2661
2859
|
"delete-repo",
|
|
2662
2860
|
"helm-uninstall",
|
|
2663
2861
|
"drop-table",
|
|
@@ -2670,6 +2868,7 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2670
2868
|
];
|
|
2671
2869
|
if (criticalPatterns.some((p) => n.includes(p))) return "critical";
|
|
2672
2870
|
const highPatterns = [
|
|
2871
|
+
"copy-env",
|
|
2673
2872
|
"force-push",
|
|
2674
2873
|
"force_push",
|
|
2675
2874
|
"git-destructive",
|
|
@@ -2687,6 +2886,11 @@ function narrativeRuleLabel(name) {
|
|
|
2687
2886
|
const map = {
|
|
2688
2887
|
"read-aws": "AWS credentials read",
|
|
2689
2888
|
"read-ssh": "SSH private key read",
|
|
2889
|
+
// Stage 4 copy twins, so `scan --narrative` prints a label, not a raw slug.
|
|
2890
|
+
"copy-ssh": "SSH private key copied out",
|
|
2891
|
+
"copy-aws": "AWS credentials copied out",
|
|
2892
|
+
"copy-env": ".env file copied out",
|
|
2893
|
+
"copy-cred": "credential file copied out",
|
|
2690
2894
|
"read-gcp": "GCP credentials read",
|
|
2691
2895
|
"read-cred": "credential file read",
|
|
2692
2896
|
"delete-repo": "GitHub repository deletion",
|
|
@@ -3276,7 +3480,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3276
3480
|
}
|
|
3277
3481
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3278
3482
|
}
|
|
3279
|
-
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, 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;
|
|
3483
|
+
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, 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;
|
|
3280
3484
|
var init_dist = __esm({
|
|
3281
3485
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3282
3486
|
"use strict";
|
|
@@ -3901,13 +4105,32 @@ var init_dist = __esm({
|
|
|
3901
4105
|
})
|
|
3902
4106
|
);
|
|
3903
4107
|
SENSITIVE_PATH_PATTERNS = [
|
|
3904
|
-
/[/\\]\.ssh[/\\]/i,
|
|
3905
|
-
/[/\\]\.aws[/\\]/i,
|
|
4108
|
+
/[/\\]\.ssh([/\\]|$)/i,
|
|
4109
|
+
/[/\\]\.aws([/\\]|$)/i,
|
|
3906
4110
|
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
3907
4111
|
/[/\\]\.azure[/\\]/i,
|
|
3908
4112
|
/[/\\]\.kube[/\\]config$/i,
|
|
3909
|
-
|
|
3910
|
-
// .
|
|
4113
|
+
// ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
|
|
4114
|
+
// (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
|
|
4115
|
+
// structural suffix chain rather than a hand-written list, `example|sample|
|
|
4116
|
+
// template` exempt because a fixture stays a fixture whatever follows, and
|
|
4117
|
+
// `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
|
|
4118
|
+
// committed template, `.env.test.local` is gitignored and holds real values.
|
|
4119
|
+
//
|
|
4120
|
+
// It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
|
|
4121
|
+
// blocked while `cat .env.example` allowed: the same file, opposite verdicts,
|
|
4122
|
+
// decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
|
|
4123
|
+
// which is the contract that now holds these copies in step, and stage 5 of
|
|
4124
|
+
// doc/credential-jail-architecture.md, which replaces them with one generated
|
|
4125
|
+
// source.
|
|
4126
|
+
// ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
|
|
4127
|
+
// fixture whatever follows it -- `.env.example.md` is documentation -- but
|
|
4128
|
+
// `.env.example.local` is gitignored by the `.env*.local` convention and holds
|
|
4129
|
+
// real values, exactly the reasoning that anchors `(?!\.test$)` rather than
|
|
4130
|
+
// using `\b`. Without this branch the fixture exemption also bought a two-step
|
|
4131
|
+
// bypass: `cp .env .env.sample`, then read the copy.
|
|
4132
|
+
/[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
4133
|
+
// .env + any suffix chain; fixtures exempt unless .local
|
|
3911
4134
|
/[/\\]\.git-credentials$/i,
|
|
3912
4135
|
/[/\\]\.npmrc$/i,
|
|
3913
4136
|
/[/\\]\.docker[/\\]config\.json$/i,
|
|
@@ -3989,6 +4212,10 @@ var init_dist = __esm({
|
|
|
3989
4212
|
"od",
|
|
3990
4213
|
"xxd",
|
|
3991
4214
|
"hexdump",
|
|
4215
|
+
// Emits the file's bytes, re-encoded, so it is a read by the set's own test
|
|
4216
|
+
// ("does it emit file contents"). Absent until 2026-09-10, which is why
|
|
4217
|
+
// `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
|
|
4218
|
+
"base64",
|
|
3992
4219
|
"strings",
|
|
3993
4220
|
"sort",
|
|
3994
4221
|
"uniq",
|
|
@@ -3996,8 +4223,56 @@ var init_dist = __esm({
|
|
|
3996
4223
|
"nl",
|
|
3997
4224
|
"dd"
|
|
3998
4225
|
]);
|
|
4226
|
+
SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
4227
|
+
RSYNC_SKIP = [
|
|
4228
|
+
"e",
|
|
4229
|
+
"--rsh",
|
|
4230
|
+
"--exclude",
|
|
4231
|
+
"--exclude-from",
|
|
4232
|
+
"--include",
|
|
4233
|
+
"--include-from",
|
|
4234
|
+
"--files-from",
|
|
4235
|
+
"f",
|
|
4236
|
+
"--filter"
|
|
4237
|
+
];
|
|
4238
|
+
COPY_VERBS = {
|
|
4239
|
+
cp: { source: "allButLast", targetDirFlag: true },
|
|
4240
|
+
mv: { source: "allButLast", targetDirFlag: true },
|
|
4241
|
+
install: { source: "allButLast", targetDirFlag: true },
|
|
4242
|
+
ln: { source: "first", targetDirFlag: true },
|
|
4243
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
|
|
4244
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
|
|
4245
|
+
tar: {
|
|
4246
|
+
source: "archive",
|
|
4247
|
+
archive: "tar",
|
|
4248
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
|
|
4249
|
+
},
|
|
4250
|
+
zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
|
|
4251
|
+
ar: { source: "archive", archive: "ar" },
|
|
4252
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
|
|
4253
|
+
gzip: { source: "all" },
|
|
4254
|
+
bzip2: { source: "all" },
|
|
4255
|
+
xz: { source: "all" },
|
|
4256
|
+
"docker cp": { source: "allButLast" },
|
|
4257
|
+
"kubectl cp": { source: "allButLast" },
|
|
4258
|
+
"gsutil cp": { source: "allButLast" },
|
|
4259
|
+
"gsutil rsync": { source: "allButLast" },
|
|
4260
|
+
"rclone copy": { source: "allButLast" },
|
|
4261
|
+
"rclone sync": { source: "allButLast" },
|
|
4262
|
+
"aws s3 cp": { source: "allButLast" },
|
|
4263
|
+
"aws s3 mv": { source: "allButLast" },
|
|
4264
|
+
"aws s3 sync": { source: "allButLast" },
|
|
4265
|
+
"gcloud storage cp": { source: "allButLast" },
|
|
4266
|
+
"az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
|
|
4267
|
+
};
|
|
4268
|
+
TAR_MODE_WORD = /^[a-zA-Z]+$/;
|
|
4269
|
+
COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
|
|
3999
4270
|
FS_OP_PRESCREEN_RE = new RegExp(
|
|
4000
|
-
|
|
4271
|
+
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
4272
|
+
// reader right after `"` / `'`, and without these two characters the
|
|
4273
|
+
// prescreen rejected every string-wrapped read before the parser ran.
|
|
4274
|
+
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
4275
|
+
`(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4001
4276
|
);
|
|
4002
4277
|
HOME_CACHE_ALLOWLIST = [
|
|
4003
4278
|
".cache",
|
|
@@ -4018,12 +4293,12 @@ var init_dist = __esm({
|
|
|
4018
4293
|
{
|
|
4019
4294
|
rule: "shield:project-jail:block-read-ssh",
|
|
4020
4295
|
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
4021
|
-
match: (p) => /(
|
|
4296
|
+
match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
|
|
4022
4297
|
},
|
|
4023
4298
|
{
|
|
4024
4299
|
rule: "shield:project-jail:block-read-aws",
|
|
4025
4300
|
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
4026
|
-
match: (p) => /(
|
|
4301
|
+
match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
|
|
4027
4302
|
},
|
|
4028
4303
|
{
|
|
4029
4304
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
@@ -4067,7 +4342,9 @@ var init_dist = __esm({
|
|
|
4067
4342
|
// symmetry — silently exempts every `.env.test.*` file.
|
|
4068
4343
|
//
|
|
4069
4344
|
// shields.test.ts:983-995 is the canonical contract; keep both in step.
|
|
4070
|
-
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]
|
|
4345
|
+
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
|
|
4346
|
+
p
|
|
4347
|
+
)
|
|
4071
4348
|
},
|
|
4072
4349
|
{
|
|
4073
4350
|
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
@@ -4178,8 +4455,18 @@ var init_dist = __esm({
|
|
|
4178
4455
|
_redirStdinOps = null;
|
|
4179
4456
|
_listOps = null;
|
|
4180
4457
|
WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
|
|
4458
|
+
FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
4181
4459
|
INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
4182
|
-
NET_BINARIES = /* @__PURE__ */ new Set([
|
|
4460
|
+
NET_BINARIES = /* @__PURE__ */ new Set([
|
|
4461
|
+
"curl",
|
|
4462
|
+
"wget",
|
|
4463
|
+
"scp",
|
|
4464
|
+
"ssh",
|
|
4465
|
+
"nc",
|
|
4466
|
+
"ncat",
|
|
4467
|
+
"netcat",
|
|
4468
|
+
"rsync"
|
|
4469
|
+
]);
|
|
4183
4470
|
VALUE_FLAGS = {
|
|
4184
4471
|
curl: /* @__PURE__ */ new Set([
|
|
4185
4472
|
"-d",
|
|
@@ -4268,10 +4555,22 @@ var init_dist = __esm({
|
|
|
4268
4555
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
4269
4556
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
4270
4557
|
REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
4558
|
+
REDIR_FILE_IN_OPS = new Set(
|
|
4559
|
+
[deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
|
|
4560
|
+
);
|
|
4271
4561
|
REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
4272
4562
|
deriveRedirOp("cat <<X\nX"),
|
|
4273
4563
|
deriveRedirOp("cat <<-X\nX")
|
|
4274
4564
|
]);
|
|
4565
|
+
FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
4566
|
+
COPY_RULE_OF = {
|
|
4567
|
+
"shield:project-jail:block-read-ssh": "shield:project-jail:review-copy-ssh",
|
|
4568
|
+
"shield:project-jail:block-read-aws": "shield:project-jail:review-copy-aws",
|
|
4569
|
+
"shield:project-jail:block-read-env": "shield:project-jail:review-copy-env",
|
|
4570
|
+
"shield:project-jail:review-read-credentials": "shield:project-jail:review-copy-credentials"
|
|
4571
|
+
};
|
|
4572
|
+
isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
4573
|
+
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
4275
4574
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
4276
4575
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
4277
4576
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -4295,21 +4594,7 @@ var init_dist = __esm({
|
|
|
4295
4594
|
"deb.debian.org",
|
|
4296
4595
|
"*.ubuntu.com"
|
|
4297
4596
|
];
|
|
4298
|
-
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
4299
|
-
"cat",
|
|
4300
|
-
"head",
|
|
4301
|
-
"tail",
|
|
4302
|
-
"grep",
|
|
4303
|
-
"awk",
|
|
4304
|
-
"sed",
|
|
4305
|
-
"cut",
|
|
4306
|
-
"sort",
|
|
4307
|
-
"tee",
|
|
4308
|
-
"less",
|
|
4309
|
-
"more",
|
|
4310
|
-
"strings",
|
|
4311
|
-
"xxd"
|
|
4312
|
-
]);
|
|
4597
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
4313
4598
|
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
4314
4599
|
"curl",
|
|
4315
4600
|
"wget",
|
|
@@ -4340,16 +4625,25 @@ var init_dist = __esm({
|
|
|
4340
4625
|
"node"
|
|
4341
4626
|
]);
|
|
4342
4627
|
SENSITIVE_PATTERNS = [
|
|
4343
|
-
/
|
|
4344
|
-
|
|
4628
|
+
// Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
|
|
4629
|
+
/(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
4630
|
+
// .env chain; fixtures exempt unless .local
|
|
4345
4631
|
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
4346
4632
|
// SSH private keys
|
|
4347
4633
|
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
4348
4634
|
// certificate files
|
|
4349
|
-
/
|
|
4350
|
-
//
|
|
4351
|
-
/
|
|
4352
|
-
//
|
|
4635
|
+
// The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
|
|
4636
|
+
// the directory counts wherever it appears, while the directory ITSELF counts
|
|
4637
|
+
// only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
|
|
4638
|
+
// `config/.ssh` is more likely a search pattern than a read. These are
|
|
4639
|
+
// extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
|
|
4640
|
+
// contract as the shell tier, so the same boundary is the right one.
|
|
4641
|
+
// Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
|
|
4642
|
+
// identical pipeline naming a file inside that directory.
|
|
4643
|
+
/(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
|
|
4644
|
+
// ~/.ssh/ and ~/.ssh
|
|
4645
|
+
/(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
|
|
4646
|
+
// AWS creds + dir
|
|
4353
4647
|
/(?:^|\/)\.netrc$/i,
|
|
4354
4648
|
// netrc (stores HTTP credentials)
|
|
4355
4649
|
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
@@ -5017,7 +5311,7 @@ var init_dist = __esm({
|
|
|
5017
5311
|
{
|
|
5018
5312
|
field: "command",
|
|
5019
5313
|
op: "matches",
|
|
5020
|
-
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[\\/\\\\]",
|
|
5314
|
+
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[\\/\\\\]",
|
|
5021
5315
|
flags: "i"
|
|
5022
5316
|
}
|
|
5023
5317
|
],
|
|
@@ -5031,7 +5325,7 @@ var init_dist = __esm({
|
|
|
5031
5325
|
{
|
|
5032
5326
|
field: "command",
|
|
5033
5327
|
op: "matches",
|
|
5034
|
-
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[\\/\\\\]",
|
|
5328
|
+
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[\\/\\\\]",
|
|
5035
5329
|
flags: "i"
|
|
5036
5330
|
}
|
|
5037
5331
|
],
|
|
@@ -5045,7 +5339,7 @@ var init_dist = __esm({
|
|
|
5045
5339
|
{
|
|
5046
5340
|
field: "command",
|
|
5047
5341
|
op: "matches",
|
|
5048
|
-
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|$|[;&|>)<])",
|
|
5342
|
+
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|$|[;&|>)<])",
|
|
5049
5343
|
flags: "i"
|
|
5050
5344
|
}
|
|
5051
5345
|
],
|
|
@@ -5059,7 +5353,7 @@ var init_dist = __esm({
|
|
|
5059
5353
|
{
|
|
5060
5354
|
field: "command",
|
|
5061
5355
|
op: "matches",
|
|
5062
|
-
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)",
|
|
5356
|
+
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)",
|
|
5063
5357
|
flags: "i"
|
|
5064
5358
|
}
|
|
5065
5359
|
],
|
|
@@ -5073,7 +5367,7 @@ var init_dist = __esm({
|
|
|
5073
5367
|
{
|
|
5074
5368
|
field: "file_path",
|
|
5075
5369
|
op: "matches",
|
|
5076
|
-
value: "(
|
|
5370
|
+
value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
|
|
5077
5371
|
flags: "i"
|
|
5078
5372
|
}
|
|
5079
5373
|
],
|
|
@@ -5087,7 +5381,7 @@ var init_dist = __esm({
|
|
|
5087
5381
|
{
|
|
5088
5382
|
field: "file_path",
|
|
5089
5383
|
op: "matches",
|
|
5090
|
-
value: "(
|
|
5384
|
+
value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
|
|
5091
5385
|
flags: "i"
|
|
5092
5386
|
}
|
|
5093
5387
|
],
|
|
@@ -5101,7 +5395,7 @@ var init_dist = __esm({
|
|
|
5101
5395
|
{
|
|
5102
5396
|
field: "file_path",
|
|
5103
5397
|
op: "matches",
|
|
5104
|
-
value: "(^|[\\/\\\\])\\.env(
|
|
5398
|
+
value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
|
|
5105
5399
|
flags: "i"
|
|
5106
5400
|
}
|
|
5107
5401
|
],
|
|
@@ -5244,7 +5538,7 @@ var init_dist = __esm({
|
|
|
5244
5538
|
};
|
|
5245
5539
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
5246
5540
|
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;
|
|
5247
|
-
SENSITIVE_PATH_RE =
|
|
5541
|
+
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;
|
|
5248
5542
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
5249
5543
|
"read",
|
|
5250
5544
|
"read_file",
|
|
@@ -5321,7 +5615,7 @@ var init_dist = __esm({
|
|
|
5321
5615
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
5322
5616
|
];
|
|
5323
5617
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5324
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
5618
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
|
|
5325
5619
|
DEDUPE_PREVIEW_LEN = 120;
|
|
5326
5620
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
5327
5621
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -8640,7 +8934,7 @@ function isNetworkTool(toolName, args) {
|
|
|
8640
8934
|
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
|
|
8641
8935
|
const a = args;
|
|
8642
8936
|
const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
|
|
8643
|
-
return
|
|
8937
|
+
return NETWORK_COMMAND_RE.test(cmd);
|
|
8644
8938
|
}
|
|
8645
8939
|
return false;
|
|
8646
8940
|
}
|
|
@@ -9515,7 +9809,7 @@ function canaryRecordById(id) {
|
|
|
9515
9809
|
return null;
|
|
9516
9810
|
}
|
|
9517
9811
|
}
|
|
9518
|
-
var import_crypto7, WRITE_TOOLS;
|
|
9812
|
+
var import_crypto7, WRITE_TOOLS, NETWORK_COMMAND_RE;
|
|
9519
9813
|
var init_orchestrator = __esm({
|
|
9520
9814
|
"src/auth/orchestrator.ts"() {
|
|
9521
9815
|
"use strict";
|
|
@@ -9546,6 +9840,7 @@ var init_orchestrator = __esm({
|
|
|
9546
9840
|
"notebook_edit",
|
|
9547
9841
|
"notebookedit"
|
|
9548
9842
|
]);
|
|
9843
|
+
NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
|
|
9549
9844
|
}
|
|
9550
9845
|
});
|
|
9551
9846
|
|
|
@@ -12609,9 +12904,10 @@ async function ensurePricingLoaded() {
|
|
|
12609
12904
|
memCacheAt = Date.now();
|
|
12610
12905
|
lookupCache.clear();
|
|
12611
12906
|
}
|
|
12612
|
-
function pricingFor(model) {
|
|
12907
|
+
function pricingFor(model, options = {}) {
|
|
12613
12908
|
const norm = normalizeModel(model);
|
|
12614
|
-
const
|
|
12909
|
+
const lookupKey = options.exact ? `exact:${norm}` : norm;
|
|
12910
|
+
const cached = lookupCache.get(lookupKey);
|
|
12615
12911
|
if (cached !== void 0) return cached;
|
|
12616
12912
|
if (memCache === null && !diskChecked) {
|
|
12617
12913
|
diskChecked = true;
|
|
@@ -12631,6 +12927,7 @@ function pricingFor(model) {
|
|
|
12631
12927
|
resolved = exact;
|
|
12632
12928
|
break;
|
|
12633
12929
|
}
|
|
12930
|
+
if (options.exact) continue;
|
|
12634
12931
|
let best = null;
|
|
12635
12932
|
for (const key of Object.keys(source)) {
|
|
12636
12933
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
@@ -12642,7 +12939,7 @@ function pricingFor(model) {
|
|
|
12642
12939
|
break;
|
|
12643
12940
|
}
|
|
12644
12941
|
}
|
|
12645
|
-
lookupCache.set(
|
|
12942
|
+
lookupCache.set(lookupKey, resolved);
|
|
12646
12943
|
return resolved;
|
|
12647
12944
|
}
|
|
12648
12945
|
var import_fs18, import_path20, import_os17, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
@@ -12679,6 +12976,18 @@ var init_litellm = __esm({
|
|
|
12679
12976
|
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
12680
12977
|
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
12681
12978
|
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
12979
|
+
// Codex offline rates checked against official OpenAI model pages, 2026-09-11.
|
|
12980
|
+
"gpt-5.1-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
12981
|
+
"gpt-5.1-codex-max": [125e-8, 1e-5, 0, 125e-9],
|
|
12982
|
+
"gpt-5.1-codex-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
12983
|
+
"gpt-5.2-codex": [175e-8, 14e-6, 0, 175e-9],
|
|
12984
|
+
"gpt-5.3-codex": [175e-8, 14e-6, 0, 175e-9],
|
|
12985
|
+
"gpt-5.4": [25e-7, 15e-6, 0, 25e-8],
|
|
12986
|
+
"gpt-5.4-mini": [75e-8, 45e-7, 0, 75e-9],
|
|
12987
|
+
"gpt-5.5": [5e-6, 3e-5, 0, 5e-7],
|
|
12988
|
+
"gpt-5.6-sol": [4e-6, 2e-5, 5e-6, 4e-7],
|
|
12989
|
+
"gpt-5.6-terra": [2e-6, 12e-6, 25e-7, 2e-7],
|
|
12990
|
+
"gpt-6-astra": [1e-5, 5e-5, 125e-7, 1e-6],
|
|
12682
12991
|
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
12683
12992
|
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
12684
12993
|
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
@@ -12847,103 +13156,259 @@ var init_cost_gemini = __esm({
|
|
|
12847
13156
|
|
|
12848
13157
|
// src/cost-codex.ts
|
|
12849
13158
|
function codexSessionsDir() {
|
|
12850
|
-
return import_path22.default.join(import_os19.default.homedir(), ".codex", "sessions");
|
|
13159
|
+
return import_path22.default.join(process.env.CODEX_HOME?.trim() || import_path22.default.join(import_os19.default.homedir(), ".codex"), "sessions");
|
|
12851
13160
|
}
|
|
12852
13161
|
function codexPriceFor(model) {
|
|
12853
|
-
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
13162
|
+
return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
|
|
12854
13163
|
}
|
|
12855
|
-
function
|
|
12856
|
-
|
|
12857
|
-
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
12858
|
-
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
13164
|
+
function codexModel(model) {
|
|
13165
|
+
return normalizeModel(model.replace(/^openai\//i, "").replace(/-\d{4}-\d{2}-\d{2}$/, ""));
|
|
12859
13166
|
}
|
|
12860
|
-
function
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
13167
|
+
function addTokens(previous, delta) {
|
|
13168
|
+
return {
|
|
13169
|
+
input: (previous?.input ?? 0) + delta.input,
|
|
13170
|
+
cached: (previous?.cached ?? 0) + delta.cached,
|
|
13171
|
+
output: (previous?.output ?? 0) + delta.output,
|
|
13172
|
+
cacheWrite: (previous?.cacheWrite ?? 0) + delta.cacheWrite
|
|
13173
|
+
};
|
|
13174
|
+
}
|
|
13175
|
+
function codexSessionCost(model, tokens, request2) {
|
|
13176
|
+
const input = tokenNumber(tokens.input);
|
|
13177
|
+
const cached = Math.min(input, tokenNumber(tokens.cached));
|
|
13178
|
+
const written = Math.min(input - cached, tokenNumber(tokens.cacheWrite));
|
|
13179
|
+
const [pin, pout, pcw, pcr] = codexPriceFor(model || "gpt-5");
|
|
13180
|
+
const longContext = request2 && request2.inputTokens > 272e3 && ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"].includes(
|
|
13181
|
+
codexModel(model)
|
|
13182
|
+
);
|
|
13183
|
+
const inputMultiplier = longContext ? 2 : 1;
|
|
13184
|
+
const outputMultiplier = longContext ? 1.5 : 1;
|
|
13185
|
+
const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
|
|
13186
|
+
return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
|
|
13187
|
+
}
|
|
13188
|
+
function statAndFirstLine(file) {
|
|
13189
|
+
const CAP = 4 * 1024 * 1024;
|
|
13190
|
+
const CHUNK = 64 * 1024;
|
|
13191
|
+
const fd = import_fs20.default.openSync(file, "r");
|
|
13192
|
+
try {
|
|
13193
|
+
const stat = import_fs20.default.fstatSync(fd);
|
|
13194
|
+
const limit = Math.min(stat.size, CAP);
|
|
13195
|
+
const parts = [];
|
|
13196
|
+
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
13197
|
+
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
13198
|
+
const read2 = import_fs20.default.readSync(fd, buf, 0, buf.length, pos);
|
|
13199
|
+
if (read2 <= 0) break;
|
|
13200
|
+
const slice = buf.subarray(0, read2);
|
|
13201
|
+
const nl = slice.indexOf(10);
|
|
13202
|
+
parts.push(nl >= 0 ? slice.subarray(0, nl) : slice);
|
|
13203
|
+
if (nl >= 0) break;
|
|
13204
|
+
}
|
|
13205
|
+
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
13206
|
+
} finally {
|
|
13207
|
+
import_fs20.default.closeSync(fd);
|
|
13208
|
+
}
|
|
13209
|
+
}
|
|
13210
|
+
function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
13211
|
+
const files = [];
|
|
13212
|
+
const walk = (dir) => {
|
|
13213
|
+
try {
|
|
13214
|
+
for (const entry of import_fs20.default.readdirSync(dir, { withFileTypes: true })) {
|
|
13215
|
+
const file = import_path22.default.join(dir, entry.name);
|
|
13216
|
+
if (entry.isDirectory()) walk(file);
|
|
13217
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
|
|
13218
|
+
}
|
|
13219
|
+
} catch {
|
|
13220
|
+
}
|
|
13221
|
+
};
|
|
13222
|
+
walk(base);
|
|
13223
|
+
if (import_path22.default.basename(base) === "sessions") walk(import_path22.default.join(import_path22.default.dirname(base), "archived_sessions"));
|
|
13224
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
13225
|
+
for (const file of files.sort()) {
|
|
13226
|
+
try {
|
|
13227
|
+
const { stat, first: head } = statAndFirstLine(file);
|
|
13228
|
+
let id = "";
|
|
13229
|
+
try {
|
|
13230
|
+
const first = JSON.parse(head);
|
|
13231
|
+
if (first?.type === "session_meta" && typeof first.payload?.id === "string")
|
|
13232
|
+
id = first.payload.id;
|
|
13233
|
+
} catch {
|
|
12874
13234
|
}
|
|
13235
|
+
const key = id ? `session:${id}` : `file:${file}`;
|
|
13236
|
+
const prior = sessions.get(key);
|
|
13237
|
+
if (!prior || stat.mtimeMs > prior.mtime || stat.mtimeMs === prior.mtime && stat.size > prior.size) {
|
|
13238
|
+
sessions.set(key, { file, mtime: stat.mtimeMs, size: stat.size });
|
|
13239
|
+
}
|
|
13240
|
+
} catch {
|
|
12875
13241
|
}
|
|
12876
13242
|
}
|
|
12877
|
-
return
|
|
13243
|
+
return [...sessions.values()].map((s) => s.file);
|
|
12878
13244
|
}
|
|
12879
|
-
function
|
|
12880
|
-
|
|
12881
|
-
return import_fs20.default.readdirSync(dir);
|
|
12882
|
-
} catch {
|
|
12883
|
-
return [];
|
|
12884
|
-
}
|
|
13245
|
+
function record(value) {
|
|
13246
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12885
13247
|
}
|
|
12886
|
-
function
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
13248
|
+
function tokenNumber(value) {
|
|
13249
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
13250
|
+
}
|
|
13251
|
+
function usage(value, fallback) {
|
|
13252
|
+
const u = record(value);
|
|
13253
|
+
if (!["input_tokens", "output_tokens"].some((k) => typeof u[k] === "number")) return null;
|
|
13254
|
+
for (const key of [
|
|
13255
|
+
"input_tokens",
|
|
13256
|
+
"cached_input_tokens",
|
|
13257
|
+
"cache_read_input_tokens",
|
|
13258
|
+
"output_tokens",
|
|
13259
|
+
"cache_write_input_tokens"
|
|
13260
|
+
]) {
|
|
13261
|
+
if (u[key] !== void 0 && (typeof u[key] !== "number" || !Number.isFinite(u[key]) || u[key] < 0))
|
|
13262
|
+
return null;
|
|
12891
13263
|
}
|
|
13264
|
+
return {
|
|
13265
|
+
input: tokenNumber(u.input_tokens ?? fallback?.input),
|
|
13266
|
+
cached: tokenNumber(u.cached_input_tokens ?? u.cache_read_input_tokens ?? fallback?.cached),
|
|
13267
|
+
output: tokenNumber(u.output_tokens ?? fallback?.output),
|
|
13268
|
+
cacheWrite: tokenNumber(u.cache_write_input_tokens ?? fallback?.cacheWrite)
|
|
13269
|
+
};
|
|
12892
13270
|
}
|
|
12893
|
-
function
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
13271
|
+
function timestamp(value) {
|
|
13272
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : "";
|
|
13273
|
+
}
|
|
13274
|
+
function parseCodexUsage(lines) {
|
|
13275
|
+
const result = {
|
|
13276
|
+
events: [],
|
|
13277
|
+
sessionStart: "",
|
|
13278
|
+
runId: "",
|
|
13279
|
+
workingDir: "",
|
|
13280
|
+
legacyModels: []
|
|
13281
|
+
};
|
|
13282
|
+
let model = "gpt-5";
|
|
13283
|
+
let serviceTier;
|
|
13284
|
+
let previous = null;
|
|
13285
|
+
const legacyModels = /* @__PURE__ */ new Set();
|
|
13286
|
+
const seenStandalone = /* @__PURE__ */ new Set();
|
|
12902
13287
|
for (const raw of lines) {
|
|
12903
|
-
if (!raw.trim()) continue;
|
|
12904
13288
|
let entry;
|
|
12905
13289
|
try {
|
|
12906
|
-
entry = JSON.parse(raw);
|
|
13290
|
+
entry = record(JSON.parse(raw));
|
|
12907
13291
|
} catch {
|
|
12908
13292
|
continue;
|
|
12909
13293
|
}
|
|
12910
|
-
const p = entry.payload
|
|
13294
|
+
const p = record(entry.payload);
|
|
12911
13295
|
if (entry.type === "session_meta") {
|
|
12912
|
-
|
|
12913
|
-
if (!runId && typeof p
|
|
12914
|
-
if (!
|
|
13296
|
+
result.sessionStart ||= timestamp(p.timestamp ?? entry.timestamp);
|
|
13297
|
+
if (!result.runId && typeof p.id === "string") result.runId = p.id;
|
|
13298
|
+
if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
|
|
12915
13299
|
continue;
|
|
12916
13300
|
}
|
|
12917
13301
|
if (entry.type === "turn_context") {
|
|
12918
|
-
if (typeof p
|
|
12919
|
-
|
|
13302
|
+
if (typeof p.model === "string" && p.model) {
|
|
13303
|
+
model = p.model;
|
|
13304
|
+
legacyModels.add(normalizeModel(model));
|
|
13305
|
+
}
|
|
13306
|
+
if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
|
|
13307
|
+
serviceTier = typeof p.service_tier === "string" ? p.service_tier : void 0;
|
|
12920
13308
|
continue;
|
|
12921
13309
|
}
|
|
12922
|
-
if (entry.type
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
13310
|
+
if (entry.type !== "event_msg" || p.type !== "token_count") continue;
|
|
13311
|
+
const info = record(p.info);
|
|
13312
|
+
const total = usage(info.total_token_usage, previous);
|
|
13313
|
+
const last = usage(info.last_token_usage);
|
|
13314
|
+
if (!total && !last) continue;
|
|
13315
|
+
const eventModel = [info.model, info.model_name, p.model].find(
|
|
13316
|
+
(v) => typeof v === "string" && v
|
|
13317
|
+
);
|
|
13318
|
+
if (typeof eventModel === "string") model = eventModel;
|
|
13319
|
+
let delta;
|
|
13320
|
+
if (total) {
|
|
13321
|
+
if (previous && Object.keys(total).every(
|
|
13322
|
+
(k) => total[k] === previous[k]
|
|
13323
|
+
))
|
|
13324
|
+
continue;
|
|
13325
|
+
const reset = previous && (total.input < previous.input || total.output < previous.output);
|
|
13326
|
+
delta = reset ? last ?? total : {
|
|
13327
|
+
input: Math.max(0, total.input - (previous?.input ?? 0)),
|
|
13328
|
+
cached: Math.max(0, total.cached - (previous?.cached ?? 0)),
|
|
13329
|
+
output: Math.max(0, total.output - (previous?.output ?? 0)),
|
|
13330
|
+
cacheWrite: Math.max(0, total.cacheWrite - (previous?.cacheWrite ?? 0))
|
|
13331
|
+
};
|
|
13332
|
+
previous = total;
|
|
13333
|
+
} else {
|
|
13334
|
+
delta = last;
|
|
13335
|
+
const key = JSON.stringify([entry.timestamp, model, delta]);
|
|
13336
|
+
if (entry.timestamp && seenStandalone.has(key)) continue;
|
|
13337
|
+
if (entry.timestamp) seenStandalone.add(key);
|
|
13338
|
+
previous = addTokens(previous, delta);
|
|
13339
|
+
}
|
|
13340
|
+
if (delta.input === 0 && delta.output === 0) continue;
|
|
13341
|
+
const ts = timestamp(entry.timestamp) || result.sessionStart;
|
|
13342
|
+
if (!ts) continue;
|
|
13343
|
+
const cached = Math.min(delta.input, delta.cached);
|
|
13344
|
+
const written = Math.min(delta.input - cached, delta.cacheWrite);
|
|
13345
|
+
result.events.push({
|
|
13346
|
+
timestamp: ts,
|
|
13347
|
+
date: ts.slice(0, 10),
|
|
13348
|
+
model: normalizeModel(model),
|
|
13349
|
+
workingDir: result.workingDir,
|
|
13350
|
+
runId: result.runId,
|
|
13351
|
+
costUSD: codexSessionCost(model, delta, {
|
|
13352
|
+
inputTokens: last?.input ?? delta.input,
|
|
13353
|
+
serviceTier: typeof info.service_tier === "string" ? info.service_tier : serviceTier
|
|
13354
|
+
}),
|
|
13355
|
+
inputTokens: delta.input - cached - written,
|
|
13356
|
+
outputTokens: delta.output,
|
|
13357
|
+
cacheReadTokens: cached,
|
|
13358
|
+
cacheWriteTokens: written
|
|
13359
|
+
});
|
|
13360
|
+
}
|
|
13361
|
+
result.legacyModels = [...legacyModels.size ? legacyModels : ["gpt-5"]];
|
|
13362
|
+
return result;
|
|
13363
|
+
}
|
|
13364
|
+
function codexUsageInWindow(usage2, start, end) {
|
|
13365
|
+
return usage2.events.filter(
|
|
13366
|
+
(e) => (!start || Date.parse(e.timestamp) >= start.getTime()) && (!end || Date.parse(e.timestamp) <= end.getTime())
|
|
13367
|
+
);
|
|
13368
|
+
}
|
|
13369
|
+
function parseCodexSession(lines) {
|
|
13370
|
+
const parsed = parseCodexUsage(lines);
|
|
13371
|
+
if (!parsed.events.length) return [];
|
|
13372
|
+
const rows = /* @__PURE__ */ new Map();
|
|
13373
|
+
if (parsed.sessionStart) {
|
|
13374
|
+
for (const model of parsed.legacyModels) {
|
|
13375
|
+
rows.set(`${parsed.sessionStart.slice(0, 10)}::${model}`, {
|
|
13376
|
+
date: parsed.sessionStart.slice(0, 10),
|
|
13377
|
+
model,
|
|
13378
|
+
workingDir: parsed.workingDir,
|
|
13379
|
+
runId: parsed.runId,
|
|
13380
|
+
costUSD: 0,
|
|
13381
|
+
inputTokens: 0,
|
|
13382
|
+
outputTokens: 0,
|
|
13383
|
+
cacheReadTokens: 0,
|
|
13384
|
+
cacheWriteTokens: 0
|
|
13385
|
+
});
|
|
13386
|
+
}
|
|
13387
|
+
}
|
|
13388
|
+
for (const event of parsed.events) {
|
|
13389
|
+
const e = {
|
|
13390
|
+
date: event.date,
|
|
13391
|
+
model: event.model,
|
|
13392
|
+
workingDir: event.workingDir,
|
|
13393
|
+
runId: event.runId,
|
|
13394
|
+
costUSD: event.costUSD,
|
|
13395
|
+
inputTokens: event.inputTokens,
|
|
13396
|
+
outputTokens: event.outputTokens,
|
|
13397
|
+
cacheReadTokens: event.cacheReadTokens,
|
|
13398
|
+
cacheWriteTokens: event.cacheWriteTokens
|
|
13399
|
+
};
|
|
13400
|
+
const key = `${e.date}::${e.model}`;
|
|
13401
|
+
const prev = rows.get(key);
|
|
13402
|
+
if (!prev) rows.set(key, { ...e });
|
|
13403
|
+
else {
|
|
13404
|
+
prev.costUSD += e.costUSD;
|
|
13405
|
+
prev.inputTokens += e.inputTokens;
|
|
13406
|
+
prev.outputTokens += e.outputTokens;
|
|
13407
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
13408
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
12929
13409
|
}
|
|
12930
13410
|
}
|
|
12931
|
-
|
|
12932
|
-
const nonCached = Math.max(0, input - cached);
|
|
12933
|
-
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
12934
|
-
const norm = normalizeModel(model || "gpt-5");
|
|
12935
|
-
const costUSD = codexSessionCost(model, { input, cached, output });
|
|
12936
|
-
return {
|
|
12937
|
-
date: sessionStart.slice(0, 10),
|
|
12938
|
-
model: norm,
|
|
12939
|
-
workingDir: cwd,
|
|
12940
|
-
runId,
|
|
12941
|
-
costUSD,
|
|
12942
|
-
inputTokens: nonCached,
|
|
12943
|
-
outputTokens: output,
|
|
12944
|
-
cacheReadTokens: cached,
|
|
12945
|
-
cacheWriteTokens: 0
|
|
12946
|
-
};
|
|
13411
|
+
return [...rows.values()];
|
|
12947
13412
|
}
|
|
12948
13413
|
var import_fs20, import_os19, import_path22, CODEX_FALLBACK, codexSource;
|
|
12949
13414
|
var init_cost_codex = __esm({
|
|
@@ -12956,43 +13421,17 @@ var init_cost_codex = __esm({
|
|
|
12956
13421
|
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
12957
13422
|
codexSource = {
|
|
12958
13423
|
id: "codex",
|
|
12959
|
-
available()
|
|
12960
|
-
try {
|
|
12961
|
-
return import_fs20.default.existsSync(codexSessionsDir());
|
|
12962
|
-
} catch {
|
|
12963
|
-
return false;
|
|
12964
|
-
}
|
|
12965
|
-
},
|
|
13424
|
+
available: () => import_fs20.default.existsSync(codexSessionsDir()) || import_fs20.default.existsSync(import_path22.default.join(import_path22.default.dirname(codexSessionsDir()), "archived_sessions")),
|
|
12966
13425
|
collect(sinceMs) {
|
|
12967
|
-
const
|
|
12968
|
-
const
|
|
12969
|
-
for (const file of listCodexSessionFiles(base)) {
|
|
13426
|
+
const entries = [];
|
|
13427
|
+
for (const file of listCodexSessionFiles()) {
|
|
12970
13428
|
try {
|
|
12971
13429
|
if (sinceMs !== void 0 && import_fs20.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
13430
|
+
entries.push(...parseCodexSession(import_fs20.default.readFileSync(file, "utf8").split("\n")));
|
|
12972
13431
|
} catch {
|
|
12973
|
-
continue;
|
|
12974
|
-
}
|
|
12975
|
-
let content;
|
|
12976
|
-
try {
|
|
12977
|
-
content = import_fs20.default.readFileSync(file, "utf8");
|
|
12978
|
-
} catch {
|
|
12979
|
-
continue;
|
|
12980
|
-
}
|
|
12981
|
-
const e = parseCodexSession(content.split("\n"));
|
|
12982
|
-
if (!e) continue;
|
|
12983
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
12984
|
-
const prev = combined.get(key);
|
|
12985
|
-
if (prev) {
|
|
12986
|
-
prev.costUSD += e.costUSD;
|
|
12987
|
-
prev.inputTokens += e.inputTokens;
|
|
12988
|
-
prev.outputTokens += e.outputTokens;
|
|
12989
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
12990
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
12991
|
-
} else {
|
|
12992
|
-
combined.set(key, { ...e });
|
|
12993
13432
|
}
|
|
12994
13433
|
}
|
|
12995
|
-
return
|
|
13434
|
+
return entries;
|
|
12996
13435
|
}
|
|
12997
13436
|
};
|
|
12998
13437
|
}
|
|
@@ -13002,7 +13441,7 @@ var init_cost_codex = __esm({
|
|
|
13002
13441
|
function copilotSessionsDir() {
|
|
13003
13442
|
return import_path23.default.join(import_os20.default.homedir(), ".copilot", "session-state");
|
|
13004
13443
|
}
|
|
13005
|
-
function
|
|
13444
|
+
function safeReaddir2(dir) {
|
|
13006
13445
|
try {
|
|
13007
13446
|
return import_fs21.default.readdirSync(dir);
|
|
13008
13447
|
} catch {
|
|
@@ -13091,7 +13530,7 @@ var init_cost_copilot = __esm({
|
|
|
13091
13530
|
collect(sinceMs) {
|
|
13092
13531
|
const base = copilotSessionsDir();
|
|
13093
13532
|
const combined = /* @__PURE__ */ new Map();
|
|
13094
|
-
for (const sid of
|
|
13533
|
+
for (const sid of safeReaddir2(base)) {
|
|
13095
13534
|
const file = import_path23.default.join(base, sid, "events.jsonl");
|
|
13096
13535
|
try {
|
|
13097
13536
|
if (sinceMs !== void 0 && import_fs21.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
@@ -13705,8 +14144,8 @@ function originForRule(ruleName, sections, enabled) {
|
|
|
13705
14144
|
}
|
|
13706
14145
|
return "";
|
|
13707
14146
|
}
|
|
13708
|
-
function relativeDate(
|
|
13709
|
-
const t = new Date(
|
|
14147
|
+
function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
14148
|
+
const t = new Date(timestamp2).getTime();
|
|
13710
14149
|
if (Number.isNaN(t)) return "?";
|
|
13711
14150
|
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
13712
14151
|
if (days < 1) return "today";
|
|
@@ -13817,7 +14256,7 @@ function readPreviousScan(opts = {}) {
|
|
|
13817
14256
|
return null;
|
|
13818
14257
|
}
|
|
13819
14258
|
}
|
|
13820
|
-
function appendScanHistory(
|
|
14259
|
+
function appendScanHistory(record2, opts = {}) {
|
|
13821
14260
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
13822
14261
|
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
13823
14262
|
try {
|
|
@@ -13832,7 +14271,7 @@ function appendScanHistory(record, opts = {}) {
|
|
|
13832
14271
|
} catch {
|
|
13833
14272
|
}
|
|
13834
14273
|
}
|
|
13835
|
-
history.push(
|
|
14274
|
+
history.push(record2);
|
|
13836
14275
|
if (history.length > cap) {
|
|
13837
14276
|
history = history.slice(history.length - cap);
|
|
13838
14277
|
}
|
|
@@ -13893,17 +14332,17 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
13893
14332
|
if (row["type"] !== "assistant") continue;
|
|
13894
14333
|
const msg = row["message"];
|
|
13895
14334
|
if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
|
|
13896
|
-
const
|
|
14335
|
+
const usage2 = msg["usage"];
|
|
13897
14336
|
const model = msg["model"];
|
|
13898
|
-
const
|
|
13899
|
-
if (typeof
|
|
13900
|
-
const date =
|
|
14337
|
+
const timestamp2 = row["timestamp"];
|
|
14338
|
+
if (typeof timestamp2 !== "string" || timestamp2.length < 10) continue;
|
|
14339
|
+
const date = timestamp2.slice(0, 10);
|
|
13901
14340
|
const p = pricingFor(model);
|
|
13902
14341
|
if (!p) continue;
|
|
13903
|
-
const inp = Number(
|
|
13904
|
-
const out = Number(
|
|
13905
|
-
const cw = Number(
|
|
13906
|
-
const cr = Number(
|
|
14342
|
+
const inp = Number(usage2["input_tokens"] ?? 0);
|
|
14343
|
+
const out = Number(usage2["output_tokens"] ?? 0);
|
|
14344
|
+
const cw = Number(usage2["cache_creation_input_tokens"] ?? 0);
|
|
14345
|
+
const cr = Number(usage2["cache_read_input_tokens"] ?? 0);
|
|
13907
14346
|
const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
|
|
13908
14347
|
const rowCwd = typeof row["cwd"] === "string" ? row["cwd"] : null;
|
|
13909
14348
|
const workingDir = rowCwd && rowCwd.startsWith("/") ? rowCwd : fallbackWorkingDir;
|
|
@@ -14931,7 +15370,7 @@ function safeCanaryScanValues() {
|
|
|
14931
15370
|
return [];
|
|
14932
15371
|
}
|
|
14933
15372
|
}
|
|
14934
|
-
function recordCanaries(scanned, toolName,
|
|
15373
|
+
function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, agent, result, dedup, values) {
|
|
14935
15374
|
if (values.length === 0) return [];
|
|
14936
15375
|
let pool = [...values];
|
|
14937
15376
|
const matched = [];
|
|
@@ -14945,8 +15384,8 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
|
|
|
14945
15384
|
const existing = dedup.canaryIndex.get(key);
|
|
14946
15385
|
if (existing) {
|
|
14947
15386
|
existing.count++;
|
|
14948
|
-
if (
|
|
14949
|
-
existing.timestamp =
|
|
15387
|
+
if (timestamp2 && (!existing.timestamp || timestamp2 < existing.timestamp)) {
|
|
15388
|
+
existing.timestamp = timestamp2;
|
|
14950
15389
|
}
|
|
14951
15390
|
continue;
|
|
14952
15391
|
}
|
|
@@ -14959,7 +15398,7 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
|
|
|
14959
15398
|
view: hit.view,
|
|
14960
15399
|
retired: hit.retired,
|
|
14961
15400
|
toolName,
|
|
14962
|
-
timestamp,
|
|
15401
|
+
timestamp: timestamp2,
|
|
14963
15402
|
project: projLabel,
|
|
14964
15403
|
sessionId,
|
|
14965
15404
|
agent,
|
|
@@ -14991,7 +15430,7 @@ function scrubDecoys(subject, values) {
|
|
|
14991
15430
|
};
|
|
14992
15431
|
return walk(subject, 0);
|
|
14993
15432
|
}
|
|
14994
|
-
function pushFsOpAstFinding(command, toolName, input,
|
|
15433
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
|
|
14995
15434
|
const fsVerdict = analyzeFsOperation(command);
|
|
14996
15435
|
if (!fsVerdict) return false;
|
|
14997
15436
|
const synthRule = {
|
|
@@ -15021,7 +15460,7 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
15021
15460
|
source: synthSource,
|
|
15022
15461
|
toolName,
|
|
15023
15462
|
input,
|
|
15024
|
-
timestamp,
|
|
15463
|
+
timestamp: timestamp2,
|
|
15025
15464
|
project: projLabel,
|
|
15026
15465
|
sessionId,
|
|
15027
15466
|
agent
|
|
@@ -15029,9 +15468,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
15029
15468
|
}
|
|
15030
15469
|
return true;
|
|
15031
15470
|
}
|
|
15032
|
-
function isStaleFinding(
|
|
15033
|
-
if (!
|
|
15034
|
-
const t = Date.parse(
|
|
15471
|
+
function isStaleFinding(timestamp2, now = Date.now()) {
|
|
15472
|
+
if (!timestamp2) return false;
|
|
15473
|
+
const t = Date.parse(timestamp2);
|
|
15035
15474
|
if (Number.isNaN(t)) return false;
|
|
15036
15475
|
const ageDays = (now - t) / 864e5;
|
|
15037
15476
|
return ageDays > STALE_AGE_DAYS;
|
|
@@ -15179,37 +15618,7 @@ function countScanFiles() {
|
|
|
15179
15618
|
} catch {
|
|
15180
15619
|
}
|
|
15181
15620
|
}
|
|
15182
|
-
|
|
15183
|
-
if (import_fs27.default.existsSync(codexDir)) {
|
|
15184
|
-
try {
|
|
15185
|
-
for (const year of import_fs27.default.readdirSync(codexDir)) {
|
|
15186
|
-
const yp = import_path29.default.join(codexDir, year);
|
|
15187
|
-
try {
|
|
15188
|
-
if (!import_fs27.default.statSync(yp).isDirectory()) continue;
|
|
15189
|
-
for (const month of import_fs27.default.readdirSync(yp)) {
|
|
15190
|
-
const mp = import_path29.default.join(yp, month);
|
|
15191
|
-
try {
|
|
15192
|
-
if (!import_fs27.default.statSync(mp).isDirectory()) continue;
|
|
15193
|
-
for (const day of import_fs27.default.readdirSync(mp)) {
|
|
15194
|
-
const dp = import_path29.default.join(mp, day);
|
|
15195
|
-
try {
|
|
15196
|
-
if (!import_fs27.default.statSync(dp).isDirectory()) continue;
|
|
15197
|
-
total += listSessionFiles(dp).length;
|
|
15198
|
-
} catch {
|
|
15199
|
-
continue;
|
|
15200
|
-
}
|
|
15201
|
-
}
|
|
15202
|
-
} catch {
|
|
15203
|
-
continue;
|
|
15204
|
-
}
|
|
15205
|
-
}
|
|
15206
|
-
} catch {
|
|
15207
|
-
continue;
|
|
15208
|
-
}
|
|
15209
|
-
}
|
|
15210
|
-
} catch {
|
|
15211
|
-
}
|
|
15212
|
-
}
|
|
15621
|
+
total += listCodexSessionFiles().length;
|
|
15213
15622
|
return total;
|
|
15214
15623
|
}
|
|
15215
15624
|
function renderProgressBar(done, total, lines) {
|
|
@@ -15332,12 +15741,12 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
15332
15741
|
}
|
|
15333
15742
|
continue;
|
|
15334
15743
|
}
|
|
15335
|
-
const
|
|
15744
|
+
const usage2 = entry.message?.usage;
|
|
15336
15745
|
const model = entry.message?.model;
|
|
15337
|
-
if (
|
|
15746
|
+
if (usage2 && model) {
|
|
15338
15747
|
const p = claudeModelPrice(model);
|
|
15339
15748
|
if (p) {
|
|
15340
|
-
const rowCost = (
|
|
15749
|
+
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;
|
|
15341
15750
|
result.totalCostUSD += rowCost;
|
|
15342
15751
|
session.costUSD += rowCost;
|
|
15343
15752
|
}
|
|
@@ -15871,15 +16280,15 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15871
16280
|
} catch {
|
|
15872
16281
|
continue;
|
|
15873
16282
|
}
|
|
15874
|
-
const
|
|
15875
|
-
if (startDate &&
|
|
16283
|
+
const timestamp2 = step.created_at ?? "";
|
|
16284
|
+
if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
|
|
15876
16285
|
if (step.type === "USER_INPUT") {
|
|
15877
16286
|
const text = typeof step.content === "string" ? step.content : "";
|
|
15878
16287
|
if (text) {
|
|
15879
16288
|
const decoysHere5 = recordCanaries(
|
|
15880
16289
|
{ text },
|
|
15881
16290
|
"user-prompt",
|
|
15882
|
-
|
|
16291
|
+
timestamp2,
|
|
15883
16292
|
projLabel,
|
|
15884
16293
|
sessionId,
|
|
15885
16294
|
"antigravity",
|
|
@@ -15896,7 +16305,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15896
16305
|
patternName: dlpMatch.patternName,
|
|
15897
16306
|
redactedSample: dlpMatch.redactedSample,
|
|
15898
16307
|
toolName: "user-prompt",
|
|
15899
|
-
timestamp,
|
|
16308
|
+
timestamp: timestamp2,
|
|
15900
16309
|
project: projLabel,
|
|
15901
16310
|
sessionId,
|
|
15902
16311
|
agent: "antigravity"
|
|
@@ -15907,16 +16316,16 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15907
16316
|
continue;
|
|
15908
16317
|
}
|
|
15909
16318
|
if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
|
|
15910
|
-
if (
|
|
15911
|
-
if (!result.firstDate ||
|
|
15912
|
-
if (!result.lastDate ||
|
|
16319
|
+
if (timestamp2) {
|
|
16320
|
+
if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
|
|
16321
|
+
if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
|
|
15913
16322
|
}
|
|
15914
16323
|
for (const tc of step.tool_calls) {
|
|
15915
16324
|
result.totalToolCalls++;
|
|
15916
16325
|
const toolName = tc.name ?? "";
|
|
15917
16326
|
const toolNameLower = toolName.toLowerCase();
|
|
15918
16327
|
const input = canonicalToolInput(toolName, tc.args ?? {});
|
|
15919
|
-
sessionCalls.push({ toolName, input, timestamp });
|
|
16328
|
+
sessionCalls.push({ toolName, input, timestamp: timestamp2 });
|
|
15920
16329
|
const isShellTool = toolNameLower === "run_command";
|
|
15921
16330
|
if (isShellTool) {
|
|
15922
16331
|
result.bashCalls++;
|
|
@@ -15931,7 +16340,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15931
16340
|
const decoysHere6 = recordCanaries(
|
|
15932
16341
|
input,
|
|
15933
16342
|
toolName,
|
|
15934
|
-
|
|
16343
|
+
timestamp2,
|
|
15935
16344
|
projLabel,
|
|
15936
16345
|
sessionId,
|
|
15937
16346
|
"antigravity",
|
|
@@ -15948,7 +16357,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15948
16357
|
patternName: dlpMatch.patternName,
|
|
15949
16358
|
redactedSample: dlpMatch.redactedSample,
|
|
15950
16359
|
toolName,
|
|
15951
|
-
timestamp,
|
|
16360
|
+
timestamp: timestamp2,
|
|
15952
16361
|
project: projLabel,
|
|
15953
16362
|
sessionId,
|
|
15954
16363
|
agent: "antigravity"
|
|
@@ -15961,7 +16370,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15961
16370
|
String(input.command ?? ""),
|
|
15962
16371
|
toolName,
|
|
15963
16372
|
input,
|
|
15964
|
-
|
|
16373
|
+
timestamp2,
|
|
15965
16374
|
projLabel,
|
|
15966
16375
|
sessionId,
|
|
15967
16376
|
"antigravity",
|
|
@@ -15985,7 +16394,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15985
16394
|
source,
|
|
15986
16395
|
toolName,
|
|
15987
16396
|
input,
|
|
15988
|
-
timestamp,
|
|
16397
|
+
timestamp: timestamp2,
|
|
15989
16398
|
project: projLabel,
|
|
15990
16399
|
sessionId,
|
|
15991
16400
|
agent: "antigravity"
|
|
@@ -16017,7 +16426,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
16017
16426
|
},
|
|
16018
16427
|
toolName,
|
|
16019
16428
|
input,
|
|
16020
|
-
timestamp,
|
|
16429
|
+
timestamp: timestamp2,
|
|
16021
16430
|
project: projLabel,
|
|
16022
16431
|
sessionId,
|
|
16023
16432
|
agent: "antigravity"
|
|
@@ -16087,7 +16496,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16087
16496
|
} catch {
|
|
16088
16497
|
continue;
|
|
16089
16498
|
}
|
|
16090
|
-
const
|
|
16499
|
+
const timestamp2 = ev.timestamp ?? "";
|
|
16091
16500
|
if (ev.type === "session.start") {
|
|
16092
16501
|
const cwd = ev.data?.context?.cwd;
|
|
16093
16502
|
if (typeof cwd === "string" && cwd) {
|
|
@@ -16095,14 +16504,14 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16095
16504
|
}
|
|
16096
16505
|
continue;
|
|
16097
16506
|
}
|
|
16098
|
-
if (startDate &&
|
|
16507
|
+
if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
|
|
16099
16508
|
if (ev.type === "user.message") {
|
|
16100
16509
|
const text = ev.data?.content ?? ev.data?.text ?? "";
|
|
16101
16510
|
if (typeof text === "string" && text) {
|
|
16102
16511
|
const decoysHere7 = recordCanaries(
|
|
16103
16512
|
{ text },
|
|
16104
16513
|
"user-prompt",
|
|
16105
|
-
|
|
16514
|
+
timestamp2,
|
|
16106
16515
|
projLabel,
|
|
16107
16516
|
sessionId,
|
|
16108
16517
|
"copilot",
|
|
@@ -16119,7 +16528,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16119
16528
|
patternName: dlpMatch2.patternName,
|
|
16120
16529
|
redactedSample: dlpMatch2.redactedSample,
|
|
16121
16530
|
toolName: "user-prompt",
|
|
16122
|
-
timestamp,
|
|
16531
|
+
timestamp: timestamp2,
|
|
16123
16532
|
project: projLabel,
|
|
16124
16533
|
sessionId,
|
|
16125
16534
|
agent: "copilot"
|
|
@@ -16134,19 +16543,19 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16134
16543
|
const toolNameLower = toolName.toLowerCase();
|
|
16135
16544
|
const input = ev.data?.arguments ?? {};
|
|
16136
16545
|
result.totalToolCalls++;
|
|
16137
|
-
sessionCalls.push({ toolName, input, timestamp });
|
|
16546
|
+
sessionCalls.push({ toolName, input, timestamp: timestamp2 });
|
|
16138
16547
|
const isShellTool = isShellShapedTool(toolNameLower, toolInspectionMap);
|
|
16139
16548
|
if (isShellTool) result.bashCalls++;
|
|
16140
|
-
if (
|
|
16141
|
-
if (!result.firstDate ||
|
|
16142
|
-
if (!result.lastDate ||
|
|
16549
|
+
if (timestamp2) {
|
|
16550
|
+
if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
|
|
16551
|
+
if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
|
|
16143
16552
|
}
|
|
16144
16553
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
16145
16554
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
16146
16555
|
const decoysHere8 = recordCanaries(
|
|
16147
16556
|
input,
|
|
16148
16557
|
toolName,
|
|
16149
|
-
|
|
16558
|
+
timestamp2,
|
|
16150
16559
|
projLabel,
|
|
16151
16560
|
sessionId,
|
|
16152
16561
|
"copilot",
|
|
@@ -16163,7 +16572,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16163
16572
|
patternName: dlpMatch.patternName,
|
|
16164
16573
|
redactedSample: dlpMatch.redactedSample,
|
|
16165
16574
|
toolName,
|
|
16166
|
-
timestamp,
|
|
16575
|
+
timestamp: timestamp2,
|
|
16167
16576
|
project: projLabel,
|
|
16168
16577
|
sessionId,
|
|
16169
16578
|
agent: "copilot"
|
|
@@ -16176,7 +16585,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16176
16585
|
String(input.command ?? ""),
|
|
16177
16586
|
toolName,
|
|
16178
16587
|
input,
|
|
16179
|
-
|
|
16588
|
+
timestamp2,
|
|
16180
16589
|
projLabel,
|
|
16181
16590
|
sessionId,
|
|
16182
16591
|
"copilot",
|
|
@@ -16199,7 +16608,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16199
16608
|
source,
|
|
16200
16609
|
toolName,
|
|
16201
16610
|
input,
|
|
16202
|
-
timestamp,
|
|
16611
|
+
timestamp: timestamp2,
|
|
16203
16612
|
project: projLabel,
|
|
16204
16613
|
sessionId,
|
|
16205
16614
|
agent: "copilot"
|
|
@@ -16231,7 +16640,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16231
16640
|
},
|
|
16232
16641
|
toolName,
|
|
16233
16642
|
input,
|
|
16234
|
-
timestamp,
|
|
16643
|
+
timestamp: timestamp2,
|
|
16235
16644
|
project: projLabel,
|
|
16236
16645
|
sessionId,
|
|
16237
16646
|
agent: "copilot"
|
|
@@ -16246,7 +16655,6 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16246
16655
|
}
|
|
16247
16656
|
function scanCodexHistory(startDate, onProgress, onLine) {
|
|
16248
16657
|
const canaryVals = safeCanaryScanValues();
|
|
16249
|
-
const sessionsBase = import_path29.default.join(import_os26.default.homedir(), ".codex", "sessions");
|
|
16250
16658
|
const result = {
|
|
16251
16659
|
filesScanned: 0,
|
|
16252
16660
|
sessions: 0,
|
|
@@ -16263,39 +16671,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16263
16671
|
perSession: []
|
|
16264
16672
|
};
|
|
16265
16673
|
const dedup = emptyScanDedup();
|
|
16266
|
-
|
|
16267
|
-
const jsonlFiles = [];
|
|
16268
|
-
try {
|
|
16269
|
-
for (const year of import_fs27.default.readdirSync(sessionsBase)) {
|
|
16270
|
-
const yearPath = import_path29.default.join(sessionsBase, year);
|
|
16271
|
-
try {
|
|
16272
|
-
if (!import_fs27.default.statSync(yearPath).isDirectory()) continue;
|
|
16273
|
-
} catch {
|
|
16274
|
-
continue;
|
|
16275
|
-
}
|
|
16276
|
-
for (const month of import_fs27.default.readdirSync(yearPath)) {
|
|
16277
|
-
const monthPath = import_path29.default.join(yearPath, month);
|
|
16278
|
-
try {
|
|
16279
|
-
if (!import_fs27.default.statSync(monthPath).isDirectory()) continue;
|
|
16280
|
-
} catch {
|
|
16281
|
-
continue;
|
|
16282
|
-
}
|
|
16283
|
-
for (const day of import_fs27.default.readdirSync(monthPath)) {
|
|
16284
|
-
const dayPath = import_path29.default.join(monthPath, day);
|
|
16285
|
-
try {
|
|
16286
|
-
if (!import_fs27.default.statSync(dayPath).isDirectory()) continue;
|
|
16287
|
-
} catch {
|
|
16288
|
-
continue;
|
|
16289
|
-
}
|
|
16290
|
-
for (const file of import_fs27.default.readdirSync(dayPath)) {
|
|
16291
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path29.default.join(dayPath, file));
|
|
16292
|
-
}
|
|
16293
|
-
}
|
|
16294
|
-
}
|
|
16295
|
-
}
|
|
16296
|
-
} catch {
|
|
16297
|
-
return result;
|
|
16298
|
-
}
|
|
16674
|
+
const jsonlFiles = listCodexSessionFiles();
|
|
16299
16675
|
const ruleSources = buildRuleSources();
|
|
16300
16676
|
for (const filePath of jsonlFiles) {
|
|
16301
16677
|
result.filesScanned++;
|
|
@@ -16311,10 +16687,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16311
16687
|
let projLabel = "";
|
|
16312
16688
|
result.sessions++;
|
|
16313
16689
|
const sessionCalls = [];
|
|
16314
|
-
let lastTotalInput = 0;
|
|
16315
|
-
let lastTotalCached = 0;
|
|
16316
|
-
let lastTotalOutput = 0;
|
|
16317
|
-
let model = "";
|
|
16318
16690
|
for (const line of lines) {
|
|
16319
16691
|
if (!line.trim()) continue;
|
|
16320
16692
|
onLine?.();
|
|
@@ -16332,18 +16704,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16332
16704
|
projLabel = stripTerminalEscapes(cwd.replace(import_os26.default.homedir(), "~")).slice(0, 40);
|
|
16333
16705
|
continue;
|
|
16334
16706
|
}
|
|
16335
|
-
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
16336
|
-
model = payload["model"];
|
|
16337
|
-
continue;
|
|
16338
|
-
}
|
|
16339
|
-
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
16340
|
-
const info = payload["info"];
|
|
16341
|
-
const usage = info?.["total_token_usage"] ?? {};
|
|
16342
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16343
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16344
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16345
|
-
continue;
|
|
16346
|
-
}
|
|
16347
16707
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
16348
16708
|
const text = String(payload["message"] ?? "");
|
|
16349
16709
|
if (text) {
|
|
@@ -16500,13 +16860,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16500
16860
|
}
|
|
16501
16861
|
}
|
|
16502
16862
|
}
|
|
16503
|
-
const
|
|
16504
|
-
|
|
16505
|
-
result.totalCostUSD += codexSessionCost(model, {
|
|
16506
|
-
input: lastTotalInput,
|
|
16507
|
-
cached: lastTotalCached,
|
|
16508
|
-
output: lastTotalOutput
|
|
16509
|
-
});
|
|
16863
|
+
for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
|
|
16864
|
+
result.totalCostUSD += event.costUSD;
|
|
16510
16865
|
}
|
|
16511
16866
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
16512
16867
|
}
|
|
@@ -17943,13 +18298,13 @@ var init_taint_store = __esm({
|
|
|
17943
18298
|
*/
|
|
17944
18299
|
check(filePath) {
|
|
17945
18300
|
const resolved = this._resolve(filePath);
|
|
17946
|
-
const
|
|
17947
|
-
if (!
|
|
17948
|
-
if (Date.now() >
|
|
18301
|
+
const record2 = this.records.get(resolved);
|
|
18302
|
+
if (!record2) return null;
|
|
18303
|
+
if (Date.now() > record2.expiresAt) {
|
|
17949
18304
|
this.records.delete(resolved);
|
|
17950
18305
|
return null;
|
|
17951
18306
|
}
|
|
17952
|
-
return
|
|
18307
|
+
return record2;
|
|
17953
18308
|
}
|
|
17954
18309
|
/**
|
|
17955
18310
|
* Propagate taint from sourcePath to destPath (e.g. cp, mv).
|
|
@@ -17970,8 +18325,8 @@ var init_taint_store = __esm({
|
|
|
17970
18325
|
/** Remove all expired records. Called periodically by the daemon. */
|
|
17971
18326
|
prune() {
|
|
17972
18327
|
const now = Date.now();
|
|
17973
|
-
for (const [key,
|
|
17974
|
-
if (now >
|
|
18328
|
+
for (const [key, record2] of this.records) {
|
|
18329
|
+
if (now > record2.expiresAt) this.records.delete(key);
|
|
17975
18330
|
}
|
|
17976
18331
|
}
|
|
17977
18332
|
/** Return all non-expired taint records (for audit/debug). */
|
|
@@ -18010,13 +18365,13 @@ var init_taint_store = __esm({
|
|
|
18010
18365
|
* Expired records are pruned on access. */
|
|
18011
18366
|
check(sessionId) {
|
|
18012
18367
|
if (!sessionId) return null;
|
|
18013
|
-
const
|
|
18014
|
-
if (!
|
|
18015
|
-
if (Date.now() >
|
|
18368
|
+
const record2 = this.records.get(sessionId);
|
|
18369
|
+
if (!record2) return null;
|
|
18370
|
+
if (Date.now() > record2.expiresAt) {
|
|
18016
18371
|
this.records.delete(sessionId);
|
|
18017
18372
|
return null;
|
|
18018
18373
|
}
|
|
18019
|
-
return
|
|
18374
|
+
return record2;
|
|
18020
18375
|
}
|
|
18021
18376
|
/** Clear a session's taint (e.g. the user resolved it). Returns true if a
|
|
18022
18377
|
* record was actually removed (false if the session wasn't tainted). */
|
|
@@ -18031,8 +18386,8 @@ var init_taint_store = __esm({
|
|
|
18031
18386
|
/** Remove all expired records. Called periodically by the daemon. */
|
|
18032
18387
|
prune() {
|
|
18033
18388
|
const now = Date.now();
|
|
18034
|
-
for (const [key,
|
|
18035
|
-
if (now >
|
|
18389
|
+
for (const [key, record2] of this.records) {
|
|
18390
|
+
if (now > record2.expiresAt) this.records.delete(key);
|
|
18036
18391
|
}
|
|
18037
18392
|
}
|
|
18038
18393
|
/** 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
|
|
22769
|
-
if (
|
|
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
|
|
23175
|
+
const record2 = sessionTaintStore.check(body.sessionId);
|
|
22821
23176
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22822
|
-
return res.end(JSON.stringify(
|
|
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;
|
|
@@ -28773,8 +29128,8 @@ var require_util2 = __commonJS({
|
|
|
28773
29128
|
request2.headersList.append("origin", serializedOrigin, true);
|
|
28774
29129
|
}
|
|
28775
29130
|
}
|
|
28776
|
-
function coarsenTime(
|
|
28777
|
-
return
|
|
29131
|
+
function coarsenTime(timestamp2, crossOriginIsolatedCapability) {
|
|
29132
|
+
return timestamp2;
|
|
28778
29133
|
}
|
|
28779
29134
|
function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
|
|
28780
29135
|
if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
|
|
@@ -38855,20 +39210,20 @@ var require_dns = __commonJS({
|
|
|
38855
39210
|
return ip;
|
|
38856
39211
|
}
|
|
38857
39212
|
setRecords(origin, addresses) {
|
|
38858
|
-
const
|
|
39213
|
+
const timestamp2 = Date.now();
|
|
38859
39214
|
const records = { records: { 4: null, 6: null } };
|
|
38860
39215
|
let minTTL = this.#maxTTL;
|
|
38861
|
-
for (const
|
|
38862
|
-
|
|
38863
|
-
if (typeof
|
|
38864
|
-
|
|
38865
|
-
minTTL = Math.min(minTTL,
|
|
39216
|
+
for (const record2 of addresses) {
|
|
39217
|
+
record2.timestamp = timestamp2;
|
|
39218
|
+
if (typeof record2.ttl === "number") {
|
|
39219
|
+
record2.ttl = Math.min(record2.ttl, this.#maxTTL);
|
|
39220
|
+
minTTL = Math.min(minTTL, record2.ttl);
|
|
38866
39221
|
} else {
|
|
38867
|
-
|
|
39222
|
+
record2.ttl = this.#maxTTL;
|
|
38868
39223
|
}
|
|
38869
|
-
const familyRecords = records.records[
|
|
38870
|
-
familyRecords.ips.push(
|
|
38871
|
-
records.records[
|
|
39224
|
+
const familyRecords = records.records[record2.family] ?? { ips: [] };
|
|
39225
|
+
familyRecords.ips.push(record2);
|
|
39226
|
+
records.records[record2.family] = familyRecords;
|
|
38872
39227
|
}
|
|
38873
39228
|
this.storage.set(origin.hostname, records, { ttl: minTTL });
|
|
38874
39229
|
}
|
|
@@ -53532,15 +53887,15 @@ var import_fs55 = __toESM(require("fs"));
|
|
|
53532
53887
|
var import_path53 = __toESM(require("path"));
|
|
53533
53888
|
init_decision();
|
|
53534
53889
|
var import_os49 = __toESM(require("os"));
|
|
53535
|
-
function formatRelativeTime(
|
|
53536
|
-
const diff = Date.now() - new Date(
|
|
53890
|
+
function formatRelativeTime(timestamp2) {
|
|
53891
|
+
const diff = Date.now() - new Date(timestamp2).getTime();
|
|
53537
53892
|
const sec = Math.floor(diff / 1e3);
|
|
53538
53893
|
if (sec < 60) return `${sec}s ago`;
|
|
53539
53894
|
const min = Math.floor(sec / 60);
|
|
53540
53895
|
if (min < 60) return `${min}m ago`;
|
|
53541
53896
|
const hrs = Math.floor(min / 60);
|
|
53542
53897
|
if (hrs < 24) return `${hrs}h ago`;
|
|
53543
|
-
return new Date(
|
|
53898
|
+
return new Date(timestamp2).toLocaleDateString();
|
|
53544
53899
|
}
|
|
53545
53900
|
function registerAuditCommand(program2) {
|
|
53546
53901
|
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) => {
|
|
@@ -53786,15 +54141,15 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
53786
54141
|
if (!entry.timestamp) continue;
|
|
53787
54142
|
const ts = new Date(entry.timestamp);
|
|
53788
54143
|
if (ts < start || ts > end) continue;
|
|
53789
|
-
const
|
|
54144
|
+
const usage2 = entry.message?.usage;
|
|
53790
54145
|
const model = entry.message?.model;
|
|
53791
|
-
if (!
|
|
54146
|
+
if (!usage2 || !model) continue;
|
|
53792
54147
|
const p = claudeModelPrice2(model);
|
|
53793
54148
|
if (!p) continue;
|
|
53794
|
-
const inp =
|
|
53795
|
-
const out =
|
|
53796
|
-
const cw =
|
|
53797
|
-
const cr =
|
|
54149
|
+
const inp = usage2.input_tokens ?? 0;
|
|
54150
|
+
const out = usage2.output_tokens ?? 0;
|
|
54151
|
+
const cw = usage2.cache_creation_input_tokens ?? 0;
|
|
54152
|
+
const cr = usage2.cache_read_input_tokens ?? 0;
|
|
53798
54153
|
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
53799
54154
|
acc.total += cost;
|
|
53800
54155
|
acc.inputTokens += inp;
|
|
@@ -53842,90 +54197,21 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
53842
54197
|
} catch {
|
|
53843
54198
|
return;
|
|
53844
54199
|
}
|
|
53845
|
-
|
|
53846
|
-
|
|
53847
|
-
|
|
53848
|
-
|
|
53849
|
-
|
|
53850
|
-
|
|
54200
|
+
const parsed = parseCodexUsage(lines);
|
|
54201
|
+
for (const event of codexUsageInWindow(parsed, start, end)) {
|
|
54202
|
+
acc.total += event.costUSD;
|
|
54203
|
+
acc.byDay.set(event.date, (acc.byDay.get(event.date) ?? 0) + event.costUSD);
|
|
54204
|
+
acc.byModel.set(event.model, (acc.byModel.get(event.model) ?? 0) + event.costUSD);
|
|
54205
|
+
}
|
|
53851
54206
|
for (const line of lines) {
|
|
53852
|
-
if (!line.trim()) continue;
|
|
53853
|
-
let entry;
|
|
53854
54207
|
try {
|
|
53855
|
-
entry = JSON.parse(line);
|
|
54208
|
+
const entry = JSON.parse(line);
|
|
54209
|
+
if (entry?.type !== "response_item" || entry.payload?.type !== "function_call") continue;
|
|
54210
|
+
const ts = new Date(entry.timestamp ?? parsed.sessionStart);
|
|
54211
|
+
if (ts >= start && ts <= end) acc.toolCalls++;
|
|
53856
54212
|
} catch {
|
|
53857
|
-
continue;
|
|
53858
|
-
}
|
|
53859
|
-
const p = entry.payload ?? {};
|
|
53860
|
-
if (entry.type === "session_meta") {
|
|
53861
|
-
sessionStart = String(p["timestamp"] ?? "");
|
|
53862
|
-
continue;
|
|
53863
|
-
}
|
|
53864
|
-
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
53865
|
-
model = p["model"];
|
|
53866
|
-
continue;
|
|
53867
|
-
}
|
|
53868
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
53869
|
-
const info = p["info"] ?? {};
|
|
53870
|
-
const usage = info["total_token_usage"] ?? {};
|
|
53871
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
53872
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
53873
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
53874
|
-
}
|
|
53875
|
-
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
53876
|
-
sessionToolCalls++;
|
|
53877
|
-
}
|
|
53878
|
-
}
|
|
53879
|
-
if (!sessionStart) return;
|
|
53880
|
-
const ts = new Date(sessionStart);
|
|
53881
|
-
if (ts < start || ts > end) return;
|
|
53882
|
-
const cost = codexSessionCost(model, {
|
|
53883
|
-
input: lastTotalInput,
|
|
53884
|
-
cached: lastTotalCached,
|
|
53885
|
-
output: lastTotalOutput
|
|
53886
|
-
});
|
|
53887
|
-
acc.total += cost;
|
|
53888
|
-
acc.toolCalls += sessionToolCalls;
|
|
53889
|
-
const dateKey = sessionStart.slice(0, 10);
|
|
53890
|
-
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
53891
|
-
const normModel = normalizeModel(model || "gpt-5");
|
|
53892
|
-
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
53893
|
-
}
|
|
53894
|
-
function listCodexSessionFiles2(sessionsBase) {
|
|
53895
|
-
const jsonlFiles = [];
|
|
53896
|
-
if (!import_fs56.default.existsSync(sessionsBase)) return jsonlFiles;
|
|
53897
|
-
try {
|
|
53898
|
-
for (const year of import_fs56.default.readdirSync(sessionsBase)) {
|
|
53899
|
-
const yearPath = import_path54.default.join(sessionsBase, year);
|
|
53900
|
-
try {
|
|
53901
|
-
if (!import_fs56.default.statSync(yearPath).isDirectory()) continue;
|
|
53902
|
-
} catch {
|
|
53903
|
-
continue;
|
|
53904
|
-
}
|
|
53905
|
-
for (const month of import_fs56.default.readdirSync(yearPath)) {
|
|
53906
|
-
const monthPath = import_path54.default.join(yearPath, month);
|
|
53907
|
-
try {
|
|
53908
|
-
if (!import_fs56.default.statSync(monthPath).isDirectory()) continue;
|
|
53909
|
-
} catch {
|
|
53910
|
-
continue;
|
|
53911
|
-
}
|
|
53912
|
-
for (const day of import_fs56.default.readdirSync(monthPath)) {
|
|
53913
|
-
const dayPath = import_path54.default.join(monthPath, day);
|
|
53914
|
-
try {
|
|
53915
|
-
if (!import_fs56.default.statSync(dayPath).isDirectory()) continue;
|
|
53916
|
-
} catch {
|
|
53917
|
-
continue;
|
|
53918
|
-
}
|
|
53919
|
-
for (const file of import_fs56.default.readdirSync(dayPath)) {
|
|
53920
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path54.default.join(dayPath, file));
|
|
53921
|
-
}
|
|
53922
|
-
}
|
|
53923
|
-
}
|
|
53924
54213
|
}
|
|
53925
|
-
} catch {
|
|
53926
|
-
return [];
|
|
53927
54214
|
}
|
|
53928
|
-
return jsonlFiles;
|
|
53929
54215
|
}
|
|
53930
54216
|
function mergeByModel(...maps) {
|
|
53931
54217
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -53941,7 +54227,7 @@ function loadCodexCost(start, end, sessionsBase) {
|
|
|
53941
54227
|
byDay: /* @__PURE__ */ new Map(),
|
|
53942
54228
|
byModel: /* @__PURE__ */ new Map()
|
|
53943
54229
|
};
|
|
53944
|
-
const files =
|
|
54230
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
53945
54231
|
for (const filePath of files) {
|
|
53946
54232
|
processCodexCostFile(filePath, start, end, acc);
|
|
53947
54233
|
}
|
|
@@ -54081,7 +54367,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
54081
54367
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
54082
54368
|
const auditLogPath = opts.auditLogPath ?? import_path54.default.join(import_os50.default.homedir(), ".node9", "audit.log");
|
|
54083
54369
|
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path54.default.join(import_os50.default.homedir(), ".claude", "projects");
|
|
54084
|
-
const codexSessionsDir2 = opts.codexSessionsDir ??
|
|
54370
|
+
const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
|
|
54085
54371
|
const geminiTmpDir2 = opts.geminiTmpDir ?? import_path54.default.join(import_os50.default.homedir(), ".gemini", "tmp");
|
|
54086
54372
|
const hasAuditFile = import_fs56.default.existsSync(auditLogPath);
|
|
54087
54373
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
@@ -59406,7 +59692,11 @@ var BUILTIN_JAIL = [
|
|
|
59406
59692
|
"~/.ssh \u2014 SSH private keys",
|
|
59407
59693
|
"~/.aws \u2014 AWS credentials",
|
|
59408
59694
|
".env files",
|
|
59409
|
-
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
|
|
59695
|
+
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud",
|
|
59696
|
+
// Stage 4 (2026-09-11): reads are blocked; a COPY out of the jail (cp, tar,
|
|
59697
|
+
// scp, rsync, aws s3 cp, ...) is reviewed, because a backup and a theft are
|
|
59698
|
+
// the same command shape.
|
|
59699
|
+
"copies out of the jail (cp, tar, scp, rsync, cloud upload) \u2014 review"
|
|
59410
59700
|
];
|
|
59411
59701
|
function registerJailCommand(program2) {
|
|
59412
59702
|
const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
|
|
@@ -60280,12 +60570,12 @@ function parseSessionLines(lines) {
|
|
|
60280
60570
|
continue;
|
|
60281
60571
|
}
|
|
60282
60572
|
if (entry.type !== "assistant") continue;
|
|
60283
|
-
const
|
|
60573
|
+
const usage2 = entry.message?.usage;
|
|
60284
60574
|
const model = entry.message?.model;
|
|
60285
|
-
if (
|
|
60575
|
+
if (usage2 && model) {
|
|
60286
60576
|
const p = modelPrice(model);
|
|
60287
60577
|
if (p) {
|
|
60288
|
-
costUSD += (
|
|
60578
|
+
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;
|
|
60289
60579
|
}
|
|
60290
60580
|
}
|
|
60291
60581
|
const content = entry.message?.content;
|
|
@@ -60465,46 +60755,13 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
60465
60755
|
return summaries;
|
|
60466
60756
|
}
|
|
60467
60757
|
function buildCodexSessions(days, allAuditEntries) {
|
|
60468
|
-
const sessionsBase = import_path65.default.join(import_os57.default.homedir(), ".codex", "sessions");
|
|
60469
|
-
if (!import_fs70.default.existsSync(sessionsBase)) return [];
|
|
60470
60758
|
const cutoff = days !== null ? (() => {
|
|
60471
60759
|
const d = /* @__PURE__ */ new Date();
|
|
60472
60760
|
d.setDate(d.getDate() - days);
|
|
60473
60761
|
d.setHours(0, 0, 0, 0);
|
|
60474
60762
|
return d;
|
|
60475
60763
|
})() : null;
|
|
60476
|
-
const jsonlFiles =
|
|
60477
|
-
try {
|
|
60478
|
-
for (const year of import_fs70.default.readdirSync(sessionsBase)) {
|
|
60479
|
-
const yearPath = import_path65.default.join(sessionsBase, year);
|
|
60480
|
-
try {
|
|
60481
|
-
if (!import_fs70.default.statSync(yearPath).isDirectory()) continue;
|
|
60482
|
-
} catch {
|
|
60483
|
-
continue;
|
|
60484
|
-
}
|
|
60485
|
-
for (const month of import_fs70.default.readdirSync(yearPath)) {
|
|
60486
|
-
const monthPath = import_path65.default.join(yearPath, month);
|
|
60487
|
-
try {
|
|
60488
|
-
if (!import_fs70.default.statSync(monthPath).isDirectory()) continue;
|
|
60489
|
-
} catch {
|
|
60490
|
-
continue;
|
|
60491
|
-
}
|
|
60492
|
-
for (const day of import_fs70.default.readdirSync(monthPath)) {
|
|
60493
|
-
const dayPath = import_path65.default.join(monthPath, day);
|
|
60494
|
-
try {
|
|
60495
|
-
if (!import_fs70.default.statSync(dayPath).isDirectory()) continue;
|
|
60496
|
-
} catch {
|
|
60497
|
-
continue;
|
|
60498
|
-
}
|
|
60499
|
-
for (const file of import_fs70.default.readdirSync(dayPath)) {
|
|
60500
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path65.default.join(dayPath, file));
|
|
60501
|
-
}
|
|
60502
|
-
}
|
|
60503
|
-
}
|
|
60504
|
-
}
|
|
60505
|
-
} catch {
|
|
60506
|
-
return [];
|
|
60507
|
-
}
|
|
60764
|
+
const jsonlFiles = listCodexSessionFiles();
|
|
60508
60765
|
const summaries = [];
|
|
60509
60766
|
for (const filePath of jsonlFiles) {
|
|
60510
60767
|
let lines;
|
|
@@ -60519,10 +60776,6 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60519
60776
|
let firstPrompt = "";
|
|
60520
60777
|
const toolCalls = [];
|
|
60521
60778
|
let lastToolTs = "";
|
|
60522
|
-
let lastTotalInput = 0;
|
|
60523
|
-
let lastTotalCached = 0;
|
|
60524
|
-
let lastTotalOutput = 0;
|
|
60525
|
-
let model = "";
|
|
60526
60779
|
for (const line of lines) {
|
|
60527
60780
|
if (!line.trim()) continue;
|
|
60528
60781
|
let entry;
|
|
@@ -60538,22 +60791,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60538
60791
|
cwd = String(p["cwd"] ?? "");
|
|
60539
60792
|
continue;
|
|
60540
60793
|
}
|
|
60541
|
-
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
60542
|
-
model = p["model"];
|
|
60543
|
-
continue;
|
|
60544
|
-
}
|
|
60545
60794
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
60546
60795
|
firstPrompt = String(p["message"] ?? "");
|
|
60547
60796
|
continue;
|
|
60548
60797
|
}
|
|
60549
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
60550
|
-
const info = p["info"] ?? {};
|
|
60551
|
-
const usage = info["total_token_usage"] ?? {};
|
|
60552
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
60553
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
60554
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
60555
|
-
continue;
|
|
60556
|
-
}
|
|
60557
60798
|
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
60558
60799
|
const tool = String(p["name"] ?? "");
|
|
60559
60800
|
let input = {};
|
|
@@ -60567,12 +60808,13 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60567
60808
|
}
|
|
60568
60809
|
}
|
|
60569
60810
|
if (!sessionId || !startTime) continue;
|
|
60570
|
-
|
|
60571
|
-
const
|
|
60572
|
-
|
|
60573
|
-
|
|
60574
|
-
|
|
60575
|
-
|
|
60811
|
+
const parsedUsage = parseCodexUsage(lines);
|
|
60812
|
+
const usageEvents = codexUsageInWindow(parsedUsage, cutoff);
|
|
60813
|
+
if (cutoff && new Date(startTime) < cutoff && usageEvents.length === 0 && !toolCalls.some((call) => new Date(call.timestamp) >= cutoff))
|
|
60814
|
+
continue;
|
|
60815
|
+
const costUSD = usageEvents.reduce((sum, event) => sum + event.costUSD, 0);
|
|
60816
|
+
const lastUsageTs = parsedUsage.events.at(-1)?.timestamp ?? "";
|
|
60817
|
+
if (lastUsageTs > lastToolTs) lastToolTs = lastUsageTs;
|
|
60576
60818
|
const windowEnd = new Date(
|
|
60577
60819
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
60578
60820
|
).toISOString();
|