@node9/proxy 2.13.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +518 -512
- package/dist/cli.mjs +518 -512
- package/dist/dashboard.mjs +2580 -2327
- package/dist/index.js +169 -84
- package/dist/index.mjs +169 -84
- package/dist/scan-ink.mjs +43 -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
|
}
|
|
@@ -1291,34 +1293,18 @@ function isProtectedHomePath(rawPath) {
|
|
|
1291
1293
|
}
|
|
1292
1294
|
function extractLiteralArgs(callExpr) {
|
|
1293
1295
|
const args = callExpr.Args || [];
|
|
1294
|
-
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
let s = "";
|
|
1298
|
-
for (const p of parts) {
|
|
1299
|
-
const t = syntax.NodeType(p);
|
|
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
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
return s;
|
|
1311
|
-
};
|
|
1312
|
-
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
1296
|
+
if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
|
|
1297
|
+
const words = args.map((a) => resolveWordLiteral(a));
|
|
1298
|
+
const name = (words[0] ?? "").toLowerCase();
|
|
1313
1299
|
const flags = [];
|
|
1314
1300
|
const paths = [];
|
|
1315
|
-
for (let i = 1; i <
|
|
1316
|
-
const v =
|
|
1301
|
+
for (let i = 1; i < words.length; i++) {
|
|
1302
|
+
const v = words[i];
|
|
1317
1303
|
if (v === null) continue;
|
|
1318
1304
|
if (v.startsWith("-")) flags.push(v);
|
|
1319
1305
|
else paths.push(v);
|
|
1320
1306
|
}
|
|
1321
|
-
return { name, flags, paths };
|
|
1307
|
+
return { name, flags, paths, words };
|
|
1322
1308
|
}
|
|
1323
1309
|
function resolveWordLiteral(w) {
|
|
1324
1310
|
const parts = w?.Parts || [];
|
|
@@ -1576,16 +1562,21 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
1576
1562
|
}
|
|
1577
1563
|
return sawRm && ok2;
|
|
1578
1564
|
}
|
|
1579
|
-
function analyzeFsOperationImpl(command) {
|
|
1565
|
+
function analyzeFsOperationImpl(command, depth = 0) {
|
|
1580
1566
|
const f = parseShared(command);
|
|
1581
1567
|
if (f === PARSE_FAIL) return null;
|
|
1582
1568
|
let result = null;
|
|
1583
1569
|
try {
|
|
1584
1570
|
syntax.Walk(f, (node) => {
|
|
1585
|
-
if (!node || result) return false;
|
|
1571
|
+
if (!node || result?.verdict === "block") return false;
|
|
1586
1572
|
const n = node;
|
|
1587
|
-
|
|
1588
|
-
|
|
1573
|
+
const nodeType = syntax.NodeType(n);
|
|
1574
|
+
if (nodeType === "Stmt") {
|
|
1575
|
+
result = stricter(result, jailedRedirectRead(n));
|
|
1576
|
+
return result?.verdict !== "block";
|
|
1577
|
+
}
|
|
1578
|
+
if (nodeType !== "CallExpr") return true;
|
|
1579
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
1589
1580
|
if (!name) return true;
|
|
1590
1581
|
if (name === "rm") {
|
|
1591
1582
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -1614,19 +1605,22 @@ function analyzeFsOperationImpl(command) {
|
|
|
1614
1605
|
}
|
|
1615
1606
|
}
|
|
1616
1607
|
}
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
reason: sp.reason,
|
|
1625
|
-
path: p
|
|
1626
|
-
};
|
|
1627
|
-
return false;
|
|
1628
|
-
}
|
|
1608
|
+
if (depth < 1) {
|
|
1609
|
+
const payload = literalShellPayload(words, name);
|
|
1610
|
+
if (payload !== null) {
|
|
1611
|
+
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
1612
|
+
if (inner) {
|
|
1613
|
+
result = inner;
|
|
1614
|
+
return false;
|
|
1629
1615
|
}
|
|
1616
|
+
return true;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
|
|
1620
|
+
if (readPaths) {
|
|
1621
|
+
for (const p of readPaths) {
|
|
1622
|
+
result = stricter(result, matchSensitivePath2(p));
|
|
1623
|
+
if (result?.verdict === "block") return false;
|
|
1630
1624
|
}
|
|
1631
1625
|
}
|
|
1632
1626
|
return true;
|
|
@@ -1636,6 +1630,55 @@ function analyzeFsOperationImpl(command) {
|
|
|
1636
1630
|
return null;
|
|
1637
1631
|
}
|
|
1638
1632
|
}
|
|
1633
|
+
function stricter(a, b) {
|
|
1634
|
+
if (!a) return b;
|
|
1635
|
+
if (!b) return a;
|
|
1636
|
+
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
1637
|
+
}
|
|
1638
|
+
function matchSensitivePath2(p) {
|
|
1639
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1640
|
+
if (sp.match(p))
|
|
1641
|
+
return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
|
|
1642
|
+
}
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
function wrappedReadPaths(words, name) {
|
|
1646
|
+
if (name === "find") {
|
|
1647
|
+
const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1648
|
+
if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
|
|
1649
|
+
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
1650
|
+
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
1651
|
+
}
|
|
1652
|
+
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
1653
|
+
const h = unwrapCommandHead(words);
|
|
1654
|
+
return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
|
|
1655
|
+
}
|
|
1656
|
+
function literalShellPayload(words, name) {
|
|
1657
|
+
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
1658
|
+
const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1659
|
+
if (head === "eval") {
|
|
1660
|
+
const rest = words.slice(h + 1);
|
|
1661
|
+
if (rest.length === 0 || rest.some((w) => w === null)) return null;
|
|
1662
|
+
return rest.join(" ");
|
|
1663
|
+
}
|
|
1664
|
+
if (SHELL_INTERPRETERS.has(head)) {
|
|
1665
|
+
const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
|
|
1666
|
+
if (c < 0) return null;
|
|
1667
|
+
return words[c + 1] ?? null;
|
|
1668
|
+
}
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
function jailedRedirectRead(stmt) {
|
|
1672
|
+
const redirs = stmt.Redirs || [];
|
|
1673
|
+
for (const r of redirs) {
|
|
1674
|
+
if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
|
|
1675
|
+
const p = resolveWordLiteral(r.Word);
|
|
1676
|
+
if (p === null || p === "") continue;
|
|
1677
|
+
const hit = matchSensitivePath2(p);
|
|
1678
|
+
if (hit) return hit;
|
|
1679
|
+
}
|
|
1680
|
+
return null;
|
|
1681
|
+
}
|
|
1639
1682
|
function analyzeShellCommand(command) {
|
|
1640
1683
|
const actions = [];
|
|
1641
1684
|
const paths = [];
|
|
@@ -1771,8 +1814,8 @@ function splitOnPipe(cmd) {
|
|
|
1771
1814
|
if (current.trim()) segments2.push(current.trim());
|
|
1772
1815
|
return segments2.filter(Boolean);
|
|
1773
1816
|
}
|
|
1774
|
-
function positionalTokens(
|
|
1775
|
-
return
|
|
1817
|
+
function positionalTokens(tokens) {
|
|
1818
|
+
return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
1776
1819
|
}
|
|
1777
1820
|
function analyzePipeChain(command) {
|
|
1778
1821
|
const segments2 = splitOnPipe(command);
|
|
@@ -1795,8 +1838,10 @@ function analyzePipeChain(command) {
|
|
|
1795
1838
|
for (const segment of segments2) {
|
|
1796
1839
|
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
1797
1840
|
if (tokens.length === 0) continue;
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
1841
|
+
const h = unwrapCommandHead(tokens);
|
|
1842
|
+
const head = h < tokens.length ? h : 0;
|
|
1843
|
+
const binary = tokens[head].toLowerCase();
|
|
1844
|
+
const args = positionalTokens(tokens.slice(head));
|
|
1800
1845
|
if (SOURCE_COMMANDS.has(binary)) {
|
|
1801
1846
|
sourceFiles.push(...args);
|
|
1802
1847
|
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
@@ -3276,7 +3321,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3276
3321
|
}
|
|
3277
3322
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3278
3323
|
}
|
|
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;
|
|
3324
|
+
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, 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, 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
3325
|
var init_dist = __esm({
|
|
3281
3326
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3282
3327
|
"use strict";
|
|
@@ -3901,13 +3946,32 @@ var init_dist = __esm({
|
|
|
3901
3946
|
})
|
|
3902
3947
|
);
|
|
3903
3948
|
SENSITIVE_PATH_PATTERNS = [
|
|
3904
|
-
/[/\\]\.ssh[/\\]/i,
|
|
3905
|
-
/[/\\]\.aws[/\\]/i,
|
|
3949
|
+
/[/\\]\.ssh([/\\]|$)/i,
|
|
3950
|
+
/[/\\]\.aws([/\\]|$)/i,
|
|
3906
3951
|
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
3907
3952
|
/[/\\]\.azure[/\\]/i,
|
|
3908
3953
|
/[/\\]\.kube[/\\]config$/i,
|
|
3909
|
-
|
|
3910
|
-
// .
|
|
3954
|
+
// ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
|
|
3955
|
+
// (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
|
|
3956
|
+
// structural suffix chain rather than a hand-written list, `example|sample|
|
|
3957
|
+
// template` exempt because a fixture stays a fixture whatever follows, and
|
|
3958
|
+
// `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
|
|
3959
|
+
// committed template, `.env.test.local` is gitignored and holds real values.
|
|
3960
|
+
//
|
|
3961
|
+
// It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
|
|
3962
|
+
// blocked while `cat .env.example` allowed: the same file, opposite verdicts,
|
|
3963
|
+
// decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
|
|
3964
|
+
// which is the contract that now holds these copies in step, and stage 5 of
|
|
3965
|
+
// doc/credential-jail-architecture.md, which replaces them with one generated
|
|
3966
|
+
// source.
|
|
3967
|
+
// ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
|
|
3968
|
+
// fixture whatever follows it -- `.env.example.md` is documentation -- but
|
|
3969
|
+
// `.env.example.local` is gitignored by the `.env*.local` convention and holds
|
|
3970
|
+
// real values, exactly the reasoning that anchors `(?!\.test$)` rather than
|
|
3971
|
+
// using `\b`. Without this branch the fixture exemption also bought a two-step
|
|
3972
|
+
// bypass: `cp .env .env.sample`, then read the copy.
|
|
3973
|
+
/[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
3974
|
+
// .env + any suffix chain; fixtures exempt unless .local
|
|
3911
3975
|
/[/\\]\.git-credentials$/i,
|
|
3912
3976
|
/[/\\]\.npmrc$/i,
|
|
3913
3977
|
/[/\\]\.docker[/\\]config\.json$/i,
|
|
@@ -3989,6 +4053,10 @@ var init_dist = __esm({
|
|
|
3989
4053
|
"od",
|
|
3990
4054
|
"xxd",
|
|
3991
4055
|
"hexdump",
|
|
4056
|
+
// Emits the file's bytes, re-encoded, so it is a read by the set's own test
|
|
4057
|
+
// ("does it emit file contents"). Absent until 2026-09-10, which is why
|
|
4058
|
+
// `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
|
|
4059
|
+
"base64",
|
|
3992
4060
|
"strings",
|
|
3993
4061
|
"sort",
|
|
3994
4062
|
"uniq",
|
|
@@ -3997,7 +4065,11 @@ var init_dist = __esm({
|
|
|
3997
4065
|
"dd"
|
|
3998
4066
|
]);
|
|
3999
4067
|
FS_OP_PRESCREEN_RE = new RegExp(
|
|
4000
|
-
|
|
4068
|
+
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
4069
|
+
// reader right after `"` / `'`, and without these two characters the
|
|
4070
|
+
// prescreen rejected every string-wrapped read before the parser ran.
|
|
4071
|
+
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
4072
|
+
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4001
4073
|
);
|
|
4002
4074
|
HOME_CACHE_ALLOWLIST = [
|
|
4003
4075
|
".cache",
|
|
@@ -4018,12 +4090,12 @@ var init_dist = __esm({
|
|
|
4018
4090
|
{
|
|
4019
4091
|
rule: "shield:project-jail:block-read-ssh",
|
|
4020
4092
|
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
4021
|
-
match: (p) => /(
|
|
4093
|
+
match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
|
|
4022
4094
|
},
|
|
4023
4095
|
{
|
|
4024
4096
|
rule: "shield:project-jail:block-read-aws",
|
|
4025
4097
|
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
4026
|
-
match: (p) => /(
|
|
4098
|
+
match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
|
|
4027
4099
|
},
|
|
4028
4100
|
{
|
|
4029
4101
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
@@ -4067,7 +4139,9 @@ var init_dist = __esm({
|
|
|
4067
4139
|
// symmetry — silently exempts every `.env.test.*` file.
|
|
4068
4140
|
//
|
|
4069
4141
|
// shields.test.ts:983-995 is the canonical contract; keep both in step.
|
|
4070
|
-
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]
|
|
4142
|
+
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
|
|
4143
|
+
p
|
|
4144
|
+
)
|
|
4071
4145
|
},
|
|
4072
4146
|
{
|
|
4073
4147
|
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
@@ -4178,8 +4252,18 @@ var init_dist = __esm({
|
|
|
4178
4252
|
_redirStdinOps = null;
|
|
4179
4253
|
_listOps = null;
|
|
4180
4254
|
WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
|
|
4255
|
+
FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
4181
4256
|
INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
4182
|
-
NET_BINARIES = /* @__PURE__ */ new Set([
|
|
4257
|
+
NET_BINARIES = /* @__PURE__ */ new Set([
|
|
4258
|
+
"curl",
|
|
4259
|
+
"wget",
|
|
4260
|
+
"scp",
|
|
4261
|
+
"ssh",
|
|
4262
|
+
"nc",
|
|
4263
|
+
"ncat",
|
|
4264
|
+
"netcat",
|
|
4265
|
+
"rsync"
|
|
4266
|
+
]);
|
|
4183
4267
|
VALUE_FLAGS = {
|
|
4184
4268
|
curl: /* @__PURE__ */ new Set([
|
|
4185
4269
|
"-d",
|
|
@@ -4268,10 +4352,15 @@ var init_dist = __esm({
|
|
|
4268
4352
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
4269
4353
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
4270
4354
|
REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
4355
|
+
REDIR_FILE_IN_OPS = new Set(
|
|
4356
|
+
[deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
|
|
4357
|
+
);
|
|
4271
4358
|
REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
4272
4359
|
deriveRedirOp("cat <<X\nX"),
|
|
4273
4360
|
deriveRedirOp("cat <<-X\nX")
|
|
4274
4361
|
]);
|
|
4362
|
+
isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
|
|
4363
|
+
positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
|
|
4275
4364
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
4276
4365
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
4277
4366
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -4295,21 +4384,7 @@ var init_dist = __esm({
|
|
|
4295
4384
|
"deb.debian.org",
|
|
4296
4385
|
"*.ubuntu.com"
|
|
4297
4386
|
];
|
|
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
|
-
]);
|
|
4387
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
4313
4388
|
SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
4314
4389
|
"curl",
|
|
4315
4390
|
"wget",
|
|
@@ -4340,16 +4415,25 @@ var init_dist = __esm({
|
|
|
4340
4415
|
"node"
|
|
4341
4416
|
]);
|
|
4342
4417
|
SENSITIVE_PATTERNS = [
|
|
4343
|
-
/
|
|
4344
|
-
|
|
4418
|
+
// Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
|
|
4419
|
+
/(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
4420
|
+
// .env chain; fixtures exempt unless .local
|
|
4345
4421
|
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
4346
4422
|
// SSH private keys
|
|
4347
4423
|
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
4348
4424
|
// certificate files
|
|
4349
|
-
/
|
|
4350
|
-
//
|
|
4351
|
-
/
|
|
4352
|
-
//
|
|
4425
|
+
// The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
|
|
4426
|
+
// the directory counts wherever it appears, while the directory ITSELF counts
|
|
4427
|
+
// only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
|
|
4428
|
+
// `config/.ssh` is more likely a search pattern than a read. These are
|
|
4429
|
+
// extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
|
|
4430
|
+
// contract as the shell tier, so the same boundary is the right one.
|
|
4431
|
+
// Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
|
|
4432
|
+
// identical pipeline naming a file inside that directory.
|
|
4433
|
+
/(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
|
|
4434
|
+
// ~/.ssh/ and ~/.ssh
|
|
4435
|
+
/(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
|
|
4436
|
+
// AWS creds + dir
|
|
4353
4437
|
/(?:^|\/)\.netrc$/i,
|
|
4354
4438
|
// netrc (stores HTTP credentials)
|
|
4355
4439
|
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
@@ -5017,7 +5101,7 @@ var init_dist = __esm({
|
|
|
5017
5101
|
{
|
|
5018
5102
|
field: "command",
|
|
5019
5103
|
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[\\/\\\\]",
|
|
5104
|
+
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
5105
|
flags: "i"
|
|
5022
5106
|
}
|
|
5023
5107
|
],
|
|
@@ -5031,7 +5115,7 @@ var init_dist = __esm({
|
|
|
5031
5115
|
{
|
|
5032
5116
|
field: "command",
|
|
5033
5117
|
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[\\/\\\\]",
|
|
5118
|
+
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
5119
|
flags: "i"
|
|
5036
5120
|
}
|
|
5037
5121
|
],
|
|
@@ -5045,7 +5129,7 @@ var init_dist = __esm({
|
|
|
5045
5129
|
{
|
|
5046
5130
|
field: "command",
|
|
5047
5131
|
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|$|[;&|>)<])",
|
|
5132
|
+
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
5133
|
flags: "i"
|
|
5050
5134
|
}
|
|
5051
5135
|
],
|
|
@@ -5059,7 +5143,7 @@ var init_dist = __esm({
|
|
|
5059
5143
|
{
|
|
5060
5144
|
field: "command",
|
|
5061
5145
|
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)",
|
|
5146
|
+
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
5147
|
flags: "i"
|
|
5064
5148
|
}
|
|
5065
5149
|
],
|
|
@@ -5073,7 +5157,7 @@ var init_dist = __esm({
|
|
|
5073
5157
|
{
|
|
5074
5158
|
field: "file_path",
|
|
5075
5159
|
op: "matches",
|
|
5076
|
-
value: "(
|
|
5160
|
+
value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
|
|
5077
5161
|
flags: "i"
|
|
5078
5162
|
}
|
|
5079
5163
|
],
|
|
@@ -5087,7 +5171,7 @@ var init_dist = __esm({
|
|
|
5087
5171
|
{
|
|
5088
5172
|
field: "file_path",
|
|
5089
5173
|
op: "matches",
|
|
5090
|
-
value: "(
|
|
5174
|
+
value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
|
|
5091
5175
|
flags: "i"
|
|
5092
5176
|
}
|
|
5093
5177
|
],
|
|
@@ -5101,7 +5185,7 @@ var init_dist = __esm({
|
|
|
5101
5185
|
{
|
|
5102
5186
|
field: "file_path",
|
|
5103
5187
|
op: "matches",
|
|
5104
|
-
value: "(^|[\\/\\\\])\\.env(
|
|
5188
|
+
value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
|
|
5105
5189
|
flags: "i"
|
|
5106
5190
|
}
|
|
5107
5191
|
],
|
|
@@ -5244,7 +5328,7 @@ var init_dist = __esm({
|
|
|
5244
5328
|
};
|
|
5245
5329
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
5246
5330
|
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 =
|
|
5331
|
+
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
5332
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
5249
5333
|
"read",
|
|
5250
5334
|
"read_file",
|
|
@@ -5321,7 +5405,7 @@ var init_dist = __esm({
|
|
|
5321
5405
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
5322
5406
|
];
|
|
5323
5407
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5324
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
5408
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
|
|
5325
5409
|
DEDUPE_PREVIEW_LEN = 120;
|
|
5326
5410
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
5327
5411
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -8640,7 +8724,7 @@ function isNetworkTool(toolName, args) {
|
|
|
8640
8724
|
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
|
|
8641
8725
|
const a = args;
|
|
8642
8726
|
const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
|
|
8643
|
-
return
|
|
8727
|
+
return NETWORK_COMMAND_RE.test(cmd);
|
|
8644
8728
|
}
|
|
8645
8729
|
return false;
|
|
8646
8730
|
}
|
|
@@ -9515,7 +9599,7 @@ function canaryRecordById(id) {
|
|
|
9515
9599
|
return null;
|
|
9516
9600
|
}
|
|
9517
9601
|
}
|
|
9518
|
-
var import_crypto7, WRITE_TOOLS;
|
|
9602
|
+
var import_crypto7, WRITE_TOOLS, NETWORK_COMMAND_RE;
|
|
9519
9603
|
var init_orchestrator = __esm({
|
|
9520
9604
|
"src/auth/orchestrator.ts"() {
|
|
9521
9605
|
"use strict";
|
|
@@ -9546,6 +9630,7 @@ var init_orchestrator = __esm({
|
|
|
9546
9630
|
"notebook_edit",
|
|
9547
9631
|
"notebookedit"
|
|
9548
9632
|
]);
|
|
9633
|
+
NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
|
|
9549
9634
|
}
|
|
9550
9635
|
});
|
|
9551
9636
|
|
|
@@ -12609,9 +12694,10 @@ async function ensurePricingLoaded() {
|
|
|
12609
12694
|
memCacheAt = Date.now();
|
|
12610
12695
|
lookupCache.clear();
|
|
12611
12696
|
}
|
|
12612
|
-
function pricingFor(model) {
|
|
12697
|
+
function pricingFor(model, options = {}) {
|
|
12613
12698
|
const norm = normalizeModel(model);
|
|
12614
|
-
const
|
|
12699
|
+
const lookupKey = options.exact ? `exact:${norm}` : norm;
|
|
12700
|
+
const cached = lookupCache.get(lookupKey);
|
|
12615
12701
|
if (cached !== void 0) return cached;
|
|
12616
12702
|
if (memCache === null && !diskChecked) {
|
|
12617
12703
|
diskChecked = true;
|
|
@@ -12631,6 +12717,7 @@ function pricingFor(model) {
|
|
|
12631
12717
|
resolved = exact;
|
|
12632
12718
|
break;
|
|
12633
12719
|
}
|
|
12720
|
+
if (options.exact) continue;
|
|
12634
12721
|
let best = null;
|
|
12635
12722
|
for (const key of Object.keys(source)) {
|
|
12636
12723
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
@@ -12642,7 +12729,7 @@ function pricingFor(model) {
|
|
|
12642
12729
|
break;
|
|
12643
12730
|
}
|
|
12644
12731
|
}
|
|
12645
|
-
lookupCache.set(
|
|
12732
|
+
lookupCache.set(lookupKey, resolved);
|
|
12646
12733
|
return resolved;
|
|
12647
12734
|
}
|
|
12648
12735
|
var import_fs18, import_path20, import_os17, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
@@ -12679,6 +12766,18 @@ var init_litellm = __esm({
|
|
|
12679
12766
|
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
12680
12767
|
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
12681
12768
|
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
12769
|
+
// Codex offline rates checked against official OpenAI model pages, 2026-09-11.
|
|
12770
|
+
"gpt-5.1-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
12771
|
+
"gpt-5.1-codex-max": [125e-8, 1e-5, 0, 125e-9],
|
|
12772
|
+
"gpt-5.1-codex-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
12773
|
+
"gpt-5.2-codex": [175e-8, 14e-6, 0, 175e-9],
|
|
12774
|
+
"gpt-5.3-codex": [175e-8, 14e-6, 0, 175e-9],
|
|
12775
|
+
"gpt-5.4": [25e-7, 15e-6, 0, 25e-8],
|
|
12776
|
+
"gpt-5.4-mini": [75e-8, 45e-7, 0, 75e-9],
|
|
12777
|
+
"gpt-5.5": [5e-6, 3e-5, 0, 5e-7],
|
|
12778
|
+
"gpt-5.6-sol": [4e-6, 2e-5, 5e-6, 4e-7],
|
|
12779
|
+
"gpt-5.6-terra": [2e-6, 12e-6, 25e-7, 2e-7],
|
|
12780
|
+
"gpt-6-astra": [1e-5, 5e-5, 125e-7, 1e-6],
|
|
12682
12781
|
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
12683
12782
|
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
12684
12783
|
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
@@ -12847,103 +12946,237 @@ var init_cost_gemini = __esm({
|
|
|
12847
12946
|
|
|
12848
12947
|
// src/cost-codex.ts
|
|
12849
12948
|
function codexSessionsDir() {
|
|
12850
|
-
return import_path22.default.join(import_os19.default.homedir(), ".codex", "sessions");
|
|
12949
|
+
return import_path22.default.join(process.env.CODEX_HOME?.trim() || import_path22.default.join(import_os19.default.homedir(), ".codex"), "sessions");
|
|
12851
12950
|
}
|
|
12852
12951
|
function codexPriceFor(model) {
|
|
12853
|
-
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
12952
|
+
return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
|
|
12854
12953
|
}
|
|
12855
|
-
function
|
|
12856
|
-
|
|
12857
|
-
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
12858
|
-
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
12954
|
+
function codexModel(model) {
|
|
12955
|
+
return normalizeModel(model.replace(/^openai\//i, "").replace(/-\d{4}-\d{2}-\d{2}$/, ""));
|
|
12859
12956
|
}
|
|
12860
|
-
function
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12957
|
+
function addTokens(previous, delta) {
|
|
12958
|
+
return {
|
|
12959
|
+
input: (previous?.input ?? 0) + delta.input,
|
|
12960
|
+
cached: (previous?.cached ?? 0) + delta.cached,
|
|
12961
|
+
output: (previous?.output ?? 0) + delta.output,
|
|
12962
|
+
cacheWrite: (previous?.cacheWrite ?? 0) + delta.cacheWrite
|
|
12963
|
+
};
|
|
12964
|
+
}
|
|
12965
|
+
function codexSessionCost(model, tokens, request2) {
|
|
12966
|
+
const input = tokenNumber(tokens.input);
|
|
12967
|
+
const cached = Math.min(input, tokenNumber(tokens.cached));
|
|
12968
|
+
const written = Math.min(input - cached, tokenNumber(tokens.cacheWrite));
|
|
12969
|
+
const [pin, pout, pcw, pcr] = codexPriceFor(model || "gpt-5");
|
|
12970
|
+
const longContext = request2 && request2.inputTokens > 272e3 && ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"].includes(
|
|
12971
|
+
codexModel(model)
|
|
12972
|
+
);
|
|
12973
|
+
const inputMultiplier = longContext ? 2 : 1;
|
|
12974
|
+
const outputMultiplier = longContext ? 1.5 : 1;
|
|
12975
|
+
const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
|
|
12976
|
+
return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
|
|
12977
|
+
}
|
|
12978
|
+
function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
12979
|
+
const files = [];
|
|
12980
|
+
const walk = (dir) => {
|
|
12981
|
+
try {
|
|
12982
|
+
for (const entry of import_fs20.default.readdirSync(dir, { withFileTypes: true })) {
|
|
12983
|
+
const file = import_path22.default.join(dir, entry.name);
|
|
12984
|
+
if (entry.isDirectory()) walk(file);
|
|
12985
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
|
|
12874
12986
|
}
|
|
12987
|
+
} catch {
|
|
12988
|
+
}
|
|
12989
|
+
};
|
|
12990
|
+
walk(base);
|
|
12991
|
+
if (import_path22.default.basename(base) === "sessions") walk(import_path22.default.join(import_path22.default.dirname(base), "archived_sessions"));
|
|
12992
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
12993
|
+
for (const file of files.sort()) {
|
|
12994
|
+
try {
|
|
12995
|
+
const stat = import_fs20.default.statSync(file);
|
|
12996
|
+
let id = "";
|
|
12997
|
+
try {
|
|
12998
|
+
const first = JSON.parse(import_fs20.default.readFileSync(file, "utf8").split("\n", 1)[0]);
|
|
12999
|
+
if (first?.type === "session_meta" && typeof first.payload?.id === "string")
|
|
13000
|
+
id = first.payload.id;
|
|
13001
|
+
} catch {
|
|
13002
|
+
}
|
|
13003
|
+
const key = id ? `session:${id}` : `file:${file}`;
|
|
13004
|
+
const prior = sessions.get(key);
|
|
13005
|
+
if (!prior || stat.mtimeMs > prior.mtime || stat.mtimeMs === prior.mtime && stat.size > prior.size) {
|
|
13006
|
+
sessions.set(key, { file, mtime: stat.mtimeMs, size: stat.size });
|
|
13007
|
+
}
|
|
13008
|
+
} catch {
|
|
12875
13009
|
}
|
|
12876
13010
|
}
|
|
12877
|
-
return
|
|
13011
|
+
return [...sessions.values()].map((s) => s.file);
|
|
12878
13012
|
}
|
|
12879
|
-
function
|
|
12880
|
-
|
|
12881
|
-
return import_fs20.default.readdirSync(dir);
|
|
12882
|
-
} catch {
|
|
12883
|
-
return [];
|
|
12884
|
-
}
|
|
13013
|
+
function record(value) {
|
|
13014
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12885
13015
|
}
|
|
12886
|
-
function
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
13016
|
+
function tokenNumber(value) {
|
|
13017
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
13018
|
+
}
|
|
13019
|
+
function usage(value, fallback) {
|
|
13020
|
+
const u = record(value);
|
|
13021
|
+
if (!["input_tokens", "output_tokens"].some((k) => typeof u[k] === "number")) return null;
|
|
13022
|
+
for (const key of [
|
|
13023
|
+
"input_tokens",
|
|
13024
|
+
"cached_input_tokens",
|
|
13025
|
+
"cache_read_input_tokens",
|
|
13026
|
+
"output_tokens",
|
|
13027
|
+
"cache_write_input_tokens"
|
|
13028
|
+
]) {
|
|
13029
|
+
if (u[key] !== void 0 && (typeof u[key] !== "number" || !Number.isFinite(u[key]) || u[key] < 0))
|
|
13030
|
+
return null;
|
|
12891
13031
|
}
|
|
13032
|
+
return {
|
|
13033
|
+
input: tokenNumber(u.input_tokens ?? fallback?.input),
|
|
13034
|
+
cached: tokenNumber(u.cached_input_tokens ?? u.cache_read_input_tokens ?? fallback?.cached),
|
|
13035
|
+
output: tokenNumber(u.output_tokens ?? fallback?.output),
|
|
13036
|
+
cacheWrite: tokenNumber(u.cache_write_input_tokens ?? fallback?.cacheWrite)
|
|
13037
|
+
};
|
|
12892
13038
|
}
|
|
12893
|
-
function
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
13039
|
+
function timestamp(value) {
|
|
13040
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : "";
|
|
13041
|
+
}
|
|
13042
|
+
function parseCodexUsage(lines) {
|
|
13043
|
+
const result = {
|
|
13044
|
+
events: [],
|
|
13045
|
+
sessionStart: "",
|
|
13046
|
+
runId: "",
|
|
13047
|
+
workingDir: "",
|
|
13048
|
+
legacyModels: []
|
|
13049
|
+
};
|
|
13050
|
+
let model = "gpt-5";
|
|
13051
|
+
let serviceTier;
|
|
13052
|
+
let previous = null;
|
|
13053
|
+
const legacyModels = /* @__PURE__ */ new Set();
|
|
13054
|
+
const seenStandalone = /* @__PURE__ */ new Set();
|
|
12902
13055
|
for (const raw of lines) {
|
|
12903
|
-
if (!raw.trim()) continue;
|
|
12904
13056
|
let entry;
|
|
12905
13057
|
try {
|
|
12906
|
-
entry = JSON.parse(raw);
|
|
13058
|
+
entry = record(JSON.parse(raw));
|
|
12907
13059
|
} catch {
|
|
12908
13060
|
continue;
|
|
12909
13061
|
}
|
|
12910
|
-
const p = entry.payload
|
|
13062
|
+
const p = record(entry.payload);
|
|
12911
13063
|
if (entry.type === "session_meta") {
|
|
12912
|
-
|
|
12913
|
-
if (!runId && typeof p
|
|
12914
|
-
if (!
|
|
13064
|
+
result.sessionStart ||= timestamp(p.timestamp ?? entry.timestamp);
|
|
13065
|
+
if (!result.runId && typeof p.id === "string") result.runId = p.id;
|
|
13066
|
+
if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
|
|
12915
13067
|
continue;
|
|
12916
13068
|
}
|
|
12917
13069
|
if (entry.type === "turn_context") {
|
|
12918
|
-
if (typeof p
|
|
12919
|
-
|
|
13070
|
+
if (typeof p.model === "string" && p.model) {
|
|
13071
|
+
model = p.model;
|
|
13072
|
+
legacyModels.add(normalizeModel(model));
|
|
13073
|
+
}
|
|
13074
|
+
if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
|
|
13075
|
+
serviceTier = typeof p.service_tier === "string" ? p.service_tier : void 0;
|
|
12920
13076
|
continue;
|
|
12921
13077
|
}
|
|
12922
|
-
if (entry.type
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
13078
|
+
if (entry.type !== "event_msg" || p.type !== "token_count") continue;
|
|
13079
|
+
const info = record(p.info);
|
|
13080
|
+
const total = usage(info.total_token_usage, previous);
|
|
13081
|
+
const last = usage(info.last_token_usage);
|
|
13082
|
+
if (!total && !last) continue;
|
|
13083
|
+
const eventModel = [info.model, info.model_name, p.model].find(
|
|
13084
|
+
(v) => typeof v === "string" && v
|
|
13085
|
+
);
|
|
13086
|
+
if (typeof eventModel === "string") model = eventModel;
|
|
13087
|
+
let delta;
|
|
13088
|
+
if (total) {
|
|
13089
|
+
if (previous && Object.keys(total).every(
|
|
13090
|
+
(k) => total[k] === previous[k]
|
|
13091
|
+
))
|
|
13092
|
+
continue;
|
|
13093
|
+
const reset = previous && (total.input < previous.input || total.output < previous.output);
|
|
13094
|
+
delta = reset ? last ?? total : {
|
|
13095
|
+
input: Math.max(0, total.input - (previous?.input ?? 0)),
|
|
13096
|
+
cached: Math.max(0, total.cached - (previous?.cached ?? 0)),
|
|
13097
|
+
output: Math.max(0, total.output - (previous?.output ?? 0)),
|
|
13098
|
+
cacheWrite: Math.max(0, total.cacheWrite - (previous?.cacheWrite ?? 0))
|
|
13099
|
+
};
|
|
13100
|
+
previous = total;
|
|
13101
|
+
} else {
|
|
13102
|
+
delta = last;
|
|
13103
|
+
const key = JSON.stringify([entry.timestamp, model, delta]);
|
|
13104
|
+
if (entry.timestamp && seenStandalone.has(key)) continue;
|
|
13105
|
+
if (entry.timestamp) seenStandalone.add(key);
|
|
13106
|
+
previous = addTokens(previous, delta);
|
|
13107
|
+
}
|
|
13108
|
+
if (delta.input === 0 && delta.output === 0) continue;
|
|
13109
|
+
const ts = timestamp(entry.timestamp) || result.sessionStart;
|
|
13110
|
+
if (!ts) continue;
|
|
13111
|
+
const cached = Math.min(delta.input, delta.cached);
|
|
13112
|
+
const written = Math.min(delta.input - cached, delta.cacheWrite);
|
|
13113
|
+
result.events.push({
|
|
13114
|
+
timestamp: ts,
|
|
13115
|
+
date: ts.slice(0, 10),
|
|
13116
|
+
model: normalizeModel(model),
|
|
13117
|
+
workingDir: result.workingDir,
|
|
13118
|
+
runId: result.runId,
|
|
13119
|
+
costUSD: codexSessionCost(model, delta, {
|
|
13120
|
+
inputTokens: last?.input ?? delta.input,
|
|
13121
|
+
serviceTier: typeof info.service_tier === "string" ? info.service_tier : serviceTier
|
|
13122
|
+
}),
|
|
13123
|
+
inputTokens: delta.input - cached - written,
|
|
13124
|
+
outputTokens: delta.output,
|
|
13125
|
+
cacheReadTokens: cached,
|
|
13126
|
+
cacheWriteTokens: written
|
|
13127
|
+
});
|
|
13128
|
+
}
|
|
13129
|
+
result.legacyModels = [...legacyModels.size ? legacyModels : ["gpt-5"]];
|
|
13130
|
+
return result;
|
|
13131
|
+
}
|
|
13132
|
+
function codexUsageInWindow(usage2, start, end) {
|
|
13133
|
+
return usage2.events.filter(
|
|
13134
|
+
(e) => (!start || Date.parse(e.timestamp) >= start.getTime()) && (!end || Date.parse(e.timestamp) <= end.getTime())
|
|
13135
|
+
);
|
|
13136
|
+
}
|
|
13137
|
+
function parseCodexSession(lines) {
|
|
13138
|
+
const parsed = parseCodexUsage(lines);
|
|
13139
|
+
if (!parsed.events.length) return [];
|
|
13140
|
+
const rows = /* @__PURE__ */ new Map();
|
|
13141
|
+
if (parsed.sessionStart) {
|
|
13142
|
+
for (const model of parsed.legacyModels) {
|
|
13143
|
+
rows.set(`${parsed.sessionStart.slice(0, 10)}::${model}`, {
|
|
13144
|
+
date: parsed.sessionStart.slice(0, 10),
|
|
13145
|
+
model,
|
|
13146
|
+
workingDir: parsed.workingDir,
|
|
13147
|
+
runId: parsed.runId,
|
|
13148
|
+
costUSD: 0,
|
|
13149
|
+
inputTokens: 0,
|
|
13150
|
+
outputTokens: 0,
|
|
13151
|
+
cacheReadTokens: 0,
|
|
13152
|
+
cacheWriteTokens: 0
|
|
13153
|
+
});
|
|
12929
13154
|
}
|
|
12930
13155
|
}
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
13156
|
+
for (const event of parsed.events) {
|
|
13157
|
+
const e = {
|
|
13158
|
+
date: event.date,
|
|
13159
|
+
model: event.model,
|
|
13160
|
+
workingDir: event.workingDir,
|
|
13161
|
+
runId: event.runId,
|
|
13162
|
+
costUSD: event.costUSD,
|
|
13163
|
+
inputTokens: event.inputTokens,
|
|
13164
|
+
outputTokens: event.outputTokens,
|
|
13165
|
+
cacheReadTokens: event.cacheReadTokens,
|
|
13166
|
+
cacheWriteTokens: event.cacheWriteTokens
|
|
13167
|
+
};
|
|
13168
|
+
const key = `${e.date}::${e.model}`;
|
|
13169
|
+
const prev = rows.get(key);
|
|
13170
|
+
if (!prev) rows.set(key, { ...e });
|
|
13171
|
+
else {
|
|
13172
|
+
prev.costUSD += e.costUSD;
|
|
13173
|
+
prev.inputTokens += e.inputTokens;
|
|
13174
|
+
prev.outputTokens += e.outputTokens;
|
|
13175
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
13176
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
13177
|
+
}
|
|
13178
|
+
}
|
|
13179
|
+
return [...rows.values()];
|
|
12947
13180
|
}
|
|
12948
13181
|
var import_fs20, import_os19, import_path22, CODEX_FALLBACK, codexSource;
|
|
12949
13182
|
var init_cost_codex = __esm({
|
|
@@ -12956,43 +13189,17 @@ var init_cost_codex = __esm({
|
|
|
12956
13189
|
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
12957
13190
|
codexSource = {
|
|
12958
13191
|
id: "codex",
|
|
12959
|
-
available()
|
|
12960
|
-
try {
|
|
12961
|
-
return import_fs20.default.existsSync(codexSessionsDir());
|
|
12962
|
-
} catch {
|
|
12963
|
-
return false;
|
|
12964
|
-
}
|
|
12965
|
-
},
|
|
13192
|
+
available: () => import_fs20.default.existsSync(codexSessionsDir()) || import_fs20.default.existsSync(import_path22.default.join(import_path22.default.dirname(codexSessionsDir()), "archived_sessions")),
|
|
12966
13193
|
collect(sinceMs) {
|
|
12967
|
-
const
|
|
12968
|
-
const
|
|
12969
|
-
for (const file of listCodexSessionFiles(base)) {
|
|
13194
|
+
const entries = [];
|
|
13195
|
+
for (const file of listCodexSessionFiles()) {
|
|
12970
13196
|
try {
|
|
12971
13197
|
if (sinceMs !== void 0 && import_fs20.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
13198
|
+
entries.push(...parseCodexSession(import_fs20.default.readFileSync(file, "utf8").split("\n")));
|
|
12972
13199
|
} 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
13200
|
}
|
|
12994
13201
|
}
|
|
12995
|
-
return
|
|
13202
|
+
return entries;
|
|
12996
13203
|
}
|
|
12997
13204
|
};
|
|
12998
13205
|
}
|
|
@@ -13002,7 +13209,7 @@ var init_cost_codex = __esm({
|
|
|
13002
13209
|
function copilotSessionsDir() {
|
|
13003
13210
|
return import_path23.default.join(import_os20.default.homedir(), ".copilot", "session-state");
|
|
13004
13211
|
}
|
|
13005
|
-
function
|
|
13212
|
+
function safeReaddir2(dir) {
|
|
13006
13213
|
try {
|
|
13007
13214
|
return import_fs21.default.readdirSync(dir);
|
|
13008
13215
|
} catch {
|
|
@@ -13091,7 +13298,7 @@ var init_cost_copilot = __esm({
|
|
|
13091
13298
|
collect(sinceMs) {
|
|
13092
13299
|
const base = copilotSessionsDir();
|
|
13093
13300
|
const combined = /* @__PURE__ */ new Map();
|
|
13094
|
-
for (const sid of
|
|
13301
|
+
for (const sid of safeReaddir2(base)) {
|
|
13095
13302
|
const file = import_path23.default.join(base, sid, "events.jsonl");
|
|
13096
13303
|
try {
|
|
13097
13304
|
if (sinceMs !== void 0 && import_fs21.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
@@ -13705,8 +13912,8 @@ function originForRule(ruleName, sections, enabled) {
|
|
|
13705
13912
|
}
|
|
13706
13913
|
return "";
|
|
13707
13914
|
}
|
|
13708
|
-
function relativeDate(
|
|
13709
|
-
const t = new Date(
|
|
13915
|
+
function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
|
|
13916
|
+
const t = new Date(timestamp2).getTime();
|
|
13710
13917
|
if (Number.isNaN(t)) return "?";
|
|
13711
13918
|
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
13712
13919
|
if (days < 1) return "today";
|
|
@@ -13817,7 +14024,7 @@ function readPreviousScan(opts = {}) {
|
|
|
13817
14024
|
return null;
|
|
13818
14025
|
}
|
|
13819
14026
|
}
|
|
13820
|
-
function appendScanHistory(
|
|
14027
|
+
function appendScanHistory(record2, opts = {}) {
|
|
13821
14028
|
const filePath = opts.path ?? defaultHistoryPath();
|
|
13822
14029
|
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
13823
14030
|
try {
|
|
@@ -13832,7 +14039,7 @@ function appendScanHistory(record, opts = {}) {
|
|
|
13832
14039
|
} catch {
|
|
13833
14040
|
}
|
|
13834
14041
|
}
|
|
13835
|
-
history.push(
|
|
14042
|
+
history.push(record2);
|
|
13836
14043
|
if (history.length > cap) {
|
|
13837
14044
|
history = history.slice(history.length - cap);
|
|
13838
14045
|
}
|
|
@@ -13893,17 +14100,17 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
13893
14100
|
if (row["type"] !== "assistant") continue;
|
|
13894
14101
|
const msg = row["message"];
|
|
13895
14102
|
if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
|
|
13896
|
-
const
|
|
14103
|
+
const usage2 = msg["usage"];
|
|
13897
14104
|
const model = msg["model"];
|
|
13898
|
-
const
|
|
13899
|
-
if (typeof
|
|
13900
|
-
const date =
|
|
14105
|
+
const timestamp2 = row["timestamp"];
|
|
14106
|
+
if (typeof timestamp2 !== "string" || timestamp2.length < 10) continue;
|
|
14107
|
+
const date = timestamp2.slice(0, 10);
|
|
13901
14108
|
const p = pricingFor(model);
|
|
13902
14109
|
if (!p) continue;
|
|
13903
|
-
const inp = Number(
|
|
13904
|
-
const out = Number(
|
|
13905
|
-
const cw = Number(
|
|
13906
|
-
const cr = Number(
|
|
14110
|
+
const inp = Number(usage2["input_tokens"] ?? 0);
|
|
14111
|
+
const out = Number(usage2["output_tokens"] ?? 0);
|
|
14112
|
+
const cw = Number(usage2["cache_creation_input_tokens"] ?? 0);
|
|
14113
|
+
const cr = Number(usage2["cache_read_input_tokens"] ?? 0);
|
|
13907
14114
|
const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
|
|
13908
14115
|
const rowCwd = typeof row["cwd"] === "string" ? row["cwd"] : null;
|
|
13909
14116
|
const workingDir = rowCwd && rowCwd.startsWith("/") ? rowCwd : fallbackWorkingDir;
|
|
@@ -14931,7 +15138,7 @@ function safeCanaryScanValues() {
|
|
|
14931
15138
|
return [];
|
|
14932
15139
|
}
|
|
14933
15140
|
}
|
|
14934
|
-
function recordCanaries(scanned, toolName,
|
|
15141
|
+
function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, agent, result, dedup, values) {
|
|
14935
15142
|
if (values.length === 0) return [];
|
|
14936
15143
|
let pool = [...values];
|
|
14937
15144
|
const matched = [];
|
|
@@ -14945,8 +15152,8 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
|
|
|
14945
15152
|
const existing = dedup.canaryIndex.get(key);
|
|
14946
15153
|
if (existing) {
|
|
14947
15154
|
existing.count++;
|
|
14948
|
-
if (
|
|
14949
|
-
existing.timestamp =
|
|
15155
|
+
if (timestamp2 && (!existing.timestamp || timestamp2 < existing.timestamp)) {
|
|
15156
|
+
existing.timestamp = timestamp2;
|
|
14950
15157
|
}
|
|
14951
15158
|
continue;
|
|
14952
15159
|
}
|
|
@@ -14959,7 +15166,7 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
|
|
|
14959
15166
|
view: hit.view,
|
|
14960
15167
|
retired: hit.retired,
|
|
14961
15168
|
toolName,
|
|
14962
|
-
timestamp,
|
|
15169
|
+
timestamp: timestamp2,
|
|
14963
15170
|
project: projLabel,
|
|
14964
15171
|
sessionId,
|
|
14965
15172
|
agent,
|
|
@@ -14991,7 +15198,7 @@ function scrubDecoys(subject, values) {
|
|
|
14991
15198
|
};
|
|
14992
15199
|
return walk(subject, 0);
|
|
14993
15200
|
}
|
|
14994
|
-
function pushFsOpAstFinding(command, toolName, input,
|
|
15201
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
|
|
14995
15202
|
const fsVerdict = analyzeFsOperation(command);
|
|
14996
15203
|
if (!fsVerdict) return false;
|
|
14997
15204
|
const synthRule = {
|
|
@@ -15021,7 +15228,7 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
15021
15228
|
source: synthSource,
|
|
15022
15229
|
toolName,
|
|
15023
15230
|
input,
|
|
15024
|
-
timestamp,
|
|
15231
|
+
timestamp: timestamp2,
|
|
15025
15232
|
project: projLabel,
|
|
15026
15233
|
sessionId,
|
|
15027
15234
|
agent
|
|
@@ -15029,9 +15236,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
15029
15236
|
}
|
|
15030
15237
|
return true;
|
|
15031
15238
|
}
|
|
15032
|
-
function isStaleFinding(
|
|
15033
|
-
if (!
|
|
15034
|
-
const t = Date.parse(
|
|
15239
|
+
function isStaleFinding(timestamp2, now = Date.now()) {
|
|
15240
|
+
if (!timestamp2) return false;
|
|
15241
|
+
const t = Date.parse(timestamp2);
|
|
15035
15242
|
if (Number.isNaN(t)) return false;
|
|
15036
15243
|
const ageDays = (now - t) / 864e5;
|
|
15037
15244
|
return ageDays > STALE_AGE_DAYS;
|
|
@@ -15179,37 +15386,7 @@ function countScanFiles() {
|
|
|
15179
15386
|
} catch {
|
|
15180
15387
|
}
|
|
15181
15388
|
}
|
|
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
|
-
}
|
|
15389
|
+
total += listCodexSessionFiles().length;
|
|
15213
15390
|
return total;
|
|
15214
15391
|
}
|
|
15215
15392
|
function renderProgressBar(done, total, lines) {
|
|
@@ -15332,12 +15509,12 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
15332
15509
|
}
|
|
15333
15510
|
continue;
|
|
15334
15511
|
}
|
|
15335
|
-
const
|
|
15512
|
+
const usage2 = entry.message?.usage;
|
|
15336
15513
|
const model = entry.message?.model;
|
|
15337
|
-
if (
|
|
15514
|
+
if (usage2 && model) {
|
|
15338
15515
|
const p = claudeModelPrice(model);
|
|
15339
15516
|
if (p) {
|
|
15340
|
-
const rowCost = (
|
|
15517
|
+
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
15518
|
result.totalCostUSD += rowCost;
|
|
15342
15519
|
session.costUSD += rowCost;
|
|
15343
15520
|
}
|
|
@@ -15871,15 +16048,15 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15871
16048
|
} catch {
|
|
15872
16049
|
continue;
|
|
15873
16050
|
}
|
|
15874
|
-
const
|
|
15875
|
-
if (startDate &&
|
|
16051
|
+
const timestamp2 = step.created_at ?? "";
|
|
16052
|
+
if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
|
|
15876
16053
|
if (step.type === "USER_INPUT") {
|
|
15877
16054
|
const text = typeof step.content === "string" ? step.content : "";
|
|
15878
16055
|
if (text) {
|
|
15879
16056
|
const decoysHere5 = recordCanaries(
|
|
15880
16057
|
{ text },
|
|
15881
16058
|
"user-prompt",
|
|
15882
|
-
|
|
16059
|
+
timestamp2,
|
|
15883
16060
|
projLabel,
|
|
15884
16061
|
sessionId,
|
|
15885
16062
|
"antigravity",
|
|
@@ -15896,7 +16073,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15896
16073
|
patternName: dlpMatch.patternName,
|
|
15897
16074
|
redactedSample: dlpMatch.redactedSample,
|
|
15898
16075
|
toolName: "user-prompt",
|
|
15899
|
-
timestamp,
|
|
16076
|
+
timestamp: timestamp2,
|
|
15900
16077
|
project: projLabel,
|
|
15901
16078
|
sessionId,
|
|
15902
16079
|
agent: "antigravity"
|
|
@@ -15907,16 +16084,16 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15907
16084
|
continue;
|
|
15908
16085
|
}
|
|
15909
16086
|
if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
|
|
15910
|
-
if (
|
|
15911
|
-
if (!result.firstDate ||
|
|
15912
|
-
if (!result.lastDate ||
|
|
16087
|
+
if (timestamp2) {
|
|
16088
|
+
if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
|
|
16089
|
+
if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
|
|
15913
16090
|
}
|
|
15914
16091
|
for (const tc of step.tool_calls) {
|
|
15915
16092
|
result.totalToolCalls++;
|
|
15916
16093
|
const toolName = tc.name ?? "";
|
|
15917
16094
|
const toolNameLower = toolName.toLowerCase();
|
|
15918
16095
|
const input = canonicalToolInput(toolName, tc.args ?? {});
|
|
15919
|
-
sessionCalls.push({ toolName, input, timestamp });
|
|
16096
|
+
sessionCalls.push({ toolName, input, timestamp: timestamp2 });
|
|
15920
16097
|
const isShellTool = toolNameLower === "run_command";
|
|
15921
16098
|
if (isShellTool) {
|
|
15922
16099
|
result.bashCalls++;
|
|
@@ -15931,7 +16108,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15931
16108
|
const decoysHere6 = recordCanaries(
|
|
15932
16109
|
input,
|
|
15933
16110
|
toolName,
|
|
15934
|
-
|
|
16111
|
+
timestamp2,
|
|
15935
16112
|
projLabel,
|
|
15936
16113
|
sessionId,
|
|
15937
16114
|
"antigravity",
|
|
@@ -15948,7 +16125,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15948
16125
|
patternName: dlpMatch.patternName,
|
|
15949
16126
|
redactedSample: dlpMatch.redactedSample,
|
|
15950
16127
|
toolName,
|
|
15951
|
-
timestamp,
|
|
16128
|
+
timestamp: timestamp2,
|
|
15952
16129
|
project: projLabel,
|
|
15953
16130
|
sessionId,
|
|
15954
16131
|
agent: "antigravity"
|
|
@@ -15961,7 +16138,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15961
16138
|
String(input.command ?? ""),
|
|
15962
16139
|
toolName,
|
|
15963
16140
|
input,
|
|
15964
|
-
|
|
16141
|
+
timestamp2,
|
|
15965
16142
|
projLabel,
|
|
15966
16143
|
sessionId,
|
|
15967
16144
|
"antigravity",
|
|
@@ -15985,7 +16162,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
15985
16162
|
source,
|
|
15986
16163
|
toolName,
|
|
15987
16164
|
input,
|
|
15988
|
-
timestamp,
|
|
16165
|
+
timestamp: timestamp2,
|
|
15989
16166
|
project: projLabel,
|
|
15990
16167
|
sessionId,
|
|
15991
16168
|
agent: "antigravity"
|
|
@@ -16017,7 +16194,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
|
|
|
16017
16194
|
},
|
|
16018
16195
|
toolName,
|
|
16019
16196
|
input,
|
|
16020
|
-
timestamp,
|
|
16197
|
+
timestamp: timestamp2,
|
|
16021
16198
|
project: projLabel,
|
|
16022
16199
|
sessionId,
|
|
16023
16200
|
agent: "antigravity"
|
|
@@ -16087,7 +16264,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16087
16264
|
} catch {
|
|
16088
16265
|
continue;
|
|
16089
16266
|
}
|
|
16090
|
-
const
|
|
16267
|
+
const timestamp2 = ev.timestamp ?? "";
|
|
16091
16268
|
if (ev.type === "session.start") {
|
|
16092
16269
|
const cwd = ev.data?.context?.cwd;
|
|
16093
16270
|
if (typeof cwd === "string" && cwd) {
|
|
@@ -16095,14 +16272,14 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16095
16272
|
}
|
|
16096
16273
|
continue;
|
|
16097
16274
|
}
|
|
16098
|
-
if (startDate &&
|
|
16275
|
+
if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
|
|
16099
16276
|
if (ev.type === "user.message") {
|
|
16100
16277
|
const text = ev.data?.content ?? ev.data?.text ?? "";
|
|
16101
16278
|
if (typeof text === "string" && text) {
|
|
16102
16279
|
const decoysHere7 = recordCanaries(
|
|
16103
16280
|
{ text },
|
|
16104
16281
|
"user-prompt",
|
|
16105
|
-
|
|
16282
|
+
timestamp2,
|
|
16106
16283
|
projLabel,
|
|
16107
16284
|
sessionId,
|
|
16108
16285
|
"copilot",
|
|
@@ -16119,7 +16296,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16119
16296
|
patternName: dlpMatch2.patternName,
|
|
16120
16297
|
redactedSample: dlpMatch2.redactedSample,
|
|
16121
16298
|
toolName: "user-prompt",
|
|
16122
|
-
timestamp,
|
|
16299
|
+
timestamp: timestamp2,
|
|
16123
16300
|
project: projLabel,
|
|
16124
16301
|
sessionId,
|
|
16125
16302
|
agent: "copilot"
|
|
@@ -16134,19 +16311,19 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16134
16311
|
const toolNameLower = toolName.toLowerCase();
|
|
16135
16312
|
const input = ev.data?.arguments ?? {};
|
|
16136
16313
|
result.totalToolCalls++;
|
|
16137
|
-
sessionCalls.push({ toolName, input, timestamp });
|
|
16314
|
+
sessionCalls.push({ toolName, input, timestamp: timestamp2 });
|
|
16138
16315
|
const isShellTool = isShellShapedTool(toolNameLower, toolInspectionMap);
|
|
16139
16316
|
if (isShellTool) result.bashCalls++;
|
|
16140
|
-
if (
|
|
16141
|
-
if (!result.firstDate ||
|
|
16142
|
-
if (!result.lastDate ||
|
|
16317
|
+
if (timestamp2) {
|
|
16318
|
+
if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
|
|
16319
|
+
if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
|
|
16143
16320
|
}
|
|
16144
16321
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
16145
16322
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
16146
16323
|
const decoysHere8 = recordCanaries(
|
|
16147
16324
|
input,
|
|
16148
16325
|
toolName,
|
|
16149
|
-
|
|
16326
|
+
timestamp2,
|
|
16150
16327
|
projLabel,
|
|
16151
16328
|
sessionId,
|
|
16152
16329
|
"copilot",
|
|
@@ -16163,7 +16340,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16163
16340
|
patternName: dlpMatch.patternName,
|
|
16164
16341
|
redactedSample: dlpMatch.redactedSample,
|
|
16165
16342
|
toolName,
|
|
16166
|
-
timestamp,
|
|
16343
|
+
timestamp: timestamp2,
|
|
16167
16344
|
project: projLabel,
|
|
16168
16345
|
sessionId,
|
|
16169
16346
|
agent: "copilot"
|
|
@@ -16176,7 +16353,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16176
16353
|
String(input.command ?? ""),
|
|
16177
16354
|
toolName,
|
|
16178
16355
|
input,
|
|
16179
|
-
|
|
16356
|
+
timestamp2,
|
|
16180
16357
|
projLabel,
|
|
16181
16358
|
sessionId,
|
|
16182
16359
|
"copilot",
|
|
@@ -16199,7 +16376,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16199
16376
|
source,
|
|
16200
16377
|
toolName,
|
|
16201
16378
|
input,
|
|
16202
|
-
timestamp,
|
|
16379
|
+
timestamp: timestamp2,
|
|
16203
16380
|
project: projLabel,
|
|
16204
16381
|
sessionId,
|
|
16205
16382
|
agent: "copilot"
|
|
@@ -16231,7 +16408,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16231
16408
|
},
|
|
16232
16409
|
toolName,
|
|
16233
16410
|
input,
|
|
16234
|
-
timestamp,
|
|
16411
|
+
timestamp: timestamp2,
|
|
16235
16412
|
project: projLabel,
|
|
16236
16413
|
sessionId,
|
|
16237
16414
|
agent: "copilot"
|
|
@@ -16246,7 +16423,6 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
|
|
|
16246
16423
|
}
|
|
16247
16424
|
function scanCodexHistory(startDate, onProgress, onLine) {
|
|
16248
16425
|
const canaryVals = safeCanaryScanValues();
|
|
16249
|
-
const sessionsBase = import_path29.default.join(import_os26.default.homedir(), ".codex", "sessions");
|
|
16250
16426
|
const result = {
|
|
16251
16427
|
filesScanned: 0,
|
|
16252
16428
|
sessions: 0,
|
|
@@ -16263,39 +16439,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16263
16439
|
perSession: []
|
|
16264
16440
|
};
|
|
16265
16441
|
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
|
-
}
|
|
16442
|
+
const jsonlFiles = listCodexSessionFiles();
|
|
16299
16443
|
const ruleSources = buildRuleSources();
|
|
16300
16444
|
for (const filePath of jsonlFiles) {
|
|
16301
16445
|
result.filesScanned++;
|
|
@@ -16311,10 +16455,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16311
16455
|
let projLabel = "";
|
|
16312
16456
|
result.sessions++;
|
|
16313
16457
|
const sessionCalls = [];
|
|
16314
|
-
let lastTotalInput = 0;
|
|
16315
|
-
let lastTotalCached = 0;
|
|
16316
|
-
let lastTotalOutput = 0;
|
|
16317
|
-
let model = "";
|
|
16318
16458
|
for (const line of lines) {
|
|
16319
16459
|
if (!line.trim()) continue;
|
|
16320
16460
|
onLine?.();
|
|
@@ -16332,18 +16472,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16332
16472
|
projLabel = stripTerminalEscapes(cwd.replace(import_os26.default.homedir(), "~")).slice(0, 40);
|
|
16333
16473
|
continue;
|
|
16334
16474
|
}
|
|
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
16475
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
16348
16476
|
const text = String(payload["message"] ?? "");
|
|
16349
16477
|
if (text) {
|
|
@@ -16500,13 +16628,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
16500
16628
|
}
|
|
16501
16629
|
}
|
|
16502
16630
|
}
|
|
16503
|
-
const
|
|
16504
|
-
|
|
16505
|
-
result.totalCostUSD += codexSessionCost(model, {
|
|
16506
|
-
input: lastTotalInput,
|
|
16507
|
-
cached: lastTotalCached,
|
|
16508
|
-
output: lastTotalOutput
|
|
16509
|
-
});
|
|
16631
|
+
for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
|
|
16632
|
+
result.totalCostUSD += event.costUSD;
|
|
16510
16633
|
}
|
|
16511
16634
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
16512
16635
|
}
|
|
@@ -17943,13 +18066,13 @@ var init_taint_store = __esm({
|
|
|
17943
18066
|
*/
|
|
17944
18067
|
check(filePath) {
|
|
17945
18068
|
const resolved = this._resolve(filePath);
|
|
17946
|
-
const
|
|
17947
|
-
if (!
|
|
17948
|
-
if (Date.now() >
|
|
18069
|
+
const record2 = this.records.get(resolved);
|
|
18070
|
+
if (!record2) return null;
|
|
18071
|
+
if (Date.now() > record2.expiresAt) {
|
|
17949
18072
|
this.records.delete(resolved);
|
|
17950
18073
|
return null;
|
|
17951
18074
|
}
|
|
17952
|
-
return
|
|
18075
|
+
return record2;
|
|
17953
18076
|
}
|
|
17954
18077
|
/**
|
|
17955
18078
|
* Propagate taint from sourcePath to destPath (e.g. cp, mv).
|
|
@@ -17970,8 +18093,8 @@ var init_taint_store = __esm({
|
|
|
17970
18093
|
/** Remove all expired records. Called periodically by the daemon. */
|
|
17971
18094
|
prune() {
|
|
17972
18095
|
const now = Date.now();
|
|
17973
|
-
for (const [key,
|
|
17974
|
-
if (now >
|
|
18096
|
+
for (const [key, record2] of this.records) {
|
|
18097
|
+
if (now > record2.expiresAt) this.records.delete(key);
|
|
17975
18098
|
}
|
|
17976
18099
|
}
|
|
17977
18100
|
/** Return all non-expired taint records (for audit/debug). */
|
|
@@ -18010,13 +18133,13 @@ var init_taint_store = __esm({
|
|
|
18010
18133
|
* Expired records are pruned on access. */
|
|
18011
18134
|
check(sessionId) {
|
|
18012
18135
|
if (!sessionId) return null;
|
|
18013
|
-
const
|
|
18014
|
-
if (!
|
|
18015
|
-
if (Date.now() >
|
|
18136
|
+
const record2 = this.records.get(sessionId);
|
|
18137
|
+
if (!record2) return null;
|
|
18138
|
+
if (Date.now() > record2.expiresAt) {
|
|
18016
18139
|
this.records.delete(sessionId);
|
|
18017
18140
|
return null;
|
|
18018
18141
|
}
|
|
18019
|
-
return
|
|
18142
|
+
return record2;
|
|
18020
18143
|
}
|
|
18021
18144
|
/** Clear a session's taint (e.g. the user resolved it). Returns true if a
|
|
18022
18145
|
* record was actually removed (false if the session wasn't tainted). */
|
|
@@ -18031,8 +18154,8 @@ var init_taint_store = __esm({
|
|
|
18031
18154
|
/** Remove all expired records. Called periodically by the daemon. */
|
|
18032
18155
|
prune() {
|
|
18033
18156
|
const now = Date.now();
|
|
18034
|
-
for (const [key,
|
|
18035
|
-
if (now >
|
|
18157
|
+
for (const [key, record2] of this.records) {
|
|
18158
|
+
if (now > record2.expiresAt) this.records.delete(key);
|
|
18036
18159
|
}
|
|
18037
18160
|
}
|
|
18038
18161
|
/** Remove all records. Used by tests to reset state between runs. */
|
|
@@ -22765,10 +22888,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
22765
22888
|
return res.end(JSON.stringify({ error: "all paths must be strings" }));
|
|
22766
22889
|
}
|
|
22767
22890
|
for (const p of body.paths) {
|
|
22768
|
-
const
|
|
22769
|
-
if (
|
|
22891
|
+
const record2 = taintStore.check(p);
|
|
22892
|
+
if (record2) {
|
|
22770
22893
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22771
|
-
return res.end(JSON.stringify({ tainted: true, record }));
|
|
22894
|
+
return res.end(JSON.stringify({ tainted: true, record: record2 }));
|
|
22772
22895
|
}
|
|
22773
22896
|
}
|
|
22774
22897
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -22817,9 +22940,9 @@ data: ${JSON.stringify(item.data)}
|
|
|
22817
22940
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
22818
22941
|
return res.end(JSON.stringify({ error: "sessionId must be a string" }));
|
|
22819
22942
|
}
|
|
22820
|
-
const
|
|
22943
|
+
const record2 = sessionTaintStore.check(body.sessionId);
|
|
22821
22944
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22822
|
-
return res.end(JSON.stringify(
|
|
22945
|
+
return res.end(JSON.stringify(record2 ? { tainted: true, record: record2 } : { tainted: false }));
|
|
22823
22946
|
} catch {
|
|
22824
22947
|
res.writeHead(400).end();
|
|
22825
22948
|
return;
|
|
@@ -28773,8 +28896,8 @@ var require_util2 = __commonJS({
|
|
|
28773
28896
|
request2.headersList.append("origin", serializedOrigin, true);
|
|
28774
28897
|
}
|
|
28775
28898
|
}
|
|
28776
|
-
function coarsenTime(
|
|
28777
|
-
return
|
|
28899
|
+
function coarsenTime(timestamp2, crossOriginIsolatedCapability) {
|
|
28900
|
+
return timestamp2;
|
|
28778
28901
|
}
|
|
28779
28902
|
function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
|
|
28780
28903
|
if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
|
|
@@ -38855,20 +38978,20 @@ var require_dns = __commonJS({
|
|
|
38855
38978
|
return ip;
|
|
38856
38979
|
}
|
|
38857
38980
|
setRecords(origin, addresses) {
|
|
38858
|
-
const
|
|
38981
|
+
const timestamp2 = Date.now();
|
|
38859
38982
|
const records = { records: { 4: null, 6: null } };
|
|
38860
38983
|
let minTTL = this.#maxTTL;
|
|
38861
|
-
for (const
|
|
38862
|
-
|
|
38863
|
-
if (typeof
|
|
38864
|
-
|
|
38865
|
-
minTTL = Math.min(minTTL,
|
|
38984
|
+
for (const record2 of addresses) {
|
|
38985
|
+
record2.timestamp = timestamp2;
|
|
38986
|
+
if (typeof record2.ttl === "number") {
|
|
38987
|
+
record2.ttl = Math.min(record2.ttl, this.#maxTTL);
|
|
38988
|
+
minTTL = Math.min(minTTL, record2.ttl);
|
|
38866
38989
|
} else {
|
|
38867
|
-
|
|
38990
|
+
record2.ttl = this.#maxTTL;
|
|
38868
38991
|
}
|
|
38869
|
-
const familyRecords = records.records[
|
|
38870
|
-
familyRecords.ips.push(
|
|
38871
|
-
records.records[
|
|
38992
|
+
const familyRecords = records.records[record2.family] ?? { ips: [] };
|
|
38993
|
+
familyRecords.ips.push(record2);
|
|
38994
|
+
records.records[record2.family] = familyRecords;
|
|
38872
38995
|
}
|
|
38873
38996
|
this.storage.set(origin.hostname, records, { ttl: minTTL });
|
|
38874
38997
|
}
|
|
@@ -53532,15 +53655,15 @@ var import_fs55 = __toESM(require("fs"));
|
|
|
53532
53655
|
var import_path53 = __toESM(require("path"));
|
|
53533
53656
|
init_decision();
|
|
53534
53657
|
var import_os49 = __toESM(require("os"));
|
|
53535
|
-
function formatRelativeTime(
|
|
53536
|
-
const diff = Date.now() - new Date(
|
|
53658
|
+
function formatRelativeTime(timestamp2) {
|
|
53659
|
+
const diff = Date.now() - new Date(timestamp2).getTime();
|
|
53537
53660
|
const sec = Math.floor(diff / 1e3);
|
|
53538
53661
|
if (sec < 60) return `${sec}s ago`;
|
|
53539
53662
|
const min = Math.floor(sec / 60);
|
|
53540
53663
|
if (min < 60) return `${min}m ago`;
|
|
53541
53664
|
const hrs = Math.floor(min / 60);
|
|
53542
53665
|
if (hrs < 24) return `${hrs}h ago`;
|
|
53543
|
-
return new Date(
|
|
53666
|
+
return new Date(timestamp2).toLocaleDateString();
|
|
53544
53667
|
}
|
|
53545
53668
|
function registerAuditCommand(program2) {
|
|
53546
53669
|
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 +53909,15 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
53786
53909
|
if (!entry.timestamp) continue;
|
|
53787
53910
|
const ts = new Date(entry.timestamp);
|
|
53788
53911
|
if (ts < start || ts > end) continue;
|
|
53789
|
-
const
|
|
53912
|
+
const usage2 = entry.message?.usage;
|
|
53790
53913
|
const model = entry.message?.model;
|
|
53791
|
-
if (!
|
|
53914
|
+
if (!usage2 || !model) continue;
|
|
53792
53915
|
const p = claudeModelPrice2(model);
|
|
53793
53916
|
if (!p) continue;
|
|
53794
|
-
const inp =
|
|
53795
|
-
const out =
|
|
53796
|
-
const cw =
|
|
53797
|
-
const cr =
|
|
53917
|
+
const inp = usage2.input_tokens ?? 0;
|
|
53918
|
+
const out = usage2.output_tokens ?? 0;
|
|
53919
|
+
const cw = usage2.cache_creation_input_tokens ?? 0;
|
|
53920
|
+
const cr = usage2.cache_read_input_tokens ?? 0;
|
|
53798
53921
|
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
53799
53922
|
acc.total += cost;
|
|
53800
53923
|
acc.inputTokens += inp;
|
|
@@ -53842,90 +53965,21 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
53842
53965
|
} catch {
|
|
53843
53966
|
return;
|
|
53844
53967
|
}
|
|
53845
|
-
|
|
53846
|
-
|
|
53847
|
-
|
|
53848
|
-
|
|
53849
|
-
|
|
53850
|
-
|
|
53968
|
+
const parsed = parseCodexUsage(lines);
|
|
53969
|
+
for (const event of codexUsageInWindow(parsed, start, end)) {
|
|
53970
|
+
acc.total += event.costUSD;
|
|
53971
|
+
acc.byDay.set(event.date, (acc.byDay.get(event.date) ?? 0) + event.costUSD);
|
|
53972
|
+
acc.byModel.set(event.model, (acc.byModel.get(event.model) ?? 0) + event.costUSD);
|
|
53973
|
+
}
|
|
53851
53974
|
for (const line of lines) {
|
|
53852
|
-
if (!line.trim()) continue;
|
|
53853
|
-
let entry;
|
|
53854
53975
|
try {
|
|
53855
|
-
entry = JSON.parse(line);
|
|
53976
|
+
const entry = JSON.parse(line);
|
|
53977
|
+
if (entry?.type !== "response_item" || entry.payload?.type !== "function_call") continue;
|
|
53978
|
+
const ts = new Date(entry.timestamp ?? parsed.sessionStart);
|
|
53979
|
+
if (ts >= start && ts <= end) acc.toolCalls++;
|
|
53856
53980
|
} 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
53981
|
}
|
|
53878
53982
|
}
|
|
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
|
-
}
|
|
53925
|
-
} catch {
|
|
53926
|
-
return [];
|
|
53927
|
-
}
|
|
53928
|
-
return jsonlFiles;
|
|
53929
53983
|
}
|
|
53930
53984
|
function mergeByModel(...maps) {
|
|
53931
53985
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -53941,7 +53995,7 @@ function loadCodexCost(start, end, sessionsBase) {
|
|
|
53941
53995
|
byDay: /* @__PURE__ */ new Map(),
|
|
53942
53996
|
byModel: /* @__PURE__ */ new Map()
|
|
53943
53997
|
};
|
|
53944
|
-
const files =
|
|
53998
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
53945
53999
|
for (const filePath of files) {
|
|
53946
54000
|
processCodexCostFile(filePath, start, end, acc);
|
|
53947
54001
|
}
|
|
@@ -54081,7 +54135,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
54081
54135
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
54082
54136
|
const auditLogPath = opts.auditLogPath ?? import_path54.default.join(import_os50.default.homedir(), ".node9", "audit.log");
|
|
54083
54137
|
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path54.default.join(import_os50.default.homedir(), ".claude", "projects");
|
|
54084
|
-
const codexSessionsDir2 = opts.codexSessionsDir ??
|
|
54138
|
+
const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
|
|
54085
54139
|
const geminiTmpDir2 = opts.geminiTmpDir ?? import_path54.default.join(import_os50.default.homedir(), ".gemini", "tmp");
|
|
54086
54140
|
const hasAuditFile = import_fs56.default.existsSync(auditLogPath);
|
|
54087
54141
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
@@ -60280,12 +60334,12 @@ function parseSessionLines(lines) {
|
|
|
60280
60334
|
continue;
|
|
60281
60335
|
}
|
|
60282
60336
|
if (entry.type !== "assistant") continue;
|
|
60283
|
-
const
|
|
60337
|
+
const usage2 = entry.message?.usage;
|
|
60284
60338
|
const model = entry.message?.model;
|
|
60285
|
-
if (
|
|
60339
|
+
if (usage2 && model) {
|
|
60286
60340
|
const p = modelPrice(model);
|
|
60287
60341
|
if (p) {
|
|
60288
|
-
costUSD += (
|
|
60342
|
+
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
60343
|
}
|
|
60290
60344
|
}
|
|
60291
60345
|
const content = entry.message?.content;
|
|
@@ -60465,46 +60519,13 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
60465
60519
|
return summaries;
|
|
60466
60520
|
}
|
|
60467
60521
|
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
60522
|
const cutoff = days !== null ? (() => {
|
|
60471
60523
|
const d = /* @__PURE__ */ new Date();
|
|
60472
60524
|
d.setDate(d.getDate() - days);
|
|
60473
60525
|
d.setHours(0, 0, 0, 0);
|
|
60474
60526
|
return d;
|
|
60475
60527
|
})() : 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
|
-
}
|
|
60528
|
+
const jsonlFiles = listCodexSessionFiles();
|
|
60508
60529
|
const summaries = [];
|
|
60509
60530
|
for (const filePath of jsonlFiles) {
|
|
60510
60531
|
let lines;
|
|
@@ -60519,10 +60540,6 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60519
60540
|
let firstPrompt = "";
|
|
60520
60541
|
const toolCalls = [];
|
|
60521
60542
|
let lastToolTs = "";
|
|
60522
|
-
let lastTotalInput = 0;
|
|
60523
|
-
let lastTotalCached = 0;
|
|
60524
|
-
let lastTotalOutput = 0;
|
|
60525
|
-
let model = "";
|
|
60526
60543
|
for (const line of lines) {
|
|
60527
60544
|
if (!line.trim()) continue;
|
|
60528
60545
|
let entry;
|
|
@@ -60538,22 +60555,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60538
60555
|
cwd = String(p["cwd"] ?? "");
|
|
60539
60556
|
continue;
|
|
60540
60557
|
}
|
|
60541
|
-
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
60542
|
-
model = p["model"];
|
|
60543
|
-
continue;
|
|
60544
|
-
}
|
|
60545
60558
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
60546
60559
|
firstPrompt = String(p["message"] ?? "");
|
|
60547
60560
|
continue;
|
|
60548
60561
|
}
|
|
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
60562
|
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
60558
60563
|
const tool = String(p["name"] ?? "");
|
|
60559
60564
|
let input = {};
|
|
@@ -60567,12 +60572,13 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
60567
60572
|
}
|
|
60568
60573
|
}
|
|
60569
60574
|
if (!sessionId || !startTime) continue;
|
|
60570
|
-
|
|
60571
|
-
const
|
|
60572
|
-
|
|
60573
|
-
|
|
60574
|
-
|
|
60575
|
-
|
|
60575
|
+
const parsedUsage = parseCodexUsage(lines);
|
|
60576
|
+
const usageEvents = codexUsageInWindow(parsedUsage, cutoff);
|
|
60577
|
+
if (cutoff && new Date(startTime) < cutoff && usageEvents.length === 0 && !toolCalls.some((call) => new Date(call.timestamp) >= cutoff))
|
|
60578
|
+
continue;
|
|
60579
|
+
const costUSD = usageEvents.reduce((sum, event) => sum + event.costUSD, 0);
|
|
60580
|
+
const lastUsageTs = parsedUsage.events.at(-1)?.timestamp ?? "";
|
|
60581
|
+
if (lastUsageTs > lastToolTs) lastToolTs = lastUsageTs;
|
|
60576
60582
|
const windowEnd = new Date(
|
|
60577
60583
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
60578
60584
|
).toISOString();
|