@node9/proxy 1.18.1 → 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.mjs
CHANGED
|
@@ -428,9 +428,65 @@ function redactText(text) {
|
|
|
428
428
|
}
|
|
429
429
|
return { result, found };
|
|
430
430
|
}
|
|
431
|
+
function isCatHeredocOrLit(part) {
|
|
432
|
+
if (!part) return false;
|
|
433
|
+
const t = syntax.NodeType(part);
|
|
434
|
+
if (t === "Lit") return true;
|
|
435
|
+
if (t !== "CmdSubst") return false;
|
|
436
|
+
const stmts = part.Stmts || [];
|
|
437
|
+
if (stmts.length !== 1) return false;
|
|
438
|
+
const stmt = stmts[0];
|
|
439
|
+
const redirs = stmt.Redirs || stmt.Cmd?.Redirs || [];
|
|
440
|
+
const hasHeredoc = redirs.some((r) => r && r.Hdoc);
|
|
441
|
+
if (!hasHeredoc) return false;
|
|
442
|
+
const cmd = stmt.Cmd;
|
|
443
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return false;
|
|
444
|
+
const firstArg2 = cmd.Args?.[0]?.Parts || [];
|
|
445
|
+
if (firstArg2.length !== 1 || syntax.NodeType(firstArg2[0]) !== "Lit") return false;
|
|
446
|
+
return (firstArg2[0].Value || "").toLowerCase() === "cat";
|
|
447
|
+
}
|
|
448
|
+
function parseShared(command) {
|
|
449
|
+
const cached = astCache.get(command);
|
|
450
|
+
if (cached !== void 0) {
|
|
451
|
+
astCache.delete(command);
|
|
452
|
+
astCache.set(command, cached);
|
|
453
|
+
return cached;
|
|
454
|
+
}
|
|
455
|
+
let parsed;
|
|
456
|
+
try {
|
|
457
|
+
parsed = sharedParser.Parse(command, "cmd");
|
|
458
|
+
} catch {
|
|
459
|
+
parsed = PARSE_FAIL;
|
|
460
|
+
}
|
|
461
|
+
if (astCache.size >= AST_CACHE_MAX) {
|
|
462
|
+
const oldest = astCache.keys().next().value;
|
|
463
|
+
if (oldest !== void 0) astCache.delete(oldest);
|
|
464
|
+
}
|
|
465
|
+
astCache.set(command, parsed);
|
|
466
|
+
return parsed;
|
|
467
|
+
}
|
|
468
|
+
function cachedNormalize(command, compute) {
|
|
469
|
+
const hit = normalizeCache.get(command);
|
|
470
|
+
if (hit !== void 0) {
|
|
471
|
+
normalizeCache.delete(command);
|
|
472
|
+
normalizeCache.set(command, hit);
|
|
473
|
+
return hit;
|
|
474
|
+
}
|
|
475
|
+
const result = compute();
|
|
476
|
+
if (normalizeCache.size >= NORMALIZE_CACHE_MAX) {
|
|
477
|
+
const oldest = normalizeCache.keys().next().value;
|
|
478
|
+
if (oldest !== void 0) normalizeCache.delete(oldest);
|
|
479
|
+
}
|
|
480
|
+
normalizeCache.set(command, result);
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
431
483
|
function normalizeCommandForPolicy(command) {
|
|
484
|
+
return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
|
|
485
|
+
}
|
|
486
|
+
function normalizeCommandForPolicyImpl(command) {
|
|
487
|
+
const f = parseShared(command);
|
|
488
|
+
if (f === PARSE_FAIL) return command;
|
|
432
489
|
try {
|
|
433
|
-
const f = sharedParser.Parse(command, "cmd");
|
|
434
490
|
const strips = [];
|
|
435
491
|
syntax.Walk(f, (node) => {
|
|
436
492
|
if (!node) return false;
|
|
@@ -452,7 +508,11 @@ function normalizeCommandForPolicy(command) {
|
|
|
452
508
|
} else if (nt === "DblQuoted") {
|
|
453
509
|
const innerParts = quotedNode.Parts || [];
|
|
454
510
|
const allLit = innerParts.length === 0 || innerParts.every((p) => syntax.NodeType(p) === "Lit");
|
|
455
|
-
if (allLit)
|
|
511
|
+
if (allLit) {
|
|
512
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
513
|
+
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
514
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
515
|
+
}
|
|
456
516
|
}
|
|
457
517
|
}
|
|
458
518
|
return true;
|
|
@@ -520,6 +580,127 @@ function detectDangerousShellExec(command) {
|
|
|
520
580
|
return null;
|
|
521
581
|
}
|
|
522
582
|
}
|
|
583
|
+
function isProtectedHomePath(rawPath) {
|
|
584
|
+
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
585
|
+
let underHome = false;
|
|
586
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
|
|
587
|
+
p = p.replace(/^~[\\/]?/, "");
|
|
588
|
+
underHome = true;
|
|
589
|
+
} else if (/^\/home\/[^/]+/.test(p) || /^\/root(\/|$)/.test(p)) {
|
|
590
|
+
p = p.replace(/^\/home\/[^/]+[\\/]?|^\/root[\\/]?/, "");
|
|
591
|
+
underHome = true;
|
|
592
|
+
}
|
|
593
|
+
if (!underHome) return false;
|
|
594
|
+
if (p === "" || p === "." || p === "./") return true;
|
|
595
|
+
for (const safe of HOME_CACHE_ALLOWLIST) {
|
|
596
|
+
if (p === safe || p.startsWith(safe + "/") || p.startsWith(safe + "\\")) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
function extractLiteralArgs(callExpr) {
|
|
603
|
+
const args = callExpr.Args || [];
|
|
604
|
+
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
605
|
+
const litFromWord = (w) => {
|
|
606
|
+
const parts = w?.Parts || [];
|
|
607
|
+
let s = "";
|
|
608
|
+
for (const p of parts) {
|
|
609
|
+
const t = syntax.NodeType(p);
|
|
610
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
611
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
612
|
+
else if (t === "DblQuoted") {
|
|
613
|
+
const inner = p.Parts || [];
|
|
614
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
615
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
616
|
+
} else {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return s;
|
|
621
|
+
};
|
|
622
|
+
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
623
|
+
const flags = [];
|
|
624
|
+
const paths = [];
|
|
625
|
+
for (let i = 1; i < args.length; i++) {
|
|
626
|
+
const v = litFromWord(args[i]);
|
|
627
|
+
if (v === null) continue;
|
|
628
|
+
if (v.startsWith("-")) flags.push(v);
|
|
629
|
+
else paths.push(v);
|
|
630
|
+
}
|
|
631
|
+
return { name, flags, paths };
|
|
632
|
+
}
|
|
633
|
+
function analyzeFsOperation(command) {
|
|
634
|
+
if (!FS_OP_PRESCREEN_RE.test(command)) return null;
|
|
635
|
+
if (fsOpCache.has(command)) {
|
|
636
|
+
const hit = fsOpCache.get(command) ?? null;
|
|
637
|
+
fsOpCache.delete(command);
|
|
638
|
+
fsOpCache.set(command, hit);
|
|
639
|
+
return hit;
|
|
640
|
+
}
|
|
641
|
+
const computed = analyzeFsOperationImpl(command);
|
|
642
|
+
if (fsOpCache.size >= FS_OP_CACHE_MAX) {
|
|
643
|
+
const oldest = fsOpCache.keys().next().value;
|
|
644
|
+
if (oldest !== void 0) fsOpCache.delete(oldest);
|
|
645
|
+
}
|
|
646
|
+
fsOpCache.set(command, computed);
|
|
647
|
+
return computed;
|
|
648
|
+
}
|
|
649
|
+
function analyzeFsOperationImpl(command) {
|
|
650
|
+
const f = parseShared(command);
|
|
651
|
+
if (f === PARSE_FAIL) return null;
|
|
652
|
+
let result = null;
|
|
653
|
+
try {
|
|
654
|
+
syntax.Walk(f, (node) => {
|
|
655
|
+
if (!node || result) return false;
|
|
656
|
+
const n = node;
|
|
657
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
658
|
+
const { name, flags, paths } = extractLiteralArgs(n);
|
|
659
|
+
if (!name) return true;
|
|
660
|
+
if (name === "rm") {
|
|
661
|
+
const flagStr = flags.join("").toLowerCase();
|
|
662
|
+
const hasR = /[r]/.test(flagStr) || flags.includes("--recursive");
|
|
663
|
+
const hasF = /[f]/.test(flagStr) || flags.includes("--force");
|
|
664
|
+
if (hasR && hasF) {
|
|
665
|
+
for (const p of paths) {
|
|
666
|
+
if (isProtectedHomePath(p)) {
|
|
667
|
+
result = {
|
|
668
|
+
ruleName: "block-rm-rf-home",
|
|
669
|
+
verdict: "block",
|
|
670
|
+
reason: "Recursive delete of home directory is irreversible",
|
|
671
|
+
path: p
|
|
672
|
+
};
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
if (p === "/" || /^\/+$/.test(p)) {
|
|
676
|
+
result = {
|
|
677
|
+
ruleName: "block-rm-rf-home",
|
|
678
|
+
verdict: "block",
|
|
679
|
+
reason: "Recursive delete of root is catastrophic",
|
|
680
|
+
path: p
|
|
681
|
+
};
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (FS_READ_TOOLS.has(name)) {
|
|
688
|
+
for (const p of paths) {
|
|
689
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
690
|
+
if (sp.match(p)) {
|
|
691
|
+
result = { ruleName: sp.rule, verdict: "block", reason: sp.reason, path: p };
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return true;
|
|
698
|
+
});
|
|
699
|
+
return result;
|
|
700
|
+
} catch {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
523
704
|
function analyzeShellCommand(command) {
|
|
524
705
|
const actions = [];
|
|
525
706
|
const paths = [];
|
|
@@ -809,10 +990,18 @@ function getNestedValue(obj, path49) {
|
|
|
809
990
|
function evaluateSmartConditions(args, rule) {
|
|
810
991
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
811
992
|
const mode = rule.conditionMode ?? "all";
|
|
993
|
+
const fieldCache = /* @__PURE__ */ new Map();
|
|
994
|
+
const resolveField = (field) => {
|
|
995
|
+
if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
|
|
996
|
+
const rawVal = getNestedValue(args, field);
|
|
997
|
+
const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
|
|
998
|
+
const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
|
|
999
|
+
const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
|
|
1000
|
+
fieldCache.set(field, val);
|
|
1001
|
+
return val;
|
|
1002
|
+
};
|
|
812
1003
|
const results = rule.conditions.map((cond) => {
|
|
813
|
-
const
|
|
814
|
-
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
815
|
-
const val = cond.field === "command" && normalized !== null ? normalizeCommandForPolicy(normalized) : normalized;
|
|
1004
|
+
const val = resolveField(cond.field);
|
|
816
1005
|
switch (cond.op) {
|
|
817
1006
|
case "exists":
|
|
818
1007
|
return val !== null && val !== "";
|
|
@@ -1294,7 +1483,7 @@ function summarizeBlast(result, opts = {}) {
|
|
|
1294
1483
|
}))
|
|
1295
1484
|
};
|
|
1296
1485
|
}
|
|
1297
|
-
var 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;
|
|
1486
|
+
var 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;
|
|
1298
1487
|
var init_dist = __esm({
|
|
1299
1488
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1300
1489
|
"use strict";
|
|
@@ -1762,6 +1951,69 @@ var init_dist = __esm({
|
|
|
1762
1951
|
]);
|
|
1763
1952
|
SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish", "dash", "ksh"]);
|
|
1764
1953
|
DOWNLOAD_CMDS = /* @__PURE__ */ new Set(["curl", "wget"]);
|
|
1954
|
+
NORMALIZE_CACHE_MAX = 5e3;
|
|
1955
|
+
normalizeCache = /* @__PURE__ */ new Map();
|
|
1956
|
+
AST_CACHE_MAX = 5e3;
|
|
1957
|
+
astCache = /* @__PURE__ */ new Map();
|
|
1958
|
+
PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
1959
|
+
FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
1960
|
+
"cat",
|
|
1961
|
+
"less",
|
|
1962
|
+
"head",
|
|
1963
|
+
"tail",
|
|
1964
|
+
"bat",
|
|
1965
|
+
"more",
|
|
1966
|
+
"open",
|
|
1967
|
+
"print",
|
|
1968
|
+
"nano",
|
|
1969
|
+
"vim",
|
|
1970
|
+
"vi",
|
|
1971
|
+
"emacs",
|
|
1972
|
+
"code",
|
|
1973
|
+
"type"
|
|
1974
|
+
]);
|
|
1975
|
+
FS_OP_PRESCREEN_RE = /(?:^|[\s|;&(`\n])(?:rm|cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\b/;
|
|
1976
|
+
HOME_CACHE_ALLOWLIST = [
|
|
1977
|
+
".cache",
|
|
1978
|
+
".npm/_npx",
|
|
1979
|
+
".npm/_cacache",
|
|
1980
|
+
".cargo/registry",
|
|
1981
|
+
".gradle/caches",
|
|
1982
|
+
".gradle/.tmp",
|
|
1983
|
+
".m2/repository",
|
|
1984
|
+
".pnpm-store",
|
|
1985
|
+
".yarn/cache",
|
|
1986
|
+
".yarn/.cache",
|
|
1987
|
+
".cache/pip",
|
|
1988
|
+
".local/share/Trash",
|
|
1989
|
+
".rustup/downloads"
|
|
1990
|
+
];
|
|
1991
|
+
SENSITIVE_PATH_RULES = [
|
|
1992
|
+
{
|
|
1993
|
+
rule: "shield:project-jail:block-read-ssh",
|
|
1994
|
+
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1995
|
+
match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
|
|
1996
|
+
},
|
|
1997
|
+
{
|
|
1998
|
+
rule: "shield:project-jail:block-read-aws",
|
|
1999
|
+
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
2000
|
+
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
rule: "shield:project-jail:block-read-env",
|
|
2004
|
+
reason: "Reading .env files is blocked by project-jail shield",
|
|
2005
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.local|\.production|\.staging)?$/i.test(p)
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
rule: "shield:project-jail:block-read-credentials",
|
|
2009
|
+
reason: "Reading credential files is blocked by project-jail shield",
|
|
2010
|
+
match: (p) => /(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials)$/i.test(
|
|
2011
|
+
p
|
|
2012
|
+
)
|
|
2013
|
+
}
|
|
2014
|
+
];
|
|
2015
|
+
FS_OP_CACHE_MAX = 5e3;
|
|
2016
|
+
fsOpCache = /* @__PURE__ */ new Map();
|
|
1765
2017
|
SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1766
2018
|
"cat",
|
|
1767
2019
|
"head",
|
|
@@ -5541,9 +5793,7 @@ function removeNode9McpServer(servers) {
|
|
|
5541
5793
|
return true;
|
|
5542
5794
|
}
|
|
5543
5795
|
function printDaemonTip() {
|
|
5544
|
-
console.log(
|
|
5545
|
-
chalk.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + chalk.white("\n To view your history or manage persistent rules, run:") + chalk.green("\n node9 daemon --openui")
|
|
5546
|
-
);
|
|
5796
|
+
console.log(chalk.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups."));
|
|
5547
5797
|
}
|
|
5548
5798
|
function fullPathCommand(subcommand) {
|
|
5549
5799
|
if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
|
|
@@ -6627,7 +6877,8 @@ function buildScanSummary(agents) {
|
|
|
6627
6877
|
timestamp: f.timestamp,
|
|
6628
6878
|
project: f.project,
|
|
6629
6879
|
sessionId: f.sessionId,
|
|
6630
|
-
agent: f.agent
|
|
6880
|
+
agent: f.agent,
|
|
6881
|
+
kind: f.kind
|
|
6631
6882
|
}))
|
|
6632
6883
|
);
|
|
6633
6884
|
const byVerdict = {
|
|
@@ -6645,10 +6896,7 @@ function buildScanSummary(agents) {
|
|
|
6645
6896
|
costUSD: a.scan.totalCostUSD
|
|
6646
6897
|
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
6647
6898
|
const sections = buildSections(allFindings);
|
|
6648
|
-
const wastedIters = allLoops.reduce(
|
|
6649
|
-
(sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE),
|
|
6650
|
-
0
|
|
6651
|
-
);
|
|
6899
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
6652
6900
|
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
6653
6901
|
return {
|
|
6654
6902
|
stats,
|
|
@@ -7812,6 +8060,20 @@ function geminiModelPrice(model) {
|
|
|
7812
8060
|
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
7813
8061
|
return null;
|
|
7814
8062
|
}
|
|
8063
|
+
function isNode9SelfOutput(text) {
|
|
8064
|
+
let hits = 0;
|
|
8065
|
+
for (const re of SELF_OUTPUT_MARKERS) {
|
|
8066
|
+
if (re.test(text)) hits++;
|
|
8067
|
+
if (hits >= 2) return true;
|
|
8068
|
+
}
|
|
8069
|
+
return false;
|
|
8070
|
+
}
|
|
8071
|
+
function looksLikeFixtureToken(sample) {
|
|
8072
|
+
for (const re of FIXTURE_TOKEN_PATTERNS) {
|
|
8073
|
+
if (re.test(sample)) return true;
|
|
8074
|
+
}
|
|
8075
|
+
return false;
|
|
8076
|
+
}
|
|
7815
8077
|
function num(n) {
|
|
7816
8078
|
return n.toLocaleString();
|
|
7817
8079
|
}
|
|
@@ -7875,6 +8137,45 @@ function buildRecurringPatternSet(findings) {
|
|
|
7875
8137
|
}
|
|
7876
8138
|
return recurring;
|
|
7877
8139
|
}
|
|
8140
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result) {
|
|
8141
|
+
const fsVerdict = analyzeFsOperation(command);
|
|
8142
|
+
if (!fsVerdict) return false;
|
|
8143
|
+
const synthRule = {
|
|
8144
|
+
name: fsVerdict.ruleName,
|
|
8145
|
+
tool: "bash",
|
|
8146
|
+
conditions: [],
|
|
8147
|
+
verdict: fsVerdict.verdict,
|
|
8148
|
+
reason: fsVerdict.reason
|
|
8149
|
+
};
|
|
8150
|
+
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
8151
|
+
const synthSource = isShieldRule ? {
|
|
8152
|
+
shieldName: "project-jail",
|
|
8153
|
+
shieldLabel: "project-jail (AST)",
|
|
8154
|
+
sourceType: "shield",
|
|
8155
|
+
rule: synthRule
|
|
8156
|
+
} : {
|
|
8157
|
+
shieldName: "",
|
|
8158
|
+
shieldLabel: "default (AST)",
|
|
8159
|
+
sourceType: "default",
|
|
8160
|
+
rule: synthRule
|
|
8161
|
+
};
|
|
8162
|
+
const inputPreview = preview(input, 120);
|
|
8163
|
+
const isDupe = result.findings.some(
|
|
8164
|
+
(f) => f.source.rule.name === synthRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
8165
|
+
);
|
|
8166
|
+
if (!isDupe) {
|
|
8167
|
+
result.findings.push({
|
|
8168
|
+
source: synthSource,
|
|
8169
|
+
toolName,
|
|
8170
|
+
input,
|
|
8171
|
+
timestamp,
|
|
8172
|
+
project: projLabel,
|
|
8173
|
+
sessionId,
|
|
8174
|
+
agent
|
|
8175
|
+
});
|
|
8176
|
+
}
|
|
8177
|
+
return true;
|
|
8178
|
+
}
|
|
7878
8179
|
function isStaleFinding(timestamp, now = Date.now()) {
|
|
7879
8180
|
if (!timestamp) return false;
|
|
7880
8181
|
const t = Date.parse(timestamp);
|
|
@@ -7908,15 +8209,24 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
7908
8209
|
const entry = counts.get(key) ?? {
|
|
7909
8210
|
count: 0,
|
|
7910
8211
|
timestamp: call.timestamp,
|
|
8212
|
+
firstTs: null,
|
|
8213
|
+
lastTs: null,
|
|
7911
8214
|
input: call.input,
|
|
7912
8215
|
toolName: call.toolName
|
|
7913
8216
|
};
|
|
7914
8217
|
entry.count++;
|
|
8218
|
+
const t = call.timestamp ? Date.parse(call.timestamp) : NaN;
|
|
8219
|
+
if (!Number.isNaN(t)) {
|
|
8220
|
+
if (entry.firstTs === null || t < entry.firstTs) entry.firstTs = t;
|
|
8221
|
+
if (entry.lastTs === null || t > entry.lastTs) entry.lastTs = t;
|
|
8222
|
+
}
|
|
7915
8223
|
counts.set(key, entry);
|
|
7916
8224
|
}
|
|
7917
8225
|
const findings = [];
|
|
7918
8226
|
for (const [, entry] of counts) {
|
|
7919
8227
|
if (entry.count >= LOOP_THRESHOLD) {
|
|
8228
|
+
const span = entry.firstTs !== null && entry.lastTs !== null ? entry.lastTs - entry.firstTs : 0;
|
|
8229
|
+
const kind = span >= LOOP_TIMESPAN_THRESHOLD_MS ? "long-iteration" : "loop";
|
|
7920
8230
|
findings.push({
|
|
7921
8231
|
toolName: entry.toolName,
|
|
7922
8232
|
commandPreview: preview(entry.input, 80),
|
|
@@ -7924,7 +8234,8 @@ function detectLoops(calls, project, sessionId, agent) {
|
|
|
7924
8234
|
timestamp: entry.timestamp,
|
|
7925
8235
|
project,
|
|
7926
8236
|
sessionId,
|
|
7927
|
-
agent
|
|
8237
|
+
agent,
|
|
8238
|
+
kind
|
|
7928
8239
|
});
|
|
7929
8240
|
}
|
|
7930
8241
|
}
|
|
@@ -8143,8 +8454,10 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
8143
8454
|
}
|
|
8144
8455
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
8145
8456
|
if (!resultText) continue;
|
|
8457
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
8146
8458
|
const dlpMatch = scanArgs({ text: resultText });
|
|
8147
8459
|
if (dlpMatch) {
|
|
8460
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
8148
8461
|
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
8149
8462
|
const isDupe = result.dlpFindings.some(
|
|
8150
8463
|
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
@@ -8215,11 +8528,26 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
8215
8528
|
});
|
|
8216
8529
|
}
|
|
8217
8530
|
}
|
|
8218
|
-
let
|
|
8531
|
+
let astFsMatched = false;
|
|
8532
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
8533
|
+
if (astRanForBash) {
|
|
8534
|
+
astFsMatched = pushFsOpAstFinding(
|
|
8535
|
+
String(input.command ?? ""),
|
|
8536
|
+
toolName,
|
|
8537
|
+
input,
|
|
8538
|
+
entry.timestamp ?? "",
|
|
8539
|
+
projLabel,
|
|
8540
|
+
sessionId,
|
|
8541
|
+
"claude",
|
|
8542
|
+
result
|
|
8543
|
+
);
|
|
8544
|
+
}
|
|
8545
|
+
let ruleMatched = astFsMatched;
|
|
8219
8546
|
for (const source of ruleSources) {
|
|
8220
8547
|
const { rule } = source;
|
|
8221
8548
|
if (rule.verdict === "allow") continue;
|
|
8222
8549
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
8550
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8223
8551
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8224
8552
|
const inputPreview = preview(input, 120);
|
|
8225
8553
|
const isDupe = result.findings.some(
|
|
@@ -8415,11 +8743,26 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
8415
8743
|
});
|
|
8416
8744
|
}
|
|
8417
8745
|
}
|
|
8418
|
-
let
|
|
8746
|
+
let astFsMatched = false;
|
|
8747
|
+
const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
|
|
8748
|
+
if (astRanForBash) {
|
|
8749
|
+
astFsMatched = pushFsOpAstFinding(
|
|
8750
|
+
String(input.command ?? ""),
|
|
8751
|
+
toolName,
|
|
8752
|
+
input,
|
|
8753
|
+
msg.timestamp ?? "",
|
|
8754
|
+
projLabel,
|
|
8755
|
+
sessionId,
|
|
8756
|
+
"gemini",
|
|
8757
|
+
result
|
|
8758
|
+
);
|
|
8759
|
+
}
|
|
8760
|
+
let ruleMatched = astFsMatched;
|
|
8419
8761
|
for (const source of ruleSources) {
|
|
8420
8762
|
const { rule } = source;
|
|
8421
8763
|
if (rule.verdict === "allow") continue;
|
|
8422
8764
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
8765
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8423
8766
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8424
8767
|
const inputPreview = preview(input, 120);
|
|
8425
8768
|
const isDupe = result.findings.some(
|
|
@@ -8637,12 +8980,27 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
8637
8980
|
});
|
|
8638
8981
|
}
|
|
8639
8982
|
}
|
|
8640
|
-
let
|
|
8983
|
+
let astFsMatched = false;
|
|
8984
|
+
const astRanForBash = toolNameLower === "exec_command" || toolNameLower === "bash";
|
|
8985
|
+
if (astRanForBash) {
|
|
8986
|
+
astFsMatched = pushFsOpAstFinding(
|
|
8987
|
+
String(input["command"] ?? ""),
|
|
8988
|
+
toolName,
|
|
8989
|
+
input,
|
|
8990
|
+
ts,
|
|
8991
|
+
projLabel,
|
|
8992
|
+
sessionId,
|
|
8993
|
+
"codex",
|
|
8994
|
+
result
|
|
8995
|
+
);
|
|
8996
|
+
}
|
|
8997
|
+
let ruleMatched = astFsMatched;
|
|
8641
8998
|
for (const source of ruleSources) {
|
|
8642
8999
|
const { rule } = source;
|
|
8643
9000
|
if (rule.verdict === "allow") continue;
|
|
8644
9001
|
if (rule.tool && !matchesPattern(toolNameLower === "exec_command" ? "bash" : toolNameLower, rule.tool))
|
|
8645
9002
|
continue;
|
|
9003
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
8646
9004
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
8647
9005
|
const inputPreview = preview(input, 120);
|
|
8648
9006
|
const isDupe = result.findings.some(
|
|
@@ -8842,15 +9200,22 @@ function renderCompactScorecard(input) {
|
|
|
8842
9200
|
chalk4.red("\u{1F6D1} ") + chalk4.red.bold(String(blockedCount).padEnd(4)) + chalk4.dim("would have blocked".padEnd(20)) + chalk4.dim(`(${topBlocked})`)
|
|
8843
9201
|
);
|
|
8844
9202
|
}
|
|
8845
|
-
|
|
8846
|
-
|
|
9203
|
+
const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
|
|
9204
|
+
const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
|
|
9205
|
+
if (realLoops.length > 0) {
|
|
9206
|
+
const wastedCalls = realLoops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
8847
9207
|
const wastePct = scan.totalToolCalls > 0 ? Math.round(wastedCalls / scan.totalToolCalls * 100) : 0;
|
|
8848
9208
|
const wasteParts = [];
|
|
8849
9209
|
if (wastePct > 0) wasteParts.push(`${wastePct}% wasted`);
|
|
8850
9210
|
if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
|
|
8851
9211
|
const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
|
|
8852
9212
|
console.log(
|
|
8853
|
-
chalk4.yellow("\u{1F501} ") + chalk4.yellow.bold(String(
|
|
9213
|
+
chalk4.yellow("\u{1F501} ") + chalk4.yellow.bold(String(realLoops.length).padEnd(4)) + chalk4.dim("agent loops".padEnd(20)) + chalk4.dim(wasteSummary)
|
|
9214
|
+
);
|
|
9215
|
+
}
|
|
9216
|
+
if (longIterations.length > 0) {
|
|
9217
|
+
console.log(
|
|
9218
|
+
chalk4.dim("\u{1F4C2} ") + chalk4.dim.bold(String(longIterations.length).padEnd(4)) + chalk4.dim("long iterations".padEnd(20)) + chalk4.dim("(deep work \u2014 not waste)")
|
|
8854
9219
|
);
|
|
8855
9220
|
}
|
|
8856
9221
|
if (reviewCount > 0) {
|
|
@@ -9382,13 +9747,14 @@ function registerScanCommand(program2) {
|
|
|
9382
9747
|
}
|
|
9383
9748
|
);
|
|
9384
9749
|
}
|
|
9385
|
-
var 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;
|
|
9750
|
+
var 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;
|
|
9386
9751
|
var init_scan = __esm({
|
|
9387
9752
|
"src/cli/commands/scan.ts"() {
|
|
9388
9753
|
"use strict";
|
|
9389
9754
|
init_shields();
|
|
9390
9755
|
init_config();
|
|
9391
9756
|
init_policy();
|
|
9757
|
+
init_dist();
|
|
9392
9758
|
init_dlp();
|
|
9393
9759
|
init_dist();
|
|
9394
9760
|
init_scan_summary();
|
|
@@ -9441,6 +9807,22 @@ var init_scan = __esm({
|
|
|
9441
9807
|
".vue",
|
|
9442
9808
|
".svelte"
|
|
9443
9809
|
]);
|
|
9810
|
+
SELF_OUTPUT_MARKERS = [
|
|
9811
|
+
/redactedSample:\s*['"]/,
|
|
9812
|
+
/patternName:\s*['"]/,
|
|
9813
|
+
/\bseverity:\s*['"](?:block|review|allow)['"]/,
|
|
9814
|
+
/NODE9 SECURITY ALERT/
|
|
9815
|
+
];
|
|
9816
|
+
FIXTURE_TOKEN_PATTERNS = [
|
|
9817
|
+
/(.)\1{5,}/,
|
|
9818
|
+
// 6+ repeated characters (aaaaaa, 000000)
|
|
9819
|
+
/(?:EXAMPLE|FAKE|DUMMY|PLACEHOLDER|XXXXX)/i,
|
|
9820
|
+
/abcdefghijklmn/i,
|
|
9821
|
+
// long alpha sequence — fixture, not entropy
|
|
9822
|
+
/1234567890/,
|
|
9823
|
+
// long digit sequence — fixture, not entropy
|
|
9824
|
+
/qwerty/i
|
|
9825
|
+
];
|
|
9444
9826
|
TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
9445
9827
|
LOOP_TOOLS = /* @__PURE__ */ new Set([
|
|
9446
9828
|
"bash",
|
|
@@ -9453,10 +9835,18 @@ var init_scan = __esm({
|
|
|
9453
9835
|
"multiedit"
|
|
9454
9836
|
]);
|
|
9455
9837
|
LOOP_THRESHOLD = 3;
|
|
9838
|
+
LOOP_TIMESPAN_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
9456
9839
|
STUCK_TOOLS_MIN_WASTE = 5;
|
|
9457
9840
|
STUCK_TOOLS_LIMIT = 3;
|
|
9458
9841
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
9459
9842
|
STALE_AGE_DAYS = 30;
|
|
9843
|
+
AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
9844
|
+
"block-rm-rf-home",
|
|
9845
|
+
"shield:project-jail:block-read-ssh",
|
|
9846
|
+
"shield:project-jail:block-read-aws",
|
|
9847
|
+
"shield:project-jail:block-read-env",
|
|
9848
|
+
"shield:project-jail:block-read-credentials"
|
|
9849
|
+
]);
|
|
9460
9850
|
DEFAULT_RULE_NAMES = new Set(
|
|
9461
9851
|
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
9462
9852
|
);
|
|
@@ -9984,7 +10374,86 @@ function abandonPending() {
|
|
|
9984
10374
|
}, 200);
|
|
9985
10375
|
}
|
|
9986
10376
|
}
|
|
10377
|
+
function logActivitySocket(msg) {
|
|
10378
|
+
try {
|
|
10379
|
+
fs20.appendFileSync(
|
|
10380
|
+
path22.join(homeDir, ".node9", "hook-debug.log"),
|
|
10381
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
|
|
10382
|
+
`
|
|
10383
|
+
);
|
|
10384
|
+
} catch {
|
|
10385
|
+
}
|
|
10386
|
+
}
|
|
10387
|
+
function shouldRebind(now = Date.now()) {
|
|
10388
|
+
if (activityCircuitTripped) return false;
|
|
10389
|
+
activityRebindAttempts = activityRebindAttempts.filter(
|
|
10390
|
+
(t) => now - t < ACTIVITY_REBIND_WINDOW_MS
|
|
10391
|
+
);
|
|
10392
|
+
activityRebindAttempts.push(now);
|
|
10393
|
+
if (activityRebindAttempts.length > ACTIVITY_REBIND_MAX_ATTEMPTS) {
|
|
10394
|
+
activityCircuitTripped = true;
|
|
10395
|
+
return false;
|
|
10396
|
+
}
|
|
10397
|
+
return true;
|
|
10398
|
+
}
|
|
9987
10399
|
function startActivitySocket() {
|
|
10400
|
+
bindActivitySocket();
|
|
10401
|
+
if (process.platform !== "win32") {
|
|
10402
|
+
try {
|
|
10403
|
+
activitySocketWatcher = fs20.watch(os18.tmpdir(), (eventType, filename) => {
|
|
10404
|
+
if (filename !== path22.basename(ACTIVITY_SOCKET_PATH2)) return;
|
|
10405
|
+
if (eventType !== "rename") return;
|
|
10406
|
+
if (fs20.existsSync(ACTIVITY_SOCKET_PATH2)) return;
|
|
10407
|
+
attemptRebind("watch-unlink");
|
|
10408
|
+
});
|
|
10409
|
+
activitySocketWatcher.on("error", (err2) => {
|
|
10410
|
+
logActivitySocket(`watcher error: ${err2.message}`);
|
|
10411
|
+
});
|
|
10412
|
+
activitySocketWatcher.unref();
|
|
10413
|
+
} catch (err2) {
|
|
10414
|
+
logActivitySocket(`failed to start watcher: ${err2.message}`);
|
|
10415
|
+
}
|
|
10416
|
+
}
|
|
10417
|
+
activityHealthInterval = setInterval(() => {
|
|
10418
|
+
if (!fs20.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
|
|
10419
|
+
}, ACTIVITY_HEALTH_PROBE_MS);
|
|
10420
|
+
activityHealthInterval.unref();
|
|
10421
|
+
process.on("exit", () => {
|
|
10422
|
+
if (activitySocketWatcher) {
|
|
10423
|
+
try {
|
|
10424
|
+
activitySocketWatcher.close();
|
|
10425
|
+
} catch {
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
if (activityHealthInterval) clearInterval(activityHealthInterval);
|
|
10429
|
+
try {
|
|
10430
|
+
fs20.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10431
|
+
} catch {
|
|
10432
|
+
}
|
|
10433
|
+
});
|
|
10434
|
+
}
|
|
10435
|
+
function attemptRebind(reason) {
|
|
10436
|
+
if (!shouldRebind()) {
|
|
10437
|
+
logActivitySocket(
|
|
10438
|
+
`circuit breaker tripped after ${ACTIVITY_REBIND_MAX_ATTEMPTS} attempts in ${ACTIVITY_REBIND_WINDOW_MS}ms \u2014 flight recorder down`
|
|
10439
|
+
);
|
|
10440
|
+
broadcast("flight-recorder-down", {
|
|
10441
|
+
reason: "rebind-loop",
|
|
10442
|
+
message: "Activity socket repeatedly disappearing \u2014 run: node9 daemon restart"
|
|
10443
|
+
});
|
|
10444
|
+
return;
|
|
10445
|
+
}
|
|
10446
|
+
logActivitySocket(`rebinding (reason: ${reason}, attempt ${activityRebindAttempts.length})`);
|
|
10447
|
+
if (activitySocketServer) {
|
|
10448
|
+
try {
|
|
10449
|
+
activitySocketServer.close();
|
|
10450
|
+
} catch {
|
|
10451
|
+
}
|
|
10452
|
+
activitySocketServer = null;
|
|
10453
|
+
}
|
|
10454
|
+
bindActivitySocket();
|
|
10455
|
+
}
|
|
10456
|
+
function bindActivitySocket() {
|
|
9988
10457
|
try {
|
|
9989
10458
|
fs20.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
9990
10459
|
} catch {
|
|
@@ -10082,15 +10551,15 @@ function startActivitySocket() {
|
|
|
10082
10551
|
socket.on("error", () => {
|
|
10083
10552
|
});
|
|
10084
10553
|
});
|
|
10085
|
-
unixServer.
|
|
10086
|
-
|
|
10087
|
-
try {
|
|
10088
|
-
fs20.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10089
|
-
} catch {
|
|
10090
|
-
}
|
|
10554
|
+
unixServer.on("error", (err2) => {
|
|
10555
|
+
logActivitySocket(`server error: ${err2.message}`);
|
|
10091
10556
|
});
|
|
10557
|
+
unixServer.listen(ACTIVITY_SOCKET_PATH2, () => {
|
|
10558
|
+
logActivitySocket(`bound to ${ACTIVITY_SOCKET_PATH2}`);
|
|
10559
|
+
});
|
|
10560
|
+
activitySocketServer = unixServer;
|
|
10092
10561
|
}
|
|
10093
|
-
var 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;
|
|
10562
|
+
var 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;
|
|
10094
10563
|
var init_state2 = __esm({
|
|
10095
10564
|
"src/daemon/state.ts"() {
|
|
10096
10565
|
"use strict";
|
|
@@ -10146,6 +10615,14 @@ var init_state2 = __esm({
|
|
|
10146
10615
|
"notebook_edit",
|
|
10147
10616
|
"notebookedit"
|
|
10148
10617
|
]);
|
|
10618
|
+
ACTIVITY_REBIND_MAX_ATTEMPTS = 5;
|
|
10619
|
+
ACTIVITY_REBIND_WINDOW_MS = 6e4;
|
|
10620
|
+
ACTIVITY_HEALTH_PROBE_MS = 3e4;
|
|
10621
|
+
activitySocketServer = null;
|
|
10622
|
+
activitySocketWatcher = null;
|
|
10623
|
+
activityHealthInterval = null;
|
|
10624
|
+
activityRebindAttempts = [];
|
|
10625
|
+
activityCircuitTripped = false;
|
|
10149
10626
|
}
|
|
10150
10627
|
});
|
|
10151
10628
|
|
|
@@ -12292,6 +12769,8 @@ async function startTail(options = {}) {
|
|
|
12292
12769
|
let initialReplayDone = false;
|
|
12293
12770
|
const activityPending = /* @__PURE__ */ new Map();
|
|
12294
12771
|
const orphanedResults = /* @__PURE__ */ new Map();
|
|
12772
|
+
let lastActivityFromDaemon = Date.now();
|
|
12773
|
+
let stallWarned = false;
|
|
12295
12774
|
const authToken = getInternalToken() ?? "";
|
|
12296
12775
|
const approvalQueue = [];
|
|
12297
12776
|
let cardActive = false;
|
|
@@ -12518,6 +12997,24 @@ async function startTail(options = {}) {
|
|
|
12518
12997
|
console.log(chalk29.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
12519
12998
|
process.exit(0);
|
|
12520
12999
|
});
|
|
13000
|
+
const STALL_THRESHOLD_MS = 6e4;
|
|
13001
|
+
const stallWatchdog = setInterval(() => {
|
|
13002
|
+
if (stallWarned) return;
|
|
13003
|
+
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
13004
|
+
try {
|
|
13005
|
+
const auditMtime = fs44.statSync(auditLog).mtimeMs;
|
|
13006
|
+
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
13007
|
+
console.log("");
|
|
13008
|
+
console.log(
|
|
13009
|
+
chalk29.yellow(
|
|
13010
|
+
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
13011
|
+
)
|
|
13012
|
+
);
|
|
13013
|
+
stallWarned = true;
|
|
13014
|
+
} catch {
|
|
13015
|
+
}
|
|
13016
|
+
}, STALL_THRESHOLD_MS / 2);
|
|
13017
|
+
stallWatchdog.unref();
|
|
12521
13018
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
12522
13019
|
const req = http2.get(
|
|
12523
13020
|
sseUrl,
|
|
@@ -12563,7 +13060,18 @@ async function startTail(options = {}) {
|
|
|
12563
13060
|
}
|
|
12564
13061
|
);
|
|
12565
13062
|
function handleMessage(event, rawData) {
|
|
13063
|
+
lastActivityFromDaemon = Date.now();
|
|
12566
13064
|
if (event === "csrf") return;
|
|
13065
|
+
if (event === "flight-recorder-down") {
|
|
13066
|
+
try {
|
|
13067
|
+
const parsed = JSON.parse(rawData);
|
|
13068
|
+
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
13069
|
+
console.log("");
|
|
13070
|
+
console.log(chalk29.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
13071
|
+
} catch {
|
|
13072
|
+
}
|
|
13073
|
+
return;
|
|
13074
|
+
}
|
|
12567
13075
|
if (event === "init") {
|
|
12568
13076
|
try {
|
|
12569
13077
|
const parsed = JSON.parse(rawData);
|
|
@@ -16139,7 +16647,6 @@ function registerInitCommand(program2) {
|
|
|
16139
16647
|
console.log(chalk15.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
16140
16648
|
console.log("");
|
|
16141
16649
|
console.log(chalk15.white(" Watch live: ") + chalk15.cyan("node9 tail"));
|
|
16142
|
-
console.log(chalk15.white(" Local UI: ") + chalk15.cyan("node9 daemon --openui"));
|
|
16143
16650
|
console.log("");
|
|
16144
16651
|
console.log(chalk15.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"));
|
|
16145
16652
|
console.log(
|