@kenhkl/agent-moss 0.12.8 → 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.
@@ -2,6 +2,12 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
5
11
  var __esm = (fn, res) => function __init() {
6
12
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
13
  };
@@ -127,6 +133,7 @@ __export(config_exports, {
127
133
  getRetryConfig: () => getRetryConfig,
128
134
  getTimeoutConfig: () => getTimeoutConfig,
129
135
  isBrainEnabled: () => isBrainEnabled,
136
+ isFormalObserveMode: () => isFormalObserveMode,
130
137
  isHeuristicEnabled: () => isHeuristicEnabled,
131
138
  isLLMAvailable: () => isLLMAvailable,
132
139
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -242,6 +249,7 @@ function getCustomRules() {
242
249
  }
243
250
  function isLLMAvailable() {
244
251
  if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
252
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
245
253
  return llmConfig.apiKey.length > 0;
246
254
  }
247
255
  async function probeLLMConnection(timeoutMs = 8e3) {
@@ -306,13 +314,26 @@ function getProviderHeaders(provider) {
306
314
  return PROVIDER_HEADERS[provider] || {};
307
315
  }
308
316
  function isHeuristicEnabled() {
309
- return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
317
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
318
+ return layerEnabledOrDefault("L1_heuristic");
319
+ }
320
+ function layerEnabledOrDefault(key) {
321
+ try {
322
+ const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
323
+ return isLayerEnabled2(key);
324
+ } catch {
325
+ return true;
326
+ }
310
327
  }
311
328
  function isBrainEnabled() {
312
329
  return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
313
330
  }
314
331
  function isLogicRulesEnabled() {
315
- return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
332
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
333
+ return layerEnabledOrDefault("L2_logic_rules");
334
+ }
335
+ function isFormalObserveMode() {
336
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
316
337
  }
317
338
  var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
318
339
  var init_config = __esm({
@@ -570,8 +591,12 @@ var init_inline_analyzer = __esm({
570
591
 
571
592
  // src/infra/rules-loader.ts
572
593
  import { readFileSync as readFileSync2, existsSync } from "fs";
573
- import { resolve, dirname } from "path";
594
+ import { resolve, dirname, join as join2 } from "path";
574
595
  import { fileURLToPath as fileURLToPath3 } from "url";
596
+ import { homedir as homedir2 } from "os";
597
+ function dataRootDir() {
598
+ return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
599
+ }
575
600
  function findRulesFile(name) {
576
601
  for (const dir of RULES_DIR_CANDS) {
577
602
  const p = resolve(dir, name);
@@ -597,6 +622,24 @@ function loadL2() {
597
622
  function loadL3() {
598
623
  return loadJson("l3_rules.json");
599
624
  }
625
+ function loadMC() {
626
+ const mc = loadJson("mc_rules.json");
627
+ const rules = mc.rules || [];
628
+ try {
629
+ const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
630
+ if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
631
+ const rtRules = rt.loadRuntimeConfig()?.L2_4?.ltl_rules?.rules || [];
632
+ const enabledById = {};
633
+ for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
634
+ for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
635
+ }
636
+ } catch {
637
+ }
638
+ return mc;
639
+ }
640
+ function loadToolRegistry() {
641
+ return loadJson("tool_registry.json");
642
+ }
600
643
  var __filename2, __dirname2, RULES_DIR_CANDS;
601
644
  var init_rules_loader = __esm({
602
645
  "src/infra/rules-loader.ts"() {
@@ -606,6 +649,10 @@ var init_rules_loader = __esm({
606
649
  __dirname2 = dirname(__filename2);
607
650
  RULES_DIR_CANDS = (() => {
608
651
  const cands = [
652
+ // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
653
+ // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
654
+ // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
655
+ resolve(dataRootDir(), "rules"),
609
656
  // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
610
657
  resolve(__dirname2, "../rules"),
611
658
  resolve(__dirname2, "rules"),
@@ -1343,6 +1390,7 @@ var init_skill_loader = __esm({
1343
1390
  // src/infra/runtime-config.ts
1344
1391
  var runtime_config_exports = {};
1345
1392
  __export(runtime_config_exports, {
1393
+ DEFAULT_LAYERS: () => DEFAULT_LAYERS,
1346
1394
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1347
1395
  addAgentRule: () => addAgentRule,
1348
1396
  addCustomRule: () => addCustomRule,
@@ -1394,7 +1442,7 @@ __export(runtime_config_exports, {
1394
1442
  updateSkillEnabled: () => updateSkillEnabled
1395
1443
  });
1396
1444
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1397
- import { join as join2 } from "path";
1445
+ import { join as join3 } from "path";
1398
1446
  import { fileURLToPath as fileURLToPath5 } from "url";
1399
1447
  import { dirname as dirname3 } from "path";
1400
1448
  function runtimeDir() {
@@ -1418,6 +1466,7 @@ function emptyRuntimeConfig() {
1418
1466
  L1_rules: {},
1419
1467
  L2_rules: {},
1420
1468
  L3_skills: {},
1469
+ L2_4: {},
1421
1470
  agent_rules: {},
1422
1471
  scenario_rules: {}
1423
1472
  };
@@ -1445,6 +1494,7 @@ function loadRuntimeConfig() {
1445
1494
  L1_rules: merged.L1_rules || {},
1446
1495
  L2_rules: merged.L2_rules || {},
1447
1496
  L3_skills: merged.L3_skills || {},
1497
+ L2_4: merged.L2_4 || {},
1448
1498
  agent_rules: merged.agent_rules || {},
1449
1499
  scenario_rules: merged.scenario_rules || {}
1450
1500
  };
@@ -1692,7 +1742,7 @@ function _scenarioRulesToDefaults() {
1692
1742
  return out;
1693
1743
  }
1694
1744
  for (const scenarioId of entries) {
1695
- const rulesFile = join2(scenDir, scenarioId, "rules.json");
1745
+ const rulesFile = join3(scenDir, scenarioId, "rules.json");
1696
1746
  if (!existsSync4(rulesFile)) continue;
1697
1747
  let data;
1698
1748
  try {
@@ -1741,7 +1791,7 @@ function _agentRulesToDefaults() {
1741
1791
  return out;
1742
1792
  }
1743
1793
  for (const agentId of entries) {
1744
- const rulesFile = join2(agentsDir, agentId, "rules.json");
1794
+ const rulesFile = join3(agentsDir, agentId, "rules.json");
1745
1795
  if (!existsSync4(rulesFile)) continue;
1746
1796
  let data;
1747
1797
  try {
@@ -1782,7 +1832,7 @@ function _agentRulesToDefaults() {
1782
1832
  function generateSourceDefaults() {
1783
1833
  return {
1784
1834
  version: 1,
1785
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1835
+ layers: { ...DEFAULT_LAYERS },
1786
1836
  L1_rules: {
1787
1837
  dangerous_commands: {
1788
1838
  category_enabled: true,
@@ -1821,10 +1871,45 @@ function generateSourceDefaults() {
1821
1871
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1822
1872
  },
1823
1873
  L3_skills: _l3SkillsToRules(),
1874
+ L2_4: _ltlRulesToRuntime(),
1824
1875
  agent_rules: _agentRulesToDefaults(),
1825
1876
  scenario_rules: _scenarioRulesToDefaults()
1826
1877
  };
1827
1878
  }
1879
+ function _ltlRulesToRuntime() {
1880
+ const cands = [
1881
+ join3(__dirname4, "..", "rules"),
1882
+ join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1883
+ join3(process.cwd(), "agent_moss", "rules")
1884
+ ];
1885
+ let mc = {};
1886
+ for (const dir of cands) {
1887
+ const p = join3(dir, "mc_rules.json");
1888
+ if (existsSync4(p)) {
1889
+ try {
1890
+ mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1891
+ } catch {
1892
+ mc = {};
1893
+ }
1894
+ break;
1895
+ }
1896
+ }
1897
+ const rules = mc.rules || [];
1898
+ return {
1899
+ ltl_rules: {
1900
+ category_enabled: true,
1901
+ category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
1902
+ rules: rules.map((r) => ({
1903
+ id: r.id,
1904
+ reason: r.reason || "",
1905
+ risk_level: r.risk_level || "high",
1906
+ action: r.action || "block",
1907
+ enabled: true,
1908
+ builtin: true
1909
+ }))
1910
+ }
1911
+ };
1912
+ }
1828
1913
  function normalizePatternForFingerprint(pattern) {
1829
1914
  return pattern.replace(/\\\//g, "/");
1830
1915
  }
@@ -1974,6 +2059,7 @@ function mergeConfig(userCfg, srcDefaults) {
1974
2059
  );
1975
2060
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1976
2061
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2062
+ _mergeRuleCategories(cfg, srcDefaults, "L2_4");
1977
2063
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1978
2064
  _mergeAgentRules(cfg, srcDefaults);
1979
2065
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2166,12 +2252,12 @@ function deleteCustomRule(layer, category, ruleId) {
2166
2252
  function addCustomSkill(skillId, category, keywords, content) {
2167
2253
  _validateSkillId(skillId);
2168
2254
  const runtime = loadRuntimeConfig();
2169
- const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2255
+ const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2170
2256
  try {
2171
2257
  mkdirSync(skillsDir, { recursive: true });
2172
2258
  } catch {
2173
2259
  }
2174
- writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
2260
+ writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2175
2261
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2176
2262
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2177
2263
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2186,7 +2272,7 @@ function deleteCustomSkill(skillId, category) {
2186
2272
  if (idx >= 0) {
2187
2273
  if (skills[idx].builtin) return null;
2188
2274
  skills.splice(idx, 1);
2189
- const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2275
+ const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2190
2276
  try {
2191
2277
  if (existsSync4(skillPath)) unlinkSync(skillPath);
2192
2278
  } catch {
@@ -2380,7 +2466,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2380
2466
  saveRuntimeConfig(runtime);
2381
2467
  return runtime;
2382
2468
  }
2383
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2469
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2384
2470
  var init_runtime_config = __esm({
2385
2471
  "src/infra/runtime-config.ts"() {
2386
2472
  "use strict";
@@ -2392,21 +2478,28 @@ var init_runtime_config = __esm({
2392
2478
  __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2393
2479
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2394
2480
  RUNTIME_CONFIG_DIR = runtimeDir();
2395
- RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2481
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2396
2482
  LAYER_DISABLE_ENV = {
2397
2483
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2398
2484
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2399
2485
  L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2400
2486
  };
2487
+ DEFAULT_LAYERS = {
2488
+ L1_heuristic: true,
2489
+ L2_logic_rules: true,
2490
+ L3_llm_analysis: true,
2491
+ agent_rules_enabled: true,
2492
+ scenario_rules_enabled: true
2493
+ };
2401
2494
  DENY_MODE_LABELS = {
2402
2495
  deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2403
2496
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2404
2497
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2405
2498
  };
2406
- _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2407
- _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2408
- _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2409
- _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2499
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2500
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2501
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2502
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2410
2503
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2411
2504
  }
2412
2505
  });
@@ -2544,11 +2637,21 @@ function getScenarioExtensions(scenarioId) {
2544
2637
  );
2545
2638
  return ext;
2546
2639
  }
2640
+ function getScenarioExtensionsMulti(scenarioSpec) {
2641
+ if (!scenarioSpec) return null;
2642
+ const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
2643
+ if (ids.length === 0) return null;
2644
+ const exts = ids.map((id) => getScenarioExtensions(id));
2645
+ const nonNull = exts.filter((e) => e !== null);
2646
+ if (nonNull.length === 0) return null;
2647
+ return mergeAgentExtensions(...nonNull);
2648
+ }
2547
2649
  var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2548
2650
  var init_scenarios = __esm({
2549
2651
  "src/scenarios/index.ts"() {
2550
2652
  "use strict";
2551
2653
  init_esm_shims();
2654
+ init_agents();
2552
2655
  __filename5 = fileURLToPath7(import.meta.url);
2553
2656
  __dirname6 = dirname5(__filename5);
2554
2657
  SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
@@ -2597,8 +2700,8 @@ function runHeuristicCheck(input) {
2597
2700
  }
2598
2701
  highestRiskType = topRiskType || highestRiskType;
2599
2702
  const agentExt = mergeAgentExtensions(
2600
- getAgentExtensions(input.agentId),
2601
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2703
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
2704
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
2602
2705
  );
2603
2706
  if (agentExt?.extra_command_patterns) {
2604
2707
  for (const rule of agentExt.extra_command_patterns) {
@@ -3043,6 +3146,7 @@ var init_heuristic = __esm({
3043
3146
  init_config();
3044
3147
  init_agents();
3045
3148
  init_scenarios();
3149
+ init_runtime_config();
3046
3150
  init_rules_loader();
3047
3151
  init_logic_rules();
3048
3152
  INJECTION_KEYWORDS = (() => {
@@ -3242,6 +3346,112 @@ var init_token_stats = __esm({
3242
3346
  }
3243
3347
  });
3244
3348
 
3349
+ // src/engine/formal-engine/decode.ts
3350
+ var decode_exports = {};
3351
+ __export(decode_exports, {
3352
+ allCandidates: () => allCandidates,
3353
+ decodeBase64Literals: () => decodeBase64Literals,
3354
+ decodeUrlAndNumeric: () => decodeUrlAndNumeric,
3355
+ matchStageAny: () => matchStageAny
3356
+ });
3357
+ function isValidBase64Token(tok) {
3358
+ if (tok.length < 8) return false;
3359
+ if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
3360
+ return true;
3361
+ }
3362
+ function isPrintable(s) {
3363
+ for (let i = 0; i < s.length; i++) {
3364
+ const c = s.charCodeAt(i);
3365
+ if (c < 32 || c > 126) return false;
3366
+ }
3367
+ return true;
3368
+ }
3369
+ function decodeBase64Literals(text) {
3370
+ const out = [];
3371
+ const seen = /* @__PURE__ */ new Set();
3372
+ for (const m of text.matchAll(B64_TOKEN_RE)) {
3373
+ const tok = m[0];
3374
+ if (!isValidBase64Token(tok)) continue;
3375
+ if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
3376
+ try {
3377
+ const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
3378
+ const bin = Buffer.from(padded, "base64");
3379
+ const s = bin.toString("utf8");
3380
+ if (s && isPrintable(s) && !seen.has(s)) {
3381
+ seen.add(s);
3382
+ out.push(s);
3383
+ }
3384
+ } catch {
3385
+ }
3386
+ }
3387
+ return out;
3388
+ }
3389
+ function decodeUrlAndNumeric(text) {
3390
+ const out = [];
3391
+ let s = text;
3392
+ for (let i = 0; i < 3; i++) {
3393
+ let nxt = s;
3394
+ try {
3395
+ nxt = decodeURIComponent(s);
3396
+ } catch {
3397
+ break;
3398
+ }
3399
+ if (nxt === s) break;
3400
+ s = nxt;
3401
+ }
3402
+ if (s !== text) out.push(s);
3403
+ if (/\\[0-7]{3}/.test(text)) {
3404
+ let expanded = "";
3405
+ try {
3406
+ expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
3407
+ } catch {
3408
+ }
3409
+ if (expanded && expanded !== text) out.push(expanded);
3410
+ }
3411
+ if (/\\x[0-9a-fA-F]{2}/.test(text)) {
3412
+ let expanded = "";
3413
+ try {
3414
+ expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
3415
+ } catch {
3416
+ }
3417
+ if (expanded && expanded !== text) out.push(expanded);
3418
+ }
3419
+ return out;
3420
+ }
3421
+ function allCandidates(detail, max = 8) {
3422
+ const cands = [detail];
3423
+ cands.push(...decodeBase64Literals(detail));
3424
+ cands.push(...decodeUrlAndNumeric(detail));
3425
+ const seen = /* @__PURE__ */ new Set();
3426
+ const out = [];
3427
+ for (const c of cands) {
3428
+ if (seen.has(c) || c.length > 4096) continue;
3429
+ seen.add(c);
3430
+ out.push(c);
3431
+ if (out.length >= max) break;
3432
+ }
3433
+ return out;
3434
+ }
3435
+ function matchStageAny(detail, stages) {
3436
+ for (const cand of allCandidates(detail)) {
3437
+ for (const [name, pats] of Object.entries(stages)) {
3438
+ for (const p of pats) {
3439
+ p.lastIndex = 0;
3440
+ if (p.test(cand)) return name;
3441
+ }
3442
+ }
3443
+ }
3444
+ return "";
3445
+ }
3446
+ var B64_TOKEN_RE;
3447
+ var init_decode = __esm({
3448
+ "src/engine/formal-engine/decode.ts"() {
3449
+ "use strict";
3450
+ init_esm_shims();
3451
+ B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
3452
+ }
3453
+ });
3454
+
3245
3455
  // src/engine/coordinator.ts
3246
3456
  init_esm_shims();
3247
3457
  init_heuristic();
@@ -3286,7 +3496,7 @@ function loadTemplates() {
3286
3496
  // src/engine/script-analyzer.ts
3287
3497
  init_esm_shims();
3288
3498
  import { readFileSync as readFileSync8 } from "fs";
3289
- import { resolve as resolve6, join as join3 } from "path";
3499
+ import { resolve as resolve6, join as join4 } from "path";
3290
3500
  var SUSPICIOUS_PATTERNS = [
3291
3501
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
3292
3502
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3419,7 +3629,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3419
3629
  }
3420
3630
  function readScriptContent(scriptPath, maxLines = 500) {
3421
3631
  try {
3422
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3632
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3423
3633
  const raw = readFileSync8(expandedPath, "utf-8");
3424
3634
  const lines = raw.split("\n").slice(0, maxLines);
3425
3635
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -3475,8 +3685,8 @@ init_esm_shims();
3475
3685
  init_heuristic();
3476
3686
  init_logic_rules();
3477
3687
  import { statSync as statSync2, existsSync as existsSync9 } from "fs";
3478
- import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
3479
- import { homedir as homedir3 } from "os";
3688
+ import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
3689
+ import { homedir as homedir4 } from "os";
3480
3690
 
3481
3691
  // src/engine/script-engine/tracker.ts
3482
3692
  init_esm_shims();
@@ -5121,7 +5331,7 @@ function extractInlineScriptPath(actionDetail) {
5121
5331
  function resolvePath2(p) {
5122
5332
  let expanded = p;
5123
5333
  if (expanded.startsWith("~/") || expanded === "~") {
5124
- expanded = join5(homedir3(), expanded.slice(1));
5334
+ expanded = join6(homedir4(), expanded.slice(1));
5125
5335
  }
5126
5336
  return resolve8(expanded);
5127
5337
  }
@@ -5202,7 +5412,7 @@ function analyzeScriptContent2(input) {
5202
5412
  isInline = true;
5203
5413
  }
5204
5414
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
5205
- resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
5415
+ resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5206
5416
  }
5207
5417
  const l1Adapter = (content) => {
5208
5418
  const virtualInput = {
@@ -5513,6 +5723,7 @@ async function analyzeWithLLM(prompt, config) {
5513
5723
 
5514
5724
  // src/engine/coordinator.ts
5515
5725
  init_config();
5726
+ init_runtime_config();
5516
5727
  init_agents();
5517
5728
  init_scenarios();
5518
5729
 
@@ -5522,9 +5733,9 @@ init_esm_shims();
5522
5733
  // src/brain/rule-store.ts
5523
5734
  init_esm_shims();
5524
5735
  import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5525
- import { join as join6 } from "path";
5526
- import { homedir as homedir4 } from "os";
5527
- var DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
5736
+ import { join as join7 } from "path";
5737
+ import { homedir as homedir5 } from "os";
5738
+ var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
5528
5739
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5529
5740
  var STATUS_DRAFT = "draft";
5530
5741
  var STATUS_APPROVED = "approved";
@@ -6050,98 +6261,132 @@ init_esm_shims();
6050
6261
  // src/engine/formal-engine/builder.ts
6051
6262
  init_esm_shims();
6052
6263
  init_rules_loader();
6264
+ init_decode();
6053
6265
 
6054
- // src/engine/formal-engine/decode.ts
6266
+ // src/engine/formal-engine/ltl.ts
6055
6267
  init_esm_shims();
6056
- var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6057
- function isValidBase64Token(tok) {
6058
- if (tok.length < 8) return false;
6059
- if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
6060
- return true;
6061
- }
6062
- function isPrintable(s) {
6063
- for (let i = 0; i < s.length; i++) {
6064
- const c = s.charCodeAt(i);
6065
- if (c < 32 || c > 126) return false;
6066
- }
6067
- return true;
6268
+ init_rules_loader();
6269
+
6270
+ // src/engine/formal-engine/ir.ts
6271
+ init_esm_shims();
6272
+ function allNodes(mg) {
6273
+ const out = [];
6274
+ for (const s of mg.sessions) out.push(...s.nodes);
6275
+ return out;
6068
6276
  }
6069
- function decodeBase64Literals(text) {
6277
+ function allEdges(mg) {
6070
6278
  const out = [];
6071
- const seen = /* @__PURE__ */ new Set();
6072
- for (const m of text.matchAll(B64_TOKEN_RE)) {
6073
- const tok = m[0];
6074
- if (!isValidBase64Token(tok)) continue;
6075
- if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
6076
- try {
6077
- const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
6078
- const bin = Buffer.from(padded, "base64");
6079
- const s = bin.toString("utf8");
6080
- if (s && isPrintable(s) && !seen.has(s)) {
6081
- seen.add(s);
6082
- out.push(s);
6279
+ for (const s of mg.sessions) out.push(...s.edges);
6280
+ out.push(...mg.crossEdges);
6281
+ return out;
6282
+ }
6283
+ function emptyVerificationResult() {
6284
+ return { verified: true, violations: [] };
6285
+ }
6286
+
6287
+ // src/engine/formal-engine/ltl.ts
6288
+ var AP_RE = null;
6289
+ function compileAps(apPatterns) {
6290
+ if (AP_RE) return;
6291
+ AP_RE = {};
6292
+ for (const [name, pats] of Object.entries(apPatterns || {})) {
6293
+ AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
6294
+ }
6295
+ }
6296
+ function labelEvent(actionDetail) {
6297
+ if (!AP_RE) compileAps(loadMC().ap_patterns || {});
6298
+ let candidates = [actionDetail];
6299
+ try {
6300
+ candidates = (init_decode(), __toCommonJS(decode_exports)).allCandidates(actionDetail);
6301
+ } catch {
6302
+ }
6303
+ const hits = /* @__PURE__ */ new Set();
6304
+ for (const [name, pats] of Object.entries(AP_RE || {})) {
6305
+ for (const c of candidates) {
6306
+ const m = pats.some((p) => p.test(c));
6307
+ if (m) {
6308
+ hits.add(name);
6309
+ break;
6083
6310
  }
6084
- } catch {
6085
6311
  }
6086
6312
  }
6087
- return out;
6313
+ return hits;
6088
6314
  }
6089
- function decodeUrlAndNumeric(text) {
6090
- const out = [];
6091
- let s = text;
6092
- for (let i = 0; i < 3; i++) {
6093
- let nxt = s;
6094
- try {
6095
- nxt = decodeURIComponent(s);
6096
- } catch {
6097
- break;
6315
+ function evaluateLTL(formula, trace, pos) {
6316
+ const op = formula[0];
6317
+ switch (op) {
6318
+ case "ap":
6319
+ return trace[pos].has(formula[1]);
6320
+ case "!":
6321
+ return !evaluateLTL(formula[1], trace, pos);
6322
+ case "&":
6323
+ return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
6324
+ case "|":
6325
+ return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6326
+ case "->":
6327
+ return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
6328
+ case "X":
6329
+ return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
6330
+ case "G": {
6331
+ for (let j = pos; j < trace.length; j++) {
6332
+ if (!evaluateLTL(formula[1], trace, j)) return false;
6333
+ }
6334
+ return true;
6098
6335
  }
6099
- if (nxt === s) break;
6100
- s = nxt;
6101
- }
6102
- if (s !== text) out.push(s);
6103
- if (/\\[0-7]{3}/.test(text)) {
6104
- let expanded = "";
6105
- try {
6106
- expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
6107
- } catch {
6336
+ case "F": {
6337
+ for (let j = pos; j < trace.length; j++) {
6338
+ if (evaluateLTL(formula[1], trace, j)) return true;
6339
+ }
6340
+ return false;
6108
6341
  }
6109
- if (expanded && expanded !== text) out.push(expanded);
6110
- }
6111
- if (/\\x[0-9a-fA-F]{2}/.test(text)) {
6112
- let expanded = "";
6113
- try {
6114
- expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
6115
- } catch {
6342
+ case "U": {
6343
+ for (let j = pos; j < trace.length; j++) {
6344
+ if (evaluateLTL(formula[2], trace, j)) {
6345
+ for (let k = pos; k < j; k++) {
6346
+ if (!evaluateLTL(formula[1], trace, k)) return false;
6347
+ }
6348
+ return true;
6349
+ }
6350
+ }
6351
+ return false;
6116
6352
  }
6117
- if (expanded && expanded !== text) out.push(expanded);
6353
+ default:
6354
+ return false;
6118
6355
  }
6119
- return out;
6120
6356
  }
6121
- function allCandidates(detail, max = 8) {
6122
- const cands = [detail];
6123
- cands.push(...decodeBase64Literals(detail));
6124
- cands.push(...decodeUrlAndNumeric(detail));
6125
- const seen = /* @__PURE__ */ new Set();
6126
- const out = [];
6127
- for (const c of cands) {
6128
- if (seen.has(c) || c.length > 4096) continue;
6129
- seen.add(c);
6130
- out.push(c);
6131
- if (out.length >= max) break;
6357
+ function ruleViolates(rule, traceWith, witnessMode) {
6358
+ const formula = rule?.formula;
6359
+ if (!Array.isArray(formula) || formula.length === 0) return false;
6360
+ const satWith = evaluateLTL(formula, traceWith, 0);
6361
+ if (!satWith) return false;
6362
+ if (witnessMode && traceWith.length > 1) {
6363
+ const traceWithout = traceWith.slice(0, -1);
6364
+ if (evaluateLTL(formula, traceWithout, 0)) return false;
6132
6365
  }
6133
- return out;
6366
+ return true;
6134
6367
  }
6135
- function matchStageAny(detail, stages) {
6136
- for (const cand of allCandidates(detail)) {
6137
- for (const [name, pats] of Object.entries(stages)) {
6138
- for (const p of pats) {
6139
- p.lastIndex = 0;
6140
- if (p.test(cand)) return name;
6141
- }
6368
+ function checkMCRules(graph) {
6369
+ const violations = [];
6370
+ const nodes = allNodes(graph);
6371
+ if (nodes.length === 0) return violations;
6372
+ const mc = loadMC();
6373
+ const rules = mc.rules || [];
6374
+ if (rules.length === 0) return violations;
6375
+ const trace = nodes.map((n) => labelEvent(n.actionDetail));
6376
+ const witnessMode = graph.witnessIdx !== void 0;
6377
+ for (const rule of rules) {
6378
+ if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
6379
+ if (ruleViolates(rule, trace, witnessMode)) {
6380
+ violations.push({
6381
+ propertyId: String(rule.id || "mc-ltl"),
6382
+ reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
6383
+ riskLevel: String(rule.risk_level || "high"),
6384
+ 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) }))
6385
+ });
6386
+ if (witnessMode) return violations;
6142
6387
  }
6143
6388
  }
6144
- return "";
6389
+ return violations;
6145
6390
  }
6146
6391
 
6147
6392
  // src/engine/formal-engine/builder.ts
@@ -6172,6 +6417,26 @@ function extractResources(actionDetail) {
6172
6417
  function markStage(actionDetail) {
6173
6418
  return matchStageAny(actionDetail, loadStages());
6174
6419
  }
6420
+ function markIntegrity(actionDetail) {
6421
+ const aps = labelEvent(actionDetail);
6422
+ if (aps.has("download_file") || aps.has("prompt_injection")) {
6423
+ return { integrity: "low", isLowSource: true };
6424
+ }
6425
+ return { integrity: "medium", isLowSource: false };
6426
+ }
6427
+ function initNode(sessionId, seq, actionType, actionDetail) {
6428
+ const { integrity, isLowSource } = markIntegrity(actionDetail);
6429
+ return {
6430
+ sessionId,
6431
+ seq,
6432
+ actionType,
6433
+ actionDetail,
6434
+ stage: markStage(actionDetail),
6435
+ resources: extractResources(actionDetail),
6436
+ integrity,
6437
+ isLowSource
6438
+ };
6439
+ }
6175
6440
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6176
6441
  const nodes = [];
6177
6442
  const edges = [];
@@ -6185,17 +6450,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6185
6450
  details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6186
6451
  }
6187
6452
  details.forEach((d, seq) => {
6188
- const stage = markStage(d.actionDetail);
6189
- const node = {
6190
- sessionId,
6191
- seq,
6192
- actionType: d.actionType,
6193
- actionDetail: d.actionDetail,
6194
- stage,
6195
- resources: extractResources(d.actionDetail)
6196
- };
6453
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
6197
6454
  nodes.push(node);
6198
- if (stage) stageFlags.add(stage);
6455
+ if (node.stage) stageFlags.add(node.stage);
6199
6456
  });
6200
6457
  for (let i = 0; i < nodes.length - 1; i++) {
6201
6458
  edges.push({ src: i, dst: i + 1, kind: "next" });
@@ -6215,15 +6472,12 @@ function buildMultiSessionGraph(sessions, aNext) {
6215
6472
  if (aNext) {
6216
6473
  const last = mg.sessions[mg.sessions.length - 1];
6217
6474
  if (last.sessionId === "current") {
6218
- const stage = markStage(String(aNext.actionDetail || ""));
6219
- last.nodes.push({
6220
- sessionId: last.sessionId,
6221
- seq: last.nodes.length,
6222
- actionType: String(aNext.actionType || ""),
6223
- actionDetail: String(aNext.actionDetail || ""),
6224
- stage,
6225
- resources: extractResources(String(aNext.actionDetail || ""))
6226
- });
6475
+ last.nodes.push(initNode(
6476
+ last.sessionId,
6477
+ last.nodes.length,
6478
+ String(aNext.actionType || ""),
6479
+ String(aNext.actionDetail || "")
6480
+ ));
6227
6481
  if (last.nodes.length > 1) {
6228
6482
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6229
6483
  }
@@ -6237,11 +6491,25 @@ function buildMultiSessionGraph(sessions, aNext) {
6237
6491
  function addCrossDataEdges(mg) {
6238
6492
  const nodes = allNodesFlat(mg);
6239
6493
  if (nodes.length < 2) return;
6240
- for (let i = 0; i < nodes.length; i++) {
6241
- for (let j = i + 1; j < nodes.length; j++) {
6242
- if (nodes[i].sessionId === nodes[j].sessionId) continue;
6243
- const shared = intersect(nodes[i].resources, nodes[j].resources);
6244
- if (shared.size > 0) {
6494
+ const resIndex = /* @__PURE__ */ new Map();
6495
+ nodes.forEach((n, i) => {
6496
+ for (const r of n.resources) {
6497
+ const list = resIndex.get(r);
6498
+ if (list) list.push(i);
6499
+ else resIndex.set(r, [i]);
6500
+ }
6501
+ });
6502
+ const seen = /* @__PURE__ */ new Set();
6503
+ for (const idxs of resIndex.values()) {
6504
+ if (idxs.length < 2) continue;
6505
+ for (let a = 0; a < idxs.length; a++) {
6506
+ const i = idxs[a];
6507
+ for (let b = a + 1; b < idxs.length; b++) {
6508
+ const j = idxs[b];
6509
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6510
+ const key = `${i}:${j}`;
6511
+ if (seen.has(key)) continue;
6512
+ seen.add(key);
6245
6513
  mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6246
6514
  }
6247
6515
  }
@@ -6252,28 +6520,6 @@ function allNodesFlat(mg) {
6252
6520
  for (const s of mg.sessions) out.push(...s.nodes);
6253
6521
  return out;
6254
6522
  }
6255
- function intersect(a, b) {
6256
- const out = /* @__PURE__ */ new Set();
6257
- for (const v of a) if (b.has(v)) out.add(v);
6258
- return out;
6259
- }
6260
-
6261
- // src/engine/formal-engine/ir.ts
6262
- init_esm_shims();
6263
- function allNodes(mg) {
6264
- const out = [];
6265
- for (const s of mg.sessions) out.push(...s.nodes);
6266
- return out;
6267
- }
6268
- function allEdges(mg) {
6269
- const out = [];
6270
- for (const s of mg.sessions) out.push(...s.edges);
6271
- out.push(...mg.crossEdges);
6272
- return out;
6273
- }
6274
- function emptyVerificationResult() {
6275
- return { verified: true, violations: [] };
6276
- }
6277
6523
 
6278
6524
  // src/engine/formal-engine/properties.ts
6279
6525
  init_esm_shims();
@@ -6296,27 +6542,50 @@ function checkCrossSessionChain(graph) {
6296
6542
  const violations = [];
6297
6543
  const nodes = allNodes(graph);
6298
6544
  if (nodes.length === 0) return violations;
6545
+ const witness = graph.witnessIdx;
6299
6546
  for (const chain of loadCrossSessionChains()) {
6300
6547
  const stages = chain.stages;
6301
6548
  if (!stages.length) continue;
6302
- let matched = 0;
6303
- let stageIdx = 0;
6304
- const path5 = [];
6305
- for (const n of nodes) {
6306
- if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6307
- matched += 1;
6308
- path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6309
- stageIdx += 1;
6310
- if (matched >= stages.length) break;
6549
+ if (witness !== void 0) {
6550
+ if (nodes[witness].stage !== stages[stages.length - 1]) continue;
6551
+ let matched = 0;
6552
+ const path5 = [];
6553
+ for (let k = 0; k < witness; k++) {
6554
+ const n = nodes[k];
6555
+ if (matched < stages.length - 1 && n.stage === stages[matched]) {
6556
+ matched += 1;
6557
+ path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6558
+ }
6311
6559
  }
6312
- }
6313
- if (matched >= stages.length) {
6560
+ if (matched < stages.length - 1) continue;
6561
+ path5.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
6314
6562
  violations.push({
6315
6563
  propertyId: "cross-" + chain.id,
6316
6564
  reason: chain.reason,
6317
6565
  riskLevel: chain.riskLevel,
6318
6566
  path: path5
6319
6567
  });
6568
+ return violations;
6569
+ } else {
6570
+ let matched = 0;
6571
+ let stageIdx = 0;
6572
+ const path5 = [];
6573
+ for (const n of nodes) {
6574
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6575
+ matched += 1;
6576
+ path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6577
+ stageIdx += 1;
6578
+ if (matched >= stages.length) break;
6579
+ }
6580
+ }
6581
+ if (matched >= stages.length) {
6582
+ violations.push({
6583
+ propertyId: "cross-" + chain.id,
6584
+ reason: chain.reason,
6585
+ riskLevel: chain.riskLevel,
6586
+ path: path5
6587
+ });
6588
+ }
6320
6589
  }
6321
6590
  }
6322
6591
  return violations;
@@ -6351,7 +6620,10 @@ function checkSensitiveDataFlow(graph) {
6351
6620
  }
6352
6621
  }
6353
6622
  }
6354
- for (const [i, n] of nodes.entries()) {
6623
+ const witness = graph.witnessIdx;
6624
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6625
+ for (const i of candidates) {
6626
+ const n = nodes[i];
6355
6627
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6356
6628
  violations.push({
6357
6629
  propertyId: "sensitive-data-exfil",
@@ -6359,6 +6631,7 @@ function checkSensitiveDataFlow(graph) {
6359
6631
  riskLevel: "critical",
6360
6632
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6361
6633
  });
6634
+ if (witness !== void 0) return violations;
6362
6635
  }
6363
6636
  }
6364
6637
  return violations;
@@ -6389,6 +6662,32 @@ function checkNeverExfilAfterPrivesc(graph) {
6389
6662
  const violations = [];
6390
6663
  const nodes = allNodes(graph);
6391
6664
  if (nodes.length < 2) return violations;
6665
+ const witness = graph.witnessIdx;
6666
+ if (witness !== void 0) {
6667
+ const evIdx = [];
6668
+ nodes.forEach((n, i) => {
6669
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6670
+ });
6671
+ if (evIdx.length === 0) return violations;
6672
+ const firstEv = Math.min(...evIdx);
6673
+ let pre = null;
6674
+ for (let i = 0; i < firstEv; i++) {
6675
+ if (isPrivescNode(nodes[i])) pre = nodes[i];
6676
+ }
6677
+ if (pre) {
6678
+ const post = nodes[firstEv];
6679
+ return [{
6680
+ propertyId: "no-privesc-then-exfil",
6681
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6682
+ riskLevel: "critical",
6683
+ path: [
6684
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6685
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6686
+ ]
6687
+ }];
6688
+ }
6689
+ return violations;
6690
+ }
6392
6691
  for (let i = 0; i < nodes.length; i++) {
6393
6692
  if (!isPrivescNode(nodes[i])) continue;
6394
6693
  for (let j = i + 1; j < nodes.length; j++) {
@@ -6412,6 +6711,32 @@ function checkNeverExfilAfterCred(graph) {
6412
6711
  const violations = [];
6413
6712
  const nodes = allNodes(graph);
6414
6713
  if (nodes.length < 2) return violations;
6714
+ const witness = graph.witnessIdx;
6715
+ if (witness !== void 0) {
6716
+ const evIdx = [];
6717
+ nodes.forEach((n, i) => {
6718
+ if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
6719
+ });
6720
+ if (evIdx.length === 0) return violations;
6721
+ const firstEv = Math.min(...evIdx);
6722
+ let pre = null;
6723
+ for (let i = 0; i < firstEv; i++) {
6724
+ if (isCredNode(nodes[i])) pre = nodes[i];
6725
+ }
6726
+ if (pre) {
6727
+ const post = nodes[firstEv];
6728
+ return [{
6729
+ propertyId: "no-cred-then-exfil",
6730
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6731
+ riskLevel: "critical",
6732
+ path: [
6733
+ { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
6734
+ { sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
6735
+ ]
6736
+ }];
6737
+ }
6738
+ return violations;
6739
+ }
6415
6740
  for (let i = 0; i < nodes.length; i++) {
6416
6741
  if (!isCredNode(nodes[i])) continue;
6417
6742
  for (let j = i + 1; j < nodes.length; j++) {
@@ -6431,11 +6756,172 @@ function checkNeverExfilAfterCred(graph) {
6431
6756
  }
6432
6757
  return violations;
6433
6758
  }
6759
+ var TOOL_REGISTRY = null;
6760
+ function getToolRegistry() {
6761
+ if (TOOL_REGISTRY) return TOOL_REGISTRY;
6762
+ TOOL_REGISTRY = loadToolRegistry().tools || {};
6763
+ return TOOL_REGISTRY;
6764
+ }
6765
+ function lookupToolClass(actionType) {
6766
+ const entry = getToolRegistry()[actionType] || {};
6767
+ return entry.actionClass;
6768
+ }
6769
+ var COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
6770
+ var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
6771
+ function isComputeNode(n) {
6772
+ if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
6773
+ const cls = lookupToolClass(n.actionType);
6774
+ if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
6775
+ return COMPUTE_RE.test(n.actionDetail);
6776
+ }
6777
+ function checkLowIntegrityCommandExec(graph) {
6778
+ const violations = [];
6779
+ const nodes = allNodes(graph);
6780
+ if (nodes.length === 0) return violations;
6781
+ const witness = graph.witnessIdx;
6782
+ const lowResources = /* @__PURE__ */ new Set();
6783
+ const lowAt = /* @__PURE__ */ new Map();
6784
+ nodes.forEach((n, i) => {
6785
+ if (n.isLowSource) {
6786
+ lowAt.set(i, n.actionDetail.slice(0, 60));
6787
+ for (const r of n.resources) lowResources.add(r);
6788
+ }
6789
+ });
6790
+ let changed = true;
6791
+ while (changed) {
6792
+ changed = false;
6793
+ for (let i = 0; i < nodes.length; i++) {
6794
+ if (lowAt.has(i)) continue;
6795
+ let shared = "";
6796
+ for (const r of nodes[i].resources) {
6797
+ if (lowResources.has(r)) {
6798
+ shared = r;
6799
+ break;
6800
+ }
6801
+ }
6802
+ if (shared) {
6803
+ lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
6804
+ for (const r of nodes[i].resources) lowResources.add(r);
6805
+ changed = true;
6806
+ }
6807
+ }
6808
+ }
6809
+ if (lowAt.size === 0) return violations;
6810
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6811
+ for (const i of candidates) {
6812
+ const n = nodes[i];
6813
+ if (lowAt.has(i) && isComputeNode(n)) {
6814
+ violations.push({
6815
+ propertyId: "low-integrity-controls-command-exec",
6816
+ 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`,
6817
+ riskLevel: "critical",
6818
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6819
+ });
6820
+ if (witness !== void 0) return violations;
6821
+ }
6822
+ }
6823
+ return violations;
6824
+ }
6825
+ var DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
6826
+ function checkCrossSessionReachability(graph) {
6827
+ const violations = [];
6828
+ const nodes = allNodes(graph);
6829
+ if (nodes.length === 0) return violations;
6830
+ const witness = graph.witnessIdx;
6831
+ const sources = /* @__PURE__ */ new Set();
6832
+ nodes.forEach((n, i) => {
6833
+ if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
6834
+ });
6835
+ if (sources.size === 0) return violations;
6836
+ const reached = new Set(sources);
6837
+ let changed = true;
6838
+ while (changed) {
6839
+ changed = false;
6840
+ for (const e of allEdges(graph)) {
6841
+ if (reached.has(e.src) && !reached.has(e.dst)) {
6842
+ const srcRes = nodes[e.src].resources;
6843
+ const dstRes = nodes[e.dst].resources;
6844
+ for (const r of srcRes) {
6845
+ if (dstRes.has(r)) {
6846
+ reached.add(e.dst);
6847
+ changed = true;
6848
+ break;
6849
+ }
6850
+ }
6851
+ }
6852
+ }
6853
+ }
6854
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6855
+ for (const i of candidates) {
6856
+ const n = nodes[i];
6857
+ if (sources.has(i)) continue;
6858
+ if (reached.has(i)) {
6859
+ const srcIdx = Math.min(...sources);
6860
+ const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
6861
+ violations.push({
6862
+ propertyId: "cross-session-reachability",
6863
+ 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`,
6864
+ riskLevel: "high",
6865
+ path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
6866
+ });
6867
+ if (witness !== void 0) return violations;
6868
+ }
6869
+ }
6870
+ return violations;
6871
+ }
6872
+ function checkSensitiveDataAggregation(graph) {
6873
+ const violations = [];
6874
+ const nodes = allNodes(graph);
6875
+ if (nodes.length < 2) return violations;
6876
+ const witness = graph.witnessIdx;
6877
+ const resIndex = /* @__PURE__ */ new Map();
6878
+ nodes.forEach((n, i) => {
6879
+ for (const r of n.resources) {
6880
+ const list = resIndex.get(r);
6881
+ if (list) list.push(i);
6882
+ else resIndex.set(r, [i]);
6883
+ }
6884
+ });
6885
+ const sourcesFor = (exfilIdx) => {
6886
+ const srcs = /* @__PURE__ */ new Set();
6887
+ const n = nodes[exfilIdx];
6888
+ for (const r of n.resources) {
6889
+ for (const j of resIndex.get(r) || []) {
6890
+ if (j === exfilIdx) continue;
6891
+ srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
6892
+ }
6893
+ }
6894
+ return srcs;
6895
+ };
6896
+ const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
6897
+ for (const i of candidates) {
6898
+ const n = nodes[i];
6899
+ if (!isExfilNode(n)) continue;
6900
+ const srcs = sourcesFor(i);
6901
+ if (srcs.size >= 2) {
6902
+ violations.push({
6903
+ propertyId: "sensitive-data-aggregation",
6904
+ reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
6905
+ riskLevel: "critical",
6906
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6907
+ });
6908
+ if (witness !== void 0) return violations;
6909
+ }
6910
+ }
6911
+ return violations;
6912
+ }
6434
6913
  var ALL_PROPERTIES = [
6435
6914
  { id: "cross-session-chain", check: checkCrossSessionChain },
6436
6915
  { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6916
+ { id: "cross-session-reachability", check: checkCrossSessionReachability },
6437
6917
  { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6438
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
6918
+ { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred },
6919
+ // L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
6920
+ { id: "mc-ltl-rules", check: checkMCRules },
6921
+ // Rule 5 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
6922
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec },
6923
+ // Rule 12 敏感数据聚合外传(egress 聚合 ≥2 事件源)
6924
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation }
6439
6925
  ];
6440
6926
 
6441
6927
  // src/engine/formal-engine/engine.ts
@@ -6467,6 +6953,9 @@ var FormalEngine = class {
6467
6953
  });
6468
6954
  }
6469
6955
  const graph = buildMultiSessionGraph(all, aNext);
6956
+ if (aNext) {
6957
+ graph.witnessIdx = allNodes(graph).length - 1;
6958
+ }
6470
6959
  const result = emptyVerificationResult();
6471
6960
  for (const prop of this.properties) {
6472
6961
  let violations;
@@ -6495,6 +6984,7 @@ var FormalEngine = class {
6495
6984
  // src/engine/formal-engine/stateful.ts
6496
6985
  init_esm_shims();
6497
6986
  init_rules_loader();
6987
+ init_decode();
6498
6988
 
6499
6989
  // src/engine/formal-engine/chain_state.ts
6500
6990
  init_esm_shims();
@@ -6607,6 +7097,7 @@ function _resetBrainForTest() {
6607
7097
  }
6608
7098
  var formalEngine = new FormalEngine();
6609
7099
  async function runAnalysis(input) {
7100
+ const observedFormalLayers = [];
6610
7101
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
6611
7102
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
6612
7103
  const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
@@ -6661,8 +7152,8 @@ async function runAnalysis(input) {
6661
7152
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
6662
7153
  const lowerDetail = rawDetail.toLowerCase();
6663
7154
  const agentExt = mergeAgentExtensions(
6664
- getAgentExtensions(input.agentId),
6665
- getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
7155
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
7156
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
6666
7157
  );
6667
7158
  if (FULLY_SAFE_TOOLS.has(actionType)) {
6668
7159
  return {
@@ -6779,15 +7270,22 @@ async function runAnalysis(input) {
6779
7270
  const formalResult = formalEngine.verify(formalSessions, input.aNext);
6780
7271
  if (!formalResult.verified) {
6781
7272
  const v = formalResult.violations[0];
6782
- return {
6783
- allowed: false,
6784
- reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
6785
- riskLevel: v ? v.riskLevel : "high",
6786
- riskType: "formal_verification",
6787
- confidence: 90,
6788
- source: "formal_engine",
6789
- violatedLayers: ["2.4"]
6790
- };
7273
+ if (isFormalObserveMode()) {
7274
+ console.warn(
7275
+ `[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)}`
7276
+ );
7277
+ observedFormalLayers.push("2.4");
7278
+ } else {
7279
+ return {
7280
+ allowed: false,
7281
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
7282
+ riskLevel: v ? v.riskLevel : "high",
7283
+ riskType: "formal_verification",
7284
+ confidence: 90,
7285
+ source: "formal_engine",
7286
+ violatedLayers: ["2.4"]
7287
+ };
7288
+ }
6791
7289
  }
6792
7290
  }
6793
7291
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -6834,7 +7332,7 @@ async function runAnalysis(input) {
6834
7332
  riskType: "",
6835
7333
  confidence: 85,
6836
7334
  source: "heuristic_and_logic_only",
6837
- violatedLayers: []
7335
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
6838
7336
  };
6839
7337
  }
6840
7338
  let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
@@ -6879,7 +7377,7 @@ async function runAnalysis(input) {
6879
7377
  riskType: "",
6880
7378
  confidence: 50,
6881
7379
  source: "llm_failure_fallback",
6882
- violatedLayers: []
7380
+ violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
6883
7381
  };
6884
7382
  }
6885
7383
  return {
@@ -6906,6 +7404,9 @@ async function runAnalysis(input) {
6906
7404
  } catch {
6907
7405
  }
6908
7406
  }
7407
+ if (observedFormalLayers.length > 0 && llmResult.allowed) {
7408
+ llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
7409
+ }
6909
7410
  return llmResult;
6910
7411
  }
6911
7412
  export {
@@ -6913,4 +7414,7 @@ export {
6913
7414
  _resetBrainForTest,
6914
7415
  runAnalysis
6915
7416
  };
7417
+ //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
7418
+ //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
7419
+ //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
6916
7420
  //# sourceMappingURL=coordinator.js.map