@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.
package/dist/server.js CHANGED
@@ -137,6 +137,7 @@ __export(config_exports, {
137
137
  getRetryConfig: () => getRetryConfig,
138
138
  getTimeoutConfig: () => getTimeoutConfig,
139
139
  isBrainEnabled: () => isBrainEnabled,
140
+ isFormalObserveMode: () => isFormalObserveMode,
140
141
  isHeuristicEnabled: () => isHeuristicEnabled,
141
142
  isLLMAvailable: () => isLLMAvailable,
142
143
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -252,6 +253,7 @@ function getCustomRules() {
252
253
  }
253
254
  function isLLMAvailable() {
254
255
  if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
256
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
255
257
  return llmConfig.apiKey.length > 0;
256
258
  }
257
259
  async function probeLLMConnection(timeoutMs = 8e3) {
@@ -316,13 +318,26 @@ function getProviderHeaders(provider) {
316
318
  return PROVIDER_HEADERS[provider] || {};
317
319
  }
318
320
  function isHeuristicEnabled() {
319
- return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
321
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
322
+ return layerEnabledOrDefault("L1_heuristic");
323
+ }
324
+ function layerEnabledOrDefault(key) {
325
+ try {
326
+ const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
327
+ return isLayerEnabled2(key);
328
+ } catch {
329
+ return true;
330
+ }
320
331
  }
321
332
  function isBrainEnabled() {
322
333
  return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
323
334
  }
324
335
  function isLogicRulesEnabled() {
325
- return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
336
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
337
+ return layerEnabledOrDefault("L2_logic_rules");
338
+ }
339
+ function isFormalObserveMode() {
340
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
326
341
  }
327
342
  var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
328
343
  var init_config = __esm({
@@ -580,8 +595,12 @@ var init_inline_analyzer = __esm({
580
595
 
581
596
  // src/infra/rules-loader.ts
582
597
  import { readFileSync as readFileSync2, existsSync } from "fs";
583
- import { resolve, dirname } from "path";
598
+ import { resolve, dirname, join as join2 } from "path";
584
599
  import { fileURLToPath as fileURLToPath3 } from "url";
600
+ import { homedir as homedir2 } from "os";
601
+ function dataRootDir() {
602
+ return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
603
+ }
585
604
  function findRulesFile(name) {
586
605
  for (const dir of RULES_DIR_CANDS) {
587
606
  const p = resolve(dir, name);
@@ -607,6 +626,24 @@ function loadL2() {
607
626
  function loadL3() {
608
627
  return loadJson("l3_rules.json");
609
628
  }
629
+ function loadMC() {
630
+ const mc = loadJson("mc_rules.json");
631
+ const rules = mc.rules || [];
632
+ try {
633
+ const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
634
+ if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
635
+ const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
636
+ const enabledById = {};
637
+ for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
638
+ for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
639
+ }
640
+ } catch {
641
+ }
642
+ return mc;
643
+ }
644
+ function loadToolRegistry() {
645
+ return loadJson("tool_registry.json");
646
+ }
610
647
  var __filename2, __dirname2, RULES_DIR_CANDS;
611
648
  var init_rules_loader = __esm({
612
649
  "src/infra/rules-loader.ts"() {
@@ -616,6 +653,10 @@ var init_rules_loader = __esm({
616
653
  __dirname2 = dirname(__filename2);
617
654
  RULES_DIR_CANDS = (() => {
618
655
  const cands = [
656
+ // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
657
+ // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
658
+ // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
659
+ resolve(dataRootDir(), "rules"),
619
660
  // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
620
661
  resolve(__dirname2, "../rules"),
621
662
  resolve(__dirname2, "rules"),
@@ -639,7 +680,8 @@ function _loadSensitivePathScope(scope) {
639
680
  path: sp.path,
640
681
  riskLevel: sp.risk_level,
641
682
  desc: sp.desc,
642
- credential: sp.deny_mode === "deny_both" ? true : void 0
683
+ credential: sp.deny_mode === "deny_both" ? true : void 0,
684
+ dirDelOnly: sp.dir_del_only === true ? true : void 0
643
685
  }));
644
686
  }
645
687
  function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
@@ -737,7 +779,7 @@ function runLogicCheck(input) {
737
779
  riskType: "intent_deviation",
738
780
  confidence: 85,
739
781
  source: "logic_rule",
740
- violatedLayers: ["2"]
782
+ violatedLayers: ["L2.1"]
741
783
  };
742
784
  }
743
785
  }
@@ -761,7 +803,7 @@ function runLogicCheck(input) {
761
803
  riskType: "privilege_escalation",
762
804
  confidence: 85,
763
805
  source: "logic_rule",
764
- violatedLayers: ["2"]
806
+ violatedLayers: ["L2.1"]
765
807
  };
766
808
  }
767
809
  }
@@ -813,7 +855,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
813
855
  riskType: "combo_chain",
814
856
  confidence: 85,
815
857
  source: "logic_rule",
816
- violatedLayers: ["2"]
858
+ violatedLayers: ["L2.1"]
817
859
  };
818
860
  }
819
861
  }
@@ -938,7 +980,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
938
980
  riskType: "read_before_write",
939
981
  confidence: 85,
940
982
  source: "logic_rule",
941
- violatedLayers: ["2"]
983
+ violatedLayers: ["L2.1"]
942
984
  };
943
985
  }
944
986
  return null;
@@ -958,7 +1000,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
958
1000
  riskType: "consent_missing",
959
1001
  confidence: 85,
960
1002
  source: "logic_rule",
961
- violatedLayers: ["2"]
1003
+ violatedLayers: ["L2.1"]
962
1004
  };
963
1005
  }
964
1006
  function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
@@ -976,7 +1018,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
976
1018
  riskType: "consent_missing",
977
1019
  confidence: 85,
978
1020
  source: "logic_rule",
979
- violatedLayers: ["2"]
1021
+ violatedLayers: ["L2.1"]
980
1022
  };
981
1023
  }
982
1024
  function containsPrivilegeEscalation(actionDetail) {
@@ -1036,7 +1078,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1036
1078
  riskType: "file_access",
1037
1079
  confidence: 95,
1038
1080
  source: "logic_rule",
1039
- violatedLayers: ["2"],
1081
+ violatedLayers: ["L2.1"],
1040
1082
  isCredential: true
1041
1083
  };
1042
1084
  }
@@ -1052,7 +1094,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1052
1094
  riskType: "file_access",
1053
1095
  confidence: 95,
1054
1096
  source: "logic_rule",
1055
- violatedLayers: ["2"]
1097
+ violatedLayers: ["L2.1"]
1056
1098
  };
1057
1099
  }
1058
1100
  }
@@ -1066,7 +1108,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1066
1108
  riskType: "file_access",
1067
1109
  confidence: 70,
1068
1110
  source: "logic_rule",
1069
- violatedLayers: ["2"]
1111
+ violatedLayers: ["L2.1"]
1070
1112
  };
1071
1113
  }
1072
1114
  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;
@@ -1078,7 +1120,7 @@ var init_logic_rules = __esm({
1078
1120
  init_rules_loader();
1079
1121
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1080
1122
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1081
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
1123
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1082
1124
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1083
1125
  "ask_user_question",
1084
1126
  "glob",
@@ -1353,6 +1395,7 @@ var init_skill_loader = __esm({
1353
1395
  // src/infra/runtime-config.ts
1354
1396
  var runtime_config_exports = {};
1355
1397
  __export(runtime_config_exports, {
1398
+ DEFAULT_LAYERS: () => DEFAULT_LAYERS,
1356
1399
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1357
1400
  addAgentRule: () => addAgentRule,
1358
1401
  addCustomRule: () => addCustomRule,
@@ -1404,7 +1447,7 @@ __export(runtime_config_exports, {
1404
1447
  updateSkillEnabled: () => updateSkillEnabled
1405
1448
  });
1406
1449
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1407
- import { join as join2 } from "path";
1450
+ import { join as join3 } from "path";
1408
1451
  import { fileURLToPath as fileURLToPath5 } from "url";
1409
1452
  import { dirname as dirname3 } from "path";
1410
1453
  function runtimeDir() {
@@ -1428,6 +1471,7 @@ function emptyRuntimeConfig() {
1428
1471
  L1_rules: {},
1429
1472
  L2_rules: {},
1430
1473
  L3_skills: {},
1474
+ L2_2: {},
1431
1475
  agent_rules: {},
1432
1476
  scenario_rules: {}
1433
1477
  };
@@ -1455,6 +1499,7 @@ function loadRuntimeConfig() {
1455
1499
  L1_rules: merged.L1_rules || {},
1456
1500
  L2_rules: merged.L2_rules || {},
1457
1501
  L3_skills: merged.L3_skills || {},
1502
+ L2_2: merged.L2_2 || {},
1458
1503
  agent_rules: merged.agent_rules || {},
1459
1504
  scenario_rules: merged.scenario_rules || {}
1460
1505
  };
@@ -1507,6 +1552,7 @@ function getEnabledL2SensitivePaths(runtime) {
1507
1552
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1508
1553
  if (r.credential) out.credential = true;
1509
1554
  if (r.read_only) out.read_only = true;
1555
+ if (r.dir_del_only) out.dir_del_only = true;
1510
1556
  return out;
1511
1557
  });
1512
1558
  }
@@ -1615,6 +1661,7 @@ function _l2SensitivePathsToRules() {
1615
1661
  builtin: true
1616
1662
  };
1617
1663
  _syncDenyModeFields(rule);
1664
+ if (sp.dirDelOnly) rule.dir_del_only = true;
1618
1665
  return rule;
1619
1666
  });
1620
1667
  }
@@ -1702,7 +1749,7 @@ function _scenarioRulesToDefaults() {
1702
1749
  return out;
1703
1750
  }
1704
1751
  for (const scenarioId of entries) {
1705
- const rulesFile = join2(scenDir, scenarioId, "rules.json");
1752
+ const rulesFile = join3(scenDir, scenarioId, "rules.json");
1706
1753
  if (!existsSync4(rulesFile)) continue;
1707
1754
  let data;
1708
1755
  try {
@@ -1751,7 +1798,7 @@ function _agentRulesToDefaults() {
1751
1798
  return out;
1752
1799
  }
1753
1800
  for (const agentId of entries) {
1754
- const rulesFile = join2(agentsDir, agentId, "rules.json");
1801
+ const rulesFile = join3(agentsDir, agentId, "rules.json");
1755
1802
  if (!existsSync4(rulesFile)) continue;
1756
1803
  let data;
1757
1804
  try {
@@ -1792,7 +1839,7 @@ function _agentRulesToDefaults() {
1792
1839
  function generateSourceDefaults() {
1793
1840
  return {
1794
1841
  version: 1,
1795
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1842
+ layers: { ...DEFAULT_LAYERS },
1796
1843
  L1_rules: {
1797
1844
  dangerous_commands: {
1798
1845
  category_enabled: true,
@@ -1831,10 +1878,45 @@ function generateSourceDefaults() {
1831
1878
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1832
1879
  },
1833
1880
  L3_skills: _l3SkillsToRules(),
1881
+ L2_2: _ltlRulesToRuntime(),
1834
1882
  agent_rules: _agentRulesToDefaults(),
1835
1883
  scenario_rules: _scenarioRulesToDefaults()
1836
1884
  };
1837
1885
  }
1886
+ function _ltlRulesToRuntime() {
1887
+ const cands = [
1888
+ join3(__dirname4, "..", "rules"),
1889
+ join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1890
+ join3(process.cwd(), "agent_moss", "rules")
1891
+ ];
1892
+ let mc = {};
1893
+ for (const dir of cands) {
1894
+ const p = join3(dir, "mc_rules.json");
1895
+ if (existsSync4(p)) {
1896
+ try {
1897
+ mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1898
+ } catch {
1899
+ mc = {};
1900
+ }
1901
+ break;
1902
+ }
1903
+ }
1904
+ const rules = mc.rules || [];
1905
+ return {
1906
+ ltl_rules: {
1907
+ category_enabled: true,
1908
+ category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
1909
+ rules: rules.map((r) => ({
1910
+ id: r.id,
1911
+ reason: r.reason || "",
1912
+ risk_level: r.risk_level || "high",
1913
+ action: r.action || "block",
1914
+ enabled: true,
1915
+ builtin: true
1916
+ }))
1917
+ }
1918
+ };
1919
+ }
1838
1920
  function normalizePatternForFingerprint(pattern) {
1839
1921
  return pattern.replace(/\\\//g, "/");
1840
1922
  }
@@ -1984,6 +2066,11 @@ function mergeConfig(userCfg, srcDefaults) {
1984
2066
  );
1985
2067
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1986
2068
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2069
+ if (!cfg.L2_2 && cfg.L2_4) {
2070
+ cfg.L2_2 = cfg.L2_4;
2071
+ delete cfg.L2_4;
2072
+ }
2073
+ _mergeRuleCategories(cfg, srcDefaults, "L2_2");
1987
2074
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1988
2075
  _mergeAgentRules(cfg, srcDefaults);
1989
2076
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2176,12 +2263,12 @@ function deleteCustomRule(layer, category, ruleId) {
2176
2263
  function addCustomSkill(skillId, category, keywords, content) {
2177
2264
  _validateSkillId(skillId);
2178
2265
  const runtime = loadRuntimeConfig();
2179
- const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2266
+ const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2180
2267
  try {
2181
2268
  mkdirSync(skillsDir, { recursive: true });
2182
2269
  } catch {
2183
2270
  }
2184
- writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
2271
+ writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2185
2272
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2186
2273
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2187
2274
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2196,7 +2283,7 @@ function deleteCustomSkill(skillId, category) {
2196
2283
  if (idx >= 0) {
2197
2284
  if (skills[idx].builtin) return null;
2198
2285
  skills.splice(idx, 1);
2199
- const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2286
+ const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2200
2287
  try {
2201
2288
  if (existsSync4(skillPath)) unlinkSync(skillPath);
2202
2289
  } catch {
@@ -2390,7 +2477,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2390
2477
  saveRuntimeConfig(runtime);
2391
2478
  return runtime;
2392
2479
  }
2393
- 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;
2480
+ 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;
2394
2481
  var init_runtime_config = __esm({
2395
2482
  "src/infra/runtime-config.ts"() {
2396
2483
  "use strict";
@@ -2402,21 +2489,28 @@ var init_runtime_config = __esm({
2402
2489
  __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2403
2490
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2404
2491
  RUNTIME_CONFIG_DIR = runtimeDir();
2405
- RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2492
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2406
2493
  LAYER_DISABLE_ENV = {
2407
2494
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2408
2495
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2409
2496
  L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2410
2497
  };
2498
+ DEFAULT_LAYERS = {
2499
+ L1_heuristic: true,
2500
+ L2_logic_rules: true,
2501
+ L3_llm_analysis: true,
2502
+ agent_rules_enabled: true,
2503
+ scenario_rules_enabled: true
2504
+ };
2411
2505
  DENY_MODE_LABELS = {
2412
2506
  deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2413
2507
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2414
2508
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2415
2509
  };
2416
- _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2417
- _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2418
- _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2419
- _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2510
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2511
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2512
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2513
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2420
2514
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2421
2515
  }
2422
2516
  });
@@ -2554,11 +2648,21 @@ function getScenarioExtensions(scenarioId) {
2554
2648
  );
2555
2649
  return ext;
2556
2650
  }
2651
+ function getScenarioExtensionsMulti(scenarioSpec) {
2652
+ if (!scenarioSpec) return null;
2653
+ const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
2654
+ if (ids.length === 0) return null;
2655
+ const exts = ids.map((id) => getScenarioExtensions(id));
2656
+ const nonNull = exts.filter((e) => e !== null);
2657
+ if (nonNull.length === 0) return null;
2658
+ return mergeAgentExtensions(...nonNull);
2659
+ }
2557
2660
  var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2558
2661
  var init_scenarios = __esm({
2559
2662
  "src/scenarios/index.ts"() {
2560
2663
  "use strict";
2561
2664
  init_esm_shims();
2665
+ init_agents();
2562
2666
  __filename5 = fileURLToPath7(import.meta.url);
2563
2667
  __dirname6 = dirname5(__filename5);
2564
2668
  SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
@@ -2586,7 +2690,7 @@ function runHeuristicCheck(input) {
2586
2690
  riskType: "sensitive_action",
2587
2691
  confidence: 90,
2588
2692
  source: "heuristic",
2589
- violatedLayers: ["1.1"]
2693
+ violatedLayers: ["L1.1"]
2590
2694
  };
2591
2695
  }
2592
2696
  }
@@ -2596,8 +2700,10 @@ function runHeuristicCheck(input) {
2596
2700
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2597
2701
  const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
2598
2702
  let topRiskType = "";
2703
+ const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2599
2704
  for (const rule of DANGEROUS_PATTERNS) {
2600
2705
  if (rule.pattern.test(scanCommand)) {
2706
+ if (exemptKeygen && rule.riskType === "file_access") continue;
2601
2707
  matched.push(rule.description);
2602
2708
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
2603
2709
  highestSeverity = rule.severity;
@@ -2607,8 +2713,8 @@ function runHeuristicCheck(input) {
2607
2713
  }
2608
2714
  highestRiskType = topRiskType || highestRiskType;
2609
2715
  const agentExt = mergeAgentExtensions(
2610
- getAgentExtensions(input.agentId),
2611
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2716
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
2717
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
2612
2718
  );
2613
2719
  if (agentExt?.extra_command_patterns) {
2614
2720
  for (const rule of agentExt.extra_command_patterns) {
@@ -2639,10 +2745,26 @@ function runHeuristicCheck(input) {
2639
2745
  const detailL = input.aNext.actionDetail.toLowerCase();
2640
2746
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2641
2747
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2642
- if (dangerousResult) return dangerousResult;
2643
- if (!isExcludedType) {
2644
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
2645
- if (pathResult) return pathResult;
2748
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
2749
+ const homeResult = checkHomeDeletion(actionTypeL, detailL);
2750
+ const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2751
+ if (mergeResults.length > 0) {
2752
+ let best = mergeResults[0];
2753
+ for (const r of mergeResults) {
2754
+ if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
2755
+ }
2756
+ if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
2757
+ return {
2758
+ allowed: false,
2759
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
2760
+ riskLevel: highestSeverity,
2761
+ riskType: highestRiskType,
2762
+ confidence: 95,
2763
+ source: "heuristic",
2764
+ violatedLayers: ["L1.1"]
2765
+ };
2766
+ }
2767
+ return best;
2646
2768
  }
2647
2769
  if (matched.length > 0) {
2648
2770
  return {
@@ -2652,8 +2774,7 @@ function runHeuristicCheck(input) {
2652
2774
  riskType: highestRiskType,
2653
2775
  confidence: 95,
2654
2776
  source: "heuristic",
2655
- violatedLayers: ["1.1"]
2656
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
2777
+ violatedLayers: ["L1.1"]
2657
2778
  };
2658
2779
  }
2659
2780
  const lateralResult = checkLateralMovement(detailL);
@@ -2781,6 +2902,7 @@ function isSearchActionType(actionType) {
2781
2902
  function checkSensitivePath(command, cwd, agentExt) {
2782
2903
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2783
2904
  const isWriteOpFlag = isWriteOperation("", command);
2905
+ const keygenGenerate = isSshKeygenGenerate(command);
2784
2906
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2785
2907
  const pathMatches = (text, path6) => {
2786
2908
  const parts = path6.split(/(\(\?!.*?\))/);
@@ -2793,6 +2915,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2793
2915
  };
2794
2916
  for (const sp of ROOT_SENSITIVE_PATHS) {
2795
2917
  if (pathMatches(pathScanText, sp.path.toLowerCase())) {
2918
+ if (keygenGenerate) continue;
2796
2919
  if (sp.path === "/dev/random") {
2797
2920
  if (isWriteOpFlag) {
2798
2921
  continue;
@@ -2804,7 +2927,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2804
2927
  riskType: "file_access",
2805
2928
  confidence: 90,
2806
2929
  source: "heuristic",
2807
- violatedLayers: ["1.1"]
2930
+ violatedLayers: ["L1.1"]
2808
2931
  };
2809
2932
  }
2810
2933
  if (!isWriteOpFlag && !sp.credential) {
@@ -2817,16 +2940,51 @@ function checkSensitivePath(command, cwd, agentExt) {
2817
2940
  riskType: "file_access",
2818
2941
  confidence: 90,
2819
2942
  source: "heuristic",
2820
- violatedLayers: ["1.1"],
2943
+ violatedLayers: ["L1.1"],
2821
2944
  isCredential: !!sp.credential
2822
2945
  };
2823
2946
  }
2824
2947
  }
2825
2948
  for (const sp of HOME_SENSITIVE_PATHS) {
2826
2949
  const expanded = `${expandedCwd}/${sp.path}`;
2827
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2828
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2829
- if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
2950
+ let pPath = sp.path;
2951
+ if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
2952
+ let dirPrefixMatch = null;
2953
+ if (sp.dirDelOnly) {
2954
+ const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
2955
+ dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
2956
+ }
2957
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
2958
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
2959
+ if (pathReMatch || expandedReMatch) {
2960
+ if (keygenGenerate) continue;
2961
+ if (sp.dirDelOnly) {
2962
+ if (isDeleteActionForDir(command, sp.path)) {
2963
+ return {
2964
+ allowed: false,
2965
+ reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2966
+ riskLevel: sp.riskLevel,
2967
+ riskType: "file_access",
2968
+ confidence: 90,
2969
+ source: "heuristic",
2970
+ violatedLayers: ["L1.1"],
2971
+ isCredential: !!sp.credential
2972
+ };
2973
+ }
2974
+ if (isArchiveReadOfDir(command, sp.path)) {
2975
+ return {
2976
+ allowed: false,
2977
+ reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2978
+ riskLevel: sp.riskLevel,
2979
+ riskType: "file_access",
2980
+ confidence: 90,
2981
+ source: "heuristic",
2982
+ violatedLayers: ["L1.1"],
2983
+ isCredential: !!sp.credential
2984
+ };
2985
+ }
2986
+ continue;
2987
+ }
2830
2988
  if (!isWriteOpFlag && !sp.credential) {
2831
2989
  continue;
2832
2990
  }
@@ -2837,7 +2995,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2837
2995
  riskType: "file_access",
2838
2996
  confidence: 90,
2839
2997
  source: "heuristic",
2840
- violatedLayers: ["1.1"],
2998
+ violatedLayers: ["L1.1"],
2841
2999
  isCredential: !!sp.credential
2842
3000
  };
2843
3001
  }
@@ -2860,7 +3018,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2860
3018
  riskType: "file_access",
2861
3019
  confidence: 90,
2862
3020
  source: "heuristic",
2863
- violatedLayers: ["1.1"],
3021
+ violatedLayers: ["L1.1"],
2864
3022
  isCredential: !!sp.credential
2865
3023
  };
2866
3024
  }
@@ -2877,7 +3035,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2877
3035
  riskType: "file_access",
2878
3036
  confidence: 90,
2879
3037
  source: "heuristic",
2880
- violatedLayers: ["1.1"],
3038
+ violatedLayers: ["L1.1"],
2881
3039
  isCredential: true
2882
3040
  };
2883
3041
  }
@@ -2890,18 +3048,142 @@ function checkSensitivePath(command, cwd, agentExt) {
2890
3048
  riskType: "file_access",
2891
3049
  confidence: 90,
2892
3050
  source: "heuristic",
2893
- violatedLayers: ["1.1"]
3051
+ violatedLayers: ["L1.1"]
2894
3052
  };
2895
3053
  }
2896
3054
  return null;
2897
3055
  }
3056
+ function isHomeValue(path6) {
3057
+ let p = path6.trim().toLowerCase();
3058
+ if (p.startsWith("$(") && p.endsWith(")")) {
3059
+ const inner = p.slice(2, -1).trim();
3060
+ return HOME_TARGET_RE.test(" " + inner);
3061
+ }
3062
+ if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
3063
+ return HOME_TARGET_RE.test(" " + p);
3064
+ }
3065
+ function isDeleteCommand(seg) {
3066
+ const s = seg.trimStart();
3067
+ if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
3068
+ return /删除|移除/.test(s);
3069
+ }
3070
+ function homeEscapeTarget(seg) {
3071
+ const low = seg.toLowerCase();
3072
+ const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
3073
+ let m;
3074
+ while ((m = re.exec(low)) !== null) {
3075
+ const endIdx = m.index + m[0].length;
3076
+ const tail = low.slice(endIdx);
3077
+ if (!tail) continue;
3078
+ let token = tail.trimStart().split(/\s+/)[0] || "";
3079
+ token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
3080
+ token = token.replace(/^['"]|['"]$/g, "");
3081
+ const segments = token.split("/").filter((s) => s && s !== ".");
3082
+ if (!segments.some((s) => s === "..")) continue;
3083
+ let depth = 0;
3084
+ let minDepth = 0;
3085
+ for (const s of segments) {
3086
+ depth += s === ".." ? -1 : 1;
3087
+ minDepth = Math.min(minDepth, depth);
3088
+ }
3089
+ if (minDepth < 0) return true;
3090
+ if (depth <= 0) return true;
3091
+ }
3092
+ return false;
3093
+ }
3094
+ function checkHomeDeletion(actionType, actionDetail) {
3095
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
3096
+ const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
3097
+ for (const seg of segs) {
3098
+ if (!isDeleteCommand(seg)) continue;
3099
+ if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
3100
+ return {
3101
+ allowed: false,
3102
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
3103
+ riskLevel: "high",
3104
+ riskType: "script_execution",
3105
+ confidence: 90,
3106
+ source: "heuristic",
3107
+ violatedLayers: ["L1.1"]
3108
+ };
3109
+ }
3110
+ }
3111
+ const assigned = {};
3112
+ for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
3113
+ for (const [varName, path6] of Object.entries(assigned)) {
3114
+ if (!isHomeValue(path6)) continue;
3115
+ const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
3116
+ for (const seg of segs) {
3117
+ if (!isDeleteCommand(seg)) continue;
3118
+ if (varRefRe.test(seg)) {
3119
+ return {
3120
+ allowed: false,
3121
+ reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
3122
+ riskLevel: "high",
3123
+ riskType: "script_execution",
3124
+ confidence: 90,
3125
+ source: "heuristic",
3126
+ violatedLayers: ["L1.1"]
3127
+ };
3128
+ }
3129
+ }
3130
+ }
3131
+ return null;
3132
+ }
3133
+ function escapeRegExp(s) {
3134
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3135
+ }
3136
+ function isDeleteActionForDir(command, dirPath) {
3137
+ const low = command.toLowerCase();
3138
+ const dpath = dirPath.toLowerCase();
3139
+ if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
3140
+ return true;
3141
+ }
3142
+ if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
3143
+ return true;
3144
+ }
3145
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3146
+ const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3147
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
3148
+ return true;
3149
+ }
3150
+ }
3151
+ return false;
3152
+ }
3153
+ function isArchiveReadOfDir(command, dirPath) {
3154
+ const low = command.toLowerCase();
3155
+ const dpath = dirPath.toLowerCase();
3156
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3157
+ const s = seg.trim();
3158
+ if (!s || !s.includes(dpath)) continue;
3159
+ const parts = s.split(/\s+/);
3160
+ const fw = parts[0] || "";
3161
+ if (fw === "zip") return true;
3162
+ if (fw === "tar") {
3163
+ if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
3164
+ return true;
3165
+ }
3166
+ if (fw === "cp" || fw === "rsync") {
3167
+ const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3168
+ if (segPaths.length && segPaths[0].includes(dpath)) return true;
3169
+ }
3170
+ }
3171
+ return false;
3172
+ }
3173
+ function isSshKeygenGenerate(command) {
3174
+ const low = command.toLowerCase();
3175
+ if (!low.includes("ssh-keygen")) return false;
3176
+ if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
3177
+ if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
3178
+ return true;
3179
+ }
2898
3180
  function checkDangerousPatterns(actionType, actionDetail) {
2899
3181
  if (actionType === "file_write" || actionType === "file_edit") return null;
2900
3182
  const scan = stripHeredocContent(actionDetail);
2901
3183
  const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
2902
3184
  const isTempPath = TEMP_DIRS.some(
2903
3185
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
2904
- );
3186
+ ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
2905
3187
  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;
2906
3188
  if (!isTempPath && wildcardDeleteRe.test(scan)) {
2907
3189
  return {
@@ -2911,7 +3193,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2911
3193
  riskType: "script_execution",
2912
3194
  confidence: 90,
2913
3195
  source: "heuristic",
2914
- violatedLayers: ["1.1"]
3196
+ violatedLayers: ["L1.1"]
2915
3197
  };
2916
3198
  }
2917
3199
  const findWriteMatch = actionDetail.match(
@@ -2926,10 +3208,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
2926
3208
  riskType: "script_execution",
2927
3209
  confidence: 90,
2928
3210
  source: "heuristic",
2929
- violatedLayers: ["1.1"]
3211
+ violatedLayers: ["L1.1"]
2930
3212
  };
2931
3213
  }
2932
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
3214
+ if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
2933
3215
  return {
2934
3216
  allowed: false,
2935
3217
  reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
@@ -2937,7 +3219,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2937
3219
  riskType: "file_access",
2938
3220
  confidence: 95,
2939
3221
  source: "heuristic",
2940
- violatedLayers: ["1.1"]
3222
+ violatedLayers: ["L1.1"]
2941
3223
  };
2942
3224
  }
2943
3225
  if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
@@ -2948,7 +3230,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2948
3230
  riskType: "file_access",
2949
3231
  confidence: 90,
2950
3232
  source: "heuristic",
2951
- violatedLayers: ["1.1"]
3233
+ violatedLayers: ["L1.1"]
2952
3234
  };
2953
3235
  }
2954
3236
  return null;
@@ -3039,13 +3321,13 @@ function checkLateralMovement(actionDetail) {
3039
3321
  riskType: "lateral_movement",
3040
3322
  confidence: 80,
3041
3323
  source: "heuristic",
3042
- violatedLayers: ["1.1"]
3324
+ violatedLayers: ["L1.1"]
3043
3325
  };
3044
3326
  }
3045
3327
  }
3046
3328
  return null;
3047
3329
  }
3048
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
3330
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3049
3331
  var init_heuristic = __esm({
3050
3332
  "src/engine/heuristic.ts"() {
3051
3333
  "use strict";
@@ -3053,6 +3335,7 @@ var init_heuristic = __esm({
3053
3335
  init_config();
3054
3336
  init_agents();
3055
3337
  init_scenarios();
3338
+ init_runtime_config();
3056
3339
  init_rules_loader();
3057
3340
  init_logic_rules();
3058
3341
  INJECTION_KEYWORDS = (() => {
@@ -3074,6 +3357,8 @@ var init_heuristic = __esm({
3074
3357
  })();
3075
3358
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3076
3359
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3360
+ HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
3361
+ 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;
3077
3362
  }
3078
3363
  });
3079
3364
 
@@ -3335,7 +3620,7 @@ var require_bindings = __commonJS({
3335
3620
  var fs7 = __require("fs");
3336
3621
  var path6 = __require("path");
3337
3622
  var fileURLToPath10 = require_file_uri_to_path();
3338
- var join10 = path6.join;
3623
+ var join11 = path6.join;
3339
3624
  var dirname11 = path6.dirname;
3340
3625
  var exists = fs7.accessSync && function(path7) {
3341
3626
  try {
@@ -3395,7 +3680,7 @@ var require_bindings = __commonJS({
3395
3680
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
3396
3681
  var tries = [], i = 0, l = opts.try.length, n, b, err;
3397
3682
  for (; i < l; i++) {
3398
- n = join10.apply(
3683
+ n = join11.apply(
3399
3684
  null,
3400
3685
  opts.try[i].map(function(p) {
3401
3686
  return opts[p] || p;
@@ -3456,7 +3741,7 @@ var require_bindings = __commonJS({
3456
3741
  if (dir === ".") {
3457
3742
  dir = process.cwd();
3458
3743
  }
3459
- if (exists(join10(dir, "package.json")) || exists(join10(dir, "node_modules"))) {
3744
+ if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
3460
3745
  return dir;
3461
3746
  }
3462
3747
  if (prev === dir) {
@@ -3465,7 +3750,7 @@ var require_bindings = __commonJS({
3465
3750
  );
3466
3751
  }
3467
3752
  prev = dir;
3468
- dir = join10(dir, "..");
3753
+ dir = join11(dir, "..");
3469
3754
  }
3470
3755
  };
3471
3756
  }
@@ -4079,7 +4364,7 @@ var AnalyzeRequestSchema = z.object({
4079
4364
  session_history: z.array(z.object({
4080
4365
  session_id: z.string(),
4081
4366
  action_history: z.array(ActionItemSchema).default([])
4082
- })).default([]).describe("Multi-session historical actions (optional): for L2.4 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
4367
+ })).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
4083
4368
  a_next: ActionItemSchema.describe("Next action to be executed"),
4084
4369
  reason: z.string().default("").describe("Reason for executing this action"),
4085
4370
  os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
@@ -4096,7 +4381,9 @@ var AnalyzeResponseSchema = z.object({
4096
4381
  policy: z.string().default(""),
4097
4382
  violated_policy: z.string().default(""),
4098
4383
  confidence: z.number().int().min(0).max(100).default(100),
4099
- analysis_duration_ms: z.number().default(0)
4384
+ analysis_duration_ms: z.number().default(0),
4385
+ // 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
4386
+ violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
4100
4387
  });
4101
4388
  var HealthResponseSchema = z.object({
4102
4389
  status: z.string().default("healthy"),
@@ -4171,7 +4458,7 @@ function loadTemplates() {
4171
4458
  // src/engine/script-analyzer.ts
4172
4459
  init_esm_shims();
4173
4460
  import { readFileSync as readFileSync8 } from "fs";
4174
- import { resolve as resolve6, join as join3 } from "path";
4461
+ import { resolve as resolve6, join as join4 } from "path";
4175
4462
  var SUSPICIOUS_PATTERNS = [
4176
4463
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
4177
4464
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -4304,7 +4591,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4304
4591
  }
4305
4592
  function readScriptContent(scriptPath, maxLines = 500) {
4306
4593
  try {
4307
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
4594
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
4308
4595
  const raw = readFileSync8(expandedPath, "utf-8");
4309
4596
  const lines = raw.split("\n").slice(0, maxLines);
4310
4597
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4359,9 +4646,10 @@ ${result.scriptContent}
4359
4646
  init_esm_shims();
4360
4647
  init_heuristic();
4361
4648
  init_logic_rules();
4649
+ init_inline_analyzer();
4362
4650
  import { statSync as statSync2, existsSync as existsSync9 } from "fs";
4363
- import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
4364
- import { homedir as homedir3 } from "os";
4651
+ import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
4652
+ import { homedir as homedir4 } from "os";
4365
4653
 
4366
4654
  // src/engine/script-engine/tracker.ts
4367
4655
  init_esm_shims();
@@ -5973,6 +6261,13 @@ function extractScriptPath2(command) {
5973
6261
  return null;
5974
6262
  }
5975
6263
  function extractScriptPathWithArgs(command) {
6264
+ const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
6265
+ if (inline) {
6266
+ const inner = inline[2];
6267
+ const realPath = extractScriptPath2(inner);
6268
+ if (realPath) return { path: realPath, args: [] };
6269
+ return { path: null, args: [] };
6270
+ }
5976
6271
  const scriptPath = extractScriptPath2(command);
5977
6272
  if (!scriptPath) return { path: null, args: [] };
5978
6273
  const idx = command.indexOf(scriptPath);
@@ -5986,11 +6281,59 @@ function extractScriptPathWithArgs(command) {
5986
6281
  }
5987
6282
  return { path: scriptPath, args };
5988
6283
  }
6284
+ function looksLikeScriptPath(tok) {
6285
+ const t = tok.trim().replace(/^['"]|['"]$/g, "");
6286
+ if (!t) return false;
6287
+ const low = t.toLowerCase();
6288
+ if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
6289
+ if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
6290
+ if (t.includes("/") && !t.startsWith("-")) return true;
6291
+ return false;
6292
+ }
5989
6293
  function extractInlineScriptPath(actionDetail) {
6294
+ const code = extractInlineCode(actionDetail);
6295
+ if (code) {
6296
+ const candidates = [];
6297
+ const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
6298
+ let m;
6299
+ while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
6300
+ const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
6301
+ while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
6302
+ const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
6303
+ while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
6304
+ for (const cand of candidates) {
6305
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
6306
+ for (const tok of tokens) {
6307
+ if (looksLikeScriptPath(tok)) {
6308
+ try {
6309
+ const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6310
+ if (existsSync9(resolved)) {
6311
+ const stat = statSync2(resolved);
6312
+ if (stat.isFile()) return resolved;
6313
+ }
6314
+ } catch {
6315
+ }
6316
+ }
6317
+ }
6318
+ }
6319
+ const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
6320
+ while ((m = openRe.exec(code)) !== null) {
6321
+ const p = m[1];
6322
+ try {
6323
+ const resolved = resolvePath2(p);
6324
+ if (existsSync9(resolved)) {
6325
+ const stat = statSync2(resolved);
6326
+ if (stat.isFile()) return resolved;
6327
+ }
6328
+ } catch {
6329
+ }
6330
+ }
6331
+ return null;
6332
+ }
5990
6333
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5991
- let m;
5992
- while ((m = re.exec(actionDetail)) !== null) {
5993
- const p = m[1];
6334
+ let m2;
6335
+ while ((m2 = re.exec(actionDetail)) !== null) {
6336
+ const p = m2[1];
5994
6337
  let resolved;
5995
6338
  try {
5996
6339
  resolved = resolvePath2(p);
@@ -6006,7 +6349,7 @@ function extractInlineScriptPath(actionDetail) {
6006
6349
  function resolvePath2(p) {
6007
6350
  let expanded = p;
6008
6351
  if (expanded.startsWith("~/") || expanded === "~") {
6009
- expanded = join5(homedir3(), expanded.slice(1));
6352
+ expanded = join6(homedir4(), expanded.slice(1));
6010
6353
  }
6011
6354
  return resolve8(expanded);
6012
6355
  }
@@ -6087,7 +6430,7 @@ function analyzeScriptContent2(input) {
6087
6430
  isInline = true;
6088
6431
  }
6089
6432
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6090
- resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
6433
+ resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6091
6434
  }
6092
6435
  const l1Adapter = (content) => {
6093
6436
  const virtualInput = {
@@ -6375,7 +6718,7 @@ async function analyzeWithLLM(prompt, config) {
6375
6718
  riskType: riskType || "",
6376
6719
  confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
6377
6720
  source: "llm",
6378
- violatedLayers: ["3"]
6721
+ violatedLayers: ["L3.1"]
6379
6722
  };
6380
6723
  } catch (err) {
6381
6724
  if (abortController.signal.aborted) {
@@ -6398,6 +6741,7 @@ async function analyzeWithLLM(prompt, config) {
6398
6741
 
6399
6742
  // src/engine/coordinator.ts
6400
6743
  init_config();
6744
+ init_runtime_config();
6401
6745
  init_agents();
6402
6746
  init_scenarios();
6403
6747
 
@@ -6407,9 +6751,9 @@ init_esm_shims();
6407
6751
  // src/brain/rule-store.ts
6408
6752
  init_esm_shims();
6409
6753
  import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6410
- import { join as join6 } from "path";
6411
- import { homedir as homedir4 } from "os";
6412
- var DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
6754
+ import { join as join7 } from "path";
6755
+ import { homedir as homedir5 } from "os";
6756
+ var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
6413
6757
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
6414
6758
  var STATUS_DRAFT = "draft";
6415
6759
  var STATUS_APPROVED = "approved";
@@ -6971,6 +7315,15 @@ function decodeBase64Literals(text) {
6971
7315
  }
6972
7316
  return out;
6973
7317
  }
7318
+ var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
7319
+ var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
7320
+ function stripStringLiterals(text) {
7321
+ const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
7322
+ if (!m) return text;
7323
+ const cmd = m[1].split("/").pop().toLowerCase();
7324
+ if (!OUTPUT_CMDS.has(cmd)) return text;
7325
+ return text.replace(QUOTED_STR_RE, "");
7326
+ }
6974
7327
  function decodeUrlAndNumeric(text) {
6975
7328
  const out = [];
6976
7329
  let s = text;
@@ -7004,7 +7357,10 @@ function decodeUrlAndNumeric(text) {
7004
7357
  return out;
7005
7358
  }
7006
7359
  function allCandidates(detail, max = 8) {
7007
- const cands = [detail];
7360
+ const shell = stripStringLiterals(detail);
7361
+ const cands = [shell];
7362
+ cands.push(...decodeBase64Literals(shell));
7363
+ cands.push(...decodeUrlAndNumeric(shell));
7008
7364
  cands.push(...decodeBase64Literals(detail));
7009
7365
  cands.push(...decodeUrlAndNumeric(detail));
7010
7366
  const seen = /* @__PURE__ */ new Set();
@@ -7029,6 +7385,128 @@ function matchStageAny(detail, stages) {
7029
7385
  return "";
7030
7386
  }
7031
7387
 
7388
+ // src/engine/formal-engine/ltl.ts
7389
+ init_esm_shims();
7390
+ init_rules_loader();
7391
+
7392
+ // src/engine/formal-engine/ir.ts
7393
+ init_esm_shims();
7394
+ function allNodes(mg) {
7395
+ const out = [];
7396
+ for (const s of mg.sessions) out.push(...s.nodes);
7397
+ return out;
7398
+ }
7399
+ function allEdges(mg) {
7400
+ const out = [];
7401
+ for (const s of mg.sessions) out.push(...s.edges);
7402
+ out.push(...mg.crossEdges);
7403
+ return out;
7404
+ }
7405
+ function emptyVerificationResult() {
7406
+ return { verified: true, violations: [] };
7407
+ }
7408
+
7409
+ // src/engine/formal-engine/ltl.ts
7410
+ var AP_RE = null;
7411
+ function compileAps(apPatterns) {
7412
+ if (AP_RE) return;
7413
+ AP_RE = {};
7414
+ for (const [name, pats] of Object.entries(apPatterns || {})) {
7415
+ AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
7416
+ }
7417
+ }
7418
+ function labelEvent(actionDetail) {
7419
+ if (!AP_RE) compileAps(loadMC().ap_patterns || {});
7420
+ const candidates = allCandidates(actionDetail);
7421
+ const hits = /* @__PURE__ */ new Set();
7422
+ for (const [name, pats] of Object.entries(AP_RE || {})) {
7423
+ for (const c of candidates) {
7424
+ const m = pats.some((p) => p.test(c));
7425
+ if (m) {
7426
+ hits.add(name);
7427
+ break;
7428
+ }
7429
+ }
7430
+ }
7431
+ return hits;
7432
+ }
7433
+ function evaluateLTL(formula, trace, pos) {
7434
+ const op = formula[0];
7435
+ switch (op) {
7436
+ case "ap":
7437
+ return trace[pos].has(formula[1]);
7438
+ case "!":
7439
+ return !evaluateLTL(formula[1], trace, pos);
7440
+ case "&":
7441
+ return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
7442
+ case "|":
7443
+ return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
7444
+ case "->":
7445
+ return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
7446
+ case "X":
7447
+ return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
7448
+ case "G": {
7449
+ for (let j = pos; j < trace.length; j++) {
7450
+ if (!evaluateLTL(formula[1], trace, j)) return false;
7451
+ }
7452
+ return true;
7453
+ }
7454
+ case "F": {
7455
+ for (let j = pos; j < trace.length; j++) {
7456
+ if (evaluateLTL(formula[1], trace, j)) return true;
7457
+ }
7458
+ return false;
7459
+ }
7460
+ case "U": {
7461
+ for (let j = pos; j < trace.length; j++) {
7462
+ if (evaluateLTL(formula[2], trace, j)) {
7463
+ for (let k = pos; k < j; k++) {
7464
+ if (!evaluateLTL(formula[1], trace, k)) return false;
7465
+ }
7466
+ return true;
7467
+ }
7468
+ }
7469
+ return false;
7470
+ }
7471
+ default:
7472
+ return false;
7473
+ }
7474
+ }
7475
+ function ruleViolates(rule, traceWith, witnessMode) {
7476
+ const formula = rule?.formula;
7477
+ if (!Array.isArray(formula) || formula.length === 0) return false;
7478
+ const satWith = evaluateLTL(formula, traceWith, 0);
7479
+ if (!satWith) return false;
7480
+ if (witnessMode && traceWith.length > 1) {
7481
+ const traceWithout = traceWith.slice(0, -1);
7482
+ if (evaluateLTL(formula, traceWithout, 0)) return false;
7483
+ }
7484
+ return true;
7485
+ }
7486
+ function checkMCRules(graph) {
7487
+ const violations = [];
7488
+ const nodes = allNodes(graph);
7489
+ if (nodes.length === 0) return violations;
7490
+ const mc = loadMC();
7491
+ const rules = mc.rules || [];
7492
+ if (rules.length === 0) return violations;
7493
+ const trace = nodes.map((n) => labelEvent(n.actionDetail));
7494
+ const witnessMode = graph.witnessIdx !== void 0;
7495
+ for (const rule of rules) {
7496
+ if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
7497
+ if (ruleViolates(rule, trace, witnessMode)) {
7498
+ violations.push({
7499
+ propertyId: String(rule.id || "mc-ltl"),
7500
+ reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
7501
+ riskLevel: String(rule.risk_level || "high"),
7502
+ 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) }))
7503
+ });
7504
+ if (witnessMode) return violations;
7505
+ }
7506
+ }
7507
+ return violations;
7508
+ }
7509
+
7032
7510
  // src/engine/formal-engine/builder.ts
7033
7511
  var COMBO_STAGES2 = null;
7034
7512
  function loadStages() {
@@ -7057,6 +7535,26 @@ function extractResources(actionDetail) {
7057
7535
  function markStage(actionDetail) {
7058
7536
  return matchStageAny(actionDetail, loadStages());
7059
7537
  }
7538
+ function markIntegrity(actionDetail) {
7539
+ const aps = labelEvent(actionDetail);
7540
+ if (aps.has("download_file") || aps.has("prompt_injection")) {
7541
+ return { integrity: "low", isLowSource: true };
7542
+ }
7543
+ return { integrity: "medium", isLowSource: false };
7544
+ }
7545
+ function initNode(sessionId, seq, actionType, actionDetail) {
7546
+ const { integrity, isLowSource } = markIntegrity(actionDetail);
7547
+ return {
7548
+ sessionId,
7549
+ seq,
7550
+ actionType,
7551
+ actionDetail,
7552
+ stage: markStage(actionDetail),
7553
+ resources: extractResources(actionDetail),
7554
+ integrity,
7555
+ isLowSource
7556
+ };
7557
+ }
7060
7558
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
7061
7559
  const nodes = [];
7062
7560
  const edges = [];
@@ -7070,17 +7568,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
7070
7568
  details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
7071
7569
  }
7072
7570
  details.forEach((d, seq) => {
7073
- const stage = markStage(d.actionDetail);
7074
- const node = {
7075
- sessionId,
7076
- seq,
7077
- actionType: d.actionType,
7078
- actionDetail: d.actionDetail,
7079
- stage,
7080
- resources: extractResources(d.actionDetail)
7081
- };
7571
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
7082
7572
  nodes.push(node);
7083
- if (stage) stageFlags.add(stage);
7573
+ if (node.stage) stageFlags.add(node.stage);
7084
7574
  });
7085
7575
  for (let i = 0; i < nodes.length - 1; i++) {
7086
7576
  edges.push({ src: i, dst: i + 1, kind: "next" });
@@ -7100,15 +7590,12 @@ function buildMultiSessionGraph(sessions, aNext) {
7100
7590
  if (aNext) {
7101
7591
  const last = mg.sessions[mg.sessions.length - 1];
7102
7592
  if (last.sessionId === "current") {
7103
- const stage = markStage(String(aNext.actionDetail || ""));
7104
- last.nodes.push({
7105
- sessionId: last.sessionId,
7106
- seq: last.nodes.length,
7107
- actionType: String(aNext.actionType || ""),
7108
- actionDetail: String(aNext.actionDetail || ""),
7109
- stage,
7110
- resources: extractResources(String(aNext.actionDetail || ""))
7111
- });
7593
+ last.nodes.push(initNode(
7594
+ last.sessionId,
7595
+ last.nodes.length,
7596
+ String(aNext.actionType || ""),
7597
+ String(aNext.actionDetail || "")
7598
+ ));
7112
7599
  if (last.nodes.length > 1) {
7113
7600
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
7114
7601
  }
@@ -7122,11 +7609,25 @@ function buildMultiSessionGraph(sessions, aNext) {
7122
7609
  function addCrossDataEdges(mg) {
7123
7610
  const nodes = allNodesFlat(mg);
7124
7611
  if (nodes.length < 2) return;
7125
- for (let i = 0; i < nodes.length; i++) {
7126
- for (let j = i + 1; j < nodes.length; j++) {
7127
- if (nodes[i].sessionId === nodes[j].sessionId) continue;
7128
- const shared = intersect(nodes[i].resources, nodes[j].resources);
7129
- if (shared.size > 0) {
7612
+ const resIndex = /* @__PURE__ */ new Map();
7613
+ nodes.forEach((n, i) => {
7614
+ for (const r of n.resources) {
7615
+ const list = resIndex.get(r);
7616
+ if (list) list.push(i);
7617
+ else resIndex.set(r, [i]);
7618
+ }
7619
+ });
7620
+ const seen = /* @__PURE__ */ new Set();
7621
+ for (const idxs of resIndex.values()) {
7622
+ if (idxs.length < 2) continue;
7623
+ for (let a = 0; a < idxs.length; a++) {
7624
+ const i = idxs[a];
7625
+ for (let b = a + 1; b < idxs.length; b++) {
7626
+ const j = idxs[b];
7627
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
7628
+ const key = `${i}:${j}`;
7629
+ if (seen.has(key)) continue;
7630
+ seen.add(key);
7130
7631
  mg.crossEdges.push({ src: i, dst: j, kind: "data" });
7131
7632
  }
7132
7633
  }
@@ -7137,28 +7638,6 @@ function allNodesFlat(mg) {
7137
7638
  for (const s of mg.sessions) out.push(...s.nodes);
7138
7639
  return out;
7139
7640
  }
7140
- function intersect(a, b) {
7141
- const out = /* @__PURE__ */ new Set();
7142
- for (const v of a) if (b.has(v)) out.add(v);
7143
- return out;
7144
- }
7145
-
7146
- // src/engine/formal-engine/ir.ts
7147
- init_esm_shims();
7148
- function allNodes(mg) {
7149
- const out = [];
7150
- for (const s of mg.sessions) out.push(...s.nodes);
7151
- return out;
7152
- }
7153
- function allEdges(mg) {
7154
- const out = [];
7155
- for (const s of mg.sessions) out.push(...s.edges);
7156
- out.push(...mg.crossEdges);
7157
- return out;
7158
- }
7159
- function emptyVerificationResult() {
7160
- return { verified: true, violations: [] };
7161
- }
7162
7641
 
7163
7642
  // src/engine/formal-engine/properties.ts
7164
7643
  init_esm_shims();
@@ -7181,27 +7660,50 @@ function checkCrossSessionChain(graph) {
7181
7660
  const violations = [];
7182
7661
  const nodes = allNodes(graph);
7183
7662
  if (nodes.length === 0) return violations;
7663
+ const witness = graph.witnessIdx;
7184
7664
  for (const chain of loadCrossSessionChains()) {
7185
7665
  const stages = chain.stages;
7186
7666
  if (!stages.length) continue;
7187
- let matched = 0;
7188
- let stageIdx = 0;
7189
- const path6 = [];
7190
- for (const n of nodes) {
7191
- if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
7192
- matched += 1;
7193
- path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
7194
- stageIdx += 1;
7195
- if (matched >= stages.length) break;
7667
+ if (witness !== void 0) {
7668
+ if (nodes[witness].stage !== stages[stages.length - 1]) continue;
7669
+ let matched = 0;
7670
+ const path6 = [];
7671
+ for (let k = 0; k < witness; k++) {
7672
+ const n = nodes[k];
7673
+ if (matched < stages.length - 1 && n.stage === stages[matched]) {
7674
+ matched += 1;
7675
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
7676
+ }
7196
7677
  }
7197
- }
7198
- if (matched >= stages.length) {
7678
+ if (matched < stages.length - 1) continue;
7679
+ path6.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
7199
7680
  violations.push({
7200
7681
  propertyId: "cross-" + chain.id,
7201
7682
  reason: chain.reason,
7202
7683
  riskLevel: chain.riskLevel,
7203
7684
  path: path6
7204
7685
  });
7686
+ return violations;
7687
+ } else {
7688
+ let matched = 0;
7689
+ let stageIdx = 0;
7690
+ const path6 = [];
7691
+ for (const n of nodes) {
7692
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
7693
+ matched += 1;
7694
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
7695
+ stageIdx += 1;
7696
+ if (matched >= stages.length) break;
7697
+ }
7698
+ }
7699
+ if (matched >= stages.length) {
7700
+ violations.push({
7701
+ propertyId: "cross-" + chain.id,
7702
+ reason: chain.reason,
7703
+ riskLevel: chain.riskLevel,
7704
+ path: path6
7705
+ });
7706
+ }
7205
7707
  }
7206
7708
  }
7207
7709
  return violations;
@@ -7236,14 +7738,18 @@ function checkSensitiveDataFlow(graph) {
7236
7738
  }
7237
7739
  }
7238
7740
  }
7239
- for (const [i, n] of nodes.entries()) {
7741
+ const witness = graph.witnessIdx;
7742
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7743
+ for (const i of candidates) {
7744
+ const n = nodes[i];
7240
7745
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
7241
7746
  violations.push({
7242
- propertyId: "sensitive-data-exfil",
7747
+ propertyId: "sensitive-data-flow",
7243
7748
  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`,
7244
7749
  riskLevel: "critical",
7245
7750
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7246
7751
  });
7752
+ if (witness !== void 0) return violations;
7247
7753
  }
7248
7754
  }
7249
7755
  return violations;
@@ -7274,12 +7780,38 @@ function checkNeverExfilAfterPrivesc(graph) {
7274
7780
  const violations = [];
7275
7781
  const nodes = allNodes(graph);
7276
7782
  if (nodes.length < 2) return violations;
7783
+ const witness = graph.witnessIdx;
7784
+ if (witness !== void 0) {
7785
+ const evIdx = [];
7786
+ nodes.forEach((n, i) => {
7787
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
7788
+ });
7789
+ if (evIdx.length === 0) return violations;
7790
+ const firstEv = Math.min(...evIdx);
7791
+ let pre = null;
7792
+ for (let i = 0; i < firstEv; i++) {
7793
+ if (isPrivescNode(nodes[i])) pre = nodes[i];
7794
+ }
7795
+ if (pre) {
7796
+ const post = nodes[firstEv];
7797
+ return [{
7798
+ propertyId: "no-exfil-after-privesc",
7799
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
7800
+ riskLevel: "critical",
7801
+ path: [
7802
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
7803
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
7804
+ ]
7805
+ }];
7806
+ }
7807
+ return violations;
7808
+ }
7277
7809
  for (let i = 0; i < nodes.length; i++) {
7278
7810
  if (!isPrivescNode(nodes[i])) continue;
7279
7811
  for (let j = i + 1; j < nodes.length; j++) {
7280
7812
  if (isExfilNode(nodes[j])) {
7281
7813
  violations.push({
7282
- propertyId: "no-privesc-then-exfil",
7814
+ propertyId: "no-exfil-after-privesc",
7283
7815
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
7284
7816
  riskLevel: "critical",
7285
7817
  path: [
@@ -7297,12 +7829,38 @@ function checkNeverExfilAfterCred(graph) {
7297
7829
  const violations = [];
7298
7830
  const nodes = allNodes(graph);
7299
7831
  if (nodes.length < 2) return violations;
7832
+ const witness = graph.witnessIdx;
7833
+ if (witness !== void 0) {
7834
+ const evIdx = [];
7835
+ nodes.forEach((n, i) => {
7836
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
7837
+ });
7838
+ if (evIdx.length === 0) return violations;
7839
+ const firstEv = Math.min(...evIdx);
7840
+ let pre = null;
7841
+ for (let i = 0; i < firstEv; i++) {
7842
+ if (isCredNode(nodes[i])) pre = nodes[i];
7843
+ }
7844
+ if (pre) {
7845
+ const post = nodes[firstEv];
7846
+ return [{
7847
+ propertyId: "no-exfil-after-credential",
7848
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
7849
+ riskLevel: "critical",
7850
+ path: [
7851
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
7852
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
7853
+ ]
7854
+ }];
7855
+ }
7856
+ return violations;
7857
+ }
7300
7858
  for (let i = 0; i < nodes.length; i++) {
7301
7859
  if (!isCredNode(nodes[i])) continue;
7302
7860
  for (let j = i + 1; j < nodes.length; j++) {
7303
7861
  if (isExfilNode(nodes[j])) {
7304
7862
  violations.push({
7305
- propertyId: "no-cred-then-exfil",
7863
+ propertyId: "no-exfil-after-credential",
7306
7864
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
7307
7865
  riskLevel: "critical",
7308
7866
  path: [
@@ -7316,11 +7874,172 @@ function checkNeverExfilAfterCred(graph) {
7316
7874
  }
7317
7875
  return violations;
7318
7876
  }
7877
+ var TOOL_REGISTRY = null;
7878
+ function getToolRegistry() {
7879
+ if (TOOL_REGISTRY) return TOOL_REGISTRY;
7880
+ TOOL_REGISTRY = loadToolRegistry().tools || {};
7881
+ return TOOL_REGISTRY;
7882
+ }
7883
+ function lookupToolClass(actionType) {
7884
+ const entry = getToolRegistry()[actionType] || {};
7885
+ return entry.actionClass;
7886
+ }
7887
+ 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;
7888
+ var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
7889
+ function isComputeNode(n) {
7890
+ if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
7891
+ const cls = lookupToolClass(n.actionType);
7892
+ if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
7893
+ return COMPUTE_RE.test(n.actionDetail);
7894
+ }
7895
+ function checkLowIntegrityCommandExec(graph) {
7896
+ const violations = [];
7897
+ const nodes = allNodes(graph);
7898
+ if (nodes.length === 0) return violations;
7899
+ const witness = graph.witnessIdx;
7900
+ const lowResources = /* @__PURE__ */ new Set();
7901
+ const lowAt = /* @__PURE__ */ new Map();
7902
+ nodes.forEach((n, i) => {
7903
+ if (n.isLowSource) {
7904
+ lowAt.set(i, n.actionDetail.slice(0, 60));
7905
+ for (const r of n.resources) lowResources.add(r);
7906
+ }
7907
+ });
7908
+ let changed = true;
7909
+ while (changed) {
7910
+ changed = false;
7911
+ for (let i = 0; i < nodes.length; i++) {
7912
+ if (lowAt.has(i)) continue;
7913
+ let shared = "";
7914
+ for (const r of nodes[i].resources) {
7915
+ if (lowResources.has(r)) {
7916
+ shared = r;
7917
+ break;
7918
+ }
7919
+ }
7920
+ if (shared) {
7921
+ lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
7922
+ for (const r of nodes[i].resources) lowResources.add(r);
7923
+ changed = true;
7924
+ }
7925
+ }
7926
+ }
7927
+ if (lowAt.size === 0) return violations;
7928
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7929
+ for (const i of candidates) {
7930
+ const n = nodes[i];
7931
+ if (lowAt.has(i) && isComputeNode(n)) {
7932
+ violations.push({
7933
+ propertyId: "low-integrity-command-exec",
7934
+ 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`,
7935
+ riskLevel: "critical",
7936
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7937
+ });
7938
+ if (witness !== void 0) return violations;
7939
+ }
7940
+ }
7941
+ return violations;
7942
+ }
7943
+ 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;
7944
+ function checkCrossSessionReachability(graph) {
7945
+ const violations = [];
7946
+ const nodes = allNodes(graph);
7947
+ if (nodes.length === 0) return violations;
7948
+ const witness = graph.witnessIdx;
7949
+ const sources = /* @__PURE__ */ new Set();
7950
+ nodes.forEach((n, i) => {
7951
+ if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
7952
+ });
7953
+ if (sources.size === 0) return violations;
7954
+ const reached = new Set(sources);
7955
+ let changed = true;
7956
+ while (changed) {
7957
+ changed = false;
7958
+ for (const e of allEdges(graph)) {
7959
+ if (reached.has(e.src) && !reached.has(e.dst)) {
7960
+ const srcRes = nodes[e.src].resources;
7961
+ const dstRes = nodes[e.dst].resources;
7962
+ for (const r of srcRes) {
7963
+ if (dstRes.has(r)) {
7964
+ reached.add(e.dst);
7965
+ changed = true;
7966
+ break;
7967
+ }
7968
+ }
7969
+ }
7970
+ }
7971
+ }
7972
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7973
+ for (const i of candidates) {
7974
+ const n = nodes[i];
7975
+ if (sources.has(i)) continue;
7976
+ if (reached.has(i)) {
7977
+ const srcIdx = Math.min(...sources);
7978
+ const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
7979
+ violations.push({
7980
+ propertyId: "low-integrity-reachability",
7981
+ 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`,
7982
+ riskLevel: "high",
7983
+ path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
7984
+ });
7985
+ if (witness !== void 0) return violations;
7986
+ }
7987
+ }
7988
+ return violations;
7989
+ }
7990
+ function checkSensitiveDataAggregation(graph) {
7991
+ const violations = [];
7992
+ const nodes = allNodes(graph);
7993
+ if (nodes.length < 2) return violations;
7994
+ const witness = graph.witnessIdx;
7995
+ const resIndex = /* @__PURE__ */ new Map();
7996
+ nodes.forEach((n, i) => {
7997
+ for (const r of n.resources) {
7998
+ const list = resIndex.get(r);
7999
+ if (list) list.push(i);
8000
+ else resIndex.set(r, [i]);
8001
+ }
8002
+ });
8003
+ const sourcesFor = (exfilIdx) => {
8004
+ const srcs = /* @__PURE__ */ new Set();
8005
+ const n = nodes[exfilIdx];
8006
+ for (const r of n.resources) {
8007
+ for (const j of resIndex.get(r) || []) {
8008
+ if (j === exfilIdx) continue;
8009
+ srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
8010
+ }
8011
+ }
8012
+ return srcs;
8013
+ };
8014
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
8015
+ for (const i of candidates) {
8016
+ const n = nodes[i];
8017
+ if (!isExfilNode(n)) continue;
8018
+ const srcs = sourcesFor(i);
8019
+ if (srcs.size >= 2) {
8020
+ violations.push({
8021
+ propertyId: "sensitive-data-aggregation",
8022
+ reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
8023
+ riskLevel: "critical",
8024
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
8025
+ });
8026
+ if (witness !== void 0) return violations;
8027
+ }
8028
+ }
8029
+ return violations;
8030
+ }
7319
8031
  var ALL_PROPERTIES = [
7320
- { id: "cross-session-chain", check: checkCrossSessionChain },
7321
- { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
7322
- { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
7323
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
8032
+ { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
8033
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
8034
+ { id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
8035
+ { id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
8036
+ { id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
8037
+ // L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
8038
+ { id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
8039
+ // P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
8040
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
8041
+ // P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
8042
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
7324
8043
  ];
7325
8044
 
7326
8045
  // src/engine/formal-engine/engine.ts
@@ -7352,6 +8071,9 @@ var FormalEngine = class {
7352
8071
  });
7353
8072
  }
7354
8073
  const graph = buildMultiSessionGraph(all, aNext);
8074
+ if (aNext) {
8075
+ graph.witnessIdx = allNodes(graph).length - 1;
8076
+ }
7355
8077
  const result = emptyVerificationResult();
7356
8078
  for (const prop of this.properties) {
7357
8079
  let violations;
@@ -7383,7 +8105,38 @@ init_rules_loader();
7383
8105
  var DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
7384
8106
  var NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
7385
8107
  var CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
7386
- var EXFIL_RE2 = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
8108
+ var EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
8109
+ var PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
8110
+ var BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
8111
+ var SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
8112
+ var UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
8113
+ var PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
8114
+ function extractInstances(actionDetail, stage) {
8115
+ const out = [];
8116
+ if (PORT_STAGE_RE.test(stage)) {
8117
+ const d = actionDetail;
8118
+ let m;
8119
+ const re = new RegExp(PORT_RE.source, "gi");
8120
+ while ((m = re.exec(d)) !== null) {
8121
+ const p = m[1] || m[2] || m[3];
8122
+ if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
8123
+ }
8124
+ if (/SCAN/.test(stage)) {
8125
+ const bareRe = new RegExp(BARE_PORT_RE.source, "g");
8126
+ while ((m = bareRe.exec(d)) !== null) {
8127
+ if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
8128
+ }
8129
+ }
8130
+ m = SOCK_RE.exec(d);
8131
+ if (m && m[1]) out.push(`sock:${m[1]}`);
8132
+ m = UNIT_RE.exec(d);
8133
+ if (m && m[1]) out.push(`unit:${m[1]}`);
8134
+ }
8135
+ return out;
8136
+ }
8137
+ function buckKey(chainId, inst) {
8138
+ return inst ? `${chainId}@@${inst}` : chainId;
8139
+ }
7387
8140
  var STAGES_PATTERNS = null;
7388
8141
  var CHAINS = null;
7389
8142
  function loadRules() {
@@ -7426,7 +8179,11 @@ function lifecycleFor(detail, stage, isError) {
7426
8179
  return "";
7427
8180
  }
7428
8181
  var MAX_NODES = 1e5;
7429
- var StatefulChainTracker = class {
8182
+ var StatefulChainTracker = class _StatefulChainTracker {
8183
+ /** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
8184
+ static now() {
8185
+ return (/* @__PURE__ */ new Date()).toISOString();
8186
+ }
7430
8187
  agentId;
7431
8188
  nodes_ = [];
7432
8189
  ptr = {};
@@ -7448,10 +8205,14 @@ var StatefulChainTracker = class {
7448
8205
  for (const chain of chains) {
7449
8206
  const stages = chain.stages || [];
7450
8207
  if (stages.length === 0) continue;
7451
- const matched = this.ptr[String(chain.id || "")] || 0;
8208
+ const cid = String(chain.id || "");
8209
+ let matched = this.ptr[cid] || 0;
8210
+ for (const [k, v] of Object.entries(this.ptr)) {
8211
+ if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
8212
+ }
7452
8213
  if (matched > 0 && matched < stages.length) {
7453
8214
  const pf = {
7454
- chainId: String(chain.id || ""),
8215
+ chainId: cid,
7455
8216
  stages,
7456
8217
  matched,
7457
8218
  riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
@@ -7527,8 +8288,10 @@ var StatefulChainTracker = class {
7527
8288
  actionDetail: ad,
7528
8289
  stage,
7529
8290
  resources: extractResources2(ad),
8291
+ instances: extractInstances(ad, stage),
7530
8292
  isError,
7531
- lifecycle: lifecycleFor(ad, stage, isError)
8293
+ lifecycle: lifecycleFor(ad, stage, isError),
8294
+ timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
7532
8295
  };
7533
8296
  this.nodes_.push(node);
7534
8297
  this.seqNext += 1;
@@ -7536,11 +8299,19 @@ var StatefulChainTracker = class {
7536
8299
  }
7537
8300
  recordLifecycle(node) {
7538
8301
  if (node.lifecycle === "dead") {
7539
- let hit = false;
8302
+ const clearedInsts = [];
8303
+ for (const inst of node.instances) {
8304
+ const key = `inst:${inst}`;
8305
+ if (key in this.aliveResources) {
8306
+ delete this.aliveResources[key];
8307
+ clearedInsts.push(inst);
8308
+ }
8309
+ }
8310
+ let pathHit = false;
7540
8311
  for (const r of node.resources) {
7541
8312
  if (r in this.aliveResources) {
7542
8313
  delete this.aliveResources[r];
7543
- hit = true;
8314
+ pathHit = true;
7544
8315
  }
7545
8316
  }
7546
8317
  const detail = node.actionDetail;
@@ -7548,16 +8319,30 @@ var StatefulChainTracker = class {
7548
8319
  const base = r.split("/").pop() || "";
7549
8320
  if (base && detail.includes(base)) {
7550
8321
  delete this.aliveResources[r];
7551
- hit = true;
8322
+ pathHit = true;
7552
8323
  }
7553
8324
  }
7554
- if (hit) this.resetChainsOnDestroy();
8325
+ if (clearedInsts.length > 0 && !pathHit) {
8326
+ for (const inst of clearedInsts) this.resetChainsForInstance(inst);
8327
+ } else {
8328
+ this.resetChainsOnDestroy();
8329
+ }
7555
8330
  return;
7556
8331
  }
7557
8332
  if (node.lifecycle === "alive") {
7558
8333
  for (const r of node.resources) {
7559
8334
  this.aliveResources[r] = node.seq;
7560
8335
  }
8336
+ for (const inst of node.instances) {
8337
+ this.aliveResources[`inst:${inst}`] = node.seq;
8338
+ }
8339
+ }
8340
+ }
8341
+ /** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
8342
+ resetChainsForInstance(inst) {
8343
+ const key = buckKey("", inst).slice(2);
8344
+ for (const k of Object.keys(this.ptr)) {
8345
+ if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
7561
8346
  }
7562
8347
  }
7563
8348
  resetChainsOnDestroy() {
@@ -7565,6 +8350,29 @@ var StatefulChainTracker = class {
7565
8350
  this.ptr[cid] = 0;
7566
8351
  }
7567
8352
  }
8353
+ /**
8354
+ * 当前节点应推进的链桶集合。
8355
+ * - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
8356
+ * 同时为所有链的该实例建立桶(若首次出现)。
8357
+ * - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
8358
+ * (信息不足时保守推进一切可能,避免漏判)。
8359
+ */
8360
+ targetsForNode(node, chainId) {
8361
+ const insts = node.instances;
8362
+ if (insts.length > 0) {
8363
+ const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
8364
+ if (active.length > 0) return active.map((i) => buckKey(chainId, i));
8365
+ return [chainId];
8366
+ }
8367
+ const targets = [chainId];
8368
+ for (const k of Object.keys(this.aliveResources)) {
8369
+ if (k.startsWith("inst:")) {
8370
+ const inst = k.slice("inst:".length);
8371
+ targets.push(`${chainId}@@${inst}`);
8372
+ }
8373
+ }
8374
+ return targets;
8375
+ }
7568
8376
  advanceChains(node) {
7569
8377
  if (node.lifecycle === "dead") return null;
7570
8378
  if (node.lifecycle === "never_activated") {
@@ -7578,23 +8386,32 @@ var StatefulChainTracker = class {
7578
8386
  const stages = chain.stages || [];
7579
8387
  if (stages.length === 0) continue;
7580
8388
  const cid = String(chain.id || "");
7581
- const cur = this.ptr[cid] || 0;
7582
- if (cur < stages.length && stages[cur] === stage) {
7583
- this.ptr[cid] = cur + 1;
7584
- if (cur + 1 >= stages.length) {
7585
- return {
7586
- chainId: cid,
7587
- stages,
7588
- riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
7589
- reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
7590
- path: this.chainPath(stages)
7591
- };
8389
+ const targets = this.targetsForNode(node, cid);
8390
+ for (const key of targets) {
8391
+ const cur = this.ptr[key] || 0;
8392
+ if (cur < stages.length && stages[cur] === stage) {
8393
+ this.ptr[key] = cur + 1;
8394
+ if (cur + 1 >= stages.length) {
8395
+ return {
8396
+ chainId: cid,
8397
+ stages,
8398
+ riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
8399
+ reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
8400
+ path: this.chainPath(stages)
8401
+ };
8402
+ }
7592
8403
  }
7593
8404
  }
7594
8405
  }
7595
8406
  return null;
7596
8407
  }
7597
8408
  invalidateChainOnFailure(node) {
8409
+ if (node.instances.length > 0) {
8410
+ for (const inst of node.instances) {
8411
+ this.resetChainsForInstance(inst);
8412
+ }
8413
+ return;
8414
+ }
7598
8415
  const failed = new Set(node.resources);
7599
8416
  if (failed.size === 0) return;
7600
8417
  const { chains } = loadRules();
@@ -7610,7 +8427,7 @@ var StatefulChainTracker = class {
7610
8427
  let want = stages[0];
7611
8428
  for (const n of this.nodes_) {
7612
8429
  if (n.stage === want) {
7613
- path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
8430
+ path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
7614
8431
  const idx = stages.indexOf(n.stage);
7615
8432
  if (idx + 1 < stages.length) {
7616
8433
  want = stages[idx + 1];
@@ -7646,7 +8463,7 @@ var StatefulChainTracker = class {
7646
8463
  if (!this.tainted.has(node.seq)) return [];
7647
8464
  if (EXFIL_RE2.test(node.actionDetail)) {
7648
8465
  return [{
7649
- property_id: "sensitive-data-exfil",
8466
+ property_id: "sensitive-data-flow",
7650
8467
  reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
7651
8468
  risk_level: "critical",
7652
8469
  path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
@@ -7688,8 +8505,12 @@ var StatefulChainTracker = class {
7688
8505
  actionDetail: String(raw.actionDetail || ""),
7689
8506
  stage: String(raw.stage || ""),
7690
8507
  resources: [...raw.resources || []],
8508
+ instances: [...raw.instances || []],
8509
+ // 旧快照无此字段 → 空
7691
8510
  isError: Boolean(raw.isError),
7692
- lifecycle: String(raw.lifecycle || "")
8511
+ lifecycle: String(raw.lifecycle || ""),
8512
+ timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
8513
+ // 旧快照无此字段 → null
7693
8514
  });
7694
8515
  }
7695
8516
  this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
@@ -7892,6 +8713,7 @@ function getBrainStore() {
7892
8713
  }
7893
8714
  var formalEngine = new FormalEngine();
7894
8715
  async function runAnalysis(input) {
8716
+ const observedFormalLayers = [];
7895
8717
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
7896
8718
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
7897
8719
  const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
@@ -7917,7 +8739,7 @@ async function runAnalysis(input) {
7917
8739
  riskType: node.l1RiskType,
7918
8740
  confidence: 90,
7919
8741
  source: "heuristic_script_scan",
7920
- violatedLayers: ["1.1-script"]
8742
+ violatedLayers: ["L1.2"]
7921
8743
  };
7922
8744
  }
7923
8745
  }
@@ -7946,8 +8768,8 @@ async function runAnalysis(input) {
7946
8768
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
7947
8769
  const lowerDetail = rawDetail.toLowerCase();
7948
8770
  const agentExt = mergeAgentExtensions(
7949
- getAgentExtensions(input.agentId),
7950
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
8771
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
8772
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
7951
8773
  );
7952
8774
  if (FULLY_SAFE_TOOLS.has(actionType)) {
7953
8775
  return {
@@ -8035,7 +8857,7 @@ async function runAnalysis(input) {
8035
8857
  riskType: inlineJudgment.riskType,
8036
8858
  confidence: 90,
8037
8859
  source: "logic_rule",
8038
- violatedLayers: ["1.2"]
8860
+ violatedLayers: ["L2.1"]
8039
8861
  };
8040
8862
  }
8041
8863
  return {
@@ -8064,15 +8886,22 @@ async function runAnalysis(input) {
8064
8886
  const formalResult = formalEngine.verify(formalSessions, input.aNext);
8065
8887
  if (!formalResult.verified) {
8066
8888
  const v = formalResult.violations[0];
8067
- return {
8068
- allowed: false,
8069
- reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
8070
- riskLevel: v ? v.riskLevel : "high",
8071
- riskType: "formal_verification",
8072
- confidence: 90,
8073
- source: "formal_engine",
8074
- violatedLayers: ["2.4"]
8075
- };
8889
+ if (isFormalObserveMode()) {
8890
+ console.warn(
8891
+ `[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)}`
8892
+ );
8893
+ observedFormalLayers.push("L2.2");
8894
+ } else {
8895
+ return {
8896
+ allowed: false,
8897
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
8898
+ riskLevel: v ? v.riskLevel : "high",
8899
+ riskType: "formal_verification",
8900
+ confidence: 90,
8901
+ source: "formal_engine",
8902
+ violatedLayers: ["L2.2"]
8903
+ };
8904
+ }
8076
8905
  }
8077
8906
  }
8078
8907
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -8087,7 +8916,7 @@ async function runAnalysis(input) {
8087
8916
  riskType: node.l2RiskType,
8088
8917
  confidence: 85,
8089
8918
  source: "logic_rule_script_scan",
8090
- violatedLayers: ["1.2-script"]
8919
+ violatedLayers: ["L2.3"]
8091
8920
  };
8092
8921
  }
8093
8922
  }
@@ -8119,7 +8948,7 @@ async function runAnalysis(input) {
8119
8948
  riskType: "",
8120
8949
  confidence: 85,
8121
8950
  source: "heuristic_and_logic_only",
8122
- violatedLayers: []
8951
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
8123
8952
  };
8124
8953
  }
8125
8954
  let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
@@ -8164,7 +8993,7 @@ async function runAnalysis(input) {
8164
8993
  riskType: "",
8165
8994
  confidence: 50,
8166
8995
  source: "llm_failure_fallback",
8167
- violatedLayers: []
8996
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
8168
8997
  };
8169
8998
  }
8170
8999
  return {
@@ -8174,7 +9003,7 @@ async function runAnalysis(input) {
8174
9003
  riskType: "",
8175
9004
  confidence: 60,
8176
9005
  source: "llm_failure_fallback",
8177
- violatedLayers: ["3"]
9006
+ violatedLayers: ["L3.1"]
8178
9007
  };
8179
9008
  }
8180
9009
  if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
@@ -8191,6 +9020,9 @@ async function runAnalysis(input) {
8191
9020
  } catch {
8192
9021
  }
8193
9022
  }
9023
+ if (observedFormalLayers.length > 0 && llmResult.allowed) {
9024
+ llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
9025
+ }
8194
9026
  return llmResult;
8195
9027
  }
8196
9028
 
@@ -8540,7 +9372,7 @@ async function analyzeRoute(c) {
8540
9372
  const judgment = incMatchedToDeny(inc);
8541
9373
  if (judgment) {
8542
9374
  const layers = [...result.violatedLayers];
8543
- if (!layers.includes("2.4-stateful")) layers.push("2.4-stateful");
9375
+ if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
8544
9376
  console.error(
8545
9377
  `[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
8546
9378
  );
@@ -8554,7 +9386,8 @@ async function analyzeRoute(c) {
8554
9386
  policy: "",
8555
9387
  violated_policy: "",
8556
9388
  confidence: 90,
8557
- analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
9389
+ analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
9390
+ violation_chain: judgment.violation_chain
8558
9391
  })
8559
9392
  );
8560
9393
  }
@@ -8593,17 +9426,20 @@ function getChainStateStoreFor(_agentId) {
8593
9426
  }
8594
9427
  function incMatchedToDeny(inc) {
8595
9428
  if (inc.matched) {
9429
+ const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
8596
9430
  return {
8597
9431
  property_id: "cross-" + inc.matched.chainId,
8598
9432
  reason: inc.matched.reason,
8599
- risk_level: inc.matched.riskLevel
9433
+ risk_level: inc.matched.riskLevel,
9434
+ violation_chain: chain
8600
9435
  };
8601
9436
  }
8602
9437
  if (inc.exfil && inc.exfil.length > 0) {
8603
9438
  return {
8604
- property_id: "sensitive-data-exfil",
9439
+ property_id: "sensitive-data-flow",
8605
9440
  reason: inc.exfil[0].reason,
8606
- risk_level: inc.exfil[0].risk_level
9441
+ risk_level: inc.exfil[0].risk_level,
9442
+ violation_chain: []
8607
9443
  };
8608
9444
  }
8609
9445
  return null;
@@ -8682,12 +9518,13 @@ init_esm_shims();
8682
9518
  init_esm_shims();
8683
9519
  init_runtime_config();
8684
9520
  init_token_stats();
9521
+ init_rules_loader();
8685
9522
  import { Hono } from "hono";
8686
9523
  import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
8687
- import { join as join9, dirname as dirname10 } from "path";
9524
+ import { join as join10, dirname as dirname10 } from "path";
8688
9525
  import { fileURLToPath as fileURLToPath9 } from "url";
8689
9526
  var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8690
- var STATIC_DIR = join9(__dirname8, "static");
9527
+ var STATIC_DIR = join10(__dirname8, "static");
8691
9528
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
8692
9529
  function getConsoleToken() {
8693
9530
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -8711,7 +9548,7 @@ function createConsoleRouter() {
8711
9548
  const console2 = new Hono();
8712
9549
  console2.use("/api/*", authMiddleware());
8713
9550
  const serveIndex = (c) => {
8714
- const indexFile = join9(STATIC_DIR, "index.html");
9551
+ const indexFile = join10(STATIC_DIR, "index.html");
8715
9552
  if (existsSync14(indexFile)) {
8716
9553
  return c.html(readFileSync15(indexFile, "utf-8"));
8717
9554
  }
@@ -8728,24 +9565,89 @@ function createConsoleRouter() {
8728
9565
  const rc = updateLayerEnabled(body.layer_key, body.enabled);
8729
9566
  return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
8730
9567
  });
9568
+ function getL22View() {
9569
+ const ltlHuman = {
9570
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9571
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9572
+ "mc-ltl-003": "\u5148\u7981\u7528\u4E86\u5B89\u5168\u9632\u62A4\uFF0C\u4E4B\u540E\u51FA\u73B0\u7834\u574F\u6027\u64CD\u4F5C\uFF08\u6267\u884C/\u5220\u9664/\u63D0\u6743\uFF09",
9573
+ "mc-ltl-004": "\u5148\u51FA\u73B0\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4E4B\u540E\u51FA\u73B0\u654F\u611F\u64CD\u4F5C\uFF08\u5916\u4F20/\u5199\u6587\u4EF6/\u6267\u884C\uFF09",
9574
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9575
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9576
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9577
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9578
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9579
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9580
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9581
+ };
9582
+ const mc = loadMC();
9583
+ const rules = mc.rules || [];
9584
+ return {
9585
+ enabled_via: "L2_logic_rules",
9586
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9587
+ ltl_rules: rules.map((r) => ({
9588
+ id: r.id,
9589
+ type: r.type,
9590
+ formula: Array.isArray(r.formula) ? "<\u516C\u5F0F>" : r.formula,
9591
+ reason: r.reason,
9592
+ risk_level: r.risk_level,
9593
+ action: r.action,
9594
+ enabled: r.enabled !== false,
9595
+ human: ltlHuman[r.id] || r.reason || ""
9596
+ }))
9597
+ };
9598
+ }
9599
+ function withL22View(rc) {
9600
+ if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
9601
+ const cat = rc.L2_2.ltl_rules;
9602
+ const rls = cat && cat.rules || [];
9603
+ const ltlHumanLocal = {
9604
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9605
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9606
+ "mc-ltl-003": "\u5148\u7981\u7528\u4E86\u5B89\u5168\u9632\u62A4\uFF0C\u4E4B\u540E\u51FA\u73B0\u7834\u574F\u6027\u64CD\u4F5C\uFF08\u6267\u884C/\u5220\u9664/\u63D0\u6743\uFF09",
9607
+ "mc-ltl-004": "\u5148\u51FA\u73B0\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4E4B\u540E\u51FA\u73B0\u654F\u611F\u64CD\u4F5C\uFF08\u5916\u4F20/\u5199\u6587\u4EF6/\u6267\u884C\uFF09",
9608
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9609
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9610
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9611
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9612
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9613
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9614
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9615
+ };
9616
+ rc.L2_2 = {
9617
+ enabled_via: "L2_logic_rules",
9618
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9619
+ ltl_rules: rls.map((r) => ({
9620
+ id: r.id,
9621
+ type: "ltl",
9622
+ formula: "<\u516C\u5F0F>",
9623
+ reason: r.reason || "",
9624
+ risk_level: r.risk_level || "high",
9625
+ action: r.action || "block",
9626
+ enabled: r.enabled !== false,
9627
+ human: ltlHumanLocal[r.id] || r.reason || ""
9628
+ }))
9629
+ };
9630
+ }
9631
+ return rc;
9632
+ }
8731
9633
  console2.get("/api/rules", (c) => {
8732
9634
  const rc = loadRuntimeConfig();
8733
- return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules });
9635
+ return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
8734
9636
  });
8735
9637
  console2.put("/api/rules/enabled", async (c) => {
8736
9638
  const b = await c.req.json();
8737
9639
  const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
8738
- return c.json(rc);
9640
+ return c.json(withL22View(rc));
8739
9641
  });
8740
9642
  console2.put("/api/rules/deny_mode", async (c) => {
8741
9643
  const b = await c.req.json();
8742
9644
  const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
8743
- return c.json(rc);
9645
+ return c.json(withL22View(rc));
8744
9646
  });
8745
9647
  console2.put("/api/rules/skip_l3", async (c) => {
8746
9648
  const b = await c.req.json();
8747
9649
  const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
8748
- return c.json(rc);
9650
+ return c.json(withL22View(rc));
8749
9651
  });
8750
9652
  console2.put("/api/categories/enabled", async (c) => {
8751
9653
  const b = await c.req.json();
@@ -8804,9 +9706,9 @@ function createConsoleRouter() {
8804
9706
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
8805
9707
  return c.json({ error: "invalid skill_id" }, 400);
8806
9708
  }
8807
- const skillPath = join9(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
9709
+ const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
8808
9710
  if (!existsSync14(skillPath)) {
8809
- const builtinPath = join9(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
9711
+ const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
8810
9712
  if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
8811
9713
  return c.json({ error: "skill not found" }, 404);
8812
9714
  }
@@ -8895,7 +9797,7 @@ function createConsoleRouter() {
8895
9797
  });
8896
9798
  console2.get("/api/config", (c) => {
8897
9799
  const rc = loadRuntimeConfig();
8898
- return c.json(rc);
9800
+ return c.json({ ...rc, L2_2: getL22View() });
8899
9801
  });
8900
9802
  console2.get("/api/env-overrides", (c) => {
8901
9803
  return c.json({ overrides: getEnvOverrides() });
@@ -8972,4 +9874,17 @@ export {
8972
9874
  findFreePort,
8973
9875
  startServer
8974
9876
  };
9877
+ //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
9878
+ //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
9879
+ //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
9880
+ //!
9881
+ //! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
9882
+ //! P1 cross-session-chain 跨会话组合链
9883
+ //! P2 sensitive-data-flow 敏感数据传播
9884
+ //! P3 low-integrity-reachability 低完整性数据流可达
9885
+ //! P4 no-exfil-after-privesc 提权后禁外传
9886
+ //! P5 no-exfil-after-credential 凭据后禁外传
9887
+ //! P6 mc-ltl-rules LTL 模型检测
9888
+ //! P7 low-integrity-command-exec 低完整性命令执行
9889
+ //! P8 sensitive-data-aggregation 敏感数据聚合外传
8975
9890
  //# sourceMappingURL=server.js.map