@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/cli.js CHANGED
@@ -138,6 +138,7 @@ __export(config_exports, {
138
138
  getRetryConfig: () => getRetryConfig,
139
139
  getTimeoutConfig: () => getTimeoutConfig,
140
140
  isBrainEnabled: () => isBrainEnabled,
141
+ isFormalObserveMode: () => isFormalObserveMode,
141
142
  isHeuristicEnabled: () => isHeuristicEnabled,
142
143
  isLLMAvailable: () => isLLMAvailable,
143
144
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -253,6 +254,7 @@ function getCustomRules() {
253
254
  }
254
255
  function isLLMAvailable() {
255
256
  if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
257
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
256
258
  return llmConfig.apiKey.length > 0;
257
259
  }
258
260
  async function probeLLMConnection(timeoutMs = 8e3) {
@@ -317,13 +319,26 @@ function getProviderHeaders(provider) {
317
319
  return PROVIDER_HEADERS[provider] || {};
318
320
  }
319
321
  function isHeuristicEnabled() {
320
- return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
322
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
323
+ return layerEnabledOrDefault("L1_heuristic");
324
+ }
325
+ function layerEnabledOrDefault(key) {
326
+ try {
327
+ const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
328
+ return isLayerEnabled2(key);
329
+ } catch {
330
+ return true;
331
+ }
321
332
  }
322
333
  function isBrainEnabled() {
323
334
  return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
324
335
  }
325
336
  function isLogicRulesEnabled() {
326
- return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
337
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
338
+ return layerEnabledOrDefault("L2_logic_rules");
339
+ }
340
+ function isFormalObserveMode() {
341
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
327
342
  }
328
343
  var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
329
344
  var init_config = __esm({
@@ -581,8 +596,12 @@ var init_inline_analyzer = __esm({
581
596
 
582
597
  // src/infra/rules-loader.ts
583
598
  import { readFileSync as readFileSync2, existsSync } from "fs";
584
- import { resolve, dirname } from "path";
599
+ import { resolve, dirname, join as join2 } from "path";
585
600
  import { fileURLToPath as fileURLToPath3 } from "url";
601
+ import { homedir as homedir2 } from "os";
602
+ function dataRootDir() {
603
+ return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
604
+ }
586
605
  function findRulesFile(name) {
587
606
  for (const dir of RULES_DIR_CANDS) {
588
607
  const p = resolve(dir, name);
@@ -608,6 +627,24 @@ function loadL2() {
608
627
  function loadL3() {
609
628
  return loadJson("l3_rules.json");
610
629
  }
630
+ function loadMC() {
631
+ const mc = loadJson("mc_rules.json");
632
+ const rules = mc.rules || [];
633
+ try {
634
+ const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
635
+ if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
636
+ const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
637
+ const enabledById = {};
638
+ for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
639
+ for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
640
+ }
641
+ } catch {
642
+ }
643
+ return mc;
644
+ }
645
+ function loadToolRegistry() {
646
+ return loadJson("tool_registry.json");
647
+ }
611
648
  var __filename2, __dirname2, RULES_DIR_CANDS;
612
649
  var init_rules_loader = __esm({
613
650
  "src/infra/rules-loader.ts"() {
@@ -617,6 +654,10 @@ var init_rules_loader = __esm({
617
654
  __dirname2 = dirname(__filename2);
618
655
  RULES_DIR_CANDS = (() => {
619
656
  const cands = [
657
+ // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
658
+ // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
659
+ // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
660
+ resolve(dataRootDir(), "rules"),
620
661
  // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
621
662
  resolve(__dirname2, "../rules"),
622
663
  resolve(__dirname2, "rules"),
@@ -640,7 +681,8 @@ function _loadSensitivePathScope(scope) {
640
681
  path: sp.path,
641
682
  riskLevel: sp.risk_level,
642
683
  desc: sp.desc,
643
- credential: sp.deny_mode === "deny_both" ? true : void 0
684
+ credential: sp.deny_mode === "deny_both" ? true : void 0,
685
+ dirDelOnly: sp.dir_del_only === true ? true : void 0
644
686
  }));
645
687
  }
646
688
  function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
@@ -738,7 +780,7 @@ function runLogicCheck(input) {
738
780
  riskType: "intent_deviation",
739
781
  confidence: 85,
740
782
  source: "logic_rule",
741
- violatedLayers: ["2"]
783
+ violatedLayers: ["L2.1"]
742
784
  };
743
785
  }
744
786
  }
@@ -762,7 +804,7 @@ function runLogicCheck(input) {
762
804
  riskType: "privilege_escalation",
763
805
  confidence: 85,
764
806
  source: "logic_rule",
765
- violatedLayers: ["2"]
807
+ violatedLayers: ["L2.1"]
766
808
  };
767
809
  }
768
810
  }
@@ -814,7 +856,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
814
856
  riskType: "combo_chain",
815
857
  confidence: 85,
816
858
  source: "logic_rule",
817
- violatedLayers: ["2"]
859
+ violatedLayers: ["L2.1"]
818
860
  };
819
861
  }
820
862
  }
@@ -939,7 +981,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
939
981
  riskType: "read_before_write",
940
982
  confidence: 85,
941
983
  source: "logic_rule",
942
- violatedLayers: ["2"]
984
+ violatedLayers: ["L2.1"]
943
985
  };
944
986
  }
945
987
  return null;
@@ -959,7 +1001,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
959
1001
  riskType: "consent_missing",
960
1002
  confidence: 85,
961
1003
  source: "logic_rule",
962
- violatedLayers: ["2"]
1004
+ violatedLayers: ["L2.1"]
963
1005
  };
964
1006
  }
965
1007
  function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
@@ -977,7 +1019,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
977
1019
  riskType: "consent_missing",
978
1020
  confidence: 85,
979
1021
  source: "logic_rule",
980
- violatedLayers: ["2"]
1022
+ violatedLayers: ["L2.1"]
981
1023
  };
982
1024
  }
983
1025
  function containsPrivilegeEscalation(actionDetail) {
@@ -1037,7 +1079,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1037
1079
  riskType: "file_access",
1038
1080
  confidence: 95,
1039
1081
  source: "logic_rule",
1040
- violatedLayers: ["2"],
1082
+ violatedLayers: ["L2.1"],
1041
1083
  isCredential: true
1042
1084
  };
1043
1085
  }
@@ -1053,7 +1095,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1053
1095
  riskType: "file_access",
1054
1096
  confidence: 95,
1055
1097
  source: "logic_rule",
1056
- violatedLayers: ["2"]
1098
+ violatedLayers: ["L2.1"]
1057
1099
  };
1058
1100
  }
1059
1101
  }
@@ -1067,7 +1109,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1067
1109
  riskType: "file_access",
1068
1110
  confidence: 70,
1069
1111
  source: "logic_rule",
1070
- violatedLayers: ["2"]
1112
+ violatedLayers: ["L2.1"]
1071
1113
  };
1072
1114
  }
1073
1115
  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;
@@ -1079,7 +1121,7 @@ var init_logic_rules = __esm({
1079
1121
  init_rules_loader();
1080
1122
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1081
1123
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1082
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
1124
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1083
1125
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1084
1126
  "ask_user_question",
1085
1127
  "glob",
@@ -1354,6 +1396,7 @@ var init_skill_loader = __esm({
1354
1396
  // src/infra/runtime-config.ts
1355
1397
  var runtime_config_exports = {};
1356
1398
  __export(runtime_config_exports, {
1399
+ DEFAULT_LAYERS: () => DEFAULT_LAYERS,
1357
1400
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1358
1401
  addAgentRule: () => addAgentRule,
1359
1402
  addCustomRule: () => addCustomRule,
@@ -1405,7 +1448,7 @@ __export(runtime_config_exports, {
1405
1448
  updateSkillEnabled: () => updateSkillEnabled
1406
1449
  });
1407
1450
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1408
- import { join as join2 } from "path";
1451
+ import { join as join3 } from "path";
1409
1452
  import { fileURLToPath as fileURLToPath5 } from "url";
1410
1453
  import { dirname as dirname3 } from "path";
1411
1454
  function runtimeDir() {
@@ -1429,6 +1472,7 @@ function emptyRuntimeConfig() {
1429
1472
  L1_rules: {},
1430
1473
  L2_rules: {},
1431
1474
  L3_skills: {},
1475
+ L2_2: {},
1432
1476
  agent_rules: {},
1433
1477
  scenario_rules: {}
1434
1478
  };
@@ -1456,6 +1500,7 @@ function loadRuntimeConfig() {
1456
1500
  L1_rules: merged.L1_rules || {},
1457
1501
  L2_rules: merged.L2_rules || {},
1458
1502
  L3_skills: merged.L3_skills || {},
1503
+ L2_2: merged.L2_2 || {},
1459
1504
  agent_rules: merged.agent_rules || {},
1460
1505
  scenario_rules: merged.scenario_rules || {}
1461
1506
  };
@@ -1508,6 +1553,7 @@ function getEnabledL2SensitivePaths(runtime) {
1508
1553
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1509
1554
  if (r.credential) out.credential = true;
1510
1555
  if (r.read_only) out.read_only = true;
1556
+ if (r.dir_del_only) out.dir_del_only = true;
1511
1557
  return out;
1512
1558
  });
1513
1559
  }
@@ -1616,6 +1662,7 @@ function _l2SensitivePathsToRules() {
1616
1662
  builtin: true
1617
1663
  };
1618
1664
  _syncDenyModeFields(rule);
1665
+ if (sp.dirDelOnly) rule.dir_del_only = true;
1619
1666
  return rule;
1620
1667
  });
1621
1668
  }
@@ -1703,7 +1750,7 @@ function _scenarioRulesToDefaults() {
1703
1750
  return out;
1704
1751
  }
1705
1752
  for (const scenarioId of entries) {
1706
- const rulesFile = join2(scenDir, scenarioId, "rules.json");
1753
+ const rulesFile = join3(scenDir, scenarioId, "rules.json");
1707
1754
  if (!existsSync4(rulesFile)) continue;
1708
1755
  let data;
1709
1756
  try {
@@ -1752,7 +1799,7 @@ function _agentRulesToDefaults() {
1752
1799
  return out;
1753
1800
  }
1754
1801
  for (const agentId of entries) {
1755
- const rulesFile = join2(agentsDir, agentId, "rules.json");
1802
+ const rulesFile = join3(agentsDir, agentId, "rules.json");
1756
1803
  if (!existsSync4(rulesFile)) continue;
1757
1804
  let data;
1758
1805
  try {
@@ -1793,7 +1840,7 @@ function _agentRulesToDefaults() {
1793
1840
  function generateSourceDefaults() {
1794
1841
  return {
1795
1842
  version: 1,
1796
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1843
+ layers: { ...DEFAULT_LAYERS },
1797
1844
  L1_rules: {
1798
1845
  dangerous_commands: {
1799
1846
  category_enabled: true,
@@ -1832,10 +1879,45 @@ function generateSourceDefaults() {
1832
1879
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1833
1880
  },
1834
1881
  L3_skills: _l3SkillsToRules(),
1882
+ L2_2: _ltlRulesToRuntime(),
1835
1883
  agent_rules: _agentRulesToDefaults(),
1836
1884
  scenario_rules: _scenarioRulesToDefaults()
1837
1885
  };
1838
1886
  }
1887
+ function _ltlRulesToRuntime() {
1888
+ const cands = [
1889
+ join3(__dirname4, "..", "rules"),
1890
+ join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1891
+ join3(process.cwd(), "agent_moss", "rules")
1892
+ ];
1893
+ let mc = {};
1894
+ for (const dir of cands) {
1895
+ const p = join3(dir, "mc_rules.json");
1896
+ if (existsSync4(p)) {
1897
+ try {
1898
+ mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1899
+ } catch {
1900
+ mc = {};
1901
+ }
1902
+ break;
1903
+ }
1904
+ }
1905
+ const rules = mc.rules || [];
1906
+ return {
1907
+ ltl_rules: {
1908
+ category_enabled: true,
1909
+ category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
1910
+ rules: rules.map((r) => ({
1911
+ id: r.id,
1912
+ reason: r.reason || "",
1913
+ risk_level: r.risk_level || "high",
1914
+ action: r.action || "block",
1915
+ enabled: true,
1916
+ builtin: true
1917
+ }))
1918
+ }
1919
+ };
1920
+ }
1839
1921
  function normalizePatternForFingerprint(pattern) {
1840
1922
  return pattern.replace(/\\\//g, "/");
1841
1923
  }
@@ -1985,6 +2067,11 @@ function mergeConfig(userCfg, srcDefaults) {
1985
2067
  );
1986
2068
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1987
2069
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2070
+ if (!cfg.L2_2 && cfg.L2_4) {
2071
+ cfg.L2_2 = cfg.L2_4;
2072
+ delete cfg.L2_4;
2073
+ }
2074
+ _mergeRuleCategories(cfg, srcDefaults, "L2_2");
1988
2075
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1989
2076
  _mergeAgentRules(cfg, srcDefaults);
1990
2077
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2177,12 +2264,12 @@ function deleteCustomRule(layer, category, ruleId) {
2177
2264
  function addCustomSkill(skillId, category, keywords, content) {
2178
2265
  _validateSkillId(skillId);
2179
2266
  const runtime = loadRuntimeConfig();
2180
- const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2267
+ const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2181
2268
  try {
2182
2269
  mkdirSync(skillsDir, { recursive: true });
2183
2270
  } catch {
2184
2271
  }
2185
- writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
2272
+ writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2186
2273
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2187
2274
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2188
2275
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2197,7 +2284,7 @@ function deleteCustomSkill(skillId, category) {
2197
2284
  if (idx >= 0) {
2198
2285
  if (skills[idx].builtin) return null;
2199
2286
  skills.splice(idx, 1);
2200
- const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2287
+ const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2201
2288
  try {
2202
2289
  if (existsSync4(skillPath)) unlinkSync(skillPath);
2203
2290
  } catch {
@@ -2391,7 +2478,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2391
2478
  saveRuntimeConfig(runtime);
2392
2479
  return runtime;
2393
2480
  }
2394
- 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;
2481
+ 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;
2395
2482
  var init_runtime_config = __esm({
2396
2483
  "src/infra/runtime-config.ts"() {
2397
2484
  "use strict";
@@ -2403,21 +2490,28 @@ var init_runtime_config = __esm({
2403
2490
  __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2404
2491
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2405
2492
  RUNTIME_CONFIG_DIR = runtimeDir();
2406
- RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2493
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2407
2494
  LAYER_DISABLE_ENV = {
2408
2495
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2409
2496
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2410
2497
  L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2411
2498
  };
2499
+ DEFAULT_LAYERS = {
2500
+ L1_heuristic: true,
2501
+ L2_logic_rules: true,
2502
+ L3_llm_analysis: true,
2503
+ agent_rules_enabled: true,
2504
+ scenario_rules_enabled: true
2505
+ };
2412
2506
  DENY_MODE_LABELS = {
2413
2507
  deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2414
2508
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2415
2509
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2416
2510
  };
2417
- _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2418
- _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2419
- _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2420
- _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2511
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2512
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2513
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2514
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2421
2515
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2422
2516
  }
2423
2517
  });
@@ -2555,11 +2649,21 @@ function getScenarioExtensions(scenarioId) {
2555
2649
  );
2556
2650
  return ext;
2557
2651
  }
2652
+ function getScenarioExtensionsMulti(scenarioSpec) {
2653
+ if (!scenarioSpec) return null;
2654
+ const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
2655
+ if (ids.length === 0) return null;
2656
+ const exts = ids.map((id) => getScenarioExtensions(id));
2657
+ const nonNull = exts.filter((e) => e !== null);
2658
+ if (nonNull.length === 0) return null;
2659
+ return mergeAgentExtensions(...nonNull);
2660
+ }
2558
2661
  var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2559
2662
  var init_scenarios = __esm({
2560
2663
  "src/scenarios/index.ts"() {
2561
2664
  "use strict";
2562
2665
  init_esm_shims();
2666
+ init_agents();
2563
2667
  __filename5 = fileURLToPath7(import.meta.url);
2564
2668
  __dirname6 = dirname5(__filename5);
2565
2669
  SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
@@ -2587,7 +2691,7 @@ function runHeuristicCheck(input) {
2587
2691
  riskType: "sensitive_action",
2588
2692
  confidence: 90,
2589
2693
  source: "heuristic",
2590
- violatedLayers: ["1.1"]
2694
+ violatedLayers: ["L1.1"]
2591
2695
  };
2592
2696
  }
2593
2697
  }
@@ -2597,8 +2701,10 @@ function runHeuristicCheck(input) {
2597
2701
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2598
2702
  const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
2599
2703
  let topRiskType = "";
2704
+ const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2600
2705
  for (const rule of DANGEROUS_PATTERNS) {
2601
2706
  if (rule.pattern.test(scanCommand)) {
2707
+ if (exemptKeygen && rule.riskType === "file_access") continue;
2602
2708
  matched.push(rule.description);
2603
2709
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
2604
2710
  highestSeverity = rule.severity;
@@ -2608,8 +2714,8 @@ function runHeuristicCheck(input) {
2608
2714
  }
2609
2715
  highestRiskType = topRiskType || highestRiskType;
2610
2716
  const agentExt = mergeAgentExtensions(
2611
- getAgentExtensions(input.agentId),
2612
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2717
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
2718
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
2613
2719
  );
2614
2720
  if (agentExt?.extra_command_patterns) {
2615
2721
  for (const rule of agentExt.extra_command_patterns) {
@@ -2640,10 +2746,26 @@ function runHeuristicCheck(input) {
2640
2746
  const detailL = input.aNext.actionDetail.toLowerCase();
2641
2747
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2642
2748
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2643
- if (dangerousResult) return dangerousResult;
2644
- if (!isExcludedType) {
2645
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
2646
- if (pathResult) return pathResult;
2749
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
2750
+ const homeResult = checkHomeDeletion(actionTypeL, detailL);
2751
+ const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2752
+ if (mergeResults.length > 0) {
2753
+ let best = mergeResults[0];
2754
+ for (const r of mergeResults) {
2755
+ if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
2756
+ }
2757
+ if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
2758
+ return {
2759
+ allowed: false,
2760
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
2761
+ riskLevel: highestSeverity,
2762
+ riskType: highestRiskType,
2763
+ confidence: 95,
2764
+ source: "heuristic",
2765
+ violatedLayers: ["L1.1"]
2766
+ };
2767
+ }
2768
+ return best;
2647
2769
  }
2648
2770
  if (matched.length > 0) {
2649
2771
  return {
@@ -2653,8 +2775,7 @@ function runHeuristicCheck(input) {
2653
2775
  riskType: highestRiskType,
2654
2776
  confidence: 95,
2655
2777
  source: "heuristic",
2656
- violatedLayers: ["1.1"]
2657
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
2778
+ violatedLayers: ["L1.1"]
2658
2779
  };
2659
2780
  }
2660
2781
  const lateralResult = checkLateralMovement(detailL);
@@ -2782,6 +2903,7 @@ function isSearchActionType(actionType) {
2782
2903
  function checkSensitivePath(command, cwd, agentExt) {
2783
2904
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2784
2905
  const isWriteOpFlag = isWriteOperation("", command);
2906
+ const keygenGenerate = isSshKeygenGenerate(command);
2785
2907
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2786
2908
  const pathMatches = (text, path6) => {
2787
2909
  const parts = path6.split(/(\(\?!.*?\))/);
@@ -2794,6 +2916,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2794
2916
  };
2795
2917
  for (const sp of ROOT_SENSITIVE_PATHS) {
2796
2918
  if (pathMatches(pathScanText, sp.path.toLowerCase())) {
2919
+ if (keygenGenerate) continue;
2797
2920
  if (sp.path === "/dev/random") {
2798
2921
  if (isWriteOpFlag) {
2799
2922
  continue;
@@ -2805,7 +2928,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2805
2928
  riskType: "file_access",
2806
2929
  confidence: 90,
2807
2930
  source: "heuristic",
2808
- violatedLayers: ["1.1"]
2931
+ violatedLayers: ["L1.1"]
2809
2932
  };
2810
2933
  }
2811
2934
  if (!isWriteOpFlag && !sp.credential) {
@@ -2818,16 +2941,51 @@ function checkSensitivePath(command, cwd, agentExt) {
2818
2941
  riskType: "file_access",
2819
2942
  confidence: 90,
2820
2943
  source: "heuristic",
2821
- violatedLayers: ["1.1"],
2944
+ violatedLayers: ["L1.1"],
2822
2945
  isCredential: !!sp.credential
2823
2946
  };
2824
2947
  }
2825
2948
  }
2826
2949
  for (const sp of HOME_SENSITIVE_PATHS) {
2827
2950
  const expanded = `${expandedCwd}/${sp.path}`;
2828
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2829
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2830
- if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
2951
+ let pPath = sp.path;
2952
+ if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
2953
+ let dirPrefixMatch = null;
2954
+ if (sp.dirDelOnly) {
2955
+ const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
2956
+ dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
2957
+ }
2958
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
2959
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
2960
+ if (pathReMatch || expandedReMatch) {
2961
+ if (keygenGenerate) continue;
2962
+ if (sp.dirDelOnly) {
2963
+ if (isDeleteActionForDir(command, sp.path)) {
2964
+ return {
2965
+ allowed: false,
2966
+ reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2967
+ riskLevel: sp.riskLevel,
2968
+ riskType: "file_access",
2969
+ confidence: 90,
2970
+ source: "heuristic",
2971
+ violatedLayers: ["L1.1"],
2972
+ isCredential: !!sp.credential
2973
+ };
2974
+ }
2975
+ if (isArchiveReadOfDir(command, sp.path)) {
2976
+ return {
2977
+ allowed: false,
2978
+ reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2979
+ riskLevel: sp.riskLevel,
2980
+ riskType: "file_access",
2981
+ confidence: 90,
2982
+ source: "heuristic",
2983
+ violatedLayers: ["L1.1"],
2984
+ isCredential: !!sp.credential
2985
+ };
2986
+ }
2987
+ continue;
2988
+ }
2831
2989
  if (!isWriteOpFlag && !sp.credential) {
2832
2990
  continue;
2833
2991
  }
@@ -2838,7 +2996,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2838
2996
  riskType: "file_access",
2839
2997
  confidence: 90,
2840
2998
  source: "heuristic",
2841
- violatedLayers: ["1.1"],
2999
+ violatedLayers: ["L1.1"],
2842
3000
  isCredential: !!sp.credential
2843
3001
  };
2844
3002
  }
@@ -2861,7 +3019,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2861
3019
  riskType: "file_access",
2862
3020
  confidence: 90,
2863
3021
  source: "heuristic",
2864
- violatedLayers: ["1.1"],
3022
+ violatedLayers: ["L1.1"],
2865
3023
  isCredential: !!sp.credential
2866
3024
  };
2867
3025
  }
@@ -2878,7 +3036,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2878
3036
  riskType: "file_access",
2879
3037
  confidence: 90,
2880
3038
  source: "heuristic",
2881
- violatedLayers: ["1.1"],
3039
+ violatedLayers: ["L1.1"],
2882
3040
  isCredential: true
2883
3041
  };
2884
3042
  }
@@ -2891,18 +3049,142 @@ function checkSensitivePath(command, cwd, agentExt) {
2891
3049
  riskType: "file_access",
2892
3050
  confidence: 90,
2893
3051
  source: "heuristic",
2894
- violatedLayers: ["1.1"]
3052
+ violatedLayers: ["L1.1"]
2895
3053
  };
2896
3054
  }
2897
3055
  return null;
2898
3056
  }
3057
+ function isHomeValue(path6) {
3058
+ let p = path6.trim().toLowerCase();
3059
+ if (p.startsWith("$(") && p.endsWith(")")) {
3060
+ const inner = p.slice(2, -1).trim();
3061
+ return HOME_TARGET_RE.test(" " + inner);
3062
+ }
3063
+ if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
3064
+ return HOME_TARGET_RE.test(" " + p);
3065
+ }
3066
+ function isDeleteCommand(seg) {
3067
+ const s = seg.trimStart();
3068
+ if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
3069
+ return /删除|移除/.test(s);
3070
+ }
3071
+ function homeEscapeTarget(seg) {
3072
+ const low = seg.toLowerCase();
3073
+ const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
3074
+ let m;
3075
+ while ((m = re.exec(low)) !== null) {
3076
+ const endIdx = m.index + m[0].length;
3077
+ const tail = low.slice(endIdx);
3078
+ if (!tail) continue;
3079
+ let token = tail.trimStart().split(/\s+/)[0] || "";
3080
+ token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
3081
+ token = token.replace(/^['"]|['"]$/g, "");
3082
+ const segments = token.split("/").filter((s) => s && s !== ".");
3083
+ if (!segments.some((s) => s === "..")) continue;
3084
+ let depth = 0;
3085
+ let minDepth = 0;
3086
+ for (const s of segments) {
3087
+ depth += s === ".." ? -1 : 1;
3088
+ minDepth = Math.min(minDepth, depth);
3089
+ }
3090
+ if (minDepth < 0) return true;
3091
+ if (depth <= 0) return true;
3092
+ }
3093
+ return false;
3094
+ }
3095
+ function checkHomeDeletion(actionType, actionDetail) {
3096
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
3097
+ const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
3098
+ for (const seg of segs) {
3099
+ if (!isDeleteCommand(seg)) continue;
3100
+ if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
3101
+ return {
3102
+ allowed: false,
3103
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
3104
+ riskLevel: "high",
3105
+ riskType: "script_execution",
3106
+ confidence: 90,
3107
+ source: "heuristic",
3108
+ violatedLayers: ["L1.1"]
3109
+ };
3110
+ }
3111
+ }
3112
+ const assigned = {};
3113
+ for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
3114
+ for (const [varName, path6] of Object.entries(assigned)) {
3115
+ if (!isHomeValue(path6)) continue;
3116
+ const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
3117
+ for (const seg of segs) {
3118
+ if (!isDeleteCommand(seg)) continue;
3119
+ if (varRefRe.test(seg)) {
3120
+ return {
3121
+ allowed: false,
3122
+ reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
3123
+ riskLevel: "high",
3124
+ riskType: "script_execution",
3125
+ confidence: 90,
3126
+ source: "heuristic",
3127
+ violatedLayers: ["L1.1"]
3128
+ };
3129
+ }
3130
+ }
3131
+ }
3132
+ return null;
3133
+ }
3134
+ function escapeRegExp(s) {
3135
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3136
+ }
3137
+ function isDeleteActionForDir(command, dirPath) {
3138
+ const low = command.toLowerCase();
3139
+ const dpath = dirPath.toLowerCase();
3140
+ if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
3141
+ return true;
3142
+ }
3143
+ if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
3144
+ return true;
3145
+ }
3146
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3147
+ const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3148
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
3149
+ return true;
3150
+ }
3151
+ }
3152
+ return false;
3153
+ }
3154
+ function isArchiveReadOfDir(command, dirPath) {
3155
+ const low = command.toLowerCase();
3156
+ const dpath = dirPath.toLowerCase();
3157
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3158
+ const s = seg.trim();
3159
+ if (!s || !s.includes(dpath)) continue;
3160
+ const parts = s.split(/\s+/);
3161
+ const fw = parts[0] || "";
3162
+ if (fw === "zip") return true;
3163
+ if (fw === "tar") {
3164
+ if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
3165
+ return true;
3166
+ }
3167
+ if (fw === "cp" || fw === "rsync") {
3168
+ const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3169
+ if (segPaths.length && segPaths[0].includes(dpath)) return true;
3170
+ }
3171
+ }
3172
+ return false;
3173
+ }
3174
+ function isSshKeygenGenerate(command) {
3175
+ const low = command.toLowerCase();
3176
+ if (!low.includes("ssh-keygen")) return false;
3177
+ if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
3178
+ if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
3179
+ return true;
3180
+ }
2899
3181
  function checkDangerousPatterns(actionType, actionDetail) {
2900
3182
  if (actionType === "file_write" || actionType === "file_edit") return null;
2901
3183
  const scan = stripHeredocContent(actionDetail);
2902
3184
  const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
2903
3185
  const isTempPath = TEMP_DIRS.some(
2904
3186
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
2905
- );
3187
+ ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
2906
3188
  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;
2907
3189
  if (!isTempPath && wildcardDeleteRe.test(scan)) {
2908
3190
  return {
@@ -2912,7 +3194,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2912
3194
  riskType: "script_execution",
2913
3195
  confidence: 90,
2914
3196
  source: "heuristic",
2915
- violatedLayers: ["1.1"]
3197
+ violatedLayers: ["L1.1"]
2916
3198
  };
2917
3199
  }
2918
3200
  const findWriteMatch = actionDetail.match(
@@ -2927,10 +3209,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
2927
3209
  riskType: "script_execution",
2928
3210
  confidence: 90,
2929
3211
  source: "heuristic",
2930
- violatedLayers: ["1.1"]
3212
+ violatedLayers: ["L1.1"]
2931
3213
  };
2932
3214
  }
2933
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
3215
+ if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
2934
3216
  return {
2935
3217
  allowed: false,
2936
3218
  reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
@@ -2938,7 +3220,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2938
3220
  riskType: "file_access",
2939
3221
  confidence: 95,
2940
3222
  source: "heuristic",
2941
- violatedLayers: ["1.1"]
3223
+ violatedLayers: ["L1.1"]
2942
3224
  };
2943
3225
  }
2944
3226
  if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
@@ -2949,7 +3231,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
2949
3231
  riskType: "file_access",
2950
3232
  confidence: 90,
2951
3233
  source: "heuristic",
2952
- violatedLayers: ["1.1"]
3234
+ violatedLayers: ["L1.1"]
2953
3235
  };
2954
3236
  }
2955
3237
  return null;
@@ -3040,13 +3322,13 @@ function checkLateralMovement(actionDetail) {
3040
3322
  riskType: "lateral_movement",
3041
3323
  confidence: 80,
3042
3324
  source: "heuristic",
3043
- violatedLayers: ["1.1"]
3325
+ violatedLayers: ["L1.1"]
3044
3326
  };
3045
3327
  }
3046
3328
  }
3047
3329
  return null;
3048
3330
  }
3049
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
3331
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3050
3332
  var init_heuristic = __esm({
3051
3333
  "src/engine/heuristic.ts"() {
3052
3334
  "use strict";
@@ -3054,6 +3336,7 @@ var init_heuristic = __esm({
3054
3336
  init_config();
3055
3337
  init_agents();
3056
3338
  init_scenarios();
3339
+ init_runtime_config();
3057
3340
  init_rules_loader();
3058
3341
  init_logic_rules();
3059
3342
  INJECTION_KEYWORDS = (() => {
@@ -3075,6 +3358,8 @@ var init_heuristic = __esm({
3075
3358
  })();
3076
3359
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3077
3360
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3361
+ HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
3362
+ 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;
3078
3363
  }
3079
3364
  });
3080
3365
 
@@ -3116,7 +3401,7 @@ var init_template_loader = __esm({
3116
3401
 
3117
3402
  // src/engine/script-analyzer.ts
3118
3403
  import { readFileSync as readFileSync8 } from "fs";
3119
- import { resolve as resolve6, join as join3 } from "path";
3404
+ import { resolve as resolve6, join as join4 } from "path";
3120
3405
  function emptyResult() {
3121
3406
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
3122
3407
  }
@@ -3169,7 +3454,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3169
3454
  }
3170
3455
  function readScriptContent(scriptPath, maxLines = 500) {
3171
3456
  try {
3172
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3457
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3173
3458
  const raw = readFileSync8(expandedPath, "utf-8");
3174
3459
  const lines = raw.split("\n").slice(0, maxLines);
3175
3460
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4803,8 +5088,8 @@ var init_tracker = __esm({
4803
5088
 
4804
5089
  // src/engine/script-content-analyzer.ts
4805
5090
  import { statSync as statSync2, existsSync as existsSync9 } from "fs";
4806
- import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
4807
- import { homedir as homedir3 } from "os";
5091
+ import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
5092
+ import { homedir as homedir4 } from "os";
4808
5093
  function emptyChain() {
4809
5094
  return {
4810
5095
  nodes: [],
@@ -4869,6 +5154,13 @@ function extractScriptPath2(command) {
4869
5154
  return null;
4870
5155
  }
4871
5156
  function extractScriptPathWithArgs(command) {
5157
+ const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
5158
+ if (inline) {
5159
+ const inner = inline[2];
5160
+ const realPath = extractScriptPath2(inner);
5161
+ if (realPath) return { path: realPath, args: [] };
5162
+ return { path: null, args: [] };
5163
+ }
4872
5164
  const scriptPath = extractScriptPath2(command);
4873
5165
  if (!scriptPath) return { path: null, args: [] };
4874
5166
  const idx = command.indexOf(scriptPath);
@@ -4882,11 +5174,59 @@ function extractScriptPathWithArgs(command) {
4882
5174
  }
4883
5175
  return { path: scriptPath, args };
4884
5176
  }
5177
+ function looksLikeScriptPath(tok) {
5178
+ const t = tok.trim().replace(/^['"]|['"]$/g, "");
5179
+ if (!t) return false;
5180
+ const low = t.toLowerCase();
5181
+ if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
5182
+ if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
5183
+ if (t.includes("/") && !t.startsWith("-")) return true;
5184
+ return false;
5185
+ }
4885
5186
  function extractInlineScriptPath(actionDetail) {
5187
+ const code = extractInlineCode(actionDetail);
5188
+ if (code) {
5189
+ const candidates = [];
5190
+ const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
5191
+ let m;
5192
+ while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
5193
+ const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
5194
+ while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
5195
+ const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5196
+ while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5197
+ for (const cand of candidates) {
5198
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
5199
+ for (const tok of tokens) {
5200
+ if (looksLikeScriptPath(tok)) {
5201
+ try {
5202
+ const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5203
+ if (existsSync9(resolved)) {
5204
+ const stat = statSync2(resolved);
5205
+ if (stat.isFile()) return resolved;
5206
+ }
5207
+ } catch {
5208
+ }
5209
+ }
5210
+ }
5211
+ }
5212
+ const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5213
+ while ((m = openRe.exec(code)) !== null) {
5214
+ const p = m[1];
5215
+ try {
5216
+ const resolved = resolvePath2(p);
5217
+ if (existsSync9(resolved)) {
5218
+ const stat = statSync2(resolved);
5219
+ if (stat.isFile()) return resolved;
5220
+ }
5221
+ } catch {
5222
+ }
5223
+ }
5224
+ return null;
5225
+ }
4886
5226
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
4887
- let m;
4888
- while ((m = re.exec(actionDetail)) !== null) {
4889
- const p = m[1];
5227
+ let m2;
5228
+ while ((m2 = re.exec(actionDetail)) !== null) {
5229
+ const p = m2[1];
4890
5230
  let resolved;
4891
5231
  try {
4892
5232
  resolved = resolvePath2(p);
@@ -4902,7 +5242,7 @@ function extractInlineScriptPath(actionDetail) {
4902
5242
  function resolvePath2(p) {
4903
5243
  let expanded = p;
4904
5244
  if (expanded.startsWith("~/") || expanded === "~") {
4905
- expanded = join5(homedir3(), expanded.slice(1));
5245
+ expanded = join6(homedir4(), expanded.slice(1));
4906
5246
  }
4907
5247
  return resolve8(expanded);
4908
5248
  }
@@ -4983,7 +5323,7 @@ function analyzeScriptContent2(input) {
4983
5323
  isInline = true;
4984
5324
  }
4985
5325
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
4986
- resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
5326
+ resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
4987
5327
  }
4988
5328
  const l1Adapter = (content) => {
4989
5329
  const virtualInput = {
@@ -5072,6 +5412,7 @@ var init_script_content_analyzer = __esm({
5072
5412
  init_esm_shims();
5073
5413
  init_heuristic();
5074
5414
  init_logic_rules();
5415
+ init_inline_analyzer();
5075
5416
  init_tracker();
5076
5417
  SUSPICIOUS_PATTERNS2 = [
5077
5418
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -5536,7 +5877,7 @@ async function analyzeWithLLM(prompt, config) {
5536
5877
  riskType: riskType || "",
5537
5878
  confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
5538
5879
  source: "llm",
5539
- violatedLayers: ["3"]
5880
+ violatedLayers: ["L3.1"]
5540
5881
  };
5541
5882
  } catch (err) {
5542
5883
  if (abortController.signal.aborted) {
@@ -5573,8 +5914,8 @@ var init_llm_analyzer = __esm({
5573
5914
 
5574
5915
  // src/brain/rule-store.ts
5575
5916
  import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5576
- import { join as join6 } from "path";
5577
- import { homedir as homedir4 } from "os";
5917
+ import { join as join7 } from "path";
5918
+ import { homedir as homedir5 } from "os";
5578
5919
  function getRulesPath() {
5579
5920
  const envPath = process.env[ENV_RULES_PATH];
5580
5921
  if (envPath) return envPath;
@@ -5592,7 +5933,7 @@ var init_rule_store = __esm({
5592
5933
  "src/brain/rule-store.ts"() {
5593
5934
  "use strict";
5594
5935
  init_esm_shims();
5595
- DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
5936
+ DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
5596
5937
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5597
5938
  STATUS_DRAFT = "draft";
5598
5939
  STATUS_APPROVED = "approved";
@@ -6154,6 +6495,13 @@ function decodeBase64Literals(text) {
6154
6495
  }
6155
6496
  return out;
6156
6497
  }
6498
+ function stripStringLiterals(text) {
6499
+ const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
6500
+ if (!m) return text;
6501
+ const cmd = m[1].split("/").pop().toLowerCase();
6502
+ if (!OUTPUT_CMDS.has(cmd)) return text;
6503
+ return text.replace(QUOTED_STR_RE, "");
6504
+ }
6157
6505
  function decodeUrlAndNumeric(text) {
6158
6506
  const out = [];
6159
6507
  let s = text;
@@ -6187,7 +6535,10 @@ function decodeUrlAndNumeric(text) {
6187
6535
  return out;
6188
6536
  }
6189
6537
  function allCandidates(detail, max = 8) {
6190
- const cands = [detail];
6538
+ const shell = stripStringLiterals(detail);
6539
+ const cands = [shell];
6540
+ cands.push(...decodeBase64Literals(shell));
6541
+ cands.push(...decodeUrlAndNumeric(shell));
6191
6542
  cands.push(...decodeBase64Literals(detail));
6192
6543
  cands.push(...decodeUrlAndNumeric(detail));
6193
6544
  const seen = /* @__PURE__ */ new Set();
@@ -6211,12 +6562,147 @@ function matchStageAny(detail, stages) {
6211
6562
  }
6212
6563
  return "";
6213
6564
  }
6214
- var B64_TOKEN_RE;
6565
+ var B64_TOKEN_RE, QUOTED_STR_RE, OUTPUT_CMDS;
6215
6566
  var init_decode = __esm({
6216
6567
  "src/engine/formal-engine/decode.ts"() {
6217
6568
  "use strict";
6218
6569
  init_esm_shims();
6219
6570
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6571
+ QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
6572
+ OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
6573
+ }
6574
+ });
6575
+
6576
+ // src/engine/formal-engine/ir.ts
6577
+ function allNodes(mg) {
6578
+ const out = [];
6579
+ for (const s of mg.sessions) out.push(...s.nodes);
6580
+ return out;
6581
+ }
6582
+ function allEdges(mg) {
6583
+ const out = [];
6584
+ for (const s of mg.sessions) out.push(...s.edges);
6585
+ out.push(...mg.crossEdges);
6586
+ return out;
6587
+ }
6588
+ function emptyVerificationResult() {
6589
+ return { verified: true, violations: [] };
6590
+ }
6591
+ var init_ir = __esm({
6592
+ "src/engine/formal-engine/ir.ts"() {
6593
+ "use strict";
6594
+ init_esm_shims();
6595
+ }
6596
+ });
6597
+
6598
+ // src/engine/formal-engine/ltl.ts
6599
+ function compileAps(apPatterns) {
6600
+ if (AP_RE) return;
6601
+ AP_RE = {};
6602
+ for (const [name, pats] of Object.entries(apPatterns || {})) {
6603
+ AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
6604
+ }
6605
+ }
6606
+ function labelEvent(actionDetail) {
6607
+ if (!AP_RE) compileAps(loadMC().ap_patterns || {});
6608
+ const candidates = allCandidates(actionDetail);
6609
+ const hits = /* @__PURE__ */ new Set();
6610
+ for (const [name, pats] of Object.entries(AP_RE || {})) {
6611
+ for (const c of candidates) {
6612
+ const m = pats.some((p) => p.test(c));
6613
+ if (m) {
6614
+ hits.add(name);
6615
+ break;
6616
+ }
6617
+ }
6618
+ }
6619
+ return hits;
6620
+ }
6621
+ function evaluateLTL(formula, trace, pos) {
6622
+ const op = formula[0];
6623
+ switch (op) {
6624
+ case "ap":
6625
+ return trace[pos].has(formula[1]);
6626
+ case "!":
6627
+ return !evaluateLTL(formula[1], trace, pos);
6628
+ case "&":
6629
+ return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
6630
+ case "|":
6631
+ return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6632
+ case "->":
6633
+ return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6634
+ case "X":
6635
+ return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
6636
+ case "G": {
6637
+ for (let j = pos; j < trace.length; j++) {
6638
+ if (!evaluateLTL(formula[1], trace, j)) return false;
6639
+ }
6640
+ return true;
6641
+ }
6642
+ case "F": {
6643
+ for (let j = pos; j < trace.length; j++) {
6644
+ if (evaluateLTL(formula[1], trace, j)) return true;
6645
+ }
6646
+ return false;
6647
+ }
6648
+ case "U": {
6649
+ for (let j = pos; j < trace.length; j++) {
6650
+ if (evaluateLTL(formula[2], trace, j)) {
6651
+ for (let k = pos; k < j; k++) {
6652
+ if (!evaluateLTL(formula[1], trace, k)) return false;
6653
+ }
6654
+ return true;
6655
+ }
6656
+ }
6657
+ return false;
6658
+ }
6659
+ default:
6660
+ return false;
6661
+ }
6662
+ }
6663
+ function ruleViolates(rule, traceWith, witnessMode) {
6664
+ const formula = rule?.formula;
6665
+ if (!Array.isArray(formula) || formula.length === 0) return false;
6666
+ const satWith = evaluateLTL(formula, traceWith, 0);
6667
+ if (!satWith) return false;
6668
+ if (witnessMode && traceWith.length > 1) {
6669
+ const traceWithout = traceWith.slice(0, -1);
6670
+ if (evaluateLTL(formula, traceWithout, 0)) return false;
6671
+ }
6672
+ return true;
6673
+ }
6674
+ function checkMCRules(graph) {
6675
+ const violations = [];
6676
+ const nodes = allNodes(graph);
6677
+ if (nodes.length === 0) return violations;
6678
+ const mc = loadMC();
6679
+ const rules = mc.rules || [];
6680
+ if (rules.length === 0) return violations;
6681
+ const trace = nodes.map((n) => labelEvent(n.actionDetail));
6682
+ const witnessMode = graph.witnessIdx !== void 0;
6683
+ for (const rule of rules) {
6684
+ if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
6685
+ if (ruleViolates(rule, trace, witnessMode)) {
6686
+ violations.push({
6687
+ propertyId: String(rule.id || "mc-ltl"),
6688
+ reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
6689
+ riskLevel: String(rule.risk_level || "high"),
6690
+ 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) }))
6691
+ });
6692
+ if (witnessMode) return violations;
6693
+ }
6694
+ }
6695
+ return violations;
6696
+ }
6697
+ var AP_RE;
6698
+ var init_ltl = __esm({
6699
+ "src/engine/formal-engine/ltl.ts"() {
6700
+ "use strict";
6701
+ init_esm_shims();
6702
+ init_rules_loader();
6703
+ init_ir();
6704
+ init_decode();
6705
+ AP_RE = null;
6220
6706
  }
6221
6707
  });
6222
6708
 
@@ -6246,6 +6732,26 @@ function extractResources(actionDetail) {
6246
6732
  function markStage(actionDetail) {
6247
6733
  return matchStageAny(actionDetail, loadStages());
6248
6734
  }
6735
+ function markIntegrity(actionDetail) {
6736
+ const aps = labelEvent(actionDetail);
6737
+ if (aps.has("download_file") || aps.has("prompt_injection")) {
6738
+ return { integrity: "low", isLowSource: true };
6739
+ }
6740
+ return { integrity: "medium", isLowSource: false };
6741
+ }
6742
+ function initNode(sessionId, seq, actionType, actionDetail) {
6743
+ const { integrity, isLowSource } = markIntegrity(actionDetail);
6744
+ return {
6745
+ sessionId,
6746
+ seq,
6747
+ actionType,
6748
+ actionDetail,
6749
+ stage: markStage(actionDetail),
6750
+ resources: extractResources(actionDetail),
6751
+ integrity,
6752
+ isLowSource
6753
+ };
6754
+ }
6249
6755
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6250
6756
  const nodes = [];
6251
6757
  const edges = [];
@@ -6259,17 +6765,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6259
6765
  details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6260
6766
  }
6261
6767
  details.forEach((d, seq) => {
6262
- const stage = markStage(d.actionDetail);
6263
- const node = {
6264
- sessionId,
6265
- seq,
6266
- actionType: d.actionType,
6267
- actionDetail: d.actionDetail,
6268
- stage,
6269
- resources: extractResources(d.actionDetail)
6270
- };
6768
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
6271
6769
  nodes.push(node);
6272
- if (stage) stageFlags.add(stage);
6770
+ if (node.stage) stageFlags.add(node.stage);
6273
6771
  });
6274
6772
  for (let i = 0; i < nodes.length - 1; i++) {
6275
6773
  edges.push({ src: i, dst: i + 1, kind: "next" });
@@ -6289,15 +6787,12 @@ function buildMultiSessionGraph(sessions, aNext) {
6289
6787
  if (aNext) {
6290
6788
  const last = mg.sessions[mg.sessions.length - 1];
6291
6789
  if (last.sessionId === "current") {
6292
- const stage = markStage(String(aNext.actionDetail || ""));
6293
- last.nodes.push({
6294
- sessionId: last.sessionId,
6295
- seq: last.nodes.length,
6296
- actionType: String(aNext.actionType || ""),
6297
- actionDetail: String(aNext.actionDetail || ""),
6298
- stage,
6299
- resources: extractResources(String(aNext.actionDetail || ""))
6300
- });
6790
+ last.nodes.push(initNode(
6791
+ last.sessionId,
6792
+ last.nodes.length,
6793
+ String(aNext.actionType || ""),
6794
+ String(aNext.actionDetail || "")
6795
+ ));
6301
6796
  if (last.nodes.length > 1) {
6302
6797
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6303
6798
  }
@@ -6311,11 +6806,25 @@ function buildMultiSessionGraph(sessions, aNext) {
6311
6806
  function addCrossDataEdges(mg) {
6312
6807
  const nodes = allNodesFlat(mg);
6313
6808
  if (nodes.length < 2) return;
6314
- for (let i = 0; i < nodes.length; i++) {
6315
- for (let j = i + 1; j < nodes.length; j++) {
6316
- if (nodes[i].sessionId === nodes[j].sessionId) continue;
6317
- const shared = intersect(nodes[i].resources, nodes[j].resources);
6318
- if (shared.size > 0) {
6809
+ const resIndex = /* @__PURE__ */ new Map();
6810
+ nodes.forEach((n, i) => {
6811
+ for (const r of n.resources) {
6812
+ const list = resIndex.get(r);
6813
+ if (list) list.push(i);
6814
+ else resIndex.set(r, [i]);
6815
+ }
6816
+ });
6817
+ const seen = /* @__PURE__ */ new Set();
6818
+ for (const idxs of resIndex.values()) {
6819
+ if (idxs.length < 2) continue;
6820
+ for (let a = 0; a < idxs.length; a++) {
6821
+ const i = idxs[a];
6822
+ for (let b = a + 1; b < idxs.length; b++) {
6823
+ const j = idxs[b];
6824
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6825
+ const key = `${i}:${j}`;
6826
+ if (seen.has(key)) continue;
6827
+ seen.add(key);
6319
6828
  mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6320
6829
  }
6321
6830
  }
@@ -6326,11 +6835,6 @@ function allNodesFlat(mg) {
6326
6835
  for (const s of mg.sessions) out.push(...s.nodes);
6327
6836
  return out;
6328
6837
  }
6329
- function intersect(a, b) {
6330
- const out = /* @__PURE__ */ new Set();
6331
- for (const v of a) if (b.has(v)) out.add(v);
6332
- return out;
6333
- }
6334
6838
  var COMBO_STAGES2, PATH_RE;
6335
6839
  var init_builder = __esm({
6336
6840
  "src/engine/formal-engine/builder.ts"() {
@@ -6338,33 +6842,12 @@ var init_builder = __esm({
6338
6842
  init_esm_shims();
6339
6843
  init_rules_loader();
6340
6844
  init_decode();
6845
+ init_ltl();
6341
6846
  COMBO_STAGES2 = null;
6342
6847
  PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
6343
6848
  }
6344
6849
  });
6345
6850
 
6346
- // src/engine/formal-engine/ir.ts
6347
- function allNodes(mg) {
6348
- const out = [];
6349
- for (const s of mg.sessions) out.push(...s.nodes);
6350
- return out;
6351
- }
6352
- function allEdges(mg) {
6353
- const out = [];
6354
- for (const s of mg.sessions) out.push(...s.edges);
6355
- out.push(...mg.crossEdges);
6356
- return out;
6357
- }
6358
- function emptyVerificationResult() {
6359
- return { verified: true, violations: [] };
6360
- }
6361
- var init_ir = __esm({
6362
- "src/engine/formal-engine/ir.ts"() {
6363
- "use strict";
6364
- init_esm_shims();
6365
- }
6366
- });
6367
-
6368
6851
  // src/engine/formal-engine/properties.ts
6369
6852
  function loadCrossSessionChains() {
6370
6853
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
@@ -6381,27 +6864,50 @@ function checkCrossSessionChain(graph) {
6381
6864
  const violations = [];
6382
6865
  const nodes = allNodes(graph);
6383
6866
  if (nodes.length === 0) return violations;
6867
+ const witness = graph.witnessIdx;
6384
6868
  for (const chain of loadCrossSessionChains()) {
6385
6869
  const stages = chain.stages;
6386
6870
  if (!stages.length) continue;
6387
- let matched = 0;
6388
- let stageIdx = 0;
6389
- const path6 = [];
6390
- for (const n of nodes) {
6391
- if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6392
- matched += 1;
6393
- path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6394
- stageIdx += 1;
6395
- if (matched >= stages.length) break;
6871
+ if (witness !== void 0) {
6872
+ if (nodes[witness].stage !== stages[stages.length - 1]) continue;
6873
+ let matched = 0;
6874
+ const path6 = [];
6875
+ for (let k = 0; k < witness; k++) {
6876
+ const n = nodes[k];
6877
+ if (matched < stages.length - 1 && n.stage === stages[matched]) {
6878
+ matched += 1;
6879
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6880
+ }
6396
6881
  }
6397
- }
6398
- if (matched >= stages.length) {
6882
+ if (matched < stages.length - 1) continue;
6883
+ path6.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
6399
6884
  violations.push({
6400
6885
  propertyId: "cross-" + chain.id,
6401
6886
  reason: chain.reason,
6402
6887
  riskLevel: chain.riskLevel,
6403
6888
  path: path6
6404
6889
  });
6890
+ return violations;
6891
+ } else {
6892
+ let matched = 0;
6893
+ let stageIdx = 0;
6894
+ const path6 = [];
6895
+ for (const n of nodes) {
6896
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6897
+ matched += 1;
6898
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6899
+ stageIdx += 1;
6900
+ if (matched >= stages.length) break;
6901
+ }
6902
+ }
6903
+ if (matched >= stages.length) {
6904
+ violations.push({
6905
+ propertyId: "cross-" + chain.id,
6906
+ reason: chain.reason,
6907
+ riskLevel: chain.riskLevel,
6908
+ path: path6
6909
+ });
6910
+ }
6405
6911
  }
6406
6912
  }
6407
6913
  return violations;
@@ -6436,14 +6942,18 @@ function checkSensitiveDataFlow(graph) {
6436
6942
  }
6437
6943
  }
6438
6944
  }
6439
- for (const [i, n] of nodes.entries()) {
6945
+ const witness = graph.witnessIdx;
6946
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6947
+ for (const i of candidates) {
6948
+ const n = nodes[i];
6440
6949
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6441
6950
  violations.push({
6442
- propertyId: "sensitive-data-exfil",
6951
+ propertyId: "sensitive-data-flow",
6443
6952
  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`,
6444
6953
  riskLevel: "critical",
6445
6954
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6446
6955
  });
6956
+ if (witness !== void 0) return violations;
6447
6957
  }
6448
6958
  }
6449
6959
  return violations;
@@ -6469,12 +6979,38 @@ function checkNeverExfilAfterPrivesc(graph) {
6469
6979
  const violations = [];
6470
6980
  const nodes = allNodes(graph);
6471
6981
  if (nodes.length < 2) return violations;
6982
+ const witness = graph.witnessIdx;
6983
+ if (witness !== void 0) {
6984
+ const evIdx = [];
6985
+ nodes.forEach((n, i) => {
6986
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6987
+ });
6988
+ if (evIdx.length === 0) return violations;
6989
+ const firstEv = Math.min(...evIdx);
6990
+ let pre = null;
6991
+ for (let i = 0; i < firstEv; i++) {
6992
+ if (isPrivescNode(nodes[i])) pre = nodes[i];
6993
+ }
6994
+ if (pre) {
6995
+ const post = nodes[firstEv];
6996
+ return [{
6997
+ propertyId: "no-exfil-after-privesc",
6998
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6999
+ riskLevel: "critical",
7000
+ path: [
7001
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
7002
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
7003
+ ]
7004
+ }];
7005
+ }
7006
+ return violations;
7007
+ }
6472
7008
  for (let i = 0; i < nodes.length; i++) {
6473
7009
  if (!isPrivescNode(nodes[i])) continue;
6474
7010
  for (let j = i + 1; j < nodes.length; j++) {
6475
7011
  if (isExfilNode(nodes[j])) {
6476
7012
  violations.push({
6477
- propertyId: "no-privesc-then-exfil",
7013
+ propertyId: "no-exfil-after-privesc",
6478
7014
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6479
7015
  riskLevel: "critical",
6480
7016
  path: [
@@ -6492,12 +7028,38 @@ function checkNeverExfilAfterCred(graph) {
6492
7028
  const violations = [];
6493
7029
  const nodes = allNodes(graph);
6494
7030
  if (nodes.length < 2) return violations;
7031
+ const witness = graph.witnessIdx;
7032
+ if (witness !== void 0) {
7033
+ const evIdx = [];
7034
+ nodes.forEach((n, i) => {
7035
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
7036
+ });
7037
+ if (evIdx.length === 0) return violations;
7038
+ const firstEv = Math.min(...evIdx);
7039
+ let pre = null;
7040
+ for (let i = 0; i < firstEv; i++) {
7041
+ if (isCredNode(nodes[i])) pre = nodes[i];
7042
+ }
7043
+ if (pre) {
7044
+ const post = nodes[firstEv];
7045
+ return [{
7046
+ propertyId: "no-exfil-after-credential",
7047
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
7048
+ riskLevel: "critical",
7049
+ path: [
7050
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
7051
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
7052
+ ]
7053
+ }];
7054
+ }
7055
+ return violations;
7056
+ }
6495
7057
  for (let i = 0; i < nodes.length; i++) {
6496
7058
  if (!isCredNode(nodes[i])) continue;
6497
7059
  for (let j = i + 1; j < nodes.length; j++) {
6498
7060
  if (isExfilNode(nodes[j])) {
6499
7061
  violations.push({
6500
- propertyId: "no-cred-then-exfil",
7062
+ propertyId: "no-exfil-after-credential",
6501
7063
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6502
7064
  riskLevel: "critical",
6503
7065
  path: [
@@ -6511,13 +7073,164 @@ function checkNeverExfilAfterCred(graph) {
6511
7073
  }
6512
7074
  return violations;
6513
7075
  }
6514
- var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, PRIVESC_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, ALL_PROPERTIES;
7076
+ function getToolRegistry() {
7077
+ if (TOOL_REGISTRY) return TOOL_REGISTRY;
7078
+ TOOL_REGISTRY = loadToolRegistry().tools || {};
7079
+ return TOOL_REGISTRY;
7080
+ }
7081
+ function lookupToolClass(actionType) {
7082
+ const entry = getToolRegistry()[actionType] || {};
7083
+ return entry.actionClass;
7084
+ }
7085
+ function isComputeNode(n) {
7086
+ if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
7087
+ const cls = lookupToolClass(n.actionType);
7088
+ if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
7089
+ return COMPUTE_RE.test(n.actionDetail);
7090
+ }
7091
+ function checkLowIntegrityCommandExec(graph) {
7092
+ const violations = [];
7093
+ const nodes = allNodes(graph);
7094
+ if (nodes.length === 0) return violations;
7095
+ const witness = graph.witnessIdx;
7096
+ const lowResources = /* @__PURE__ */ new Set();
7097
+ const lowAt = /* @__PURE__ */ new Map();
7098
+ nodes.forEach((n, i) => {
7099
+ if (n.isLowSource) {
7100
+ lowAt.set(i, n.actionDetail.slice(0, 60));
7101
+ for (const r of n.resources) lowResources.add(r);
7102
+ }
7103
+ });
7104
+ let changed = true;
7105
+ while (changed) {
7106
+ changed = false;
7107
+ for (let i = 0; i < nodes.length; i++) {
7108
+ if (lowAt.has(i)) continue;
7109
+ let shared = "";
7110
+ for (const r of nodes[i].resources) {
7111
+ if (lowResources.has(r)) {
7112
+ shared = r;
7113
+ break;
7114
+ }
7115
+ }
7116
+ if (shared) {
7117
+ lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
7118
+ for (const r of nodes[i].resources) lowResources.add(r);
7119
+ changed = true;
7120
+ }
7121
+ }
7122
+ }
7123
+ if (lowAt.size === 0) return violations;
7124
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7125
+ for (const i of candidates) {
7126
+ const n = nodes[i];
7127
+ if (lowAt.has(i) && isComputeNode(n)) {
7128
+ violations.push({
7129
+ propertyId: "low-integrity-command-exec",
7130
+ 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`,
7131
+ riskLevel: "critical",
7132
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7133
+ });
7134
+ if (witness !== void 0) return violations;
7135
+ }
7136
+ }
7137
+ return violations;
7138
+ }
7139
+ function checkCrossSessionReachability(graph) {
7140
+ const violations = [];
7141
+ const nodes = allNodes(graph);
7142
+ if (nodes.length === 0) return violations;
7143
+ const witness = graph.witnessIdx;
7144
+ const sources = /* @__PURE__ */ new Set();
7145
+ nodes.forEach((n, i) => {
7146
+ if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
7147
+ });
7148
+ if (sources.size === 0) return violations;
7149
+ const reached = new Set(sources);
7150
+ let changed = true;
7151
+ while (changed) {
7152
+ changed = false;
7153
+ for (const e of allEdges(graph)) {
7154
+ if (reached.has(e.src) && !reached.has(e.dst)) {
7155
+ const srcRes = nodes[e.src].resources;
7156
+ const dstRes = nodes[e.dst].resources;
7157
+ for (const r of srcRes) {
7158
+ if (dstRes.has(r)) {
7159
+ reached.add(e.dst);
7160
+ changed = true;
7161
+ break;
7162
+ }
7163
+ }
7164
+ }
7165
+ }
7166
+ }
7167
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7168
+ for (const i of candidates) {
7169
+ const n = nodes[i];
7170
+ if (sources.has(i)) continue;
7171
+ if (reached.has(i)) {
7172
+ const srcIdx = Math.min(...sources);
7173
+ const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
7174
+ violations.push({
7175
+ propertyId: "low-integrity-reachability",
7176
+ 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`,
7177
+ riskLevel: "high",
7178
+ path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
7179
+ });
7180
+ if (witness !== void 0) return violations;
7181
+ }
7182
+ }
7183
+ return violations;
7184
+ }
7185
+ function checkSensitiveDataAggregation(graph) {
7186
+ const violations = [];
7187
+ const nodes = allNodes(graph);
7188
+ if (nodes.length < 2) return violations;
7189
+ const witness = graph.witnessIdx;
7190
+ const resIndex = /* @__PURE__ */ new Map();
7191
+ nodes.forEach((n, i) => {
7192
+ for (const r of n.resources) {
7193
+ const list = resIndex.get(r);
7194
+ if (list) list.push(i);
7195
+ else resIndex.set(r, [i]);
7196
+ }
7197
+ });
7198
+ const sourcesFor = (exfilIdx) => {
7199
+ const srcs = /* @__PURE__ */ new Set();
7200
+ const n = nodes[exfilIdx];
7201
+ for (const r of n.resources) {
7202
+ for (const j of resIndex.get(r) || []) {
7203
+ if (j === exfilIdx) continue;
7204
+ srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
7205
+ }
7206
+ }
7207
+ return srcs;
7208
+ };
7209
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
7210
+ for (const i of candidates) {
7211
+ const n = nodes[i];
7212
+ if (!isExfilNode(n)) continue;
7213
+ const srcs = sourcesFor(i);
7214
+ if (srcs.size >= 2) {
7215
+ violations.push({
7216
+ propertyId: "sensitive-data-aggregation",
7217
+ reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
7218
+ riskLevel: "critical",
7219
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
7220
+ });
7221
+ if (witness !== void 0) return violations;
7222
+ }
7223
+ }
7224
+ return violations;
7225
+ }
7226
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, PRIVESC_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
6515
7227
  var init_properties = __esm({
6516
7228
  "src/engine/formal-engine/properties.ts"() {
6517
7229
  "use strict";
6518
7230
  init_esm_shims();
6519
7231
  init_rules_loader();
6520
7232
  init_ir();
7233
+ init_ltl();
6521
7234
  CROSS_SESSION_CHAINS = null;
6522
7235
  CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6523
7236
  EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
@@ -6526,11 +7239,22 @@ var init_properties = __esm({
6526
7239
  PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6527
7240
  EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
6528
7241
  CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
7242
+ TOOL_REGISTRY = null;
7243
+ 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;
7244
+ STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
7245
+ 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;
6529
7246
  ALL_PROPERTIES = [
6530
- { id: "cross-session-chain", check: checkCrossSessionChain },
6531
- { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6532
- { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6533
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
7247
+ { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
7248
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
7249
+ { id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
7250
+ { id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
7251
+ { id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
7252
+ // L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
7253
+ { id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
7254
+ // P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
7255
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
7256
+ // P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
7257
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
6534
7258
  ];
6535
7259
  }
6536
7260
  });
@@ -6572,6 +7296,9 @@ var init_engine = __esm({
6572
7296
  });
6573
7297
  }
6574
7298
  const graph = buildMultiSessionGraph(all, aNext);
7299
+ if (aNext) {
7300
+ graph.witnessIdx = allNodes(graph).length - 1;
7301
+ }
6575
7302
  const result = emptyVerificationResult();
6576
7303
  for (const prop of this.properties) {
6577
7304
  let violations;
@@ -6600,6 +7327,32 @@ var init_engine = __esm({
6600
7327
  });
6601
7328
 
6602
7329
  // src/engine/formal-engine/stateful.ts
7330
+ function extractInstances(actionDetail, stage) {
7331
+ const out = [];
7332
+ if (PORT_STAGE_RE.test(stage)) {
7333
+ const d = actionDetail;
7334
+ let m;
7335
+ const re = new RegExp(PORT_RE.source, "gi");
7336
+ while ((m = re.exec(d)) !== null) {
7337
+ const p = m[1] || m[2] || m[3];
7338
+ if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
7339
+ }
7340
+ if (/SCAN/.test(stage)) {
7341
+ const bareRe = new RegExp(BARE_PORT_RE.source, "g");
7342
+ while ((m = bareRe.exec(d)) !== null) {
7343
+ if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
7344
+ }
7345
+ }
7346
+ m = SOCK_RE.exec(d);
7347
+ if (m && m[1]) out.push(`sock:${m[1]}`);
7348
+ m = UNIT_RE.exec(d);
7349
+ if (m && m[1]) out.push(`unit:${m[1]}`);
7350
+ }
7351
+ return out;
7352
+ }
7353
+ function buckKey(chainId, inst) {
7354
+ return inst ? `${chainId}@@${inst}` : chainId;
7355
+ }
6603
7356
  function loadRules() {
6604
7357
  if (STAGES_PATTERNS !== null && CHAINS !== null) {
6605
7358
  return { stages: STAGES_PATTERNS, chains: CHAINS };
@@ -6639,7 +7392,7 @@ function lifecycleFor(detail, stage, isError) {
6639
7392
  if (detail.includes(">>") || detail.includes(">")) return "alive";
6640
7393
  return "";
6641
7394
  }
6642
- var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
7395
+ var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, PORT_RE, BARE_PORT_RE, SOCK_RE, UNIT_RE, PORT_STAGE_RE, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
6643
7396
  var init_stateful = __esm({
6644
7397
  "src/engine/formal-engine/stateful.ts"() {
6645
7398
  "use strict";
@@ -6649,11 +7402,20 @@ var init_stateful = __esm({
6649
7402
  DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
6650
7403
  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;
6651
7404
  CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6652
- EXFIL_RE2 = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
7405
+ EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
7406
+ PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
7407
+ BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
7408
+ SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
7409
+ UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
7410
+ PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
6653
7411
  STAGES_PATTERNS = null;
6654
7412
  CHAINS = null;
6655
7413
  MAX_NODES = 1e5;
6656
- StatefulChainTracker = class {
7414
+ StatefulChainTracker = class _StatefulChainTracker {
7415
+ /** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
7416
+ static now() {
7417
+ return (/* @__PURE__ */ new Date()).toISOString();
7418
+ }
6657
7419
  agentId;
6658
7420
  nodes_ = [];
6659
7421
  ptr = {};
@@ -6675,10 +7437,14 @@ var init_stateful = __esm({
6675
7437
  for (const chain of chains) {
6676
7438
  const stages = chain.stages || [];
6677
7439
  if (stages.length === 0) continue;
6678
- const matched = this.ptr[String(chain.id || "")] || 0;
7440
+ const cid = String(chain.id || "");
7441
+ let matched = this.ptr[cid] || 0;
7442
+ for (const [k, v] of Object.entries(this.ptr)) {
7443
+ if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
7444
+ }
6679
7445
  if (matched > 0 && matched < stages.length) {
6680
7446
  const pf = {
6681
- chainId: String(chain.id || ""),
7447
+ chainId: cid,
6682
7448
  stages,
6683
7449
  matched,
6684
7450
  riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
@@ -6754,8 +7520,10 @@ var init_stateful = __esm({
6754
7520
  actionDetail: ad,
6755
7521
  stage,
6756
7522
  resources: extractResources2(ad),
7523
+ instances: extractInstances(ad, stage),
6757
7524
  isError,
6758
- lifecycle: lifecycleFor(ad, stage, isError)
7525
+ lifecycle: lifecycleFor(ad, stage, isError),
7526
+ timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
6759
7527
  };
6760
7528
  this.nodes_.push(node);
6761
7529
  this.seqNext += 1;
@@ -6763,11 +7531,19 @@ var init_stateful = __esm({
6763
7531
  }
6764
7532
  recordLifecycle(node) {
6765
7533
  if (node.lifecycle === "dead") {
6766
- let hit = false;
7534
+ const clearedInsts = [];
7535
+ for (const inst of node.instances) {
7536
+ const key = `inst:${inst}`;
7537
+ if (key in this.aliveResources) {
7538
+ delete this.aliveResources[key];
7539
+ clearedInsts.push(inst);
7540
+ }
7541
+ }
7542
+ let pathHit = false;
6767
7543
  for (const r of node.resources) {
6768
7544
  if (r in this.aliveResources) {
6769
7545
  delete this.aliveResources[r];
6770
- hit = true;
7546
+ pathHit = true;
6771
7547
  }
6772
7548
  }
6773
7549
  const detail = node.actionDetail;
@@ -6775,16 +7551,30 @@ var init_stateful = __esm({
6775
7551
  const base = r.split("/").pop() || "";
6776
7552
  if (base && detail.includes(base)) {
6777
7553
  delete this.aliveResources[r];
6778
- hit = true;
7554
+ pathHit = true;
6779
7555
  }
6780
7556
  }
6781
- if (hit) this.resetChainsOnDestroy();
7557
+ if (clearedInsts.length > 0 && !pathHit) {
7558
+ for (const inst of clearedInsts) this.resetChainsForInstance(inst);
7559
+ } else {
7560
+ this.resetChainsOnDestroy();
7561
+ }
6782
7562
  return;
6783
7563
  }
6784
7564
  if (node.lifecycle === "alive") {
6785
7565
  for (const r of node.resources) {
6786
7566
  this.aliveResources[r] = node.seq;
6787
7567
  }
7568
+ for (const inst of node.instances) {
7569
+ this.aliveResources[`inst:${inst}`] = node.seq;
7570
+ }
7571
+ }
7572
+ }
7573
+ /** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
7574
+ resetChainsForInstance(inst) {
7575
+ const key = buckKey("", inst).slice(2);
7576
+ for (const k of Object.keys(this.ptr)) {
7577
+ if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
6788
7578
  }
6789
7579
  }
6790
7580
  resetChainsOnDestroy() {
@@ -6792,6 +7582,29 @@ var init_stateful = __esm({
6792
7582
  this.ptr[cid] = 0;
6793
7583
  }
6794
7584
  }
7585
+ /**
7586
+ * 当前节点应推进的链桶集合。
7587
+ * - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
7588
+ * 同时为所有链的该实例建立桶(若首次出现)。
7589
+ * - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
7590
+ * (信息不足时保守推进一切可能,避免漏判)。
7591
+ */
7592
+ targetsForNode(node, chainId) {
7593
+ const insts = node.instances;
7594
+ if (insts.length > 0) {
7595
+ const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
7596
+ if (active.length > 0) return active.map((i) => buckKey(chainId, i));
7597
+ return [chainId];
7598
+ }
7599
+ const targets = [chainId];
7600
+ for (const k of Object.keys(this.aliveResources)) {
7601
+ if (k.startsWith("inst:")) {
7602
+ const inst = k.slice("inst:".length);
7603
+ targets.push(`${chainId}@@${inst}`);
7604
+ }
7605
+ }
7606
+ return targets;
7607
+ }
6795
7608
  advanceChains(node) {
6796
7609
  if (node.lifecycle === "dead") return null;
6797
7610
  if (node.lifecycle === "never_activated") {
@@ -6805,23 +7618,32 @@ var init_stateful = __esm({
6805
7618
  const stages = chain.stages || [];
6806
7619
  if (stages.length === 0) continue;
6807
7620
  const cid = String(chain.id || "");
6808
- const cur = this.ptr[cid] || 0;
6809
- if (cur < stages.length && stages[cur] === stage) {
6810
- this.ptr[cid] = cur + 1;
6811
- if (cur + 1 >= stages.length) {
6812
- return {
6813
- chainId: cid,
6814
- stages,
6815
- riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
6816
- reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
6817
- path: this.chainPath(stages)
6818
- };
7621
+ const targets = this.targetsForNode(node, cid);
7622
+ for (const key of targets) {
7623
+ const cur = this.ptr[key] || 0;
7624
+ if (cur < stages.length && stages[cur] === stage) {
7625
+ this.ptr[key] = cur + 1;
7626
+ if (cur + 1 >= stages.length) {
7627
+ return {
7628
+ chainId: cid,
7629
+ stages,
7630
+ riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
7631
+ reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
7632
+ path: this.chainPath(stages)
7633
+ };
7634
+ }
6819
7635
  }
6820
7636
  }
6821
7637
  }
6822
7638
  return null;
6823
7639
  }
6824
7640
  invalidateChainOnFailure(node) {
7641
+ if (node.instances.length > 0) {
7642
+ for (const inst of node.instances) {
7643
+ this.resetChainsForInstance(inst);
7644
+ }
7645
+ return;
7646
+ }
6825
7647
  const failed = new Set(node.resources);
6826
7648
  if (failed.size === 0) return;
6827
7649
  const { chains } = loadRules();
@@ -6837,7 +7659,7 @@ var init_stateful = __esm({
6837
7659
  let want = stages[0];
6838
7660
  for (const n of this.nodes_) {
6839
7661
  if (n.stage === want) {
6840
- path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
7662
+ path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
6841
7663
  const idx = stages.indexOf(n.stage);
6842
7664
  if (idx + 1 < stages.length) {
6843
7665
  want = stages[idx + 1];
@@ -6873,7 +7695,7 @@ var init_stateful = __esm({
6873
7695
  if (!this.tainted.has(node.seq)) return [];
6874
7696
  if (EXFIL_RE2.test(node.actionDetail)) {
6875
7697
  return [{
6876
- property_id: "sensitive-data-exfil",
7698
+ property_id: "sensitive-data-flow",
6877
7699
  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`,
6878
7700
  risk_level: "critical",
6879
7701
  path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
@@ -6915,8 +7737,12 @@ var init_stateful = __esm({
6915
7737
  actionDetail: String(raw.actionDetail || ""),
6916
7738
  stage: String(raw.stage || ""),
6917
7739
  resources: [...raw.resources || []],
7740
+ instances: [...raw.instances || []],
7741
+ // 旧快照无此字段 → 空
6918
7742
  isError: Boolean(raw.isError),
6919
- lifecycle: String(raw.lifecycle || "")
7743
+ lifecycle: String(raw.lifecycle || ""),
7744
+ timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
7745
+ // 旧快照无此字段 → null
6920
7746
  });
6921
7747
  }
6922
7748
  this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
@@ -7083,6 +7909,7 @@ function _resetBrainForTest() {
7083
7909
  _brainInitialized = false;
7084
7910
  }
7085
7911
  async function runAnalysis(input) {
7912
+ const observedFormalLayers = [];
7086
7913
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
7087
7914
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
7088
7915
  const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
@@ -7108,7 +7935,7 @@ async function runAnalysis(input) {
7108
7935
  riskType: node.l1RiskType,
7109
7936
  confidence: 90,
7110
7937
  source: "heuristic_script_scan",
7111
- violatedLayers: ["1.1-script"]
7938
+ violatedLayers: ["L1.2"]
7112
7939
  };
7113
7940
  }
7114
7941
  }
@@ -7137,8 +7964,8 @@ async function runAnalysis(input) {
7137
7964
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
7138
7965
  const lowerDetail = rawDetail.toLowerCase();
7139
7966
  const agentExt = mergeAgentExtensions(
7140
- getAgentExtensions(input.agentId),
7141
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
7967
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
7968
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
7142
7969
  );
7143
7970
  if (FULLY_SAFE_TOOLS.has(actionType)) {
7144
7971
  return {
@@ -7226,7 +8053,7 @@ async function runAnalysis(input) {
7226
8053
  riskType: inlineJudgment.riskType,
7227
8054
  confidence: 90,
7228
8055
  source: "logic_rule",
7229
- violatedLayers: ["1.2"]
8056
+ violatedLayers: ["L2.1"]
7230
8057
  };
7231
8058
  }
7232
8059
  return {
@@ -7255,15 +8082,22 @@ async function runAnalysis(input) {
7255
8082
  const formalResult = formalEngine.verify(formalSessions, input.aNext);
7256
8083
  if (!formalResult.verified) {
7257
8084
  const v = formalResult.violations[0];
7258
- return {
7259
- allowed: false,
7260
- reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
7261
- riskLevel: v ? v.riskLevel : "high",
7262
- riskType: "formal_verification",
7263
- confidence: 90,
7264
- source: "formal_engine",
7265
- violatedLayers: ["2.4"]
7266
- };
8085
+ if (isFormalObserveMode()) {
8086
+ console.warn(
8087
+ `[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)}`
8088
+ );
8089
+ observedFormalLayers.push("L2.2");
8090
+ } else {
8091
+ return {
8092
+ allowed: false,
8093
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
8094
+ riskLevel: v ? v.riskLevel : "high",
8095
+ riskType: "formal_verification",
8096
+ confidence: 90,
8097
+ source: "formal_engine",
8098
+ violatedLayers: ["L2.2"]
8099
+ };
8100
+ }
7267
8101
  }
7268
8102
  }
7269
8103
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -7278,7 +8112,7 @@ async function runAnalysis(input) {
7278
8112
  riskType: node.l2RiskType,
7279
8113
  confidence: 85,
7280
8114
  source: "logic_rule_script_scan",
7281
- violatedLayers: ["1.2-script"]
8115
+ violatedLayers: ["L2.3"]
7282
8116
  };
7283
8117
  }
7284
8118
  }
@@ -7310,7 +8144,7 @@ async function runAnalysis(input) {
7310
8144
  riskType: "",
7311
8145
  confidence: 85,
7312
8146
  source: "heuristic_and_logic_only",
7313
- violatedLayers: []
8147
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
7314
8148
  };
7315
8149
  }
7316
8150
  let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
@@ -7355,7 +8189,7 @@ async function runAnalysis(input) {
7355
8189
  riskType: "",
7356
8190
  confidence: 50,
7357
8191
  source: "llm_failure_fallback",
7358
- violatedLayers: []
8192
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
7359
8193
  };
7360
8194
  }
7361
8195
  return {
@@ -7365,7 +8199,7 @@ async function runAnalysis(input) {
7365
8199
  riskType: "",
7366
8200
  confidence: 60,
7367
8201
  source: "llm_failure_fallback",
7368
- violatedLayers: ["3"]
8202
+ violatedLayers: ["L3.1"]
7369
8203
  };
7370
8204
  }
7371
8205
  if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
@@ -7382,6 +8216,9 @@ async function runAnalysis(input) {
7382
8216
  } catch {
7383
8217
  }
7384
8218
  }
8219
+ if (observedFormalLayers.length > 0 && llmResult.allowed) {
8220
+ llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
8221
+ }
7385
8222
  return llmResult;
7386
8223
  }
7387
8224
  var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
@@ -7395,6 +8232,7 @@ var init_coordinator = __esm({
7395
8232
  init_llm_analyzer();
7396
8233
  init_script_content_analyzer();
7397
8234
  init_config();
8235
+ init_runtime_config();
7398
8236
  init_agents();
7399
8237
  init_scenarios();
7400
8238
  init_brain();
@@ -7556,7 +8394,7 @@ var require_bindings = __commonJS({
7556
8394
  var fs7 = __require("fs");
7557
8395
  var path6 = __require("path");
7558
8396
  var fileURLToPath11 = require_file_uri_to_path();
7559
- var join10 = path6.join;
8397
+ var join11 = path6.join;
7560
8398
  var dirname11 = path6.dirname;
7561
8399
  var exists = fs7.accessSync && function(path7) {
7562
8400
  try {
@@ -7616,7 +8454,7 @@ var require_bindings = __commonJS({
7616
8454
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
7617
8455
  var tries = [], i = 0, l = opts.try.length, n, b, err;
7618
8456
  for (; i < l; i++) {
7619
- n = join10.apply(
8457
+ n = join11.apply(
7620
8458
  null,
7621
8459
  opts.try[i].map(function(p) {
7622
8460
  return opts[p] || p;
@@ -7677,7 +8515,7 @@ var require_bindings = __commonJS({
7677
8515
  if (dir === ".") {
7678
8516
  dir = process.cwd();
7679
8517
  }
7680
- if (exists(join10(dir, "package.json")) || exists(join10(dir, "node_modules"))) {
8518
+ if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
7681
8519
  return dir;
7682
8520
  }
7683
8521
  if (prev === dir) {
@@ -7686,7 +8524,7 @@ var require_bindings = __commonJS({
7686
8524
  );
7687
8525
  }
7688
8526
  prev = dir;
7689
- dir = join10(dir, "..");
8527
+ dir = join11(dir, "..");
7690
8528
  }
7691
8529
  };
7692
8530
  }
@@ -8303,7 +9141,7 @@ var AnalyzeRequestSchema = z.object({
8303
9141
  session_history: z.array(z.object({
8304
9142
  session_id: z.string(),
8305
9143
  action_history: z.array(ActionItemSchema).default([])
8306
- })).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)."),
9144
+ })).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)."),
8307
9145
  a_next: ActionItemSchema.describe("Next action to be executed"),
8308
9146
  reason: z.string().default("").describe("Reason for executing this action"),
8309
9147
  os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
@@ -8320,7 +9158,9 @@ var AnalyzeResponseSchema = z.object({
8320
9158
  policy: z.string().default(""),
8321
9159
  violated_policy: z.string().default(""),
8322
9160
  confidence: z.number().int().min(0).max(100).default(100),
8323
- analysis_duration_ms: z.number().default(0)
9161
+ analysis_duration_ms: z.number().default(0),
9162
+ // 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
9163
+ violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
8324
9164
  });
8325
9165
  var HealthResponseSchema = z.object({
8326
9166
  status: z.string().default("healthy"),
@@ -8704,7 +9544,7 @@ async function analyzeRoute(c) {
8704
9544
  const judgment = incMatchedToDeny(inc);
8705
9545
  if (judgment) {
8706
9546
  const layers = [...result.violatedLayers];
8707
- if (!layers.includes("2.4-stateful")) layers.push("2.4-stateful");
9547
+ if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
8708
9548
  console.error(
8709
9549
  `[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
8710
9550
  );
@@ -8718,7 +9558,8 @@ async function analyzeRoute(c) {
8718
9558
  policy: "",
8719
9559
  violated_policy: "",
8720
9560
  confidence: 90,
8721
- analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
9561
+ analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
9562
+ violation_chain: judgment.violation_chain
8722
9563
  })
8723
9564
  );
8724
9565
  }
@@ -8757,17 +9598,20 @@ function getChainStateStoreFor(_agentId) {
8757
9598
  }
8758
9599
  function incMatchedToDeny(inc) {
8759
9600
  if (inc.matched) {
9601
+ const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
8760
9602
  return {
8761
9603
  property_id: "cross-" + inc.matched.chainId,
8762
9604
  reason: inc.matched.reason,
8763
- risk_level: inc.matched.riskLevel
9605
+ risk_level: inc.matched.riskLevel,
9606
+ violation_chain: chain
8764
9607
  };
8765
9608
  }
8766
9609
  if (inc.exfil && inc.exfil.length > 0) {
8767
9610
  return {
8768
- property_id: "sensitive-data-exfil",
9611
+ property_id: "sensitive-data-flow",
8769
9612
  reason: inc.exfil[0].reason,
8770
- risk_level: inc.exfil[0].risk_level
9613
+ risk_level: inc.exfil[0].risk_level,
9614
+ violation_chain: []
8771
9615
  };
8772
9616
  }
8773
9617
  return null;
@@ -8846,12 +9690,13 @@ init_esm_shims();
8846
9690
  init_esm_shims();
8847
9691
  init_runtime_config();
8848
9692
  init_token_stats();
9693
+ init_rules_loader();
8849
9694
  import { Hono } from "hono";
8850
9695
  import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
8851
- import { join as join9, dirname as dirname10 } from "path";
9696
+ import { join as join10, dirname as dirname10 } from "path";
8852
9697
  import { fileURLToPath as fileURLToPath9 } from "url";
8853
9698
  var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8854
- var STATIC_DIR = join9(__dirname8, "static");
9699
+ var STATIC_DIR = join10(__dirname8, "static");
8855
9700
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
8856
9701
  function getConsoleToken() {
8857
9702
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -8875,7 +9720,7 @@ function createConsoleRouter() {
8875
9720
  const console2 = new Hono();
8876
9721
  console2.use("/api/*", authMiddleware());
8877
9722
  const serveIndex = (c) => {
8878
- const indexFile = join9(STATIC_DIR, "index.html");
9723
+ const indexFile = join10(STATIC_DIR, "index.html");
8879
9724
  if (existsSync14(indexFile)) {
8880
9725
  return c.html(readFileSync15(indexFile, "utf-8"));
8881
9726
  }
@@ -8892,24 +9737,89 @@ function createConsoleRouter() {
8892
9737
  const rc = updateLayerEnabled(body.layer_key, body.enabled);
8893
9738
  return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
8894
9739
  });
9740
+ function getL22View() {
9741
+ const ltlHuman = {
9742
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9743
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9744
+ "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",
9745
+ "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",
9746
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9747
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9748
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9749
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9750
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9751
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9752
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9753
+ };
9754
+ const mc = loadMC();
9755
+ const rules = mc.rules || [];
9756
+ return {
9757
+ enabled_via: "L2_logic_rules",
9758
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9759
+ ltl_rules: rules.map((r) => ({
9760
+ id: r.id,
9761
+ type: r.type,
9762
+ formula: Array.isArray(r.formula) ? "<\u516C\u5F0F>" : r.formula,
9763
+ reason: r.reason,
9764
+ risk_level: r.risk_level,
9765
+ action: r.action,
9766
+ enabled: r.enabled !== false,
9767
+ human: ltlHuman[r.id] || r.reason || ""
9768
+ }))
9769
+ };
9770
+ }
9771
+ function withL22View(rc) {
9772
+ if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
9773
+ const cat = rc.L2_2.ltl_rules;
9774
+ const rls = cat && cat.rules || [];
9775
+ const ltlHumanLocal = {
9776
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9777
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9778
+ "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",
9779
+ "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",
9780
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9781
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9782
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9783
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9784
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9785
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9786
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9787
+ };
9788
+ rc.L2_2 = {
9789
+ enabled_via: "L2_logic_rules",
9790
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9791
+ ltl_rules: rls.map((r) => ({
9792
+ id: r.id,
9793
+ type: "ltl",
9794
+ formula: "<\u516C\u5F0F>",
9795
+ reason: r.reason || "",
9796
+ risk_level: r.risk_level || "high",
9797
+ action: r.action || "block",
9798
+ enabled: r.enabled !== false,
9799
+ human: ltlHumanLocal[r.id] || r.reason || ""
9800
+ }))
9801
+ };
9802
+ }
9803
+ return rc;
9804
+ }
8895
9805
  console2.get("/api/rules", (c) => {
8896
9806
  const rc = loadRuntimeConfig();
8897
- return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules });
9807
+ return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
8898
9808
  });
8899
9809
  console2.put("/api/rules/enabled", async (c) => {
8900
9810
  const b = await c.req.json();
8901
9811
  const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
8902
- return c.json(rc);
9812
+ return c.json(withL22View(rc));
8903
9813
  });
8904
9814
  console2.put("/api/rules/deny_mode", async (c) => {
8905
9815
  const b = await c.req.json();
8906
9816
  const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
8907
- return c.json(rc);
9817
+ return c.json(withL22View(rc));
8908
9818
  });
8909
9819
  console2.put("/api/rules/skip_l3", async (c) => {
8910
9820
  const b = await c.req.json();
8911
9821
  const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
8912
- return c.json(rc);
9822
+ return c.json(withL22View(rc));
8913
9823
  });
8914
9824
  console2.put("/api/categories/enabled", async (c) => {
8915
9825
  const b = await c.req.json();
@@ -8968,9 +9878,9 @@ function createConsoleRouter() {
8968
9878
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
8969
9879
  return c.json({ error: "invalid skill_id" }, 400);
8970
9880
  }
8971
- const skillPath = join9(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
9881
+ const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
8972
9882
  if (!existsSync14(skillPath)) {
8973
- const builtinPath = join9(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
9883
+ const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
8974
9884
  if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
8975
9885
  return c.json({ error: "skill not found" }, 404);
8976
9886
  }
@@ -9059,7 +9969,7 @@ function createConsoleRouter() {
9059
9969
  });
9060
9970
  console2.get("/api/config", (c) => {
9061
9971
  const rc = loadRuntimeConfig();
9062
- return c.json(rc);
9972
+ return c.json({ ...rc, L2_2: getL22View() });
9063
9973
  });
9064
9974
  console2.get("/api/env-overrides", (c) => {
9065
9975
  return c.json({ overrides: getEnvOverrides() });
@@ -9396,4 +10306,17 @@ if (isMain) {
9396
10306
  process.exit(1);
9397
10307
  });
9398
10308
  }
10309
+ //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
10310
+ //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
10311
+ //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
10312
+ //!
10313
+ //! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
10314
+ //! P1 cross-session-chain 跨会话组合链
10315
+ //! P2 sensitive-data-flow 敏感数据传播
10316
+ //! P3 low-integrity-reachability 低完整性数据流可达
10317
+ //! P4 no-exfil-after-privesc 提权后禁外传
10318
+ //! P5 no-exfil-after-credential 凭据后禁外传
10319
+ //! P6 mc-ltl-rules LTL 模型检测
10320
+ //! P7 low-integrity-command-exec 低完整性命令执行
10321
+ //! P8 sensitive-data-aggregation 敏感数据聚合外传
9399
10322
  //# sourceMappingURL=cli.js.map