@node9/proxy 1.18.2 → 1.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +537 -30
- package/dist/cli.mjs +537 -30
- package/dist/index.js +78 -5
- package/dist/index.mjs +78 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -444,9 +444,65 @@ function redactText(text) {
|
|
|
444
444
|
}
|
|
445
445
|
return { result, found };
|
|
446
446
|
}
|
|
447
|
+
function isCatHeredocOrLit(part) {
|
|
448
|
+
if (!part) return false;
|
|
449
|
+
const t = syntax.NodeType(part);
|
|
450
|
+
if (t === "Lit") return true;
|
|
451
|
+
if (t !== "CmdSubst") return false;
|
|
452
|
+
const stmts = part.Stmts || [];
|
|
453
|
+
if (stmts.length !== 1) return false;
|
|
454
|
+
const stmt = stmts[0];
|
|
455
|
+
const redirs = stmt.Redirs || stmt.Cmd?.Redirs || [];
|
|
456
|
+
const hasHeredoc = redirs.some((r) => r && r.Hdoc);
|
|
457
|
+
if (!hasHeredoc) return false;
|
|
458
|
+
const cmd = stmt.Cmd;
|
|
459
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return false;
|
|
460
|
+
const firstArg2 = cmd.Args?.[0]?.Parts || [];
|
|
461
|
+
if (firstArg2.length !== 1 || syntax.NodeType(firstArg2[0]) !== "Lit") return false;
|
|
462
|
+
return (firstArg2[0].Value || "").toLowerCase() === "cat";
|
|
463
|
+
}
|
|
464
|
+
function parseShared(command) {
|
|
465
|
+
const cached = astCache.get(command);
|
|
466
|
+
if (cached !== void 0) {
|
|
467
|
+
astCache.delete(command);
|
|
468
|
+
astCache.set(command, cached);
|
|
469
|
+
return cached;
|
|
470
|
+
}
|
|
471
|
+
let parsed;
|
|
472
|
+
try {
|
|
473
|
+
parsed = sharedParser.Parse(command, "cmd");
|
|
474
|
+
} catch {
|
|
475
|
+
parsed = PARSE_FAIL;
|
|
476
|
+
}
|
|
477
|
+
if (astCache.size >= AST_CACHE_MAX) {
|
|
478
|
+
const oldest = astCache.keys().next().value;
|
|
479
|
+
if (oldest !== void 0) astCache.delete(oldest);
|
|
480
|
+
}
|
|
481
|
+
astCache.set(command, parsed);
|
|
482
|
+
return parsed;
|
|
483
|
+
}
|
|
484
|
+
function cachedNormalize(command, compute) {
|
|
485
|
+
const hit = normalizeCache.get(command);
|
|
486
|
+
if (hit !== void 0) {
|
|
487
|
+
normalizeCache.delete(command);
|
|
488
|
+
normalizeCache.set(command, hit);
|
|
489
|
+
return hit;
|
|
490
|
+
}
|
|
491
|
+
const result = compute();
|
|
492
|
+
if (normalizeCache.size >= NORMALIZE_CACHE_MAX) {
|
|
493
|
+
const oldest = normalizeCache.keys().next().value;
|
|
494
|
+
if (oldest !== void 0) normalizeCache.delete(oldest);
|
|
495
|
+
}
|
|
496
|
+
normalizeCache.set(command, result);
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
447
499
|
function normalizeCommandForPolicy(command) {
|
|
500
|
+
return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
|
|
501
|
+
}
|
|
502
|
+
function normalizeCommandForPolicyImpl(command) {
|
|
503
|
+
const f = parseShared(command);
|
|
504
|
+
if (f === PARSE_FAIL) return command;
|
|
448
505
|
try {
|
|
449
|
-
const f = sharedParser.Parse(command, "cmd");
|
|
450
506
|
const strips = [];
|
|
451
507
|
syntax.Walk(f, (node) => {
|
|
452
508
|
if (!node) return false;
|
|
@@ -468,7 +524,11 @@ function normalizeCommandForPolicy(command) {
|
|
|
468
524
|
} else if (nt === "DblQuoted") {
|
|
469
525
|
const innerParts = quotedNode.Parts || [];
|
|
470
526
|
const allLit = innerParts.length === 0 || innerParts.every((p) => syntax.NodeType(p) === "Lit");
|
|
471
|
-
if (allLit)
|
|
527
|
+
if (allLit) {
|
|
528
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
529
|
+
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
530
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
531
|
+
}
|
|
472
532
|
}
|
|
473
533
|
}
|
|
474
534
|
return true;
|
|
@@ -536,6 +596,127 @@ function detectDangerousShellExec(command) {
|
|
|
536
596
|
return null;
|
|
537
597
|
}
|
|
538
598
|
}
|
|
599
|
+
function isProtectedHomePath(rawPath) {
|
|
600
|
+
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
601
|
+
let underHome = false;
|
|
602
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
|
|
603
|
+
p = p.replace(/^~[\\/]?/, "");
|
|
604
|
+
underHome = true;
|
|
605
|
+
} else if (/^\/home\/[^/]+/.test(p) || /^\/root(\/|$)/.test(p)) {
|
|
606
|
+
p = p.replace(/^\/home\/[^/]+[\\/]?|^\/root[\\/]?/, "");
|
|
607
|
+
underHome = true;
|
|
608
|
+
}
|
|
609
|
+
if (!underHome) return false;
|
|
610
|
+
if (p === "" || p === "." || p === "./") return true;
|
|
611
|
+
for (const safe of HOME_CACHE_ALLOWLIST) {
|
|
612
|
+
if (p === safe || p.startsWith(safe + "/") || p.startsWith(safe + "\\")) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
function extractLiteralArgs(callExpr) {
|
|
619
|
+
const args = callExpr.Args || [];
|
|
620
|
+
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
621
|
+
const litFromWord = (w) => {
|
|
622
|
+
const parts = w?.Parts || [];
|
|
623
|
+
let s = "";
|
|
624
|
+
for (const p of parts) {
|
|
625
|
+
const t = syntax.NodeType(p);
|
|
626
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
627
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
628
|
+
else if (t === "DblQuoted") {
|
|
629
|
+
const inner = p.Parts || [];
|
|
630
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
631
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
632
|
+
} else {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return s;
|
|
637
|
+
};
|
|
638
|
+
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
639
|
+
const flags = [];
|
|
640
|
+
const paths = [];
|
|
641
|
+
for (let i = 1; i < args.length; i++) {
|
|
642
|
+
const v = litFromWord(args[i]);
|
|
643
|
+
if (v === null) continue;
|
|
644
|
+
if (v.startsWith("-")) flags.push(v);
|
|
645
|
+
else paths.push(v);
|
|
646
|
+
}
|
|
647
|
+
return { name, flags, paths };
|
|
648
|
+
}
|
|
649
|
+
function analyzeFsOperation(command) {
|
|
650
|
+
if (!FS_OP_PRESCREEN_RE.test(command)) return null;
|
|
651
|
+
if (fsOpCache.has(command)) {
|
|
652
|
+
const hit = fsOpCache.get(command) ?? null;
|
|
653
|
+
fsOpCache.delete(command);
|
|
654
|
+
fsOpCache.set(command, hit);
|
|
655
|
+
return hit;
|
|
656
|
+
}
|
|
657
|
+
const computed = analyzeFsOperationImpl(command);
|
|
658
|
+
if (fsOpCache.size >= FS_OP_CACHE_MAX) {
|
|
659
|
+
const oldest = fsOpCache.keys().next().value;
|
|
660
|
+
if (oldest !== void 0) fsOpCache.delete(oldest);
|
|
661
|
+
}
|
|
662
|
+
fsOpCache.set(command, computed);
|
|
663
|
+
return computed;
|
|
664
|
+
}
|
|
665
|
+
function analyzeFsOperationImpl(command) {
|
|
666
|
+
const f = parseShared(command);
|
|
667
|
+
if (f === PARSE_FAIL) return null;
|
|
668
|
+
let result = null;
|
|
669
|
+
try {
|
|
670
|
+
syntax.Walk(f, (node) => {
|
|
671
|
+
if (!node || result) return false;
|
|
672
|
+
const n = node;
|
|
673
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
674
|
+
const { name, flags, paths } = extractLiteralArgs(n);
|
|
675
|
+
if (!name) return true;
|
|
676
|
+
if (name === "rm") {
|
|
677
|
+
const flagStr = flags.join("").toLowerCase();
|
|
678
|
+
const hasR = /[r]/.test(flagStr) || flags.includes("--recursive");
|
|
679
|
+
const hasF = /[f]/.test(flagStr) || flags.includes("--force");
|
|
680
|
+
if (hasR && hasF) {
|
|
681
|
+
for (const p of paths) {
|
|
682
|
+
if (isProtectedHomePath(p)) {
|
|
683
|
+
result = {
|
|
684
|
+
ruleName: "block-rm-rf-home",
|
|
685
|
+
verdict: "block",
|
|
686
|
+
reason: "Recursive delete of home directory is irreversible",
|
|
687
|
+
path: p
|
|
688
|
+
};
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
if (p === "/" || /^\/+$/.test(p)) {
|
|
692
|
+
result = {
|
|
693
|
+
ruleName: "block-rm-rf-home",
|
|
694
|
+
verdict: "block",
|
|
695
|
+
reason: "Recursive delete of root is catastrophic",
|
|
696
|
+
path: p
|
|
697
|
+
};
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (FS_READ_TOOLS.has(name)) {
|
|
704
|
+
for (const p of paths) {
|
|
705
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
706
|
+
if (sp.match(p)) {
|
|
707
|
+
result = { ruleName: sp.rule, verdict: "block", reason: sp.reason, path: p };
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return true;
|
|
714
|
+
});
|
|
715
|
+
return result;
|
|
716
|
+
} catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
539
720
|
function analyzeShellCommand(command) {
|
|
540
721
|
const actions = [];
|
|
541
722
|
const paths = [];
|
|
@@ -825,10 +1006,18 @@ function getNestedValue(obj, path49) {
|
|
|
825
1006
|
function evaluateSmartConditions(args, rule) {
|
|
826
1007
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
827
1008
|
const mode = rule.conditionMode ?? "all";
|
|
1009
|
+
const fieldCache = /* @__PURE__ */ new Map();
|
|
1010
|
+
const resolveField = (field) => {
|
|
1011
|
+
if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
|
|
1012
|
+
const rawVal = getNestedValue(args, field);
|
|
1013
|
+
const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
|
|
1014
|
+
const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
|
|
1015
|
+
const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
|
|
1016
|
+
fieldCache.set(field, val);
|
|
1017
|
+
return val;
|
|
1018
|
+
};
|
|
828
1019
|
const results = rule.conditions.map((cond) => {
|
|
829
|
-
const
|
|
830
|
-
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
831
|
-
const val = cond.field === "command" && normalized !== null ? normalizeCommandForPolicy(normalized) : normalized;
|
|
1020
|
+
const val = resolveField(cond.field);
|
|
832
1021
|
switch (cond.op) {
|
|
833
1022
|
case "exists":
|
|
834
1023
|
return val !== null && val !== "";
|
|
@@ -1310,7 +1499,7 @@ function summarizeBlast(result, opts = {}) {
|
|
|
1310
1499
|
}))
|
|
1311
1500
|
};
|
|
1312
1501
|
}
|
|
1313
|
-
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, 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, COST_PER_LOOP_ITER_USD;
|
|
1502
|
+
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, 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, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, 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, COST_PER_LOOP_ITER_USD;
|
|
1314
1503
|
var init_dist = __esm({
|
|
1315
1504
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1316
1505
|
"use strict";
|
|
@@ -1784,6 +1973,69 @@ var init_dist = __esm({
|
|
|
1784
1973
|
]);
|
|
1785
1974
|
SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish", "dash", "ksh"]);
|
|
1786
1975
|
DOWNLOAD_CMDS = /* @__PURE__ */ new Set(["curl", "wget"]);
|
|
1976
|
+
NORMALIZE_CACHE_MAX = 5e3;
|
|
1977
|
+
normalizeCache = /* @__PURE__ */ new Map();
|
|
1978
|
+
AST_CACHE_MAX = 5e3;
|
|
1979
|
+
astCache = /* @__PURE__ */ new Map();
|
|
1980
|
+
PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
1981
|
+
FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
1982
|
+
"cat",
|
|
1983
|
+
"less",
|
|
1984
|
+
"head",
|
|
1985
|
+
"tail",
|
|
1986
|
+
"bat",
|
|
1987
|
+
"more",
|
|
1988
|
+
"open",
|
|
1989
|
+
"print",
|
|
1990
|
+
"nano",
|
|
1991
|
+
"vim",
|
|
1992
|
+
"vi",
|
|
1993
|
+
"emacs",
|
|
1994
|
+
"code",
|
|
1995
|
+
"type"
|
|
1996
|
+
]);
|
|
1997
|
+
FS_OP_PRESCREEN_RE = /(?:^|[\s|;&(`\n])(?:rm|cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\b/;
|
|
1998
|
+
HOME_CACHE_ALLOWLIST = [
|
|
1999
|
+
".cache",
|
|
2000
|
+
".npm/_npx",
|
|
2001
|
+
".npm/_cacache",
|
|
2002
|
+
".cargo/registry",
|
|
2003
|
+
".gradle/caches",
|
|
2004
|
+
".gradle/.tmp",
|
|
2005
|
+
".m2/repository",
|
|
2006
|
+
".pnpm-store",
|
|
2007
|
+
".yarn/cache",
|
|
2008
|
+
".yarn/.cache",
|
|
2009
|
+
".cache/pip",
|
|
2010
|
+
".local/share/Trash",
|
|
2011
|
+
".rustup/downloads"
|
|
2012
|
+
];
|
|
2013
|
+
SENSITIVE_PATH_RULES = [
|
|
2014
|
+
{
|
|
2015
|
+
rule: "shield:project-jail:block-read-ssh",
|
|
2016
|
+
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
2017
|
+
match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
rule: "shield:project-jail:block-read-aws",
|
|
2021
|
+
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
2022
|
+
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
rule: "shield:project-jail:block-read-env",
|
|
2026
|
+
reason: "Reading .env files is blocked by project-jail shield",
|
|
2027
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.local|\.production|\.staging)?$/i.test(p)
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
rule: "shield:project-jail:block-read-credentials",
|
|
2031
|
+
reason: "Reading credential files is blocked by project-jail shield",
|
|
2032
|
+
match: (p) => /(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials)$/i.test(
|
|
2033
|
+
p
|
|
2034
|
+
)
|
|
2035
|
+
}
|
|
2036
|
+
];
|
|
2037
|
+
FS_OP_CACHE_MAX = 5e3;
|
|
2038
|
+
fsOpCache = /* @__PURE__ */ new Map();
|
|
1787
2039
|
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1788
2040
|
"cat",
|
|
1789
2041
|
"head",
|
|
@@ -5559,9 +5811,7 @@ function removeNode9McpServer(servers) {
|
|
|
5559
5811
|
return true;
|
|
5560
5812
|
}
|
|
5561
5813
|
function printDaemonTip() {
|
|
5562
|
-
console.log(
|
|
5563
|
-
import_chalk.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + import_chalk.default.white("\n To view your history or manage persistent rules, run:") + import_chalk.default.green("\n node9 daemon --openui")
|
|
5564
|
-
);
|
|
5814
|
+
console.log(import_chalk.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups."));
|
|
5565
5815
|
}
|
|
5566
5816
|
function fullPathCommand(subcommand) {
|
|
5567
5817
|
if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
|
|
@@ -6651,7 +6901,8 @@ function buildScanSummary(agents) {
|
|
|
6651
6901
|
timestamp: f.timestamp,
|
|
6652
6902
|
project: f.project,
|
|
6653
6903
|
sessionId: f.sessionId,
|
|
6654
|
-
agent: f.agent
|
|
6904
|
+
agent: f.agent,
|
|
6905
|
+
kind: f.kind
|
|
6655
6906
|
}))
|
|
6656
6907
|
);
|
|
6657
6908
|
const byVerdict = {
|
|
@@ -6669,10 +6920,7 @@ function buildScanSummary(agents) {
|
|
|
6669
6920
|
costUSD: a.scan.totalCostUSD
|
|
6670
6921
|
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
6671
6922
|
const sections = buildSections(allFindings);
|
|
6672
|
-
const wastedIters = allLoops.reduce(
|
|
6673
|
-
(sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE),
|
|
6674
|
-
0
|
|
6675
|
-
);
|
|
6923
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
6676
6924
|
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
6677
6925
|
return {
|
|
6678
6926
|
stats,
|
|
@@ -7833,6 +8081,20 @@ function geminiModelPrice(model) {
|
|
|
7833
8081
|
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
7834
8082
|
return null;
|
|
7835
8083
|
}
|
|
8084
|
+
function isNode9SelfOutput(text) {
|
|
8085
|
+
let hits = 0;
|
|
8086
|
+
for (const re of SELF_OUTPUT_MARKERS) {
|
|
8087
|
+
if (re.test(text)) hits++;
|
|
8088
|
+
if (hits >= 2) return true;
|
|
8089
|
+
}
|
|
8090
|
+
return false;
|
|
8091
|
+
}
|
|
8092
|
+
function looksLikeFixtureToken(sample) {
|
|
8093
|
+
for (const re of FIXTURE_TOKEN_PATTERNS) {
|
|
8094
|
+
if (re.test(sample)) return true;
|
|
8095
|
+
}
|
|
8096
|
+
return false;
|
|
8097
|
+
}
|
|
7836
8098
|
function num(n) {
|
|
7837
8099
|
return n.toLocaleString();
|
|
7838
8100
|
}
|
|
@@ -7896,6 +8158,45 @@ function buildRecurringPatternSet(findings) {
|
|
|
7896
8158
|
}
|
|
7897
8159
|
return recurring;
|
|
7898
8160
|
}
|
|
8161
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result) {
|
|
8162
|
+
const fsVerdict = analyzeFsOperation(command);
|
|
8163
|
+
if (!fsVerdict) return false;
|
|
8164
|
+
const synthRule = {
|
|
8165
|
+
name: fsVerdict.ruleName,
|
|
8166
|
+
tool: "bash",
|
|
8167
|
+
conditions: [],
|
|
8168
|
+
verdict: fsVerdict.verdict,
|
|
8169
|
+
reason: fsVerdict.reason
|
|
8170
|
+
};
|
|
8171
|
+
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
8172
|
+
const synthSource = isShieldRule ? {
|
|
8173
|
+
shieldName: "project-jail",
|
|
8174
|
+
shieldLabel: "project-jail (AST)",
|
|
8175
|
+
sourceType: "shield",
|
|
8176
|
+
rule: synthRule
|
|
8177
|
+
} : {
|
|
8178
|
+
shieldName: "",
|
|
8179
|
+
shieldLabel: "default (AST)",
|
|
8180
|
+
sourceType: "default",
|
|
8181
|
+
rule: synthRule
|
|
8182
|
+
};
|
|
8183
|
+
const inputPreview = preview(input, 120);
|
|
8184
|
+
const isDupe = result.findings.some(
|
|
8185
|
+
(f) => f.source.rule.name === synthRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
8186
|
+
);
|
|
8187
|
+
if (!isDupe) {
|
|
8188
|
+
result.findings.push({
|
|
8189
|
+
source: synthSource,
|
|
8190
|
+
toolName,
|
|
8191
|
+
input,
|
|
8192
|
+
timestamp,
|
|
8193
|
+
project: projLabel,
|
|
8194
|
+
sessionId,
|
|
8195
|
+
agent
|
|
8196
|
+
});
|
|
8197
|
+
}
|
|
8198
|
+
return true;
|
|
8199
|
+
}
|
|
7899
8200
|
function isStaleFinding(timestamp, now = Date.now()) {
|
|
7900
8201
|
if (!timestamp) return false;
|
|
7901
8202
|
const t = Date.parse(timestamp);
|
|
@@ -7929,15 +8230,24 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
7929
8230
|
const entry = counts.get(key) ?? {
|
|
7930
8231
|
count: 0,
|
|
7931
8232
|
timestamp: call.timestamp,
|
|
8233
|
+
firstTs: null,
|
|
8234
|
+
lastTs: null,
|
|
7932
8235
|
input: call.input,
|
|
7933
8236
|
toolName: call.toolName
|
|
7934
8237
|
};
|
|
7935
8238
|
entry.count++;
|
|
8239
|
+
const t = call.timestamp ? Date.parse(call.timestamp) : NaN;
|
|
8240
|
+
if (!Number.isNaN(t)) {
|
|
8241
|
+
if (entry.firstTs === null || t < entry.firstTs) entry.firstTs = t;
|
|
8242
|
+
if (entry.lastTs === null || t > entry.lastTs) entry.lastTs = t;
|
|
8243
|
+
}
|
|
7936
8244
|
counts.set(key, entry);
|
|
7937
8245
|
}
|
|
7938
8246
|
const findings = [];
|
|
7939
8247
|
for (const [, entry] of counts) {
|
|
7940
8248
|
if (entry.count >= LOOP_THRESHOLD) {
|
|
8249
|
+
const span = entry.firstTs !== null && entry.lastTs !== null ? entry.lastTs - entry.firstTs : 0;
|
|
8250
|
+
const kind = span >= LOOP_TIMESPAN_THRESHOLD_MS ? "long-iteration" : "loop";
|
|
7941
8251
|
findings.push({
|
|
7942
8252
|
toolName: entry.toolName,
|
|
7943
8253
|
commandPreview: preview(entry.input, 80),
|
|
@@ -7945,7 +8255,8 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
7945
8255
|
timestamp: entry.timestamp,
|
|
7946
8256
|
project,
|
|
7947
8257
|
sessionId,
|
|
7948
|
-
agent
|
|
8258
|
+
agent,
|
|
8259
|
+
kind
|
|
7949
8260
|
});
|
|
7950
8261
|
}
|
|
7951
8262
|
}
|
|
@@ -8164,8 +8475,10 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
8164
8475
|
}
|
|
8165
8476
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
8166
8477
|
if (!resultText) continue;
|
|
8478
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
8167
8479
|
const dlpMatch = scanArgs({ text: resultText });
|
|
8168
8480
|
if (dlpMatch) {
|
|
8481
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
8169
8482
|
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
8170
8483
|
const isDupe = result.dlpFindings.some(
|
|
8171
8484
|
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
@@ -8236,11 +8549,26 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
8236
8549
|
});
|
|
8237
8550
|
}
|
|
8238
8551
|
}
|
|
8239
|
-
let
|
|
8552
|
+
let astFsMatched = false;
|
|
8553
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
8554
|
+
if (astRanForBash) {
|
|
8555
|
+
astFsMatched = pushFsOpAstFinding(
|
|
8556
|
+
String(input.command ?? ""),
|
|
8557
|
+
toolName,
|
|
8558
|
+
input,
|
|
8559
|
+
entry.timestamp ?? "",
|
|
8560
|
+
projLabel,
|
|
8561
|
+
sessionId,
|
|
8562
|
+
"claude",
|
|
8563
|
+
result
|
|
8564
|
+
);
|
|
8565
|
+
}
|
|
8566
|
+
let ruleMatched = astFsMatched;
|
|
8240
8567
|
for (const source of ruleSources) {
|
|
8241
8568
|
const { rule } = source;
|
|
8242
8569
|
if (rule.verdict === "allow") continue;
|
|
8243
8570
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
8571
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8244
8572
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8245
8573
|
const inputPreview = preview(input, 120);
|
|
8246
8574
|
const isDupe = result.findings.some(
|
|
@@ -8436,11 +8764,26 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
8436
8764
|
});
|
|
8437
8765
|
}
|
|
8438
8766
|
}
|
|
8439
|
-
let
|
|
8767
|
+
let astFsMatched = false;
|
|
8768
|
+
const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
|
|
8769
|
+
if (astRanForBash) {
|
|
8770
|
+
astFsMatched = pushFsOpAstFinding(
|
|
8771
|
+
String(input.command ?? ""),
|
|
8772
|
+
toolName,
|
|
8773
|
+
input,
|
|
8774
|
+
msg.timestamp ?? "",
|
|
8775
|
+
projLabel,
|
|
8776
|
+
sessionId,
|
|
8777
|
+
"gemini",
|
|
8778
|
+
result
|
|
8779
|
+
);
|
|
8780
|
+
}
|
|
8781
|
+
let ruleMatched = astFsMatched;
|
|
8440
8782
|
for (const source of ruleSources) {
|
|
8441
8783
|
const { rule } = source;
|
|
8442
8784
|
if (rule.verdict === "allow") continue;
|
|
8443
8785
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
8786
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8444
8787
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8445
8788
|
const inputPreview = preview(input, 120);
|
|
8446
8789
|
const isDupe = result.findings.some(
|
|
@@ -8658,12 +9001,27 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
8658
9001
|
});
|
|
8659
9002
|
}
|
|
8660
9003
|
}
|
|
8661
|
-
let
|
|
9004
|
+
let astFsMatched = false;
|
|
9005
|
+
const astRanForBash = toolNameLower === "exec_command" || toolNameLower === "bash";
|
|
9006
|
+
if (astRanForBash) {
|
|
9007
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9008
|
+
String(input["command"] ?? ""),
|
|
9009
|
+
toolName,
|
|
9010
|
+
input,
|
|
9011
|
+
ts,
|
|
9012
|
+
projLabel,
|
|
9013
|
+
sessionId,
|
|
9014
|
+
"codex",
|
|
9015
|
+
result
|
|
9016
|
+
);
|
|
9017
|
+
}
|
|
9018
|
+
let ruleMatched = astFsMatched;
|
|
8662
9019
|
for (const source of ruleSources) {
|
|
8663
9020
|
const { rule } = source;
|
|
8664
9021
|
if (rule.verdict === "allow") continue;
|
|
8665
9022
|
if (rule.tool && !matchesPattern(toolNameLower === "exec_command" ? "bash" : toolNameLower, rule.tool))
|
|
8666
9023
|
continue;
|
|
9024
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8667
9025
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8668
9026
|
const inputPreview = preview(input, 120);
|
|
8669
9027
|
const isDupe = result.findings.some(
|
|
@@ -8863,15 +9221,22 @@ function renderCompactScorecard(input) {
|
|
|
8863
9221
|
import_chalk4.default.red("\u{1F6D1} ") + import_chalk4.default.red.bold(String(blockedCount).padEnd(4)) + import_chalk4.default.dim("would have blocked".padEnd(20)) + import_chalk4.default.dim(`(${topBlocked})`)
|
|
8864
9222
|
);
|
|
8865
9223
|
}
|
|
8866
|
-
|
|
8867
|
-
|
|
9224
|
+
const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
|
|
9225
|
+
const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
|
|
9226
|
+
if (realLoops.length > 0) {
|
|
9227
|
+
const wastedCalls = realLoops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
8868
9228
|
const wastePct = scan.totalToolCalls > 0 ? Math.round(wastedCalls / scan.totalToolCalls * 100) : 0;
|
|
8869
9229
|
const wasteParts = [];
|
|
8870
9230
|
if (wastePct > 0) wasteParts.push(`${wastePct}% wasted`);
|
|
8871
9231
|
if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
|
|
8872
9232
|
const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
|
|
8873
9233
|
console.log(
|
|
8874
|
-
import_chalk4.default.yellow("\u{1F501} ") + import_chalk4.default.yellow.bold(String(
|
|
9234
|
+
import_chalk4.default.yellow("\u{1F501} ") + import_chalk4.default.yellow.bold(String(realLoops.length).padEnd(4)) + import_chalk4.default.dim("agent loops".padEnd(20)) + import_chalk4.default.dim(wasteSummary)
|
|
9235
|
+
);
|
|
9236
|
+
}
|
|
9237
|
+
if (longIterations.length > 0) {
|
|
9238
|
+
console.log(
|
|
9239
|
+
import_chalk4.default.dim("\u{1F4C2} ") + import_chalk4.default.dim.bold(String(longIterations.length).padEnd(4)) + import_chalk4.default.dim("long iterations".padEnd(20)) + import_chalk4.default.dim("(deep work \u2014 not waste)")
|
|
8875
9240
|
);
|
|
8876
9241
|
}
|
|
8877
9242
|
if (reviewCount > 0) {
|
|
@@ -9403,7 +9768,7 @@ function registerScanCommand(program2) {
|
|
|
9403
9768
|
}
|
|
9404
9769
|
);
|
|
9405
9770
|
}
|
|
9406
|
-
var import_chalk4, import_fs18, import_path20, import_os17, CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, DEFAULT_RULE_NAMES, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
9771
|
+
var import_chalk4, import_fs18, import_path20, import_os17, CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, AST_FS_REGEX_RULES, DEFAULT_RULE_NAMES, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
9407
9772
|
var init_scan = __esm({
|
|
9408
9773
|
"src/cli/commands/scan.ts"() {
|
|
9409
9774
|
"use strict";
|
|
@@ -9414,6 +9779,7 @@ var init_scan = __esm({
|
|
|
9414
9779
|
init_shields();
|
|
9415
9780
|
init_config();
|
|
9416
9781
|
init_policy();
|
|
9782
|
+
init_dist();
|
|
9417
9783
|
init_dlp();
|
|
9418
9784
|
init_dist();
|
|
9419
9785
|
init_scan_summary();
|
|
@@ -9466,6 +9832,22 @@ var init_scan = __esm({
|
|
|
9466
9832
|
".vue",
|
|
9467
9833
|
".svelte"
|
|
9468
9834
|
]);
|
|
9835
|
+
SELF_OUTPUT_MARKERS = [
|
|
9836
|
+
/redactedSample:\s*['"]/,
|
|
9837
|
+
/patternName:\s*['"]/,
|
|
9838
|
+
/\bseverity:\s*['"](?:block|review|allow)['"]/,
|
|
9839
|
+
/NODE9 SECURITY ALERT/
|
|
9840
|
+
];
|
|
9841
|
+
FIXTURE_TOKEN_PATTERNS = [
|
|
9842
|
+
/(.)\1{5,}/,
|
|
9843
|
+
// 6+ repeated characters (aaaaaa, 000000)
|
|
9844
|
+
/(?:EXAMPLE|FAKE|DUMMY|PLACEHOLDER|XXXXX)/i,
|
|
9845
|
+
/abcdefghijklmn/i,
|
|
9846
|
+
// long alpha sequence — fixture, not entropy
|
|
9847
|
+
/1234567890/,
|
|
9848
|
+
// long digit sequence — fixture, not entropy
|
|
9849
|
+
/qwerty/i
|
|
9850
|
+
];
|
|
9469
9851
|
TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
9470
9852
|
LOOP_TOOLS = /* @__PURE__ */ new Set([
|
|
9471
9853
|
"bash",
|
|
@@ -9478,10 +9860,18 @@ var init_scan = __esm({
|
|
|
9478
9860
|
"multiedit"
|
|
9479
9861
|
]);
|
|
9480
9862
|
LOOP_THRESHOLD = 3;
|
|
9863
|
+
LOOP_TIMESPAN_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
9481
9864
|
STUCK_TOOLS_MIN_WASTE = 5;
|
|
9482
9865
|
STUCK_TOOLS_LIMIT = 3;
|
|
9483
9866
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
9484
9867
|
STALE_AGE_DAYS = 30;
|
|
9868
|
+
AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
9869
|
+
"block-rm-rf-home",
|
|
9870
|
+
"shield:project-jail:block-read-ssh",
|
|
9871
|
+
"shield:project-jail:block-read-aws",
|
|
9872
|
+
"shield:project-jail:block-read-env",
|
|
9873
|
+
"shield:project-jail:block-read-credentials"
|
|
9874
|
+
]);
|
|
9485
9875
|
DEFAULT_RULE_NAMES = new Set(
|
|
9486
9876
|
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
9487
9877
|
);
|
|
@@ -10004,7 +10394,86 @@ function abandonPending() {
|
|
|
10004
10394
|
}, 200);
|
|
10005
10395
|
}
|
|
10006
10396
|
}
|
|
10397
|
+
function logActivitySocket(msg) {
|
|
10398
|
+
try {
|
|
10399
|
+
import_fs20.default.appendFileSync(
|
|
10400
|
+
import_path22.default.join(homeDir, ".node9", "hook-debug.log"),
|
|
10401
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
|
|
10402
|
+
`
|
|
10403
|
+
);
|
|
10404
|
+
} catch {
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
function shouldRebind(now = Date.now()) {
|
|
10408
|
+
if (activityCircuitTripped) return false;
|
|
10409
|
+
activityRebindAttempts = activityRebindAttempts.filter(
|
|
10410
|
+
(t) => now - t < ACTIVITY_REBIND_WINDOW_MS
|
|
10411
|
+
);
|
|
10412
|
+
activityRebindAttempts.push(now);
|
|
10413
|
+
if (activityRebindAttempts.length > ACTIVITY_REBIND_MAX_ATTEMPTS) {
|
|
10414
|
+
activityCircuitTripped = true;
|
|
10415
|
+
return false;
|
|
10416
|
+
}
|
|
10417
|
+
return true;
|
|
10418
|
+
}
|
|
10007
10419
|
function startActivitySocket() {
|
|
10420
|
+
bindActivitySocket();
|
|
10421
|
+
if (process.platform !== "win32") {
|
|
10422
|
+
try {
|
|
10423
|
+
activitySocketWatcher = import_fs20.default.watch(import_os18.default.tmpdir(), (eventType, filename) => {
|
|
10424
|
+
if (filename !== import_path22.default.basename(ACTIVITY_SOCKET_PATH2)) return;
|
|
10425
|
+
if (eventType !== "rename") return;
|
|
10426
|
+
if (import_fs20.default.existsSync(ACTIVITY_SOCKET_PATH2)) return;
|
|
10427
|
+
attemptRebind("watch-unlink");
|
|
10428
|
+
});
|
|
10429
|
+
activitySocketWatcher.on("error", (err2) => {
|
|
10430
|
+
logActivitySocket(`watcher error: ${err2.message}`);
|
|
10431
|
+
});
|
|
10432
|
+
activitySocketWatcher.unref();
|
|
10433
|
+
} catch (err2) {
|
|
10434
|
+
logActivitySocket(`failed to start watcher: ${err2.message}`);
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10437
|
+
activityHealthInterval = setInterval(() => {
|
|
10438
|
+
if (!import_fs20.default.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
|
|
10439
|
+
}, ACTIVITY_HEALTH_PROBE_MS);
|
|
10440
|
+
activityHealthInterval.unref();
|
|
10441
|
+
process.on("exit", () => {
|
|
10442
|
+
if (activitySocketWatcher) {
|
|
10443
|
+
try {
|
|
10444
|
+
activitySocketWatcher.close();
|
|
10445
|
+
} catch {
|
|
10446
|
+
}
|
|
10447
|
+
}
|
|
10448
|
+
if (activityHealthInterval) clearInterval(activityHealthInterval);
|
|
10449
|
+
try {
|
|
10450
|
+
import_fs20.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10451
|
+
} catch {
|
|
10452
|
+
}
|
|
10453
|
+
});
|
|
10454
|
+
}
|
|
10455
|
+
function attemptRebind(reason) {
|
|
10456
|
+
if (!shouldRebind()) {
|
|
10457
|
+
logActivitySocket(
|
|
10458
|
+
`circuit breaker tripped after ${ACTIVITY_REBIND_MAX_ATTEMPTS} attempts in ${ACTIVITY_REBIND_WINDOW_MS}ms \u2014 flight recorder down`
|
|
10459
|
+
);
|
|
10460
|
+
broadcast("flight-recorder-down", {
|
|
10461
|
+
reason: "rebind-loop",
|
|
10462
|
+
message: "Activity socket repeatedly disappearing \u2014 run: node9 daemon restart"
|
|
10463
|
+
});
|
|
10464
|
+
return;
|
|
10465
|
+
}
|
|
10466
|
+
logActivitySocket(`rebinding (reason: ${reason}, attempt ${activityRebindAttempts.length})`);
|
|
10467
|
+
if (activitySocketServer) {
|
|
10468
|
+
try {
|
|
10469
|
+
activitySocketServer.close();
|
|
10470
|
+
} catch {
|
|
10471
|
+
}
|
|
10472
|
+
activitySocketServer = null;
|
|
10473
|
+
}
|
|
10474
|
+
bindActivitySocket();
|
|
10475
|
+
}
|
|
10476
|
+
function bindActivitySocket() {
|
|
10008
10477
|
try {
|
|
10009
10478
|
import_fs20.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10010
10479
|
} catch {
|
|
@@ -10102,15 +10571,15 @@ function startActivitySocket() {
|
|
|
10102
10571
|
socket.on("error", () => {
|
|
10103
10572
|
});
|
|
10104
10573
|
});
|
|
10105
|
-
unixServer.
|
|
10106
|
-
|
|
10107
|
-
try {
|
|
10108
|
-
import_fs20.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10109
|
-
} catch {
|
|
10110
|
-
}
|
|
10574
|
+
unixServer.on("error", (err2) => {
|
|
10575
|
+
logActivitySocket(`server error: ${err2.message}`);
|
|
10111
10576
|
});
|
|
10577
|
+
unixServer.listen(ACTIVITY_SOCKET_PATH2, () => {
|
|
10578
|
+
logActivitySocket(`bound to ${ACTIVITY_SOCKET_PATH2}`);
|
|
10579
|
+
});
|
|
10580
|
+
activitySocketServer = unixServer;
|
|
10112
10581
|
}
|
|
10113
|
-
var import_net2, import_fs20, import_path22, import_os18, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
|
|
10582
|
+
var import_net2, import_fs20, import_path22, import_os18, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activitySocketWatcher, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
10114
10583
|
var init_state2 = __esm({
|
|
10115
10584
|
"src/daemon/state.ts"() {
|
|
10116
10585
|
"use strict";
|
|
@@ -10171,6 +10640,14 @@ var init_state2 = __esm({
|
|
|
10171
10640
|
"notebook_edit",
|
|
10172
10641
|
"notebookedit"
|
|
10173
10642
|
]);
|
|
10643
|
+
ACTIVITY_REBIND_MAX_ATTEMPTS = 5;
|
|
10644
|
+
ACTIVITY_REBIND_WINDOW_MS = 6e4;
|
|
10645
|
+
ACTIVITY_HEALTH_PROBE_MS = 3e4;
|
|
10646
|
+
activitySocketServer = null;
|
|
10647
|
+
activitySocketWatcher = null;
|
|
10648
|
+
activityHealthInterval = null;
|
|
10649
|
+
activityRebindAttempts = [];
|
|
10650
|
+
activityCircuitTripped = false;
|
|
10174
10651
|
}
|
|
10175
10652
|
});
|
|
10176
10653
|
|
|
@@ -12312,6 +12789,8 @@ async function startTail(options = {}) {
|
|
|
12312
12789
|
let initialReplayDone = false;
|
|
12313
12790
|
const activityPending = /* @__PURE__ */ new Map();
|
|
12314
12791
|
const orphanedResults = /* @__PURE__ */ new Map();
|
|
12792
|
+
let lastActivityFromDaemon = Date.now();
|
|
12793
|
+
let stallWarned = false;
|
|
12315
12794
|
const authToken = getInternalToken() ?? "";
|
|
12316
12795
|
const approvalQueue = [];
|
|
12317
12796
|
let cardActive = false;
|
|
@@ -12538,6 +13017,24 @@ async function startTail(options = {}) {
|
|
|
12538
13017
|
console.log(import_chalk29.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
12539
13018
|
process.exit(0);
|
|
12540
13019
|
});
|
|
13020
|
+
const STALL_THRESHOLD_MS = 6e4;
|
|
13021
|
+
const stallWatchdog = setInterval(() => {
|
|
13022
|
+
if (stallWarned) return;
|
|
13023
|
+
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
13024
|
+
try {
|
|
13025
|
+
const auditMtime = import_fs44.default.statSync(auditLog).mtimeMs;
|
|
13026
|
+
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
13027
|
+
console.log("");
|
|
13028
|
+
console.log(
|
|
13029
|
+
import_chalk29.default.yellow(
|
|
13030
|
+
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
13031
|
+
)
|
|
13032
|
+
);
|
|
13033
|
+
stallWarned = true;
|
|
13034
|
+
} catch {
|
|
13035
|
+
}
|
|
13036
|
+
}, STALL_THRESHOLD_MS / 2);
|
|
13037
|
+
stallWatchdog.unref();
|
|
12541
13038
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
12542
13039
|
const req = import_http2.default.get(
|
|
12543
13040
|
sseUrl,
|
|
@@ -12583,7 +13080,18 @@ async function startTail(options = {}) {
|
|
|
12583
13080
|
}
|
|
12584
13081
|
);
|
|
12585
13082
|
function handleMessage(event, rawData) {
|
|
13083
|
+
lastActivityFromDaemon = Date.now();
|
|
12586
13084
|
if (event === "csrf") return;
|
|
13085
|
+
if (event === "flight-recorder-down") {
|
|
13086
|
+
try {
|
|
13087
|
+
const parsed = JSON.parse(rawData);
|
|
13088
|
+
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
13089
|
+
console.log("");
|
|
13090
|
+
console.log(import_chalk29.default.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
13091
|
+
} catch {
|
|
13092
|
+
}
|
|
13093
|
+
return;
|
|
13094
|
+
}
|
|
12587
13095
|
if (event === "init") {
|
|
12588
13096
|
try {
|
|
12589
13097
|
const parsed = JSON.parse(rawData);
|
|
@@ -16166,7 +16674,6 @@ function registerInitCommand(program2) {
|
|
|
16166
16674
|
console.log(import_chalk15.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
16167
16675
|
console.log("");
|
|
16168
16676
|
console.log(import_chalk15.default.white(" Watch live: ") + import_chalk15.default.cyan("node9 tail"));
|
|
16169
|
-
console.log(import_chalk15.default.white(" Local UI: ") + import_chalk15.default.cyan("node9 daemon --openui"));
|
|
16170
16677
|
console.log("");
|
|
16171
16678
|
console.log(import_chalk15.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
16172
16679
|
console.log(
|