@kenhkl/agent-moss 0.12.9 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,12 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
5
11
  var __esm = (fn, res) => function __init() {
6
12
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
13
  };
@@ -127,6 +133,7 @@ __export(config_exports, {
127
133
  getRetryConfig: () => getRetryConfig,
128
134
  getTimeoutConfig: () => getTimeoutConfig,
129
135
  isBrainEnabled: () => isBrainEnabled,
136
+ isFormalObserveMode: () => isFormalObserveMode,
130
137
  isHeuristicEnabled: () => isHeuristicEnabled,
131
138
  isLLMAvailable: () => isLLMAvailable,
132
139
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -242,6 +249,7 @@ function getCustomRules() {
242
249
  }
243
250
  function isLLMAvailable() {
244
251
  if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
252
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
245
253
  return llmConfig.apiKey.length > 0;
246
254
  }
247
255
  async function probeLLMConnection(timeoutMs = 8e3) {
@@ -306,13 +314,26 @@ function getProviderHeaders(provider) {
306
314
  return PROVIDER_HEADERS[provider] || {};
307
315
  }
308
316
  function isHeuristicEnabled() {
309
- return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
317
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
318
+ return layerEnabledOrDefault("L1_heuristic");
319
+ }
320
+ function layerEnabledOrDefault(key) {
321
+ try {
322
+ const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
323
+ return isLayerEnabled2(key);
324
+ } catch {
325
+ return true;
326
+ }
310
327
  }
311
328
  function isBrainEnabled() {
312
329
  return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
313
330
  }
314
331
  function isLogicRulesEnabled() {
315
- return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
332
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
333
+ return layerEnabledOrDefault("L2_logic_rules");
334
+ }
335
+ function isFormalObserveMode() {
336
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
316
337
  }
317
338
  var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
318
339
  var init_config = __esm({
@@ -570,8 +591,12 @@ var init_inline_analyzer = __esm({
570
591
 
571
592
  // src/infra/rules-loader.ts
572
593
  import { readFileSync as readFileSync2, existsSync } from "fs";
573
- import { resolve, dirname } from "path";
594
+ import { resolve, dirname, join as join2 } from "path";
574
595
  import { fileURLToPath as fileURLToPath3 } from "url";
596
+ import { homedir as homedir2 } from "os";
597
+ function dataRootDir() {
598
+ return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
599
+ }
575
600
  function findRulesFile(name) {
576
601
  for (const dir of RULES_DIR_CANDS) {
577
602
  const p = resolve(dir, name);
@@ -597,6 +622,24 @@ function loadL2() {
597
622
  function loadL3() {
598
623
  return loadJson("l3_rules.json");
599
624
  }
625
+ function loadMC() {
626
+ const mc = loadJson("mc_rules.json");
627
+ const rules = mc.rules || [];
628
+ try {
629
+ const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
630
+ if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
631
+ const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
632
+ const enabledById = {};
633
+ for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
634
+ for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
635
+ }
636
+ } catch {
637
+ }
638
+ return mc;
639
+ }
640
+ function loadToolRegistry() {
641
+ return loadJson("tool_registry.json");
642
+ }
600
643
  var __filename2, __dirname2, RULES_DIR_CANDS;
601
644
  var init_rules_loader = __esm({
602
645
  "src/infra/rules-loader.ts"() {
@@ -606,6 +649,10 @@ var init_rules_loader = __esm({
606
649
  __dirname2 = dirname(__filename2);
607
650
  RULES_DIR_CANDS = (() => {
608
651
  const cands = [
652
+ // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
653
+ // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
654
+ // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
655
+ resolve(dataRootDir(), "rules"),
609
656
  // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
610
657
  resolve(__dirname2, "../rules"),
611
658
  resolve(__dirname2, "rules"),
@@ -629,7 +676,8 @@ function _loadSensitivePathScope(scope) {
629
676
  path: sp.path,
630
677
  riskLevel: sp.risk_level,
631
678
  desc: sp.desc,
632
- credential: sp.deny_mode === "deny_both" ? true : void 0
679
+ credential: sp.deny_mode === "deny_both" ? true : void 0,
680
+ dirDelOnly: sp.dir_del_only === true ? true : void 0
633
681
  }));
634
682
  }
635
683
  function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
@@ -727,7 +775,7 @@ function runLogicCheck(input) {
727
775
  riskType: "intent_deviation",
728
776
  confidence: 85,
729
777
  source: "logic_rule",
730
- violatedLayers: ["2"]
778
+ violatedLayers: ["L2.1"]
731
779
  };
732
780
  }
733
781
  }
@@ -751,7 +799,7 @@ function runLogicCheck(input) {
751
799
  riskType: "privilege_escalation",
752
800
  confidence: 85,
753
801
  source: "logic_rule",
754
- violatedLayers: ["2"]
802
+ violatedLayers: ["L2.1"]
755
803
  };
756
804
  }
757
805
  }
@@ -803,7 +851,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
803
851
  riskType: "combo_chain",
804
852
  confidence: 85,
805
853
  source: "logic_rule",
806
- violatedLayers: ["2"]
854
+ violatedLayers: ["L2.1"]
807
855
  };
808
856
  }
809
857
  }
@@ -928,7 +976,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
928
976
  riskType: "read_before_write",
929
977
  confidence: 85,
930
978
  source: "logic_rule",
931
- violatedLayers: ["2"]
979
+ violatedLayers: ["L2.1"]
932
980
  };
933
981
  }
934
982
  return null;
@@ -948,7 +996,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
948
996
  riskType: "consent_missing",
949
997
  confidence: 85,
950
998
  source: "logic_rule",
951
- violatedLayers: ["2"]
999
+ violatedLayers: ["L2.1"]
952
1000
  };
953
1001
  }
954
1002
  function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
@@ -966,7 +1014,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
966
1014
  riskType: "consent_missing",
967
1015
  confidence: 85,
968
1016
  source: "logic_rule",
969
- violatedLayers: ["2"]
1017
+ violatedLayers: ["L2.1"]
970
1018
  };
971
1019
  }
972
1020
  function containsPrivilegeEscalation(actionDetail) {
@@ -1026,7 +1074,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1026
1074
  riskType: "file_access",
1027
1075
  confidence: 95,
1028
1076
  source: "logic_rule",
1029
- violatedLayers: ["2"],
1077
+ violatedLayers: ["L2.1"],
1030
1078
  isCredential: true
1031
1079
  };
1032
1080
  }
@@ -1042,7 +1090,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1042
1090
  riskType: "file_access",
1043
1091
  confidence: 95,
1044
1092
  source: "logic_rule",
1045
- violatedLayers: ["2"]
1093
+ violatedLayers: ["L2.1"]
1046
1094
  };
1047
1095
  }
1048
1096
  }
@@ -1056,7 +1104,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1056
1104
  riskType: "file_access",
1057
1105
  confidence: 70,
1058
1106
  source: "logic_rule",
1059
- violatedLayers: ["2"]
1107
+ violatedLayers: ["L2.1"]
1060
1108
  };
1061
1109
  }
1062
1110
  var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
@@ -1068,7 +1116,7 @@ var init_logic_rules = __esm({
1068
1116
  init_rules_loader();
1069
1117
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1070
1118
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1071
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc }) => ({ path: path5, riskLevel, desc, credential: true }));
1119
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1072
1120
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1073
1121
  "ask_user_question",
1074
1122
  "glob",
@@ -1343,6 +1391,7 @@ var init_skill_loader = __esm({
1343
1391
  // src/infra/runtime-config.ts
1344
1392
  var runtime_config_exports = {};
1345
1393
  __export(runtime_config_exports, {
1394
+ DEFAULT_LAYERS: () => DEFAULT_LAYERS,
1346
1395
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1347
1396
  addAgentRule: () => addAgentRule,
1348
1397
  addCustomRule: () => addCustomRule,
@@ -1394,7 +1443,7 @@ __export(runtime_config_exports, {
1394
1443
  updateSkillEnabled: () => updateSkillEnabled
1395
1444
  });
1396
1445
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1397
- import { join as join2 } from "path";
1446
+ import { join as join3 } from "path";
1398
1447
  import { fileURLToPath as fileURLToPath5 } from "url";
1399
1448
  import { dirname as dirname3 } from "path";
1400
1449
  function runtimeDir() {
@@ -1418,6 +1467,7 @@ function emptyRuntimeConfig() {
1418
1467
  L1_rules: {},
1419
1468
  L2_rules: {},
1420
1469
  L3_skills: {},
1470
+ L2_2: {},
1421
1471
  agent_rules: {},
1422
1472
  scenario_rules: {}
1423
1473
  };
@@ -1445,6 +1495,7 @@ function loadRuntimeConfig() {
1445
1495
  L1_rules: merged.L1_rules || {},
1446
1496
  L2_rules: merged.L2_rules || {},
1447
1497
  L3_skills: merged.L3_skills || {},
1498
+ L2_2: merged.L2_2 || {},
1448
1499
  agent_rules: merged.agent_rules || {},
1449
1500
  scenario_rules: merged.scenario_rules || {}
1450
1501
  };
@@ -1497,6 +1548,7 @@ function getEnabledL2SensitivePaths(runtime) {
1497
1548
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1498
1549
  if (r.credential) out.credential = true;
1499
1550
  if (r.read_only) out.read_only = true;
1551
+ if (r.dir_del_only) out.dir_del_only = true;
1500
1552
  return out;
1501
1553
  });
1502
1554
  }
@@ -1605,6 +1657,7 @@ function _l2SensitivePathsToRules() {
1605
1657
  builtin: true
1606
1658
  };
1607
1659
  _syncDenyModeFields(rule);
1660
+ if (sp.dirDelOnly) rule.dir_del_only = true;
1608
1661
  return rule;
1609
1662
  });
1610
1663
  }
@@ -1692,7 +1745,7 @@ function _scenarioRulesToDefaults() {
1692
1745
  return out;
1693
1746
  }
1694
1747
  for (const scenarioId of entries) {
1695
- const rulesFile = join2(scenDir, scenarioId, "rules.json");
1748
+ const rulesFile = join3(scenDir, scenarioId, "rules.json");
1696
1749
  if (!existsSync4(rulesFile)) continue;
1697
1750
  let data;
1698
1751
  try {
@@ -1741,7 +1794,7 @@ function _agentRulesToDefaults() {
1741
1794
  return out;
1742
1795
  }
1743
1796
  for (const agentId of entries) {
1744
- const rulesFile = join2(agentsDir, agentId, "rules.json");
1797
+ const rulesFile = join3(agentsDir, agentId, "rules.json");
1745
1798
  if (!existsSync4(rulesFile)) continue;
1746
1799
  let data;
1747
1800
  try {
@@ -1782,7 +1835,7 @@ function _agentRulesToDefaults() {
1782
1835
  function generateSourceDefaults() {
1783
1836
  return {
1784
1837
  version: 1,
1785
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1838
+ layers: { ...DEFAULT_LAYERS },
1786
1839
  L1_rules: {
1787
1840
  dangerous_commands: {
1788
1841
  category_enabled: true,
@@ -1821,10 +1874,45 @@ function generateSourceDefaults() {
1821
1874
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1822
1875
  },
1823
1876
  L3_skills: _l3SkillsToRules(),
1877
+ L2_2: _ltlRulesToRuntime(),
1824
1878
  agent_rules: _agentRulesToDefaults(),
1825
1879
  scenario_rules: _scenarioRulesToDefaults()
1826
1880
  };
1827
1881
  }
1882
+ function _ltlRulesToRuntime() {
1883
+ const cands = [
1884
+ join3(__dirname4, "..", "rules"),
1885
+ join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1886
+ join3(process.cwd(), "agent_moss", "rules")
1887
+ ];
1888
+ let mc = {};
1889
+ for (const dir of cands) {
1890
+ const p = join3(dir, "mc_rules.json");
1891
+ if (existsSync4(p)) {
1892
+ try {
1893
+ mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1894
+ } catch {
1895
+ mc = {};
1896
+ }
1897
+ break;
1898
+ }
1899
+ }
1900
+ const rules = mc.rules || [];
1901
+ return {
1902
+ ltl_rules: {
1903
+ category_enabled: true,
1904
+ category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
1905
+ rules: rules.map((r) => ({
1906
+ id: r.id,
1907
+ reason: r.reason || "",
1908
+ risk_level: r.risk_level || "high",
1909
+ action: r.action || "block",
1910
+ enabled: true,
1911
+ builtin: true
1912
+ }))
1913
+ }
1914
+ };
1915
+ }
1828
1916
  function normalizePatternForFingerprint(pattern) {
1829
1917
  return pattern.replace(/\\\//g, "/");
1830
1918
  }
@@ -1974,6 +2062,11 @@ function mergeConfig(userCfg, srcDefaults) {
1974
2062
  );
1975
2063
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1976
2064
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2065
+ if (!cfg.L2_2 && cfg.L2_4) {
2066
+ cfg.L2_2 = cfg.L2_4;
2067
+ delete cfg.L2_4;
2068
+ }
2069
+ _mergeRuleCategories(cfg, srcDefaults, "L2_2");
1977
2070
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1978
2071
  _mergeAgentRules(cfg, srcDefaults);
1979
2072
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2166,12 +2259,12 @@ function deleteCustomRule(layer, category, ruleId) {
2166
2259
  function addCustomSkill(skillId, category, keywords, content) {
2167
2260
  _validateSkillId(skillId);
2168
2261
  const runtime = loadRuntimeConfig();
2169
- const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2262
+ const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2170
2263
  try {
2171
2264
  mkdirSync(skillsDir, { recursive: true });
2172
2265
  } catch {
2173
2266
  }
2174
- writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
2267
+ writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2175
2268
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2176
2269
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2177
2270
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2186,7 +2279,7 @@ function deleteCustomSkill(skillId, category) {
2186
2279
  if (idx >= 0) {
2187
2280
  if (skills[idx].builtin) return null;
2188
2281
  skills.splice(idx, 1);
2189
- const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2282
+ const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2190
2283
  try {
2191
2284
  if (existsSync4(skillPath)) unlinkSync(skillPath);
2192
2285
  } catch {
@@ -2380,7 +2473,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2380
2473
  saveRuntimeConfig(runtime);
2381
2474
  return runtime;
2382
2475
  }
2383
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2476
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2384
2477
  var init_runtime_config = __esm({
2385
2478
  "src/infra/runtime-config.ts"() {
2386
2479
  "use strict";
@@ -2392,21 +2485,28 @@ var init_runtime_config = __esm({
2392
2485
  __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2393
2486
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2394
2487
  RUNTIME_CONFIG_DIR = runtimeDir();
2395
- RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2488
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2396
2489
  LAYER_DISABLE_ENV = {
2397
2490
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2398
2491
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2399
2492
  L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2400
2493
  };
2494
+ DEFAULT_LAYERS = {
2495
+ L1_heuristic: true,
2496
+ L2_logic_rules: true,
2497
+ L3_llm_analysis: true,
2498
+ agent_rules_enabled: true,
2499
+ scenario_rules_enabled: true
2500
+ };
2401
2501
  DENY_MODE_LABELS = {
2402
2502
  deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2403
2503
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2404
2504
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2405
2505
  };
2406
- _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2407
- _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2408
- _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2409
- _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2506
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2507
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2508
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2509
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2410
2510
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2411
2511
  }
2412
2512
  });
@@ -2544,11 +2644,21 @@ function getScenarioExtensions(scenarioId) {
2544
2644
  );
2545
2645
  return ext;
2546
2646
  }
2647
+ function getScenarioExtensionsMulti(scenarioSpec) {
2648
+ if (!scenarioSpec) return null;
2649
+ const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
2650
+ if (ids.length === 0) return null;
2651
+ const exts = ids.map((id) => getScenarioExtensions(id));
2652
+ const nonNull = exts.filter((e) => e !== null);
2653
+ if (nonNull.length === 0) return null;
2654
+ return mergeAgentExtensions(...nonNull);
2655
+ }
2547
2656
  var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2548
2657
  var init_scenarios = __esm({
2549
2658
  "src/scenarios/index.ts"() {
2550
2659
  "use strict";
2551
2660
  init_esm_shims();
2661
+ init_agents();
2552
2662
  __filename5 = fileURLToPath7(import.meta.url);
2553
2663
  __dirname6 = dirname5(__filename5);
2554
2664
  SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
@@ -2576,7 +2686,7 @@ function runHeuristicCheck(input) {
2576
2686
  riskType: "sensitive_action",
2577
2687
  confidence: 90,
2578
2688
  source: "heuristic",
2579
- violatedLayers: ["1.1"]
2689
+ violatedLayers: ["L1.1"]
2580
2690
  };
2581
2691
  }
2582
2692
  }
@@ -2586,8 +2696,10 @@ function runHeuristicCheck(input) {
2586
2696
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2587
2697
  const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
2588
2698
  let topRiskType = "";
2699
+ const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2589
2700
  for (const rule of DANGEROUS_PATTERNS) {
2590
2701
  if (rule.pattern.test(scanCommand)) {
2702
+ if (exemptKeygen && rule.riskType === "file_access") continue;
2591
2703
  matched.push(rule.description);
2592
2704
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
2593
2705
  highestSeverity = rule.severity;
@@ -2597,8 +2709,8 @@ function runHeuristicCheck(input) {
2597
2709
  }
2598
2710
  highestRiskType = topRiskType || highestRiskType;
2599
2711
  const agentExt = mergeAgentExtensions(
2600
- getAgentExtensions(input.agentId),
2601
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2712
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
2713
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
2602
2714
  );
2603
2715
  if (agentExt?.extra_command_patterns) {
2604
2716
  for (const rule of agentExt.extra_command_patterns) {
@@ -2629,10 +2741,26 @@ function runHeuristicCheck(input) {
2629
2741
  const detailL = input.aNext.actionDetail.toLowerCase();
2630
2742
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2631
2743
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2632
- if (dangerousResult) return dangerousResult;
2633
- if (!isExcludedType) {
2634
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
2635
- if (pathResult) return pathResult;
2744
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
2745
+ const homeResult = checkHomeDeletion(actionTypeL, detailL);
2746
+ const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2747
+ if (mergeResults.length > 0) {
2748
+ let best = mergeResults[0];
2749
+ for (const r of mergeResults) {
2750
+ if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
2751
+ }
2752
+ if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
2753
+ return {
2754
+ allowed: false,
2755
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
2756
+ riskLevel: highestSeverity,
2757
+ riskType: highestRiskType,
2758
+ confidence: 95,
2759
+ source: "heuristic",
2760
+ violatedLayers: ["L1.1"]
2761
+ };
2762
+ }
2763
+ return best;
2636
2764
  }
2637
2765
  if (matched.length > 0) {
2638
2766
  return {
@@ -2642,8 +2770,7 @@ function runHeuristicCheck(input) {
2642
2770
  riskType: highestRiskType,
2643
2771
  confidence: 95,
2644
2772
  source: "heuristic",
2645
- violatedLayers: ["1.1"]
2646
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
2773
+ violatedLayers: ["L1.1"]
2647
2774
  };
2648
2775
  }
2649
2776
  const lateralResult = checkLateralMovement(detailL);
@@ -2771,6 +2898,7 @@ function isSearchActionType(actionType) {
2771
2898
  function checkSensitivePath(command, cwd, agentExt) {
2772
2899
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2773
2900
  const isWriteOpFlag = isWriteOperation("", command);
2901
+ const keygenGenerate = isSshKeygenGenerate(command);
2774
2902
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2775
2903
  const pathMatches = (text, path5) => {
2776
2904
  const parts = path5.split(/(\(\?!.*?\))/);
@@ -2783,6 +2911,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2783
2911
  };
2784
2912
  for (const sp of ROOT_SENSITIVE_PATHS) {
2785
2913
  if (pathMatches(pathScanText, sp.path.toLowerCase())) {
2914
+ if (keygenGenerate) continue;
2786
2915
  if (sp.path === "/dev/random") {
2787
2916
  if (isWriteOpFlag) {
2788
2917
  continue;
@@ -2794,7 +2923,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2794
2923
  riskType: "file_access",
2795
2924
  confidence: 90,
2796
2925
  source: "heuristic",
2797
- violatedLayers: ["1.1"]
2926
+ violatedLayers: ["L1.1"]
2798
2927
  };
2799
2928
  }
2800
2929
  if (!isWriteOpFlag && !sp.credential) {
@@ -2807,16 +2936,51 @@ function checkSensitivePath(command, cwd, agentExt) {
2807
2936
  riskType: "file_access",
2808
2937
  confidence: 90,
2809
2938
  source: "heuristic",
2810
- violatedLayers: ["1.1"],
2939
+ violatedLayers: ["L1.1"],
2811
2940
  isCredential: !!sp.credential
2812
2941
  };
2813
2942
  }
2814
2943
  }
2815
2944
  for (const sp of HOME_SENSITIVE_PATHS) {
2816
2945
  const expanded = `${expandedCwd}/${sp.path}`;
2817
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2818
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2819
- if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
2946
+ let pPath = sp.path;
2947
+ if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
2948
+ let dirPrefixMatch = null;
2949
+ if (sp.dirDelOnly) {
2950
+ const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
2951
+ dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
2952
+ }
2953
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
2954
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
2955
+ if (pathReMatch || expandedReMatch) {
2956
+ if (keygenGenerate) continue;
2957
+ if (sp.dirDelOnly) {
2958
+ if (isDeleteActionForDir(command, sp.path)) {
2959
+ return {
2960
+ allowed: false,
2961
+ reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2962
+ riskLevel: sp.riskLevel,
2963
+ riskType: "file_access",
2964
+ confidence: 90,
2965
+ source: "heuristic",
2966
+ violatedLayers: ["L1.1"],
2967
+ isCredential: !!sp.credential
2968
+ };
2969
+ }
2970
+ if (isArchiveReadOfDir(command, sp.path)) {
2971
+ return {
2972
+ allowed: false,
2973
+ reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2974
+ riskLevel: sp.riskLevel,
2975
+ riskType: "file_access",
2976
+ confidence: 90,
2977
+ source: "heuristic",
2978
+ violatedLayers: ["L1.1"],
2979
+ isCredential: !!sp.credential
2980
+ };
2981
+ }
2982
+ continue;
2983
+ }
2820
2984
  if (!isWriteOpFlag && !sp.credential) {
2821
2985
  continue;
2822
2986
  }
@@ -2827,7 +2991,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2827
2991
  riskType: "file_access",
2828
2992
  confidence: 90,
2829
2993
  source: "heuristic",
2830
- violatedLayers: ["1.1"],
2994
+ violatedLayers: ["L1.1"],
2831
2995
  isCredential: !!sp.credential
2832
2996
  };
2833
2997
  }
@@ -2850,7 +3014,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2850
3014
  riskType: "file_access",
2851
3015
  confidence: 90,
2852
3016
  source: "heuristic",
2853
- violatedLayers: ["1.1"],
3017
+ violatedLayers: ["L1.1"],
2854
3018
  isCredential: !!sp.credential
2855
3019
  };
2856
3020
  }
@@ -2867,7 +3031,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2867
3031
  riskType: "file_access",
2868
3032
  confidence: 90,
2869
3033
  source: "heuristic",
2870
- violatedLayers: ["1.1"],
3034
+ violatedLayers: ["L1.1"],
2871
3035
  isCredential: true
2872
3036
  };
2873
3037
  }
@@ -2880,18 +3044,142 @@ function checkSensitivePath(command, cwd, agentExt) {
2880
3044
  riskType: "file_access",
2881
3045
  confidence: 90,
2882
3046
  source: "heuristic",
2883
- violatedLayers: ["1.1"]
3047
+ violatedLayers: ["L1.1"]
2884
3048
  };
2885
3049
  }
2886
3050
  return null;
2887
3051
  }
3052
+ function isHomeValue(path5) {
3053
+ let p = path5.trim().toLowerCase();
3054
+ if (p.startsWith("$(") && p.endsWith(")")) {
3055
+ const inner = p.slice(2, -1).trim();
3056
+ return HOME_TARGET_RE.test(" " + inner);
3057
+ }
3058
+ if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
3059
+ return HOME_TARGET_RE.test(" " + p);
3060
+ }
3061
+ function isDeleteCommand(seg) {
3062
+ const s = seg.trimStart();
3063
+ if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
3064
+ return /删除|移除/.test(s);
3065
+ }
3066
+ function homeEscapeTarget(seg) {
3067
+ const low = seg.toLowerCase();
3068
+ const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
3069
+ let m;
3070
+ while ((m = re.exec(low)) !== null) {
3071
+ const endIdx = m.index + m[0].length;
3072
+ const tail = low.slice(endIdx);
3073
+ if (!tail) continue;
3074
+ let token = tail.trimStart().split(/\s+/)[0] || "";
3075
+ token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
3076
+ token = token.replace(/^['"]|['"]$/g, "");
3077
+ const segments = token.split("/").filter((s) => s && s !== ".");
3078
+ if (!segments.some((s) => s === "..")) continue;
3079
+ let depth = 0;
3080
+ let minDepth = 0;
3081
+ for (const s of segments) {
3082
+ depth += s === ".." ? -1 : 1;
3083
+ minDepth = Math.min(minDepth, depth);
3084
+ }
3085
+ if (minDepth < 0) return true;
3086
+ if (depth <= 0) return true;
3087
+ }
3088
+ return false;
3089
+ }
3090
+ function checkHomeDeletion(actionType, actionDetail) {
3091
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
3092
+ const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
3093
+ for (const seg of segs) {
3094
+ if (!isDeleteCommand(seg)) continue;
3095
+ if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
3096
+ return {
3097
+ allowed: false,
3098
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
3099
+ riskLevel: "high",
3100
+ riskType: "script_execution",
3101
+ confidence: 90,
3102
+ source: "heuristic",
3103
+ violatedLayers: ["L1.1"]
3104
+ };
3105
+ }
3106
+ }
3107
+ const assigned = {};
3108
+ for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
3109
+ for (const [varName, path5] of Object.entries(assigned)) {
3110
+ if (!isHomeValue(path5)) continue;
3111
+ const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
3112
+ for (const seg of segs) {
3113
+ if (!isDeleteCommand(seg)) continue;
3114
+ if (varRefRe.test(seg)) {
3115
+ return {
3116
+ allowed: false,
3117
+ reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path5}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
3118
+ riskLevel: "high",
3119
+ riskType: "script_execution",
3120
+ confidence: 90,
3121
+ source: "heuristic",
3122
+ violatedLayers: ["L1.1"]
3123
+ };
3124
+ }
3125
+ }
3126
+ }
3127
+ return null;
3128
+ }
3129
+ function escapeRegExp(s) {
3130
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3131
+ }
3132
+ function isDeleteActionForDir(command, dirPath) {
3133
+ const low = command.toLowerCase();
3134
+ const dpath = dirPath.toLowerCase();
3135
+ if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
3136
+ return true;
3137
+ }
3138
+ if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
3139
+ return true;
3140
+ }
3141
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3142
+ const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3143
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
3144
+ return true;
3145
+ }
3146
+ }
3147
+ return false;
3148
+ }
3149
+ function isArchiveReadOfDir(command, dirPath) {
3150
+ const low = command.toLowerCase();
3151
+ const dpath = dirPath.toLowerCase();
3152
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3153
+ const s = seg.trim();
3154
+ if (!s || !s.includes(dpath)) continue;
3155
+ const parts = s.split(/\s+/);
3156
+ const fw = parts[0] || "";
3157
+ if (fw === "zip") return true;
3158
+ if (fw === "tar") {
3159
+ if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
3160
+ return true;
3161
+ }
3162
+ if (fw === "cp" || fw === "rsync") {
3163
+ const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3164
+ if (segPaths.length && segPaths[0].includes(dpath)) return true;
3165
+ }
3166
+ }
3167
+ return false;
3168
+ }
3169
+ function isSshKeygenGenerate(command) {
3170
+ const low = command.toLowerCase();
3171
+ if (!low.includes("ssh-keygen")) return false;
3172
+ if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
3173
+ if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
3174
+ return true;
3175
+ }
2888
3176
  function checkDangerousPatterns(actionType, actionDetail) {
2889
3177
  if (actionType === "file_write" || actionType === "file_edit") return null;
2890
3178
  const scan = stripHeredocContent(actionDetail);
2891
3179
  const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
2892
3180
  const isTempPath = TEMP_DIRS.some(
2893
3181
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
2894
- );
3182
+ ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
2895
3183
  const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
2896
3184
  if (!isTempPath && wildcardDeleteRe.test(scan)) {
2897
3185
  return {
@@ -2901,7 +3189,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2901
3189
  riskType: "script_execution",
2902
3190
  confidence: 90,
2903
3191
  source: "heuristic",
2904
- violatedLayers: ["1.1"]
3192
+ violatedLayers: ["L1.1"]
2905
3193
  };
2906
3194
  }
2907
3195
  const findWriteMatch = actionDetail.match(
@@ -2916,10 +3204,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
2916
3204
  riskType: "script_execution",
2917
3205
  confidence: 90,
2918
3206
  source: "heuristic",
2919
- violatedLayers: ["1.1"]
3207
+ violatedLayers: ["L1.1"]
2920
3208
  };
2921
3209
  }
2922
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
3210
+ if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
2923
3211
  return {
2924
3212
  allowed: false,
2925
3213
  reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
@@ -2927,7 +3215,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2927
3215
  riskType: "file_access",
2928
3216
  confidence: 95,
2929
3217
  source: "heuristic",
2930
- violatedLayers: ["1.1"]
3218
+ violatedLayers: ["L1.1"]
2931
3219
  };
2932
3220
  }
2933
3221
  if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
@@ -2938,7 +3226,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2938
3226
  riskType: "file_access",
2939
3227
  confidence: 90,
2940
3228
  source: "heuristic",
2941
- violatedLayers: ["1.1"]
3229
+ violatedLayers: ["L1.1"]
2942
3230
  };
2943
3231
  }
2944
3232
  return null;
@@ -3029,13 +3317,13 @@ function checkLateralMovement(actionDetail) {
3029
3317
  riskType: "lateral_movement",
3030
3318
  confidence: 80,
3031
3319
  source: "heuristic",
3032
- violatedLayers: ["1.1"]
3320
+ violatedLayers: ["L1.1"]
3033
3321
  };
3034
3322
  }
3035
3323
  }
3036
3324
  return null;
3037
3325
  }
3038
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
3326
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3039
3327
  var init_heuristic = __esm({
3040
3328
  "src/engine/heuristic.ts"() {
3041
3329
  "use strict";
@@ -3043,6 +3331,7 @@ var init_heuristic = __esm({
3043
3331
  init_config();
3044
3332
  init_agents();
3045
3333
  init_scenarios();
3334
+ init_runtime_config();
3046
3335
  init_rules_loader();
3047
3336
  init_logic_rules();
3048
3337
  INJECTION_KEYWORDS = (() => {
@@ -3064,6 +3353,8 @@ var init_heuristic = __esm({
3064
3353
  })();
3065
3354
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3066
3355
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3356
+ HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
3357
+ HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
3067
3358
  }
3068
3359
  });
3069
3360
 
@@ -3286,7 +3577,7 @@ function loadTemplates() {
3286
3577
  // src/engine/script-analyzer.ts
3287
3578
  init_esm_shims();
3288
3579
  import { readFileSync as readFileSync8 } from "fs";
3289
- import { resolve as resolve6, join as join3 } from "path";
3580
+ import { resolve as resolve6, join as join4 } from "path";
3290
3581
  var SUSPICIOUS_PATTERNS = [
3291
3582
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
3292
3583
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3419,7 +3710,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3419
3710
  }
3420
3711
  function readScriptContent(scriptPath, maxLines = 500) {
3421
3712
  try {
3422
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3713
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3423
3714
  const raw = readFileSync8(expandedPath, "utf-8");
3424
3715
  const lines = raw.split("\n").slice(0, maxLines);
3425
3716
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -3474,9 +3765,10 @@ ${result.scriptContent}
3474
3765
  init_esm_shims();
3475
3766
  init_heuristic();
3476
3767
  init_logic_rules();
3768
+ init_inline_analyzer();
3477
3769
  import { statSync as statSync2, existsSync as existsSync9 } from "fs";
3478
- import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
3479
- import { homedir as homedir3 } from "os";
3770
+ import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
3771
+ import { homedir as homedir4 } from "os";
3480
3772
 
3481
3773
  // src/engine/script-engine/tracker.ts
3482
3774
  init_esm_shims();
@@ -5088,6 +5380,13 @@ function extractScriptPath2(command) {
5088
5380
  return null;
5089
5381
  }
5090
5382
  function extractScriptPathWithArgs(command) {
5383
+ const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
5384
+ if (inline) {
5385
+ const inner = inline[2];
5386
+ const realPath = extractScriptPath2(inner);
5387
+ if (realPath) return { path: realPath, args: [] };
5388
+ return { path: null, args: [] };
5389
+ }
5091
5390
  const scriptPath = extractScriptPath2(command);
5092
5391
  if (!scriptPath) return { path: null, args: [] };
5093
5392
  const idx = command.indexOf(scriptPath);
@@ -5101,11 +5400,59 @@ function extractScriptPathWithArgs(command) {
5101
5400
  }
5102
5401
  return { path: scriptPath, args };
5103
5402
  }
5403
+ function looksLikeScriptPath(tok) {
5404
+ const t = tok.trim().replace(/^['"]|['"]$/g, "");
5405
+ if (!t) return false;
5406
+ const low = t.toLowerCase();
5407
+ if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
5408
+ if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
5409
+ if (t.includes("/") && !t.startsWith("-")) return true;
5410
+ return false;
5411
+ }
5104
5412
  function extractInlineScriptPath(actionDetail) {
5413
+ const code = extractInlineCode(actionDetail);
5414
+ if (code) {
5415
+ const candidates = [];
5416
+ const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
5417
+ let m;
5418
+ while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
5419
+ const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
5420
+ while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
5421
+ const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5422
+ while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5423
+ for (const cand of candidates) {
5424
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
5425
+ for (const tok of tokens) {
5426
+ if (looksLikeScriptPath(tok)) {
5427
+ try {
5428
+ const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5429
+ if (existsSync9(resolved)) {
5430
+ const stat = statSync2(resolved);
5431
+ if (stat.isFile()) return resolved;
5432
+ }
5433
+ } catch {
5434
+ }
5435
+ }
5436
+ }
5437
+ }
5438
+ const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5439
+ while ((m = openRe.exec(code)) !== null) {
5440
+ const p = m[1];
5441
+ try {
5442
+ const resolved = resolvePath2(p);
5443
+ if (existsSync9(resolved)) {
5444
+ const stat = statSync2(resolved);
5445
+ if (stat.isFile()) return resolved;
5446
+ }
5447
+ } catch {
5448
+ }
5449
+ }
5450
+ return null;
5451
+ }
5105
5452
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5106
- let m;
5107
- while ((m = re.exec(actionDetail)) !== null) {
5108
- const p = m[1];
5453
+ let m2;
5454
+ while ((m2 = re.exec(actionDetail)) !== null) {
5455
+ const p = m2[1];
5109
5456
  let resolved;
5110
5457
  try {
5111
5458
  resolved = resolvePath2(p);
@@ -5121,7 +5468,7 @@ function extractInlineScriptPath(actionDetail) {
5121
5468
  function resolvePath2(p) {
5122
5469
  let expanded = p;
5123
5470
  if (expanded.startsWith("~/") || expanded === "~") {
5124
- expanded = join5(homedir3(), expanded.slice(1));
5471
+ expanded = join6(homedir4(), expanded.slice(1));
5125
5472
  }
5126
5473
  return resolve8(expanded);
5127
5474
  }
@@ -5202,7 +5549,7 @@ function analyzeScriptContent2(input) {
5202
5549
  isInline = true;
5203
5550
  }
5204
5551
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
5205
- resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
5552
+ resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5206
5553
  }
5207
5554
  const l1Adapter = (content) => {
5208
5555
  const virtualInput = {
@@ -5490,7 +5837,7 @@ async function analyzeWithLLM(prompt, config) {
5490
5837
  riskType: riskType || "",
5491
5838
  confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
5492
5839
  source: "llm",
5493
- violatedLayers: ["3"]
5840
+ violatedLayers: ["L3.1"]
5494
5841
  };
5495
5842
  } catch (err) {
5496
5843
  if (abortController.signal.aborted) {
@@ -5513,6 +5860,7 @@ async function analyzeWithLLM(prompt, config) {
5513
5860
 
5514
5861
  // src/engine/coordinator.ts
5515
5862
  init_config();
5863
+ init_runtime_config();
5516
5864
  init_agents();
5517
5865
  init_scenarios();
5518
5866
 
@@ -5522,9 +5870,9 @@ init_esm_shims();
5522
5870
  // src/brain/rule-store.ts
5523
5871
  init_esm_shims();
5524
5872
  import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5525
- import { join as join6 } from "path";
5526
- import { homedir as homedir4 } from "os";
5527
- var DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
5873
+ import { join as join7 } from "path";
5874
+ import { homedir as homedir5 } from "os";
5875
+ var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
5528
5876
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5529
5877
  var STATUS_DRAFT = "draft";
5530
5878
  var STATUS_APPROVED = "approved";
@@ -6086,6 +6434,15 @@ function decodeBase64Literals(text) {
6086
6434
  }
6087
6435
  return out;
6088
6436
  }
6437
+ var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
6438
+ var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
6439
+ function stripStringLiterals(text) {
6440
+ const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
6441
+ if (!m) return text;
6442
+ const cmd = m[1].split("/").pop().toLowerCase();
6443
+ if (!OUTPUT_CMDS.has(cmd)) return text;
6444
+ return text.replace(QUOTED_STR_RE, "");
6445
+ }
6089
6446
  function decodeUrlAndNumeric(text) {
6090
6447
  const out = [];
6091
6448
  let s = text;
@@ -6119,7 +6476,10 @@ function decodeUrlAndNumeric(text) {
6119
6476
  return out;
6120
6477
  }
6121
6478
  function allCandidates(detail, max = 8) {
6122
- const cands = [detail];
6479
+ const shell = stripStringLiterals(detail);
6480
+ const cands = [shell];
6481
+ cands.push(...decodeBase64Literals(shell));
6482
+ cands.push(...decodeUrlAndNumeric(shell));
6123
6483
  cands.push(...decodeBase64Literals(detail));
6124
6484
  cands.push(...decodeUrlAndNumeric(detail));
6125
6485
  const seen = /* @__PURE__ */ new Set();
@@ -6144,6 +6504,128 @@ function matchStageAny(detail, stages) {
6144
6504
  return "";
6145
6505
  }
6146
6506
 
6507
+ // src/engine/formal-engine/ltl.ts
6508
+ init_esm_shims();
6509
+ init_rules_loader();
6510
+
6511
+ // src/engine/formal-engine/ir.ts
6512
+ init_esm_shims();
6513
+ function allNodes(mg) {
6514
+ const out = [];
6515
+ for (const s of mg.sessions) out.push(...s.nodes);
6516
+ return out;
6517
+ }
6518
+ function allEdges(mg) {
6519
+ const out = [];
6520
+ for (const s of mg.sessions) out.push(...s.edges);
6521
+ out.push(...mg.crossEdges);
6522
+ return out;
6523
+ }
6524
+ function emptyVerificationResult() {
6525
+ return { verified: true, violations: [] };
6526
+ }
6527
+
6528
+ // src/engine/formal-engine/ltl.ts
6529
+ var AP_RE = null;
6530
+ function compileAps(apPatterns) {
6531
+ if (AP_RE) return;
6532
+ AP_RE = {};
6533
+ for (const [name, pats] of Object.entries(apPatterns || {})) {
6534
+ AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
6535
+ }
6536
+ }
6537
+ function labelEvent(actionDetail) {
6538
+ if (!AP_RE) compileAps(loadMC().ap_patterns || {});
6539
+ const candidates = allCandidates(actionDetail);
6540
+ const hits = /* @__PURE__ */ new Set();
6541
+ for (const [name, pats] of Object.entries(AP_RE || {})) {
6542
+ for (const c of candidates) {
6543
+ const m = pats.some((p) => p.test(c));
6544
+ if (m) {
6545
+ hits.add(name);
6546
+ break;
6547
+ }
6548
+ }
6549
+ }
6550
+ return hits;
6551
+ }
6552
+ function evaluateLTL(formula, trace, pos) {
6553
+ const op = formula[0];
6554
+ switch (op) {
6555
+ case "ap":
6556
+ return trace[pos].has(formula[1]);
6557
+ case "!":
6558
+ return !evaluateLTL(formula[1], trace, pos);
6559
+ case "&":
6560
+ return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
6561
+ case "|":
6562
+ return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6563
+ case "->":
6564
+ return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6565
+ case "X":
6566
+ return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
6567
+ case "G": {
6568
+ for (let j = pos; j < trace.length; j++) {
6569
+ if (!evaluateLTL(formula[1], trace, j)) return false;
6570
+ }
6571
+ return true;
6572
+ }
6573
+ case "F": {
6574
+ for (let j = pos; j < trace.length; j++) {
6575
+ if (evaluateLTL(formula[1], trace, j)) return true;
6576
+ }
6577
+ return false;
6578
+ }
6579
+ case "U": {
6580
+ for (let j = pos; j < trace.length; j++) {
6581
+ if (evaluateLTL(formula[2], trace, j)) {
6582
+ for (let k = pos; k < j; k++) {
6583
+ if (!evaluateLTL(formula[1], trace, k)) return false;
6584
+ }
6585
+ return true;
6586
+ }
6587
+ }
6588
+ return false;
6589
+ }
6590
+ default:
6591
+ return false;
6592
+ }
6593
+ }
6594
+ function ruleViolates(rule, traceWith, witnessMode) {
6595
+ const formula = rule?.formula;
6596
+ if (!Array.isArray(formula) || formula.length === 0) return false;
6597
+ const satWith = evaluateLTL(formula, traceWith, 0);
6598
+ if (!satWith) return false;
6599
+ if (witnessMode && traceWith.length > 1) {
6600
+ const traceWithout = traceWith.slice(0, -1);
6601
+ if (evaluateLTL(formula, traceWithout, 0)) return false;
6602
+ }
6603
+ return true;
6604
+ }
6605
+ function checkMCRules(graph) {
6606
+ const violations = [];
6607
+ const nodes = allNodes(graph);
6608
+ if (nodes.length === 0) return violations;
6609
+ const mc = loadMC();
6610
+ const rules = mc.rules || [];
6611
+ if (rules.length === 0) return violations;
6612
+ const trace = nodes.map((n) => labelEvent(n.actionDetail));
6613
+ const witnessMode = graph.witnessIdx !== void 0;
6614
+ for (const rule of rules) {
6615
+ if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
6616
+ if (ruleViolates(rule, trace, witnessMode)) {
6617
+ violations.push({
6618
+ propertyId: String(rule.id || "mc-ltl"),
6619
+ reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
6620
+ riskLevel: String(rule.risk_level || "high"),
6621
+ path: nodes.filter((n) => labelEvent(n.actionDetail).size > 0).slice(-3).map((n) => ({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }))
6622
+ });
6623
+ if (witnessMode) return violations;
6624
+ }
6625
+ }
6626
+ return violations;
6627
+ }
6628
+
6147
6629
  // src/engine/formal-engine/builder.ts
6148
6630
  var COMBO_STAGES2 = null;
6149
6631
  function loadStages() {
@@ -6172,6 +6654,26 @@ function extractResources(actionDetail) {
6172
6654
  function markStage(actionDetail) {
6173
6655
  return matchStageAny(actionDetail, loadStages());
6174
6656
  }
6657
+ function markIntegrity(actionDetail) {
6658
+ const aps = labelEvent(actionDetail);
6659
+ if (aps.has("download_file") || aps.has("prompt_injection")) {
6660
+ return { integrity: "low", isLowSource: true };
6661
+ }
6662
+ return { integrity: "medium", isLowSource: false };
6663
+ }
6664
+ function initNode(sessionId, seq, actionType, actionDetail) {
6665
+ const { integrity, isLowSource } = markIntegrity(actionDetail);
6666
+ return {
6667
+ sessionId,
6668
+ seq,
6669
+ actionType,
6670
+ actionDetail,
6671
+ stage: markStage(actionDetail),
6672
+ resources: extractResources(actionDetail),
6673
+ integrity,
6674
+ isLowSource
6675
+ };
6676
+ }
6175
6677
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6176
6678
  const nodes = [];
6177
6679
  const edges = [];
@@ -6185,17 +6687,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6185
6687
  details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6186
6688
  }
6187
6689
  details.forEach((d, seq) => {
6188
- const stage = markStage(d.actionDetail);
6189
- const node = {
6190
- sessionId,
6191
- seq,
6192
- actionType: d.actionType,
6193
- actionDetail: d.actionDetail,
6194
- stage,
6195
- resources: extractResources(d.actionDetail)
6196
- };
6690
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
6197
6691
  nodes.push(node);
6198
- if (stage) stageFlags.add(stage);
6692
+ if (node.stage) stageFlags.add(node.stage);
6199
6693
  });
6200
6694
  for (let i = 0; i < nodes.length - 1; i++) {
6201
6695
  edges.push({ src: i, dst: i + 1, kind: "next" });
@@ -6215,15 +6709,12 @@ function buildMultiSessionGraph(sessions, aNext) {
6215
6709
  if (aNext) {
6216
6710
  const last = mg.sessions[mg.sessions.length - 1];
6217
6711
  if (last.sessionId === "current") {
6218
- const stage = markStage(String(aNext.actionDetail || ""));
6219
- last.nodes.push({
6220
- sessionId: last.sessionId,
6221
- seq: last.nodes.length,
6222
- actionType: String(aNext.actionType || ""),
6223
- actionDetail: String(aNext.actionDetail || ""),
6224
- stage,
6225
- resources: extractResources(String(aNext.actionDetail || ""))
6226
- });
6712
+ last.nodes.push(initNode(
6713
+ last.sessionId,
6714
+ last.nodes.length,
6715
+ String(aNext.actionType || ""),
6716
+ String(aNext.actionDetail || "")
6717
+ ));
6227
6718
  if (last.nodes.length > 1) {
6228
6719
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6229
6720
  }
@@ -6237,11 +6728,25 @@ function buildMultiSessionGraph(sessions, aNext) {
6237
6728
  function addCrossDataEdges(mg) {
6238
6729
  const nodes = allNodesFlat(mg);
6239
6730
  if (nodes.length < 2) return;
6240
- for (let i = 0; i < nodes.length; i++) {
6241
- for (let j = i + 1; j < nodes.length; j++) {
6242
- if (nodes[i].sessionId === nodes[j].sessionId) continue;
6243
- const shared = intersect(nodes[i].resources, nodes[j].resources);
6244
- if (shared.size > 0) {
6731
+ const resIndex = /* @__PURE__ */ new Map();
6732
+ nodes.forEach((n, i) => {
6733
+ for (const r of n.resources) {
6734
+ const list = resIndex.get(r);
6735
+ if (list) list.push(i);
6736
+ else resIndex.set(r, [i]);
6737
+ }
6738
+ });
6739
+ const seen = /* @__PURE__ */ new Set();
6740
+ for (const idxs of resIndex.values()) {
6741
+ if (idxs.length < 2) continue;
6742
+ for (let a = 0; a < idxs.length; a++) {
6743
+ const i = idxs[a];
6744
+ for (let b = a + 1; b < idxs.length; b++) {
6745
+ const j = idxs[b];
6746
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6747
+ const key = `${i}:${j}`;
6748
+ if (seen.has(key)) continue;
6749
+ seen.add(key);
6245
6750
  mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6246
6751
  }
6247
6752
  }
@@ -6252,28 +6757,6 @@ function allNodesFlat(mg) {
6252
6757
  for (const s of mg.sessions) out.push(...s.nodes);
6253
6758
  return out;
6254
6759
  }
6255
- function intersect(a, b) {
6256
- const out = /* @__PURE__ */ new Set();
6257
- for (const v of a) if (b.has(v)) out.add(v);
6258
- return out;
6259
- }
6260
-
6261
- // src/engine/formal-engine/ir.ts
6262
- init_esm_shims();
6263
- function allNodes(mg) {
6264
- const out = [];
6265
- for (const s of mg.sessions) out.push(...s.nodes);
6266
- return out;
6267
- }
6268
- function allEdges(mg) {
6269
- const out = [];
6270
- for (const s of mg.sessions) out.push(...s.edges);
6271
- out.push(...mg.crossEdges);
6272
- return out;
6273
- }
6274
- function emptyVerificationResult() {
6275
- return { verified: true, violations: [] };
6276
- }
6277
6760
 
6278
6761
  // src/engine/formal-engine/properties.ts
6279
6762
  init_esm_shims();
@@ -6296,27 +6779,50 @@ function checkCrossSessionChain(graph) {
6296
6779
  const violations = [];
6297
6780
  const nodes = allNodes(graph);
6298
6781
  if (nodes.length === 0) return violations;
6782
+ const witness = graph.witnessIdx;
6299
6783
  for (const chain of loadCrossSessionChains()) {
6300
6784
  const stages = chain.stages;
6301
6785
  if (!stages.length) continue;
6302
- let matched = 0;
6303
- let stageIdx = 0;
6304
- const path5 = [];
6305
- for (const n of nodes) {
6306
- if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6307
- matched += 1;
6308
- path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6309
- stageIdx += 1;
6310
- if (matched >= stages.length) break;
6786
+ if (witness !== void 0) {
6787
+ if (nodes[witness].stage !== stages[stages.length - 1]) continue;
6788
+ let matched = 0;
6789
+ const path5 = [];
6790
+ for (let k = 0; k < witness; k++) {
6791
+ const n = nodes[k];
6792
+ if (matched < stages.length - 1 && n.stage === stages[matched]) {
6793
+ matched += 1;
6794
+ path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6795
+ }
6311
6796
  }
6312
- }
6313
- if (matched >= stages.length) {
6797
+ if (matched < stages.length - 1) continue;
6798
+ path5.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
6314
6799
  violations.push({
6315
6800
  propertyId: "cross-" + chain.id,
6316
6801
  reason: chain.reason,
6317
6802
  riskLevel: chain.riskLevel,
6318
6803
  path: path5
6319
6804
  });
6805
+ return violations;
6806
+ } else {
6807
+ let matched = 0;
6808
+ let stageIdx = 0;
6809
+ const path5 = [];
6810
+ for (const n of nodes) {
6811
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6812
+ matched += 1;
6813
+ path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6814
+ stageIdx += 1;
6815
+ if (matched >= stages.length) break;
6816
+ }
6817
+ }
6818
+ if (matched >= stages.length) {
6819
+ violations.push({
6820
+ propertyId: "cross-" + chain.id,
6821
+ reason: chain.reason,
6822
+ riskLevel: chain.riskLevel,
6823
+ path: path5
6824
+ });
6825
+ }
6320
6826
  }
6321
6827
  }
6322
6828
  return violations;
@@ -6351,14 +6857,18 @@ function checkSensitiveDataFlow(graph) {
6351
6857
  }
6352
6858
  }
6353
6859
  }
6354
- for (const [i, n] of nodes.entries()) {
6860
+ const witness = graph.witnessIdx;
6861
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6862
+ for (const i of candidates) {
6863
+ const n = nodes[i];
6355
6864
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6356
6865
  violations.push({
6357
- propertyId: "sensitive-data-exfil",
6866
+ propertyId: "sensitive-data-flow",
6358
6867
  reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
6359
6868
  riskLevel: "critical",
6360
6869
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6361
6870
  });
6871
+ if (witness !== void 0) return violations;
6362
6872
  }
6363
6873
  }
6364
6874
  return violations;
@@ -6389,12 +6899,38 @@ function checkNeverExfilAfterPrivesc(graph) {
6389
6899
  const violations = [];
6390
6900
  const nodes = allNodes(graph);
6391
6901
  if (nodes.length < 2) return violations;
6902
+ const witness = graph.witnessIdx;
6903
+ if (witness !== void 0) {
6904
+ const evIdx = [];
6905
+ nodes.forEach((n, i) => {
6906
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6907
+ });
6908
+ if (evIdx.length === 0) return violations;
6909
+ const firstEv = Math.min(...evIdx);
6910
+ let pre = null;
6911
+ for (let i = 0; i < firstEv; i++) {
6912
+ if (isPrivescNode(nodes[i])) pre = nodes[i];
6913
+ }
6914
+ if (pre) {
6915
+ const post = nodes[firstEv];
6916
+ return [{
6917
+ propertyId: "no-exfil-after-privesc",
6918
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6919
+ riskLevel: "critical",
6920
+ path: [
6921
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6922
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6923
+ ]
6924
+ }];
6925
+ }
6926
+ return violations;
6927
+ }
6392
6928
  for (let i = 0; i < nodes.length; i++) {
6393
6929
  if (!isPrivescNode(nodes[i])) continue;
6394
6930
  for (let j = i + 1; j < nodes.length; j++) {
6395
6931
  if (isExfilNode(nodes[j])) {
6396
6932
  violations.push({
6397
- propertyId: "no-privesc-then-exfil",
6933
+ propertyId: "no-exfil-after-privesc",
6398
6934
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6399
6935
  riskLevel: "critical",
6400
6936
  path: [
@@ -6412,12 +6948,38 @@ function checkNeverExfilAfterCred(graph) {
6412
6948
  const violations = [];
6413
6949
  const nodes = allNodes(graph);
6414
6950
  if (nodes.length < 2) return violations;
6951
+ const witness = graph.witnessIdx;
6952
+ if (witness !== void 0) {
6953
+ const evIdx = [];
6954
+ nodes.forEach((n, i) => {
6955
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6956
+ });
6957
+ if (evIdx.length === 0) return violations;
6958
+ const firstEv = Math.min(...evIdx);
6959
+ let pre = null;
6960
+ for (let i = 0; i < firstEv; i++) {
6961
+ if (isCredNode(nodes[i])) pre = nodes[i];
6962
+ }
6963
+ if (pre) {
6964
+ const post = nodes[firstEv];
6965
+ return [{
6966
+ propertyId: "no-exfil-after-credential",
6967
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6968
+ riskLevel: "critical",
6969
+ path: [
6970
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6971
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6972
+ ]
6973
+ }];
6974
+ }
6975
+ return violations;
6976
+ }
6415
6977
  for (let i = 0; i < nodes.length; i++) {
6416
6978
  if (!isCredNode(nodes[i])) continue;
6417
6979
  for (let j = i + 1; j < nodes.length; j++) {
6418
6980
  if (isExfilNode(nodes[j])) {
6419
6981
  violations.push({
6420
- propertyId: "no-cred-then-exfil",
6982
+ propertyId: "no-exfil-after-credential",
6421
6983
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6422
6984
  riskLevel: "critical",
6423
6985
  path: [
@@ -6431,11 +6993,172 @@ function checkNeverExfilAfterCred(graph) {
6431
6993
  }
6432
6994
  return violations;
6433
6995
  }
6996
+ var TOOL_REGISTRY = null;
6997
+ function getToolRegistry() {
6998
+ if (TOOL_REGISTRY) return TOOL_REGISTRY;
6999
+ TOOL_REGISTRY = loadToolRegistry().tools || {};
7000
+ return TOOL_REGISTRY;
7001
+ }
7002
+ function lookupToolClass(actionType) {
7003
+ const entry = getToolRegistry()[actionType] || {};
7004
+ return entry.actionClass;
7005
+ }
7006
+ var COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
7007
+ var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
7008
+ function isComputeNode(n) {
7009
+ if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
7010
+ const cls = lookupToolClass(n.actionType);
7011
+ if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
7012
+ return COMPUTE_RE.test(n.actionDetail);
7013
+ }
7014
+ function checkLowIntegrityCommandExec(graph) {
7015
+ const violations = [];
7016
+ const nodes = allNodes(graph);
7017
+ if (nodes.length === 0) return violations;
7018
+ const witness = graph.witnessIdx;
7019
+ const lowResources = /* @__PURE__ */ new Set();
7020
+ const lowAt = /* @__PURE__ */ new Map();
7021
+ nodes.forEach((n, i) => {
7022
+ if (n.isLowSource) {
7023
+ lowAt.set(i, n.actionDetail.slice(0, 60));
7024
+ for (const r of n.resources) lowResources.add(r);
7025
+ }
7026
+ });
7027
+ let changed = true;
7028
+ while (changed) {
7029
+ changed = false;
7030
+ for (let i = 0; i < nodes.length; i++) {
7031
+ if (lowAt.has(i)) continue;
7032
+ let shared = "";
7033
+ for (const r of nodes[i].resources) {
7034
+ if (lowResources.has(r)) {
7035
+ shared = r;
7036
+ break;
7037
+ }
7038
+ }
7039
+ if (shared) {
7040
+ lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
7041
+ for (const r of nodes[i].resources) lowResources.add(r);
7042
+ changed = true;
7043
+ }
7044
+ }
7045
+ }
7046
+ if (lowAt.size === 0) return violations;
7047
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7048
+ for (const i of candidates) {
7049
+ const n = nodes[i];
7050
+ if (lowAt.has(i) && isComputeNode(n)) {
7051
+ violations.push({
7052
+ propertyId: "low-integrity-command-exec",
7053
+ reason: `\u4F4E\u5B8C\u6574\u6027\u6570\u636E\u63A7\u5236\u547D\u4EE4\u6267\u884C\uFF1A${lowAt.get(i)} \u2192 ${n.sessionId}#${n.seq} \u6267\u884C\u4E0D\u53EF\u4FE1\u5185\u5BB9`,
7054
+ riskLevel: "critical",
7055
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7056
+ });
7057
+ if (witness !== void 0) return violations;
7058
+ }
7059
+ }
7060
+ return violations;
7061
+ }
7062
+ var DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
7063
+ function checkCrossSessionReachability(graph) {
7064
+ const violations = [];
7065
+ const nodes = allNodes(graph);
7066
+ if (nodes.length === 0) return violations;
7067
+ const witness = graph.witnessIdx;
7068
+ const sources = /* @__PURE__ */ new Set();
7069
+ nodes.forEach((n, i) => {
7070
+ if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
7071
+ });
7072
+ if (sources.size === 0) return violations;
7073
+ const reached = new Set(sources);
7074
+ let changed = true;
7075
+ while (changed) {
7076
+ changed = false;
7077
+ for (const e of allEdges(graph)) {
7078
+ if (reached.has(e.src) && !reached.has(e.dst)) {
7079
+ const srcRes = nodes[e.src].resources;
7080
+ const dstRes = nodes[e.dst].resources;
7081
+ for (const r of srcRes) {
7082
+ if (dstRes.has(r)) {
7083
+ reached.add(e.dst);
7084
+ changed = true;
7085
+ break;
7086
+ }
7087
+ }
7088
+ }
7089
+ }
7090
+ }
7091
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7092
+ for (const i of candidates) {
7093
+ const n = nodes[i];
7094
+ if (sources.has(i)) continue;
7095
+ if (reached.has(i)) {
7096
+ const srcIdx = Math.min(...sources);
7097
+ const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
7098
+ violations.push({
7099
+ propertyId: "low-integrity-reachability",
7100
+ reason: `\u4F4E\u5B8C\u6574\u6027\u6E90\u7ECF\u8DE8 session \u6570\u636E\u94FE\u591A\u8DF3\u5230\u8FBE\u5F53\u524D\u6267\u884C\uFF08\u6E90: ${srcDesc} \u2192 ${n.sessionId}#${n.seq}\uFF09`,
7101
+ riskLevel: "high",
7102
+ path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
7103
+ });
7104
+ if (witness !== void 0) return violations;
7105
+ }
7106
+ }
7107
+ return violations;
7108
+ }
7109
+ function checkSensitiveDataAggregation(graph) {
7110
+ const violations = [];
7111
+ const nodes = allNodes(graph);
7112
+ if (nodes.length < 2) return violations;
7113
+ const witness = graph.witnessIdx;
7114
+ const resIndex = /* @__PURE__ */ new Map();
7115
+ nodes.forEach((n, i) => {
7116
+ for (const r of n.resources) {
7117
+ const list = resIndex.get(r);
7118
+ if (list) list.push(i);
7119
+ else resIndex.set(r, [i]);
7120
+ }
7121
+ });
7122
+ const sourcesFor = (exfilIdx) => {
7123
+ const srcs = /* @__PURE__ */ new Set();
7124
+ const n = nodes[exfilIdx];
7125
+ for (const r of n.resources) {
7126
+ for (const j of resIndex.get(r) || []) {
7127
+ if (j === exfilIdx) continue;
7128
+ srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
7129
+ }
7130
+ }
7131
+ return srcs;
7132
+ };
7133
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7134
+ for (const i of candidates) {
7135
+ const n = nodes[i];
7136
+ if (!isExfilNode(n)) continue;
7137
+ const srcs = sourcesFor(i);
7138
+ if (srcs.size >= 2) {
7139
+ violations.push({
7140
+ propertyId: "sensitive-data-aggregation",
7141
+ reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
7142
+ riskLevel: "critical",
7143
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7144
+ });
7145
+ if (witness !== void 0) return violations;
7146
+ }
7147
+ }
7148
+ return violations;
7149
+ }
6434
7150
  var ALL_PROPERTIES = [
6435
- { id: "cross-session-chain", check: checkCrossSessionChain },
6436
- { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6437
- { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6438
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
7151
+ { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
7152
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
7153
+ { id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
7154
+ { id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
7155
+ { id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
7156
+ // L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
7157
+ { id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
7158
+ // P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
7159
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
7160
+ // P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
7161
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
6439
7162
  ];
6440
7163
 
6441
7164
  // src/engine/formal-engine/engine.ts
@@ -6467,6 +7190,9 @@ var FormalEngine = class {
6467
7190
  });
6468
7191
  }
6469
7192
  const graph = buildMultiSessionGraph(all, aNext);
7193
+ if (aNext) {
7194
+ graph.witnessIdx = allNodes(graph).length - 1;
7195
+ }
6470
7196
  const result = emptyVerificationResult();
6471
7197
  for (const prop of this.properties) {
6472
7198
  let violations;
@@ -6607,6 +7333,7 @@ function _resetBrainForTest() {
6607
7333
  }
6608
7334
  var formalEngine = new FormalEngine();
6609
7335
  async function runAnalysis(input) {
7336
+ const observedFormalLayers = [];
6610
7337
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
6611
7338
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
6612
7339
  const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
@@ -6632,7 +7359,7 @@ async function runAnalysis(input) {
6632
7359
  riskType: node.l1RiskType,
6633
7360
  confidence: 90,
6634
7361
  source: "heuristic_script_scan",
6635
- violatedLayers: ["1.1-script"]
7362
+ violatedLayers: ["L1.2"]
6636
7363
  };
6637
7364
  }
6638
7365
  }
@@ -6661,8 +7388,8 @@ async function runAnalysis(input) {
6661
7388
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
6662
7389
  const lowerDetail = rawDetail.toLowerCase();
6663
7390
  const agentExt = mergeAgentExtensions(
6664
- getAgentExtensions(input.agentId),
6665
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
7391
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
7392
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
6666
7393
  );
6667
7394
  if (FULLY_SAFE_TOOLS.has(actionType)) {
6668
7395
  return {
@@ -6750,7 +7477,7 @@ async function runAnalysis(input) {
6750
7477
  riskType: inlineJudgment.riskType,
6751
7478
  confidence: 90,
6752
7479
  source: "logic_rule",
6753
- violatedLayers: ["1.2"]
7480
+ violatedLayers: ["L2.1"]
6754
7481
  };
6755
7482
  }
6756
7483
  return {
@@ -6779,15 +7506,22 @@ async function runAnalysis(input) {
6779
7506
  const formalResult = formalEngine.verify(formalSessions, input.aNext);
6780
7507
  if (!formalResult.verified) {
6781
7508
  const v = formalResult.violations[0];
6782
- return {
6783
- allowed: false,
6784
- reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
6785
- riskLevel: v ? v.riskLevel : "high",
6786
- riskType: "formal_verification",
6787
- confidence: 90,
6788
- source: "formal_engine",
6789
- violatedLayers: ["2.4"]
6790
- };
7509
+ if (isFormalObserveMode()) {
7510
+ console.warn(
7511
+ `[AgentMoss] \u5F62\u5F0F\u5316\u9A8C\u8BC1 observe\uFF08\u53EA\u8BB0\u5F55\u4E0D\u62E6\u622A\uFF09: property=${v?.propertyId ?? "?"} risk=${v?.riskLevel ?? "?"} path_len=${v?.path?.length ?? 0} reason=${(v?.reason ?? "?").slice(0, 200)}`
7512
+ );
7513
+ observedFormalLayers.push("L2.2");
7514
+ } else {
7515
+ return {
7516
+ allowed: false,
7517
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
7518
+ riskLevel: v ? v.riskLevel : "high",
7519
+ riskType: "formal_verification",
7520
+ confidence: 90,
7521
+ source: "formal_engine",
7522
+ violatedLayers: ["L2.2"]
7523
+ };
7524
+ }
6791
7525
  }
6792
7526
  }
6793
7527
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -6802,7 +7536,7 @@ async function runAnalysis(input) {
6802
7536
  riskType: node.l2RiskType,
6803
7537
  confidence: 85,
6804
7538
  source: "logic_rule_script_scan",
6805
- violatedLayers: ["1.2-script"]
7539
+ violatedLayers: ["L2.3"]
6806
7540
  };
6807
7541
  }
6808
7542
  }
@@ -6834,7 +7568,7 @@ async function runAnalysis(input) {
6834
7568
  riskType: "",
6835
7569
  confidence: 85,
6836
7570
  source: "heuristic_and_logic_only",
6837
- violatedLayers: []
7571
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
6838
7572
  };
6839
7573
  }
6840
7574
  let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
@@ -6879,7 +7613,7 @@ async function runAnalysis(input) {
6879
7613
  riskType: "",
6880
7614
  confidence: 50,
6881
7615
  source: "llm_failure_fallback",
6882
- violatedLayers: []
7616
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
6883
7617
  };
6884
7618
  }
6885
7619
  return {
@@ -6889,7 +7623,7 @@ async function runAnalysis(input) {
6889
7623
  riskType: "",
6890
7624
  confidence: 60,
6891
7625
  source: "llm_failure_fallback",
6892
- violatedLayers: ["3"]
7626
+ violatedLayers: ["L3.1"]
6893
7627
  };
6894
7628
  }
6895
7629
  if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
@@ -6906,6 +7640,9 @@ async function runAnalysis(input) {
6906
7640
  } catch {
6907
7641
  }
6908
7642
  }
7643
+ if (observedFormalLayers.length > 0 && llmResult.allowed) {
7644
+ llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
7645
+ }
6909
7646
  return llmResult;
6910
7647
  }
6911
7648
  export {
@@ -6913,4 +7650,17 @@ export {
6913
7650
  _resetBrainForTest,
6914
7651
  runAnalysis
6915
7652
  };
7653
+ //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
7654
+ //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
7655
+ //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
7656
+ //!
7657
+ //! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
7658
+ //! P1 cross-session-chain 跨会话组合链
7659
+ //! P2 sensitive-data-flow 敏感数据传播
7660
+ //! P3 low-integrity-reachability 低完整性数据流可达
7661
+ //! P4 no-exfil-after-privesc 提权后禁外传
7662
+ //! P5 no-exfil-after-credential 凭据后禁外传
7663
+ //! P6 mc-ltl-rules LTL 模型检测
7664
+ //! P7 low-integrity-command-exec 低完整性命令执行
7665
+ //! P8 sensitive-data-aggregation 敏感数据聚合外传
6916
7666
  //# sourceMappingURL=coordinator.js.map