@kenhkl/agent-moss 0.12.9 → 0.13.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_4?.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"),
@@ -1354,6 +1395,7 @@ var init_skill_loader = __esm({
1354
1395
  // src/infra/runtime-config.ts
1355
1396
  var runtime_config_exports = {};
1356
1397
  __export(runtime_config_exports, {
1398
+ DEFAULT_LAYERS: () => DEFAULT_LAYERS,
1357
1399
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1358
1400
  addAgentRule: () => addAgentRule,
1359
1401
  addCustomRule: () => addCustomRule,
@@ -1405,7 +1447,7 @@ __export(runtime_config_exports, {
1405
1447
  updateSkillEnabled: () => updateSkillEnabled
1406
1448
  });
1407
1449
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1408
- import { join as join2 } from "path";
1450
+ import { join as join3 } from "path";
1409
1451
  import { fileURLToPath as fileURLToPath5 } from "url";
1410
1452
  import { dirname as dirname3 } from "path";
1411
1453
  function runtimeDir() {
@@ -1429,6 +1471,7 @@ function emptyRuntimeConfig() {
1429
1471
  L1_rules: {},
1430
1472
  L2_rules: {},
1431
1473
  L3_skills: {},
1474
+ L2_4: {},
1432
1475
  agent_rules: {},
1433
1476
  scenario_rules: {}
1434
1477
  };
@@ -1456,6 +1499,7 @@ function loadRuntimeConfig() {
1456
1499
  L1_rules: merged.L1_rules || {},
1457
1500
  L2_rules: merged.L2_rules || {},
1458
1501
  L3_skills: merged.L3_skills || {},
1502
+ L2_4: merged.L2_4 || {},
1459
1503
  agent_rules: merged.agent_rules || {},
1460
1504
  scenario_rules: merged.scenario_rules || {}
1461
1505
  };
@@ -1703,7 +1747,7 @@ function _scenarioRulesToDefaults() {
1703
1747
  return out;
1704
1748
  }
1705
1749
  for (const scenarioId of entries) {
1706
- const rulesFile = join2(scenDir, scenarioId, "rules.json");
1750
+ const rulesFile = join3(scenDir, scenarioId, "rules.json");
1707
1751
  if (!existsSync4(rulesFile)) continue;
1708
1752
  let data;
1709
1753
  try {
@@ -1752,7 +1796,7 @@ function _agentRulesToDefaults() {
1752
1796
  return out;
1753
1797
  }
1754
1798
  for (const agentId of entries) {
1755
- const rulesFile = join2(agentsDir, agentId, "rules.json");
1799
+ const rulesFile = join3(agentsDir, agentId, "rules.json");
1756
1800
  if (!existsSync4(rulesFile)) continue;
1757
1801
  let data;
1758
1802
  try {
@@ -1793,7 +1837,7 @@ function _agentRulesToDefaults() {
1793
1837
  function generateSourceDefaults() {
1794
1838
  return {
1795
1839
  version: 1,
1796
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1840
+ layers: { ...DEFAULT_LAYERS },
1797
1841
  L1_rules: {
1798
1842
  dangerous_commands: {
1799
1843
  category_enabled: true,
@@ -1832,10 +1876,45 @@ function generateSourceDefaults() {
1832
1876
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1833
1877
  },
1834
1878
  L3_skills: _l3SkillsToRules(),
1879
+ L2_4: _ltlRulesToRuntime(),
1835
1880
  agent_rules: _agentRulesToDefaults(),
1836
1881
  scenario_rules: _scenarioRulesToDefaults()
1837
1882
  };
1838
1883
  }
1884
+ function _ltlRulesToRuntime() {
1885
+ const cands = [
1886
+ join3(__dirname4, "..", "rules"),
1887
+ join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1888
+ join3(process.cwd(), "agent_moss", "rules")
1889
+ ];
1890
+ let mc = {};
1891
+ for (const dir of cands) {
1892
+ const p = join3(dir, "mc_rules.json");
1893
+ if (existsSync4(p)) {
1894
+ try {
1895
+ mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1896
+ } catch {
1897
+ mc = {};
1898
+ }
1899
+ break;
1900
+ }
1901
+ }
1902
+ const rules = mc.rules || [];
1903
+ return {
1904
+ ltl_rules: {
1905
+ category_enabled: true,
1906
+ category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
1907
+ rules: rules.map((r) => ({
1908
+ id: r.id,
1909
+ reason: r.reason || "",
1910
+ risk_level: r.risk_level || "high",
1911
+ action: r.action || "block",
1912
+ enabled: true,
1913
+ builtin: true
1914
+ }))
1915
+ }
1916
+ };
1917
+ }
1839
1918
  function normalizePatternForFingerprint(pattern) {
1840
1919
  return pattern.replace(/\\\//g, "/");
1841
1920
  }
@@ -1985,6 +2064,7 @@ function mergeConfig(userCfg, srcDefaults) {
1985
2064
  );
1986
2065
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1987
2066
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2067
+ _mergeRuleCategories(cfg, srcDefaults, "L2_4");
1988
2068
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1989
2069
  _mergeAgentRules(cfg, srcDefaults);
1990
2070
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2177,12 +2257,12 @@ function deleteCustomRule(layer, category, ruleId) {
2177
2257
  function addCustomSkill(skillId, category, keywords, content) {
2178
2258
  _validateSkillId(skillId);
2179
2259
  const runtime = loadRuntimeConfig();
2180
- const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2260
+ const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2181
2261
  try {
2182
2262
  mkdirSync(skillsDir, { recursive: true });
2183
2263
  } catch {
2184
2264
  }
2185
- writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
2265
+ writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2186
2266
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2187
2267
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2188
2268
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2197,7 +2277,7 @@ function deleteCustomSkill(skillId, category) {
2197
2277
  if (idx >= 0) {
2198
2278
  if (skills[idx].builtin) return null;
2199
2279
  skills.splice(idx, 1);
2200
- const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2280
+ const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2201
2281
  try {
2202
2282
  if (existsSync4(skillPath)) unlinkSync(skillPath);
2203
2283
  } catch {
@@ -2391,7 +2471,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2391
2471
  saveRuntimeConfig(runtime);
2392
2472
  return runtime;
2393
2473
  }
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;
2474
+ 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
2475
  var init_runtime_config = __esm({
2396
2476
  "src/infra/runtime-config.ts"() {
2397
2477
  "use strict";
@@ -2403,21 +2483,28 @@ var init_runtime_config = __esm({
2403
2483
  __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2404
2484
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2405
2485
  RUNTIME_CONFIG_DIR = runtimeDir();
2406
- RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2486
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2407
2487
  LAYER_DISABLE_ENV = {
2408
2488
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2409
2489
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2410
2490
  L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2411
2491
  };
2492
+ DEFAULT_LAYERS = {
2493
+ L1_heuristic: true,
2494
+ L2_logic_rules: true,
2495
+ L3_llm_analysis: true,
2496
+ agent_rules_enabled: true,
2497
+ scenario_rules_enabled: true
2498
+ };
2412
2499
  DENY_MODE_LABELS = {
2413
2500
  deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2414
2501
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2415
2502
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2416
2503
  };
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");
2504
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2505
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2506
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2507
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2421
2508
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2422
2509
  }
2423
2510
  });
@@ -2555,11 +2642,21 @@ function getScenarioExtensions(scenarioId) {
2555
2642
  );
2556
2643
  return ext;
2557
2644
  }
2645
+ function getScenarioExtensionsMulti(scenarioSpec) {
2646
+ if (!scenarioSpec) return null;
2647
+ const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
2648
+ if (ids.length === 0) return null;
2649
+ const exts = ids.map((id) => getScenarioExtensions(id));
2650
+ const nonNull = exts.filter((e) => e !== null);
2651
+ if (nonNull.length === 0) return null;
2652
+ return mergeAgentExtensions(...nonNull);
2653
+ }
2558
2654
  var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2559
2655
  var init_scenarios = __esm({
2560
2656
  "src/scenarios/index.ts"() {
2561
2657
  "use strict";
2562
2658
  init_esm_shims();
2659
+ init_agents();
2563
2660
  __filename5 = fileURLToPath7(import.meta.url);
2564
2661
  __dirname6 = dirname5(__filename5);
2565
2662
  SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
@@ -2608,8 +2705,8 @@ function runHeuristicCheck(input) {
2608
2705
  }
2609
2706
  highestRiskType = topRiskType || highestRiskType;
2610
2707
  const agentExt = mergeAgentExtensions(
2611
- getAgentExtensions(input.agentId),
2612
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2708
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
2709
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
2613
2710
  );
2614
2711
  if (agentExt?.extra_command_patterns) {
2615
2712
  for (const rule of agentExt.extra_command_patterns) {
@@ -3054,6 +3151,7 @@ var init_heuristic = __esm({
3054
3151
  init_config();
3055
3152
  init_agents();
3056
3153
  init_scenarios();
3154
+ init_runtime_config();
3057
3155
  init_rules_loader();
3058
3156
  init_logic_rules();
3059
3157
  INJECTION_KEYWORDS = (() => {
@@ -3116,7 +3214,7 @@ var init_template_loader = __esm({
3116
3214
 
3117
3215
  // src/engine/script-analyzer.ts
3118
3216
  import { readFileSync as readFileSync8 } from "fs";
3119
- import { resolve as resolve6, join as join3 } from "path";
3217
+ import { resolve as resolve6, join as join4 } from "path";
3120
3218
  function emptyResult() {
3121
3219
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
3122
3220
  }
@@ -3169,7 +3267,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3169
3267
  }
3170
3268
  function readScriptContent(scriptPath, maxLines = 500) {
3171
3269
  try {
3172
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3270
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3173
3271
  const raw = readFileSync8(expandedPath, "utf-8");
3174
3272
  const lines = raw.split("\n").slice(0, maxLines);
3175
3273
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4803,8 +4901,8 @@ var init_tracker = __esm({
4803
4901
 
4804
4902
  // src/engine/script-content-analyzer.ts
4805
4903
  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";
4904
+ import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
4905
+ import { homedir as homedir4 } from "os";
4808
4906
  function emptyChain() {
4809
4907
  return {
4810
4908
  nodes: [],
@@ -4902,7 +5000,7 @@ function extractInlineScriptPath(actionDetail) {
4902
5000
  function resolvePath2(p) {
4903
5001
  let expanded = p;
4904
5002
  if (expanded.startsWith("~/") || expanded === "~") {
4905
- expanded = join5(homedir3(), expanded.slice(1));
5003
+ expanded = join6(homedir4(), expanded.slice(1));
4906
5004
  }
4907
5005
  return resolve8(expanded);
4908
5006
  }
@@ -4983,7 +5081,7 @@ function analyzeScriptContent2(input) {
4983
5081
  isInline = true;
4984
5082
  }
4985
5083
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
4986
- resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
5084
+ resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
4987
5085
  }
4988
5086
  const l1Adapter = (content) => {
4989
5087
  const virtualInput = {
@@ -5573,8 +5671,8 @@ var init_llm_analyzer = __esm({
5573
5671
 
5574
5672
  // src/brain/rule-store.ts
5575
5673
  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";
5674
+ import { join as join7 } from "path";
5675
+ import { homedir as homedir5 } from "os";
5578
5676
  function getRulesPath() {
5579
5677
  const envPath = process.env[ENV_RULES_PATH];
5580
5678
  if (envPath) return envPath;
@@ -5592,7 +5690,7 @@ var init_rule_store = __esm({
5592
5690
  "src/brain/rule-store.ts"() {
5593
5691
  "use strict";
5594
5692
  init_esm_shims();
5595
- DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
5693
+ DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
5596
5694
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5597
5695
  STATUS_DRAFT = "draft";
5598
5696
  STATUS_APPROVED = "approved";
@@ -6122,6 +6220,13 @@ var init_brain = __esm({
6122
6220
  });
6123
6221
 
6124
6222
  // src/engine/formal-engine/decode.ts
6223
+ var decode_exports = {};
6224
+ __export(decode_exports, {
6225
+ allCandidates: () => allCandidates,
6226
+ decodeBase64Literals: () => decodeBase64Literals,
6227
+ decodeUrlAndNumeric: () => decodeUrlAndNumeric,
6228
+ matchStageAny: () => matchStageAny
6229
+ });
6125
6230
  function isValidBase64Token(tok) {
6126
6231
  if (tok.length < 8) return false;
6127
6232
  if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
@@ -6220,6 +6325,142 @@ var init_decode = __esm({
6220
6325
  }
6221
6326
  });
6222
6327
 
6328
+ // src/engine/formal-engine/ir.ts
6329
+ function allNodes(mg) {
6330
+ const out = [];
6331
+ for (const s of mg.sessions) out.push(...s.nodes);
6332
+ return out;
6333
+ }
6334
+ function allEdges(mg) {
6335
+ const out = [];
6336
+ for (const s of mg.sessions) out.push(...s.edges);
6337
+ out.push(...mg.crossEdges);
6338
+ return out;
6339
+ }
6340
+ function emptyVerificationResult() {
6341
+ return { verified: true, violations: [] };
6342
+ }
6343
+ var init_ir = __esm({
6344
+ "src/engine/formal-engine/ir.ts"() {
6345
+ "use strict";
6346
+ init_esm_shims();
6347
+ }
6348
+ });
6349
+
6350
+ // src/engine/formal-engine/ltl.ts
6351
+ function compileAps(apPatterns) {
6352
+ if (AP_RE) return;
6353
+ AP_RE = {};
6354
+ for (const [name, pats] of Object.entries(apPatterns || {})) {
6355
+ AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
6356
+ }
6357
+ }
6358
+ function labelEvent(actionDetail) {
6359
+ if (!AP_RE) compileAps(loadMC().ap_patterns || {});
6360
+ let candidates = [actionDetail];
6361
+ try {
6362
+ candidates = (init_decode(), __toCommonJS(decode_exports)).allCandidates(actionDetail);
6363
+ } catch {
6364
+ }
6365
+ const hits = /* @__PURE__ */ new Set();
6366
+ for (const [name, pats] of Object.entries(AP_RE || {})) {
6367
+ for (const c of candidates) {
6368
+ const m = pats.some((p) => p.test(c));
6369
+ if (m) {
6370
+ hits.add(name);
6371
+ break;
6372
+ }
6373
+ }
6374
+ }
6375
+ return hits;
6376
+ }
6377
+ function evaluateLTL(formula, trace, pos) {
6378
+ const op = formula[0];
6379
+ switch (op) {
6380
+ case "ap":
6381
+ return trace[pos].has(formula[1]);
6382
+ case "!":
6383
+ return !evaluateLTL(formula[1], trace, pos);
6384
+ case "&":
6385
+ return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
6386
+ case "|":
6387
+ return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6388
+ case "->":
6389
+ return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6390
+ case "X":
6391
+ return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
6392
+ case "G": {
6393
+ for (let j = pos; j < trace.length; j++) {
6394
+ if (!evaluateLTL(formula[1], trace, j)) return false;
6395
+ }
6396
+ return true;
6397
+ }
6398
+ case "F": {
6399
+ for (let j = pos; j < trace.length; j++) {
6400
+ if (evaluateLTL(formula[1], trace, j)) return true;
6401
+ }
6402
+ return false;
6403
+ }
6404
+ case "U": {
6405
+ for (let j = pos; j < trace.length; j++) {
6406
+ if (evaluateLTL(formula[2], trace, j)) {
6407
+ for (let k = pos; k < j; k++) {
6408
+ if (!evaluateLTL(formula[1], trace, k)) return false;
6409
+ }
6410
+ return true;
6411
+ }
6412
+ }
6413
+ return false;
6414
+ }
6415
+ default:
6416
+ return false;
6417
+ }
6418
+ }
6419
+ function ruleViolates(rule, traceWith, witnessMode) {
6420
+ const formula = rule?.formula;
6421
+ if (!Array.isArray(formula) || formula.length === 0) return false;
6422
+ const satWith = evaluateLTL(formula, traceWith, 0);
6423
+ if (!satWith) return false;
6424
+ if (witnessMode && traceWith.length > 1) {
6425
+ const traceWithout = traceWith.slice(0, -1);
6426
+ if (evaluateLTL(formula, traceWithout, 0)) return false;
6427
+ }
6428
+ return true;
6429
+ }
6430
+ function checkMCRules(graph) {
6431
+ const violations = [];
6432
+ const nodes = allNodes(graph);
6433
+ if (nodes.length === 0) return violations;
6434
+ const mc = loadMC();
6435
+ const rules = mc.rules || [];
6436
+ if (rules.length === 0) return violations;
6437
+ const trace = nodes.map((n) => labelEvent(n.actionDetail));
6438
+ const witnessMode = graph.witnessIdx !== void 0;
6439
+ for (const rule of rules) {
6440
+ if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
6441
+ if (ruleViolates(rule, trace, witnessMode)) {
6442
+ violations.push({
6443
+ propertyId: String(rule.id || "mc-ltl"),
6444
+ reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
6445
+ riskLevel: String(rule.risk_level || "high"),
6446
+ 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) }))
6447
+ });
6448
+ if (witnessMode) return violations;
6449
+ }
6450
+ }
6451
+ return violations;
6452
+ }
6453
+ var AP_RE;
6454
+ var init_ltl = __esm({
6455
+ "src/engine/formal-engine/ltl.ts"() {
6456
+ "use strict";
6457
+ init_esm_shims();
6458
+ init_rules_loader();
6459
+ init_ir();
6460
+ AP_RE = null;
6461
+ }
6462
+ });
6463
+
6223
6464
  // src/engine/formal-engine/builder.ts
6224
6465
  function loadStages() {
6225
6466
  if (COMBO_STAGES2) return COMBO_STAGES2;
@@ -6246,6 +6487,26 @@ function extractResources(actionDetail) {
6246
6487
  function markStage(actionDetail) {
6247
6488
  return matchStageAny(actionDetail, loadStages());
6248
6489
  }
6490
+ function markIntegrity(actionDetail) {
6491
+ const aps = labelEvent(actionDetail);
6492
+ if (aps.has("download_file") || aps.has("prompt_injection")) {
6493
+ return { integrity: "low", isLowSource: true };
6494
+ }
6495
+ return { integrity: "medium", isLowSource: false };
6496
+ }
6497
+ function initNode(sessionId, seq, actionType, actionDetail) {
6498
+ const { integrity, isLowSource } = markIntegrity(actionDetail);
6499
+ return {
6500
+ sessionId,
6501
+ seq,
6502
+ actionType,
6503
+ actionDetail,
6504
+ stage: markStage(actionDetail),
6505
+ resources: extractResources(actionDetail),
6506
+ integrity,
6507
+ isLowSource
6508
+ };
6509
+ }
6249
6510
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6250
6511
  const nodes = [];
6251
6512
  const edges = [];
@@ -6259,17 +6520,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6259
6520
  details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6260
6521
  }
6261
6522
  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
- };
6523
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
6271
6524
  nodes.push(node);
6272
- if (stage) stageFlags.add(stage);
6525
+ if (node.stage) stageFlags.add(node.stage);
6273
6526
  });
6274
6527
  for (let i = 0; i < nodes.length - 1; i++) {
6275
6528
  edges.push({ src: i, dst: i + 1, kind: "next" });
@@ -6289,15 +6542,12 @@ function buildMultiSessionGraph(sessions, aNext) {
6289
6542
  if (aNext) {
6290
6543
  const last = mg.sessions[mg.sessions.length - 1];
6291
6544
  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
- });
6545
+ last.nodes.push(initNode(
6546
+ last.sessionId,
6547
+ last.nodes.length,
6548
+ String(aNext.actionType || ""),
6549
+ String(aNext.actionDetail || "")
6550
+ ));
6301
6551
  if (last.nodes.length > 1) {
6302
6552
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6303
6553
  }
@@ -6311,11 +6561,25 @@ function buildMultiSessionGraph(sessions, aNext) {
6311
6561
  function addCrossDataEdges(mg) {
6312
6562
  const nodes = allNodesFlat(mg);
6313
6563
  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) {
6564
+ const resIndex = /* @__PURE__ */ new Map();
6565
+ nodes.forEach((n, i) => {
6566
+ for (const r of n.resources) {
6567
+ const list = resIndex.get(r);
6568
+ if (list) list.push(i);
6569
+ else resIndex.set(r, [i]);
6570
+ }
6571
+ });
6572
+ const seen = /* @__PURE__ */ new Set();
6573
+ for (const idxs of resIndex.values()) {
6574
+ if (idxs.length < 2) continue;
6575
+ for (let a = 0; a < idxs.length; a++) {
6576
+ const i = idxs[a];
6577
+ for (let b = a + 1; b < idxs.length; b++) {
6578
+ const j = idxs[b];
6579
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6580
+ const key = `${i}:${j}`;
6581
+ if (seen.has(key)) continue;
6582
+ seen.add(key);
6319
6583
  mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6320
6584
  }
6321
6585
  }
@@ -6326,11 +6590,6 @@ function allNodesFlat(mg) {
6326
6590
  for (const s of mg.sessions) out.push(...s.nodes);
6327
6591
  return out;
6328
6592
  }
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
6593
  var COMBO_STAGES2, PATH_RE;
6335
6594
  var init_builder = __esm({
6336
6595
  "src/engine/formal-engine/builder.ts"() {
@@ -6338,33 +6597,12 @@ var init_builder = __esm({
6338
6597
  init_esm_shims();
6339
6598
  init_rules_loader();
6340
6599
  init_decode();
6600
+ init_ltl();
6341
6601
  COMBO_STAGES2 = null;
6342
6602
  PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
6343
6603
  }
6344
6604
  });
6345
6605
 
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
6606
  // src/engine/formal-engine/properties.ts
6369
6607
  function loadCrossSessionChains() {
6370
6608
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
@@ -6381,27 +6619,50 @@ function checkCrossSessionChain(graph) {
6381
6619
  const violations = [];
6382
6620
  const nodes = allNodes(graph);
6383
6621
  if (nodes.length === 0) return violations;
6622
+ const witness = graph.witnessIdx;
6384
6623
  for (const chain of loadCrossSessionChains()) {
6385
6624
  const stages = chain.stages;
6386
6625
  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;
6626
+ if (witness !== void 0) {
6627
+ if (nodes[witness].stage !== stages[stages.length - 1]) continue;
6628
+ let matched = 0;
6629
+ const path6 = [];
6630
+ for (let k = 0; k < witness; k++) {
6631
+ const n = nodes[k];
6632
+ if (matched < stages.length - 1 && n.stage === stages[matched]) {
6633
+ matched += 1;
6634
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6635
+ }
6396
6636
  }
6397
- }
6398
- if (matched >= stages.length) {
6637
+ if (matched < stages.length - 1) continue;
6638
+ path6.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
6399
6639
  violations.push({
6400
6640
  propertyId: "cross-" + chain.id,
6401
6641
  reason: chain.reason,
6402
6642
  riskLevel: chain.riskLevel,
6403
6643
  path: path6
6404
6644
  });
6645
+ return violations;
6646
+ } else {
6647
+ let matched = 0;
6648
+ let stageIdx = 0;
6649
+ const path6 = [];
6650
+ for (const n of nodes) {
6651
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6652
+ matched += 1;
6653
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6654
+ stageIdx += 1;
6655
+ if (matched >= stages.length) break;
6656
+ }
6657
+ }
6658
+ if (matched >= stages.length) {
6659
+ violations.push({
6660
+ propertyId: "cross-" + chain.id,
6661
+ reason: chain.reason,
6662
+ riskLevel: chain.riskLevel,
6663
+ path: path6
6664
+ });
6665
+ }
6405
6666
  }
6406
6667
  }
6407
6668
  return violations;
@@ -6436,7 +6697,10 @@ function checkSensitiveDataFlow(graph) {
6436
6697
  }
6437
6698
  }
6438
6699
  }
6439
- for (const [i, n] of nodes.entries()) {
6700
+ const witness = graph.witnessIdx;
6701
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6702
+ for (const i of candidates) {
6703
+ const n = nodes[i];
6440
6704
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6441
6705
  violations.push({
6442
6706
  propertyId: "sensitive-data-exfil",
@@ -6444,6 +6708,7 @@ function checkSensitiveDataFlow(graph) {
6444
6708
  riskLevel: "critical",
6445
6709
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6446
6710
  });
6711
+ if (witness !== void 0) return violations;
6447
6712
  }
6448
6713
  }
6449
6714
  return violations;
@@ -6469,6 +6734,32 @@ function checkNeverExfilAfterPrivesc(graph) {
6469
6734
  const violations = [];
6470
6735
  const nodes = allNodes(graph);
6471
6736
  if (nodes.length < 2) return violations;
6737
+ const witness = graph.witnessIdx;
6738
+ if (witness !== void 0) {
6739
+ const evIdx = [];
6740
+ nodes.forEach((n, i) => {
6741
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6742
+ });
6743
+ if (evIdx.length === 0) return violations;
6744
+ const firstEv = Math.min(...evIdx);
6745
+ let pre = null;
6746
+ for (let i = 0; i < firstEv; i++) {
6747
+ if (isPrivescNode(nodes[i])) pre = nodes[i];
6748
+ }
6749
+ if (pre) {
6750
+ const post = nodes[firstEv];
6751
+ return [{
6752
+ propertyId: "no-privesc-then-exfil",
6753
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6754
+ riskLevel: "critical",
6755
+ path: [
6756
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6757
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6758
+ ]
6759
+ }];
6760
+ }
6761
+ return violations;
6762
+ }
6472
6763
  for (let i = 0; i < nodes.length; i++) {
6473
6764
  if (!isPrivescNode(nodes[i])) continue;
6474
6765
  for (let j = i + 1; j < nodes.length; j++) {
@@ -6492,6 +6783,32 @@ function checkNeverExfilAfterCred(graph) {
6492
6783
  const violations = [];
6493
6784
  const nodes = allNodes(graph);
6494
6785
  if (nodes.length < 2) return violations;
6786
+ const witness = graph.witnessIdx;
6787
+ if (witness !== void 0) {
6788
+ const evIdx = [];
6789
+ nodes.forEach((n, i) => {
6790
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6791
+ });
6792
+ if (evIdx.length === 0) return violations;
6793
+ const firstEv = Math.min(...evIdx);
6794
+ let pre = null;
6795
+ for (let i = 0; i < firstEv; i++) {
6796
+ if (isCredNode(nodes[i])) pre = nodes[i];
6797
+ }
6798
+ if (pre) {
6799
+ const post = nodes[firstEv];
6800
+ return [{
6801
+ propertyId: "no-cred-then-exfil",
6802
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6803
+ riskLevel: "critical",
6804
+ path: [
6805
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6806
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6807
+ ]
6808
+ }];
6809
+ }
6810
+ return violations;
6811
+ }
6495
6812
  for (let i = 0; i < nodes.length; i++) {
6496
6813
  if (!isCredNode(nodes[i])) continue;
6497
6814
  for (let j = i + 1; j < nodes.length; j++) {
@@ -6511,13 +6828,164 @@ function checkNeverExfilAfterCred(graph) {
6511
6828
  }
6512
6829
  return violations;
6513
6830
  }
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;
6831
+ function getToolRegistry() {
6832
+ if (TOOL_REGISTRY) return TOOL_REGISTRY;
6833
+ TOOL_REGISTRY = loadToolRegistry().tools || {};
6834
+ return TOOL_REGISTRY;
6835
+ }
6836
+ function lookupToolClass(actionType) {
6837
+ const entry = getToolRegistry()[actionType] || {};
6838
+ return entry.actionClass;
6839
+ }
6840
+ function isComputeNode(n) {
6841
+ if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
6842
+ const cls = lookupToolClass(n.actionType);
6843
+ if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
6844
+ return COMPUTE_RE.test(n.actionDetail);
6845
+ }
6846
+ function checkLowIntegrityCommandExec(graph) {
6847
+ const violations = [];
6848
+ const nodes = allNodes(graph);
6849
+ if (nodes.length === 0) return violations;
6850
+ const witness = graph.witnessIdx;
6851
+ const lowResources = /* @__PURE__ */ new Set();
6852
+ const lowAt = /* @__PURE__ */ new Map();
6853
+ nodes.forEach((n, i) => {
6854
+ if (n.isLowSource) {
6855
+ lowAt.set(i, n.actionDetail.slice(0, 60));
6856
+ for (const r of n.resources) lowResources.add(r);
6857
+ }
6858
+ });
6859
+ let changed = true;
6860
+ while (changed) {
6861
+ changed = false;
6862
+ for (let i = 0; i < nodes.length; i++) {
6863
+ if (lowAt.has(i)) continue;
6864
+ let shared = "";
6865
+ for (const r of nodes[i].resources) {
6866
+ if (lowResources.has(r)) {
6867
+ shared = r;
6868
+ break;
6869
+ }
6870
+ }
6871
+ if (shared) {
6872
+ lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
6873
+ for (const r of nodes[i].resources) lowResources.add(r);
6874
+ changed = true;
6875
+ }
6876
+ }
6877
+ }
6878
+ if (lowAt.size === 0) return violations;
6879
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6880
+ for (const i of candidates) {
6881
+ const n = nodes[i];
6882
+ if (lowAt.has(i) && isComputeNode(n)) {
6883
+ violations.push({
6884
+ propertyId: "low-integrity-controls-command-exec",
6885
+ 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`,
6886
+ riskLevel: "critical",
6887
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6888
+ });
6889
+ if (witness !== void 0) return violations;
6890
+ }
6891
+ }
6892
+ return violations;
6893
+ }
6894
+ function checkCrossSessionReachability(graph) {
6895
+ const violations = [];
6896
+ const nodes = allNodes(graph);
6897
+ if (nodes.length === 0) return violations;
6898
+ const witness = graph.witnessIdx;
6899
+ const sources = /* @__PURE__ */ new Set();
6900
+ nodes.forEach((n, i) => {
6901
+ if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
6902
+ });
6903
+ if (sources.size === 0) return violations;
6904
+ const reached = new Set(sources);
6905
+ let changed = true;
6906
+ while (changed) {
6907
+ changed = false;
6908
+ for (const e of allEdges(graph)) {
6909
+ if (reached.has(e.src) && !reached.has(e.dst)) {
6910
+ const srcRes = nodes[e.src].resources;
6911
+ const dstRes = nodes[e.dst].resources;
6912
+ for (const r of srcRes) {
6913
+ if (dstRes.has(r)) {
6914
+ reached.add(e.dst);
6915
+ changed = true;
6916
+ break;
6917
+ }
6918
+ }
6919
+ }
6920
+ }
6921
+ }
6922
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6923
+ for (const i of candidates) {
6924
+ const n = nodes[i];
6925
+ if (sources.has(i)) continue;
6926
+ if (reached.has(i)) {
6927
+ const srcIdx = Math.min(...sources);
6928
+ const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
6929
+ violations.push({
6930
+ propertyId: "cross-session-reachability",
6931
+ 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`,
6932
+ riskLevel: "high",
6933
+ path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
6934
+ });
6935
+ if (witness !== void 0) return violations;
6936
+ }
6937
+ }
6938
+ return violations;
6939
+ }
6940
+ function checkSensitiveDataAggregation(graph) {
6941
+ const violations = [];
6942
+ const nodes = allNodes(graph);
6943
+ if (nodes.length < 2) return violations;
6944
+ const witness = graph.witnessIdx;
6945
+ const resIndex = /* @__PURE__ */ new Map();
6946
+ nodes.forEach((n, i) => {
6947
+ for (const r of n.resources) {
6948
+ const list = resIndex.get(r);
6949
+ if (list) list.push(i);
6950
+ else resIndex.set(r, [i]);
6951
+ }
6952
+ });
6953
+ const sourcesFor = (exfilIdx) => {
6954
+ const srcs = /* @__PURE__ */ new Set();
6955
+ const n = nodes[exfilIdx];
6956
+ for (const r of n.resources) {
6957
+ for (const j of resIndex.get(r) || []) {
6958
+ if (j === exfilIdx) continue;
6959
+ srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
6960
+ }
6961
+ }
6962
+ return srcs;
6963
+ };
6964
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6965
+ for (const i of candidates) {
6966
+ const n = nodes[i];
6967
+ if (!isExfilNode(n)) continue;
6968
+ const srcs = sourcesFor(i);
6969
+ if (srcs.size >= 2) {
6970
+ violations.push({
6971
+ propertyId: "sensitive-data-aggregation",
6972
+ reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
6973
+ riskLevel: "critical",
6974
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6975
+ });
6976
+ if (witness !== void 0) return violations;
6977
+ }
6978
+ }
6979
+ return violations;
6980
+ }
6981
+ 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
6982
  var init_properties = __esm({
6516
6983
  "src/engine/formal-engine/properties.ts"() {
6517
6984
  "use strict";
6518
6985
  init_esm_shims();
6519
6986
  init_rules_loader();
6520
6987
  init_ir();
6988
+ init_ltl();
6521
6989
  CROSS_SESSION_CHAINS = null;
6522
6990
  CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6523
6991
  EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
@@ -6526,11 +6994,22 @@ var init_properties = __esm({
6526
6994
  PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6527
6995
  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
6996
  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;
6997
+ TOOL_REGISTRY = null;
6998
+ 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;
6999
+ STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
7000
+ 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
7001
  ALL_PROPERTIES = [
6530
7002
  { id: "cross-session-chain", check: checkCrossSessionChain },
6531
7003
  { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
7004
+ { id: "cross-session-reachability", check: checkCrossSessionReachability },
6532
7005
  { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6533
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
7006
+ { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred },
7007
+ // L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
7008
+ { id: "mc-ltl-rules", check: checkMCRules },
7009
+ // Rule 5 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
7010
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec },
7011
+ // Rule 12 敏感数据聚合外传(egress 聚合 ≥2 事件源)
7012
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation }
6534
7013
  ];
6535
7014
  }
6536
7015
  });
@@ -6572,6 +7051,9 @@ var init_engine = __esm({
6572
7051
  });
6573
7052
  }
6574
7053
  const graph = buildMultiSessionGraph(all, aNext);
7054
+ if (aNext) {
7055
+ graph.witnessIdx = allNodes(graph).length - 1;
7056
+ }
6575
7057
  const result = emptyVerificationResult();
6576
7058
  for (const prop of this.properties) {
6577
7059
  let violations;
@@ -7083,6 +7565,7 @@ function _resetBrainForTest() {
7083
7565
  _brainInitialized = false;
7084
7566
  }
7085
7567
  async function runAnalysis(input) {
7568
+ const observedFormalLayers = [];
7086
7569
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
7087
7570
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
7088
7571
  const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
@@ -7137,8 +7620,8 @@ async function runAnalysis(input) {
7137
7620
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
7138
7621
  const lowerDetail = rawDetail.toLowerCase();
7139
7622
  const agentExt = mergeAgentExtensions(
7140
- getAgentExtensions(input.agentId),
7141
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
7623
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
7624
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
7142
7625
  );
7143
7626
  if (FULLY_SAFE_TOOLS.has(actionType)) {
7144
7627
  return {
@@ -7255,15 +7738,22 @@ async function runAnalysis(input) {
7255
7738
  const formalResult = formalEngine.verify(formalSessions, input.aNext);
7256
7739
  if (!formalResult.verified) {
7257
7740
  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
- };
7741
+ if (isFormalObserveMode()) {
7742
+ console.warn(
7743
+ `[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)}`
7744
+ );
7745
+ observedFormalLayers.push("2.4");
7746
+ } else {
7747
+ return {
7748
+ allowed: false,
7749
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
7750
+ riskLevel: v ? v.riskLevel : "high",
7751
+ riskType: "formal_verification",
7752
+ confidence: 90,
7753
+ source: "formal_engine",
7754
+ violatedLayers: ["2.4"]
7755
+ };
7756
+ }
7267
7757
  }
7268
7758
  }
7269
7759
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -7310,7 +7800,7 @@ async function runAnalysis(input) {
7310
7800
  riskType: "",
7311
7801
  confidence: 85,
7312
7802
  source: "heuristic_and_logic_only",
7313
- violatedLayers: []
7803
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
7314
7804
  };
7315
7805
  }
7316
7806
  let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
@@ -7355,7 +7845,7 @@ async function runAnalysis(input) {
7355
7845
  riskType: "",
7356
7846
  confidence: 50,
7357
7847
  source: "llm_failure_fallback",
7358
- violatedLayers: []
7848
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
7359
7849
  };
7360
7850
  }
7361
7851
  return {
@@ -7382,6 +7872,9 @@ async function runAnalysis(input) {
7382
7872
  } catch {
7383
7873
  }
7384
7874
  }
7875
+ if (observedFormalLayers.length > 0 && llmResult.allowed) {
7876
+ llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
7877
+ }
7385
7878
  return llmResult;
7386
7879
  }
7387
7880
  var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
@@ -7395,6 +7888,7 @@ var init_coordinator = __esm({
7395
7888
  init_llm_analyzer();
7396
7889
  init_script_content_analyzer();
7397
7890
  init_config();
7891
+ init_runtime_config();
7398
7892
  init_agents();
7399
7893
  init_scenarios();
7400
7894
  init_brain();
@@ -7556,7 +8050,7 @@ var require_bindings = __commonJS({
7556
8050
  var fs7 = __require("fs");
7557
8051
  var path6 = __require("path");
7558
8052
  var fileURLToPath11 = require_file_uri_to_path();
7559
- var join10 = path6.join;
8053
+ var join11 = path6.join;
7560
8054
  var dirname11 = path6.dirname;
7561
8055
  var exists = fs7.accessSync && function(path7) {
7562
8056
  try {
@@ -7616,7 +8110,7 @@ var require_bindings = __commonJS({
7616
8110
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
7617
8111
  var tries = [], i = 0, l = opts.try.length, n, b, err;
7618
8112
  for (; i < l; i++) {
7619
- n = join10.apply(
8113
+ n = join11.apply(
7620
8114
  null,
7621
8115
  opts.try[i].map(function(p) {
7622
8116
  return opts[p] || p;
@@ -7677,7 +8171,7 @@ var require_bindings = __commonJS({
7677
8171
  if (dir === ".") {
7678
8172
  dir = process.cwd();
7679
8173
  }
7680
- if (exists(join10(dir, "package.json")) || exists(join10(dir, "node_modules"))) {
8174
+ if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
7681
8175
  return dir;
7682
8176
  }
7683
8177
  if (prev === dir) {
@@ -7686,7 +8180,7 @@ var require_bindings = __commonJS({
7686
8180
  );
7687
8181
  }
7688
8182
  prev = dir;
7689
- dir = join10(dir, "..");
8183
+ dir = join11(dir, "..");
7690
8184
  }
7691
8185
  };
7692
8186
  }
@@ -8846,12 +9340,13 @@ init_esm_shims();
8846
9340
  init_esm_shims();
8847
9341
  init_runtime_config();
8848
9342
  init_token_stats();
9343
+ init_rules_loader();
8849
9344
  import { Hono } from "hono";
8850
9345
  import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
8851
- import { join as join9, dirname as dirname10 } from "path";
9346
+ import { join as join10, dirname as dirname10 } from "path";
8852
9347
  import { fileURLToPath as fileURLToPath9 } from "url";
8853
9348
  var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8854
- var STATIC_DIR = join9(__dirname8, "static");
9349
+ var STATIC_DIR = join10(__dirname8, "static");
8855
9350
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
8856
9351
  function getConsoleToken() {
8857
9352
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -8875,7 +9370,7 @@ function createConsoleRouter() {
8875
9370
  const console2 = new Hono();
8876
9371
  console2.use("/api/*", authMiddleware());
8877
9372
  const serveIndex = (c) => {
8878
- const indexFile = join9(STATIC_DIR, "index.html");
9373
+ const indexFile = join10(STATIC_DIR, "index.html");
8879
9374
  if (existsSync14(indexFile)) {
8880
9375
  return c.html(readFileSync15(indexFile, "utf-8"));
8881
9376
  }
@@ -8892,24 +9387,89 @@ function createConsoleRouter() {
8892
9387
  const rc = updateLayerEnabled(body.layer_key, body.enabled);
8893
9388
  return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
8894
9389
  });
9390
+ function getL24View() {
9391
+ const ltlHuman = {
9392
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9393
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9394
+ "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",
9395
+ "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",
9396
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9397
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9398
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9399
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9400
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9401
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9402
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9403
+ };
9404
+ const mc = loadMC();
9405
+ const rules = mc.rules || [];
9406
+ return {
9407
+ enabled_via: "L2_logic_rules",
9408
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9409
+ ltl_rules: rules.map((r) => ({
9410
+ id: r.id,
9411
+ type: r.type,
9412
+ formula: Array.isArray(r.formula) ? "<\u516C\u5F0F>" : r.formula,
9413
+ reason: r.reason,
9414
+ risk_level: r.risk_level,
9415
+ action: r.action,
9416
+ enabled: r.enabled !== false,
9417
+ human: ltlHuman[r.id] || r.reason || ""
9418
+ }))
9419
+ };
9420
+ }
9421
+ function withL24View(rc) {
9422
+ if (rc && rc.L2_4 && typeof rc.L2_4 === "object" && !Array.isArray(rc.L2_4)) {
9423
+ const cat = rc.L2_4.ltl_rules;
9424
+ const rls = cat && cat.rules || [];
9425
+ const ltlHumanLocal = {
9426
+ "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9427
+ "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
9428
+ "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",
9429
+ "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",
9430
+ "mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
9431
+ "mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
9432
+ "mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9433
+ "mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
9434
+ "mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
9435
+ "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9436
+ "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9437
+ };
9438
+ rc.L2_4 = {
9439
+ enabled_via: "L2_logic_rules",
9440
+ description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9441
+ ltl_rules: rls.map((r) => ({
9442
+ id: r.id,
9443
+ type: "ltl",
9444
+ formula: "<\u516C\u5F0F>",
9445
+ reason: r.reason || "",
9446
+ risk_level: r.risk_level || "high",
9447
+ action: r.action || "block",
9448
+ enabled: r.enabled !== false,
9449
+ human: ltlHumanLocal[r.id] || r.reason || ""
9450
+ }))
9451
+ };
9452
+ }
9453
+ return rc;
9454
+ }
8895
9455
  console2.get("/api/rules", (c) => {
8896
9456
  const rc = loadRuntimeConfig();
8897
- return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules });
9457
+ return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_4: getL24View() });
8898
9458
  });
8899
9459
  console2.put("/api/rules/enabled", async (c) => {
8900
9460
  const b = await c.req.json();
8901
9461
  const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
8902
- return c.json(rc);
9462
+ return c.json(withL24View(rc));
8903
9463
  });
8904
9464
  console2.put("/api/rules/deny_mode", async (c) => {
8905
9465
  const b = await c.req.json();
8906
9466
  const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
8907
- return c.json(rc);
9467
+ return c.json(withL24View(rc));
8908
9468
  });
8909
9469
  console2.put("/api/rules/skip_l3", async (c) => {
8910
9470
  const b = await c.req.json();
8911
9471
  const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
8912
- return c.json(rc);
9472
+ return c.json(withL24View(rc));
8913
9473
  });
8914
9474
  console2.put("/api/categories/enabled", async (c) => {
8915
9475
  const b = await c.req.json();
@@ -8968,9 +9528,9 @@ function createConsoleRouter() {
8968
9528
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
8969
9529
  return c.json({ error: "invalid skill_id" }, 400);
8970
9530
  }
8971
- const skillPath = join9(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
9531
+ const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
8972
9532
  if (!existsSync14(skillPath)) {
8973
- const builtinPath = join9(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
9533
+ const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
8974
9534
  if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
8975
9535
  return c.json({ error: "skill not found" }, 404);
8976
9536
  }
@@ -9059,7 +9619,7 @@ function createConsoleRouter() {
9059
9619
  });
9060
9620
  console2.get("/api/config", (c) => {
9061
9621
  const rc = loadRuntimeConfig();
9062
- return c.json(rc);
9622
+ return c.json({ ...rc, L2_4: getL24View() });
9063
9623
  });
9064
9624
  console2.get("/api/env-overrides", (c) => {
9065
9625
  return c.json({ overrides: getEnvOverrides() });
@@ -9396,4 +9956,7 @@ if (isMain) {
9396
9956
  process.exit(1);
9397
9957
  });
9398
9958
  }
9959
+ //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
9960
+ //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
9961
+ //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
9399
9962
  //# sourceMappingURL=cli.js.map