@kenhkl/agent-moss 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -633,7 +633,7 @@ function loadMC() {
633
633
  try {
634
634
  const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
635
635
  if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
636
- const rtRules = rt.loadRuntimeConfig()?.L2_4?.ltl_rules?.rules || [];
636
+ const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
637
637
  const enabledById = {};
638
638
  for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
639
639
  for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
@@ -681,7 +681,8 @@ function _loadSensitivePathScope(scope) {
681
681
  path: sp.path,
682
682
  riskLevel: sp.risk_level,
683
683
  desc: sp.desc,
684
- credential: sp.deny_mode === "deny_both" ? true : void 0
684
+ credential: sp.deny_mode === "deny_both" ? true : void 0,
685
+ dirDelOnly: sp.dir_del_only === true ? true : void 0
685
686
  }));
686
687
  }
687
688
  function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
@@ -779,7 +780,7 @@ function runLogicCheck(input) {
779
780
  riskType: "intent_deviation",
780
781
  confidence: 85,
781
782
  source: "logic_rule",
782
- violatedLayers: ["2"]
783
+ violatedLayers: ["L2.1"]
783
784
  };
784
785
  }
785
786
  }
@@ -803,7 +804,7 @@ function runLogicCheck(input) {
803
804
  riskType: "privilege_escalation",
804
805
  confidence: 85,
805
806
  source: "logic_rule",
806
- violatedLayers: ["2"]
807
+ violatedLayers: ["L2.1"]
807
808
  };
808
809
  }
809
810
  }
@@ -855,7 +856,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
855
856
  riskType: "combo_chain",
856
857
  confidence: 85,
857
858
  source: "logic_rule",
858
- violatedLayers: ["2"]
859
+ violatedLayers: ["L2.1"]
859
860
  };
860
861
  }
861
862
  }
@@ -980,7 +981,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
980
981
  riskType: "read_before_write",
981
982
  confidence: 85,
982
983
  source: "logic_rule",
983
- violatedLayers: ["2"]
984
+ violatedLayers: ["L2.1"]
984
985
  };
985
986
  }
986
987
  return null;
@@ -1000,7 +1001,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1000
1001
  riskType: "consent_missing",
1001
1002
  confidence: 85,
1002
1003
  source: "logic_rule",
1003
- violatedLayers: ["2"]
1004
+ violatedLayers: ["L2.1"]
1004
1005
  };
1005
1006
  }
1006
1007
  function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
@@ -1018,7 +1019,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1018
1019
  riskType: "consent_missing",
1019
1020
  confidence: 85,
1020
1021
  source: "logic_rule",
1021
- violatedLayers: ["2"]
1022
+ violatedLayers: ["L2.1"]
1022
1023
  };
1023
1024
  }
1024
1025
  function containsPrivilegeEscalation(actionDetail) {
@@ -1078,7 +1079,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1078
1079
  riskType: "file_access",
1079
1080
  confidence: 95,
1080
1081
  source: "logic_rule",
1081
- violatedLayers: ["2"],
1082
+ violatedLayers: ["L2.1"],
1082
1083
  isCredential: true
1083
1084
  };
1084
1085
  }
@@ -1094,7 +1095,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1094
1095
  riskType: "file_access",
1095
1096
  confidence: 95,
1096
1097
  source: "logic_rule",
1097
- violatedLayers: ["2"]
1098
+ violatedLayers: ["L2.1"]
1098
1099
  };
1099
1100
  }
1100
1101
  }
@@ -1108,7 +1109,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1108
1109
  riskType: "file_access",
1109
1110
  confidence: 70,
1110
1111
  source: "logic_rule",
1111
- violatedLayers: ["2"]
1112
+ violatedLayers: ["L2.1"]
1112
1113
  };
1113
1114
  }
1114
1115
  var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
@@ -1120,7 +1121,7 @@ var init_logic_rules = __esm({
1120
1121
  init_rules_loader();
1121
1122
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1122
1123
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1123
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
1124
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1124
1125
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1125
1126
  "ask_user_question",
1126
1127
  "glob",
@@ -1471,7 +1472,7 @@ function emptyRuntimeConfig() {
1471
1472
  L1_rules: {},
1472
1473
  L2_rules: {},
1473
1474
  L3_skills: {},
1474
- L2_4: {},
1475
+ L2_2: {},
1475
1476
  agent_rules: {},
1476
1477
  scenario_rules: {}
1477
1478
  };
@@ -1499,7 +1500,7 @@ function loadRuntimeConfig() {
1499
1500
  L1_rules: merged.L1_rules || {},
1500
1501
  L2_rules: merged.L2_rules || {},
1501
1502
  L3_skills: merged.L3_skills || {},
1502
- L2_4: merged.L2_4 || {},
1503
+ L2_2: merged.L2_2 || {},
1503
1504
  agent_rules: merged.agent_rules || {},
1504
1505
  scenario_rules: merged.scenario_rules || {}
1505
1506
  };
@@ -1552,6 +1553,7 @@ function getEnabledL2SensitivePaths(runtime) {
1552
1553
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1553
1554
  if (r.credential) out.credential = true;
1554
1555
  if (r.read_only) out.read_only = true;
1556
+ if (r.dir_del_only) out.dir_del_only = true;
1555
1557
  return out;
1556
1558
  });
1557
1559
  }
@@ -1660,6 +1662,7 @@ function _l2SensitivePathsToRules() {
1660
1662
  builtin: true
1661
1663
  };
1662
1664
  _syncDenyModeFields(rule);
1665
+ if (sp.dirDelOnly) rule.dir_del_only = true;
1663
1666
  return rule;
1664
1667
  });
1665
1668
  }
@@ -1876,7 +1879,7 @@ function generateSourceDefaults() {
1876
1879
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1877
1880
  },
1878
1881
  L3_skills: _l3SkillsToRules(),
1879
- L2_4: _ltlRulesToRuntime(),
1882
+ L2_2: _ltlRulesToRuntime(),
1880
1883
  agent_rules: _agentRulesToDefaults(),
1881
1884
  scenario_rules: _scenarioRulesToDefaults()
1882
1885
  };
@@ -2064,7 +2067,11 @@ function mergeConfig(userCfg, srcDefaults) {
2064
2067
  );
2065
2068
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
2066
2069
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2067
- _mergeRuleCategories(cfg, srcDefaults, "L2_4");
2070
+ if (!cfg.L2_2 && cfg.L2_4) {
2071
+ cfg.L2_2 = cfg.L2_4;
2072
+ delete cfg.L2_4;
2073
+ }
2074
+ _mergeRuleCategories(cfg, srcDefaults, "L2_2");
2068
2075
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
2069
2076
  _mergeAgentRules(cfg, srcDefaults);
2070
2077
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2684,7 +2691,7 @@ function runHeuristicCheck(input) {
2684
2691
  riskType: "sensitive_action",
2685
2692
  confidence: 90,
2686
2693
  source: "heuristic",
2687
- violatedLayers: ["1.1"]
2694
+ violatedLayers: ["L1.1"]
2688
2695
  };
2689
2696
  }
2690
2697
  }
@@ -2694,8 +2701,10 @@ function runHeuristicCheck(input) {
2694
2701
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2695
2702
  const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
2696
2703
  let topRiskType = "";
2704
+ const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2697
2705
  for (const rule of DANGEROUS_PATTERNS) {
2698
2706
  if (rule.pattern.test(scanCommand)) {
2707
+ if (exemptKeygen && rule.riskType === "file_access") continue;
2699
2708
  matched.push(rule.description);
2700
2709
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
2701
2710
  highestSeverity = rule.severity;
@@ -2737,10 +2746,26 @@ function runHeuristicCheck(input) {
2737
2746
  const detailL = input.aNext.actionDetail.toLowerCase();
2738
2747
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2739
2748
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2740
- if (dangerousResult) return dangerousResult;
2741
- if (!isExcludedType) {
2742
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
2743
- if (pathResult) return pathResult;
2749
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
2750
+ const homeResult = checkHomeDeletion(actionTypeL, detailL);
2751
+ const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2752
+ if (mergeResults.length > 0) {
2753
+ let best = mergeResults[0];
2754
+ for (const r of mergeResults) {
2755
+ if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
2756
+ }
2757
+ if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
2758
+ return {
2759
+ allowed: false,
2760
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
2761
+ riskLevel: highestSeverity,
2762
+ riskType: highestRiskType,
2763
+ confidence: 95,
2764
+ source: "heuristic",
2765
+ violatedLayers: ["L1.1"]
2766
+ };
2767
+ }
2768
+ return best;
2744
2769
  }
2745
2770
  if (matched.length > 0) {
2746
2771
  return {
@@ -2750,8 +2775,7 @@ function runHeuristicCheck(input) {
2750
2775
  riskType: highestRiskType,
2751
2776
  confidence: 95,
2752
2777
  source: "heuristic",
2753
- violatedLayers: ["1.1"]
2754
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
2778
+ violatedLayers: ["L1.1"]
2755
2779
  };
2756
2780
  }
2757
2781
  const lateralResult = checkLateralMovement(detailL);
@@ -2879,6 +2903,7 @@ function isSearchActionType(actionType) {
2879
2903
  function checkSensitivePath(command, cwd, agentExt) {
2880
2904
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2881
2905
  const isWriteOpFlag = isWriteOperation("", command);
2906
+ const keygenGenerate = isSshKeygenGenerate(command);
2882
2907
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2883
2908
  const pathMatches = (text, path6) => {
2884
2909
  const parts = path6.split(/(\(\?!.*?\))/);
@@ -2891,6 +2916,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2891
2916
  };
2892
2917
  for (const sp of ROOT_SENSITIVE_PATHS) {
2893
2918
  if (pathMatches(pathScanText, sp.path.toLowerCase())) {
2919
+ if (keygenGenerate) continue;
2894
2920
  if (sp.path === "/dev/random") {
2895
2921
  if (isWriteOpFlag) {
2896
2922
  continue;
@@ -2902,7 +2928,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2902
2928
  riskType: "file_access",
2903
2929
  confidence: 90,
2904
2930
  source: "heuristic",
2905
- violatedLayers: ["1.1"]
2931
+ violatedLayers: ["L1.1"]
2906
2932
  };
2907
2933
  }
2908
2934
  if (!isWriteOpFlag && !sp.credential) {
@@ -2915,16 +2941,51 @@ function checkSensitivePath(command, cwd, agentExt) {
2915
2941
  riskType: "file_access",
2916
2942
  confidence: 90,
2917
2943
  source: "heuristic",
2918
- violatedLayers: ["1.1"],
2944
+ violatedLayers: ["L1.1"],
2919
2945
  isCredential: !!sp.credential
2920
2946
  };
2921
2947
  }
2922
2948
  }
2923
2949
  for (const sp of HOME_SENSITIVE_PATHS) {
2924
2950
  const expanded = `${expandedCwd}/${sp.path}`;
2925
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2926
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2927
- if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
2951
+ let pPath = sp.path;
2952
+ if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
2953
+ let dirPrefixMatch = null;
2954
+ if (sp.dirDelOnly) {
2955
+ const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
2956
+ dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
2957
+ }
2958
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
2959
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
2960
+ if (pathReMatch || expandedReMatch) {
2961
+ if (keygenGenerate) continue;
2962
+ if (sp.dirDelOnly) {
2963
+ if (isDeleteActionForDir(command, sp.path)) {
2964
+ return {
2965
+ allowed: false,
2966
+ reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2967
+ riskLevel: sp.riskLevel,
2968
+ riskType: "file_access",
2969
+ confidence: 90,
2970
+ source: "heuristic",
2971
+ violatedLayers: ["L1.1"],
2972
+ isCredential: !!sp.credential
2973
+ };
2974
+ }
2975
+ if (isArchiveReadOfDir(command, sp.path)) {
2976
+ return {
2977
+ allowed: false,
2978
+ reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2979
+ riskLevel: sp.riskLevel,
2980
+ riskType: "file_access",
2981
+ confidence: 90,
2982
+ source: "heuristic",
2983
+ violatedLayers: ["L1.1"],
2984
+ isCredential: !!sp.credential
2985
+ };
2986
+ }
2987
+ continue;
2988
+ }
2928
2989
  if (!isWriteOpFlag && !sp.credential) {
2929
2990
  continue;
2930
2991
  }
@@ -2935,7 +2996,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2935
2996
  riskType: "file_access",
2936
2997
  confidence: 90,
2937
2998
  source: "heuristic",
2938
- violatedLayers: ["1.1"],
2999
+ violatedLayers: ["L1.1"],
2939
3000
  isCredential: !!sp.credential
2940
3001
  };
2941
3002
  }
@@ -2958,7 +3019,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2958
3019
  riskType: "file_access",
2959
3020
  confidence: 90,
2960
3021
  source: "heuristic",
2961
- violatedLayers: ["1.1"],
3022
+ violatedLayers: ["L1.1"],
2962
3023
  isCredential: !!sp.credential
2963
3024
  };
2964
3025
  }
@@ -2975,7 +3036,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2975
3036
  riskType: "file_access",
2976
3037
  confidence: 90,
2977
3038
  source: "heuristic",
2978
- violatedLayers: ["1.1"],
3039
+ violatedLayers: ["L1.1"],
2979
3040
  isCredential: true
2980
3041
  };
2981
3042
  }
@@ -2988,18 +3049,142 @@ function checkSensitivePath(command, cwd, agentExt) {
2988
3049
  riskType: "file_access",
2989
3050
  confidence: 90,
2990
3051
  source: "heuristic",
2991
- violatedLayers: ["1.1"]
3052
+ violatedLayers: ["L1.1"]
2992
3053
  };
2993
3054
  }
2994
3055
  return null;
2995
3056
  }
3057
+ function isHomeValue(path6) {
3058
+ let p = path6.trim().toLowerCase();
3059
+ if (p.startsWith("$(") && p.endsWith(")")) {
3060
+ const inner = p.slice(2, -1).trim();
3061
+ return HOME_TARGET_RE.test(" " + inner);
3062
+ }
3063
+ if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
3064
+ return HOME_TARGET_RE.test(" " + p);
3065
+ }
3066
+ function isDeleteCommand(seg) {
3067
+ const s = seg.trimStart();
3068
+ if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
3069
+ return /删除|移除/.test(s);
3070
+ }
3071
+ function homeEscapeTarget(seg) {
3072
+ const low = seg.toLowerCase();
3073
+ const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
3074
+ let m;
3075
+ while ((m = re.exec(low)) !== null) {
3076
+ const endIdx = m.index + m[0].length;
3077
+ const tail = low.slice(endIdx);
3078
+ if (!tail) continue;
3079
+ let token = tail.trimStart().split(/\s+/)[0] || "";
3080
+ token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
3081
+ token = token.replace(/^['"]|['"]$/g, "");
3082
+ const segments = token.split("/").filter((s) => s && s !== ".");
3083
+ if (!segments.some((s) => s === "..")) continue;
3084
+ let depth = 0;
3085
+ let minDepth = 0;
3086
+ for (const s of segments) {
3087
+ depth += s === ".." ? -1 : 1;
3088
+ minDepth = Math.min(minDepth, depth);
3089
+ }
3090
+ if (minDepth < 0) return true;
3091
+ if (depth <= 0) return true;
3092
+ }
3093
+ return false;
3094
+ }
3095
+ function checkHomeDeletion(actionType, actionDetail) {
3096
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
3097
+ const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
3098
+ for (const seg of segs) {
3099
+ if (!isDeleteCommand(seg)) continue;
3100
+ if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
3101
+ return {
3102
+ allowed: false,
3103
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
3104
+ riskLevel: "high",
3105
+ riskType: "script_execution",
3106
+ confidence: 90,
3107
+ source: "heuristic",
3108
+ violatedLayers: ["L1.1"]
3109
+ };
3110
+ }
3111
+ }
3112
+ const assigned = {};
3113
+ for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
3114
+ for (const [varName, path6] of Object.entries(assigned)) {
3115
+ if (!isHomeValue(path6)) continue;
3116
+ const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
3117
+ for (const seg of segs) {
3118
+ if (!isDeleteCommand(seg)) continue;
3119
+ if (varRefRe.test(seg)) {
3120
+ return {
3121
+ allowed: false,
3122
+ reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
3123
+ riskLevel: "high",
3124
+ riskType: "script_execution",
3125
+ confidence: 90,
3126
+ source: "heuristic",
3127
+ violatedLayers: ["L1.1"]
3128
+ };
3129
+ }
3130
+ }
3131
+ }
3132
+ return null;
3133
+ }
3134
+ function escapeRegExp(s) {
3135
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3136
+ }
3137
+ function isDeleteActionForDir(command, dirPath) {
3138
+ const low = command.toLowerCase();
3139
+ const dpath = dirPath.toLowerCase();
3140
+ if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
3141
+ return true;
3142
+ }
3143
+ if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
3144
+ return true;
3145
+ }
3146
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3147
+ const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3148
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
3149
+ return true;
3150
+ }
3151
+ }
3152
+ return false;
3153
+ }
3154
+ function isArchiveReadOfDir(command, dirPath) {
3155
+ const low = command.toLowerCase();
3156
+ const dpath = dirPath.toLowerCase();
3157
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3158
+ const s = seg.trim();
3159
+ if (!s || !s.includes(dpath)) continue;
3160
+ const parts = s.split(/\s+/);
3161
+ const fw = parts[0] || "";
3162
+ if (fw === "zip") return true;
3163
+ if (fw === "tar") {
3164
+ if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
3165
+ return true;
3166
+ }
3167
+ if (fw === "cp" || fw === "rsync") {
3168
+ const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3169
+ if (segPaths.length && segPaths[0].includes(dpath)) return true;
3170
+ }
3171
+ }
3172
+ return false;
3173
+ }
3174
+ function isSshKeygenGenerate(command) {
3175
+ const low = command.toLowerCase();
3176
+ if (!low.includes("ssh-keygen")) return false;
3177
+ if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
3178
+ if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
3179
+ return true;
3180
+ }
2996
3181
  function checkDangerousPatterns(actionType, actionDetail) {
2997
3182
  if (actionType === "file_write" || actionType === "file_edit") return null;
2998
3183
  const scan = stripHeredocContent(actionDetail);
2999
3184
  const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
3000
3185
  const isTempPath = TEMP_DIRS.some(
3001
3186
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
3002
- );
3187
+ ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
3003
3188
  const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
3004
3189
  if (!isTempPath && wildcardDeleteRe.test(scan)) {
3005
3190
  return {
@@ -3009,7 +3194,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3009
3194
  riskType: "script_execution",
3010
3195
  confidence: 90,
3011
3196
  source: "heuristic",
3012
- violatedLayers: ["1.1"]
3197
+ violatedLayers: ["L1.1"]
3013
3198
  };
3014
3199
  }
3015
3200
  const findWriteMatch = actionDetail.match(
@@ -3024,10 +3209,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
3024
3209
  riskType: "script_execution",
3025
3210
  confidence: 90,
3026
3211
  source: "heuristic",
3027
- violatedLayers: ["1.1"]
3212
+ violatedLayers: ["L1.1"]
3028
3213
  };
3029
3214
  }
3030
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
3215
+ if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
3031
3216
  return {
3032
3217
  allowed: false,
3033
3218
  reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
@@ -3035,7 +3220,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3035
3220
  riskType: "file_access",
3036
3221
  confidence: 95,
3037
3222
  source: "heuristic",
3038
- violatedLayers: ["1.1"]
3223
+ violatedLayers: ["L1.1"]
3039
3224
  };
3040
3225
  }
3041
3226
  if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
@@ -3046,7 +3231,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3046
3231
  riskType: "file_access",
3047
3232
  confidence: 90,
3048
3233
  source: "heuristic",
3049
- violatedLayers: ["1.1"]
3234
+ violatedLayers: ["L1.1"]
3050
3235
  };
3051
3236
  }
3052
3237
  return null;
@@ -3137,13 +3322,13 @@ function checkLateralMovement(actionDetail) {
3137
3322
  riskType: "lateral_movement",
3138
3323
  confidence: 80,
3139
3324
  source: "heuristic",
3140
- violatedLayers: ["1.1"]
3325
+ violatedLayers: ["L1.1"]
3141
3326
  };
3142
3327
  }
3143
3328
  }
3144
3329
  return null;
3145
3330
  }
3146
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
3331
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3147
3332
  var init_heuristic = __esm({
3148
3333
  "src/engine/heuristic.ts"() {
3149
3334
  "use strict";
@@ -3173,6 +3358,8 @@ var init_heuristic = __esm({
3173
3358
  })();
3174
3359
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3175
3360
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3361
+ HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
3362
+ HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
3176
3363
  }
3177
3364
  });
3178
3365
 
@@ -4967,6 +5154,13 @@ function extractScriptPath2(command) {
4967
5154
  return null;
4968
5155
  }
4969
5156
  function extractScriptPathWithArgs(command) {
5157
+ const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
5158
+ if (inline) {
5159
+ const inner = inline[2];
5160
+ const realPath = extractScriptPath2(inner);
5161
+ if (realPath) return { path: realPath, args: [] };
5162
+ return { path: null, args: [] };
5163
+ }
4970
5164
  const scriptPath = extractScriptPath2(command);
4971
5165
  if (!scriptPath) return { path: null, args: [] };
4972
5166
  const idx = command.indexOf(scriptPath);
@@ -4980,11 +5174,59 @@ function extractScriptPathWithArgs(command) {
4980
5174
  }
4981
5175
  return { path: scriptPath, args };
4982
5176
  }
5177
+ function looksLikeScriptPath(tok) {
5178
+ const t = tok.trim().replace(/^['"]|['"]$/g, "");
5179
+ if (!t) return false;
5180
+ const low = t.toLowerCase();
5181
+ if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
5182
+ if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
5183
+ if (t.includes("/") && !t.startsWith("-")) return true;
5184
+ return false;
5185
+ }
4983
5186
  function extractInlineScriptPath(actionDetail) {
5187
+ const code = extractInlineCode(actionDetail);
5188
+ if (code) {
5189
+ const candidates = [];
5190
+ const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
5191
+ let m;
5192
+ while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
5193
+ const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
5194
+ while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
5195
+ const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5196
+ while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5197
+ for (const cand of candidates) {
5198
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
5199
+ for (const tok of tokens) {
5200
+ if (looksLikeScriptPath(tok)) {
5201
+ try {
5202
+ const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5203
+ if (existsSync9(resolved)) {
5204
+ const stat = statSync2(resolved);
5205
+ if (stat.isFile()) return resolved;
5206
+ }
5207
+ } catch {
5208
+ }
5209
+ }
5210
+ }
5211
+ }
5212
+ const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5213
+ while ((m = openRe.exec(code)) !== null) {
5214
+ const p = m[1];
5215
+ try {
5216
+ const resolved = resolvePath2(p);
5217
+ if (existsSync9(resolved)) {
5218
+ const stat = statSync2(resolved);
5219
+ if (stat.isFile()) return resolved;
5220
+ }
5221
+ } catch {
5222
+ }
5223
+ }
5224
+ return null;
5225
+ }
4984
5226
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
4985
- let m;
4986
- while ((m = re.exec(actionDetail)) !== null) {
4987
- const p = m[1];
5227
+ let m2;
5228
+ while ((m2 = re.exec(actionDetail)) !== null) {
5229
+ const p = m2[1];
4988
5230
  let resolved;
4989
5231
  try {
4990
5232
  resolved = resolvePath2(p);
@@ -5170,6 +5412,7 @@ var init_script_content_analyzer = __esm({
5170
5412
  init_esm_shims();
5171
5413
  init_heuristic();
5172
5414
  init_logic_rules();
5415
+ init_inline_analyzer();
5173
5416
  init_tracker();
5174
5417
  SUSPICIOUS_PATTERNS2 = [
5175
5418
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -5634,7 +5877,7 @@ async function analyzeWithLLM(prompt, config) {
5634
5877
  riskType: riskType || "",
5635
5878
  confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
5636
5879
  source: "llm",
5637
- violatedLayers: ["3"]
5880
+ violatedLayers: ["L3.1"]
5638
5881
  };
5639
5882
  } catch (err) {
5640
5883
  if (abortController.signal.aborted) {
@@ -6220,13 +6463,6 @@ var init_brain = __esm({
6220
6463
  });
6221
6464
 
6222
6465
  // 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
- });
6230
6466
  function isValidBase64Token(tok) {
6231
6467
  if (tok.length < 8) return false;
6232
6468
  if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
@@ -6259,6 +6495,13 @@ function decodeBase64Literals(text) {
6259
6495
  }
6260
6496
  return out;
6261
6497
  }
6498
+ function stripStringLiterals(text) {
6499
+ const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
6500
+ if (!m) return text;
6501
+ const cmd = m[1].split("/").pop().toLowerCase();
6502
+ if (!OUTPUT_CMDS.has(cmd)) return text;
6503
+ return text.replace(QUOTED_STR_RE, "");
6504
+ }
6262
6505
  function decodeUrlAndNumeric(text) {
6263
6506
  const out = [];
6264
6507
  let s = text;
@@ -6292,7 +6535,10 @@ function decodeUrlAndNumeric(text) {
6292
6535
  return out;
6293
6536
  }
6294
6537
  function allCandidates(detail, max = 8) {
6295
- const cands = [detail];
6538
+ const shell = stripStringLiterals(detail);
6539
+ const cands = [shell];
6540
+ cands.push(...decodeBase64Literals(shell));
6541
+ cands.push(...decodeUrlAndNumeric(shell));
6296
6542
  cands.push(...decodeBase64Literals(detail));
6297
6543
  cands.push(...decodeUrlAndNumeric(detail));
6298
6544
  const seen = /* @__PURE__ */ new Set();
@@ -6316,12 +6562,14 @@ function matchStageAny(detail, stages) {
6316
6562
  }
6317
6563
  return "";
6318
6564
  }
6319
- var B64_TOKEN_RE;
6565
+ var B64_TOKEN_RE, QUOTED_STR_RE, OUTPUT_CMDS;
6320
6566
  var init_decode = __esm({
6321
6567
  "src/engine/formal-engine/decode.ts"() {
6322
6568
  "use strict";
6323
6569
  init_esm_shims();
6324
6570
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6571
+ QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
6572
+ OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
6325
6573
  }
6326
6574
  });
6327
6575
 
@@ -6357,11 +6605,7 @@ function compileAps(apPatterns) {
6357
6605
  }
6358
6606
  function labelEvent(actionDetail) {
6359
6607
  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
- }
6608
+ const candidates = allCandidates(actionDetail);
6365
6609
  const hits = /* @__PURE__ */ new Set();
6366
6610
  for (const [name, pats] of Object.entries(AP_RE || {})) {
6367
6611
  for (const c of candidates) {
@@ -6457,6 +6701,7 @@ var init_ltl = __esm({
6457
6701
  init_esm_shims();
6458
6702
  init_rules_loader();
6459
6703
  init_ir();
6704
+ init_decode();
6460
6705
  AP_RE = null;
6461
6706
  }
6462
6707
  });
@@ -6703,7 +6948,7 @@ function checkSensitiveDataFlow(graph) {
6703
6948
  const n = nodes[i];
6704
6949
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6705
6950
  violations.push({
6706
- propertyId: "sensitive-data-exfil",
6951
+ propertyId: "sensitive-data-flow",
6707
6952
  reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
6708
6953
  riskLevel: "critical",
6709
6954
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
@@ -6749,7 +6994,7 @@ function checkNeverExfilAfterPrivesc(graph) {
6749
6994
  if (pre) {
6750
6995
  const post = nodes[firstEv];
6751
6996
  return [{
6752
- propertyId: "no-privesc-then-exfil",
6997
+ propertyId: "no-exfil-after-privesc",
6753
6998
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6754
6999
  riskLevel: "critical",
6755
7000
  path: [
@@ -6765,7 +7010,7 @@ function checkNeverExfilAfterPrivesc(graph) {
6765
7010
  for (let j = i + 1; j < nodes.length; j++) {
6766
7011
  if (isExfilNode(nodes[j])) {
6767
7012
  violations.push({
6768
- propertyId: "no-privesc-then-exfil",
7013
+ propertyId: "no-exfil-after-privesc",
6769
7014
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6770
7015
  riskLevel: "critical",
6771
7016
  path: [
@@ -6798,7 +7043,7 @@ function checkNeverExfilAfterCred(graph) {
6798
7043
  if (pre) {
6799
7044
  const post = nodes[firstEv];
6800
7045
  return [{
6801
- propertyId: "no-cred-then-exfil",
7046
+ propertyId: "no-exfil-after-credential",
6802
7047
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6803
7048
  riskLevel: "critical",
6804
7049
  path: [
@@ -6814,7 +7059,7 @@ function checkNeverExfilAfterCred(graph) {
6814
7059
  for (let j = i + 1; j < nodes.length; j++) {
6815
7060
  if (isExfilNode(nodes[j])) {
6816
7061
  violations.push({
6817
- propertyId: "no-cred-then-exfil",
7062
+ propertyId: "no-exfil-after-credential",
6818
7063
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6819
7064
  riskLevel: "critical",
6820
7065
  path: [
@@ -6881,7 +7126,7 @@ function checkLowIntegrityCommandExec(graph) {
6881
7126
  const n = nodes[i];
6882
7127
  if (lowAt.has(i) && isComputeNode(n)) {
6883
7128
  violations.push({
6884
- propertyId: "low-integrity-controls-command-exec",
7129
+ propertyId: "low-integrity-command-exec",
6885
7130
  reason: `\u4F4E\u5B8C\u6574\u6027\u6570\u636E\u63A7\u5236\u547D\u4EE4\u6267\u884C\uFF1A${lowAt.get(i)} \u2192 ${n.sessionId}#${n.seq} \u6267\u884C\u4E0D\u53EF\u4FE1\u5185\u5BB9`,
6886
7131
  riskLevel: "critical",
6887
7132
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
@@ -6927,7 +7172,7 @@ function checkCrossSessionReachability(graph) {
6927
7172
  const srcIdx = Math.min(...sources);
6928
7173
  const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
6929
7174
  violations.push({
6930
- propertyId: "cross-session-reachability",
7175
+ propertyId: "low-integrity-reachability",
6931
7176
  reason: `\u4F4E\u5B8C\u6574\u6027\u6E90\u7ECF\u8DE8 session \u6570\u636E\u94FE\u591A\u8DF3\u5230\u8FBE\u5F53\u524D\u6267\u884C\uFF08\u6E90: ${srcDesc} \u2192 ${n.sessionId}#${n.seq}\uFF09`,
6932
7177
  riskLevel: "high",
6933
7178
  path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
@@ -6999,17 +7244,17 @@ var init_properties = __esm({
6999
7244
  STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
7000
7245
  DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
7001
7246
  ALL_PROPERTIES = [
7002
- { id: "cross-session-chain", check: checkCrossSessionChain },
7003
- { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
7004
- { id: "cross-session-reachability", check: checkCrossSessionReachability },
7005
- { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
7006
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred },
7247
+ { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
7248
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
7249
+ { id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
7250
+ { id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
7251
+ { id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
7007
7252
  // 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 }
7253
+ { id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
7254
+ // P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
7255
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
7256
+ // P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
7257
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
7013
7258
  ];
7014
7259
  }
7015
7260
  });
@@ -7082,6 +7327,32 @@ var init_engine = __esm({
7082
7327
  });
7083
7328
 
7084
7329
  // src/engine/formal-engine/stateful.ts
7330
+ function extractInstances(actionDetail, stage) {
7331
+ const out = [];
7332
+ if (PORT_STAGE_RE.test(stage)) {
7333
+ const d = actionDetail;
7334
+ let m;
7335
+ const re = new RegExp(PORT_RE.source, "gi");
7336
+ while ((m = re.exec(d)) !== null) {
7337
+ const p = m[1] || m[2] || m[3];
7338
+ if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
7339
+ }
7340
+ if (/SCAN/.test(stage)) {
7341
+ const bareRe = new RegExp(BARE_PORT_RE.source, "g");
7342
+ while ((m = bareRe.exec(d)) !== null) {
7343
+ if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
7344
+ }
7345
+ }
7346
+ m = SOCK_RE.exec(d);
7347
+ if (m && m[1]) out.push(`sock:${m[1]}`);
7348
+ m = UNIT_RE.exec(d);
7349
+ if (m && m[1]) out.push(`unit:${m[1]}`);
7350
+ }
7351
+ return out;
7352
+ }
7353
+ function buckKey(chainId, inst) {
7354
+ return inst ? `${chainId}@@${inst}` : chainId;
7355
+ }
7085
7356
  function loadRules() {
7086
7357
  if (STAGES_PATTERNS !== null && CHAINS !== null) {
7087
7358
  return { stages: STAGES_PATTERNS, chains: CHAINS };
@@ -7121,7 +7392,7 @@ function lifecycleFor(detail, stage, isError) {
7121
7392
  if (detail.includes(">>") || detail.includes(">")) return "alive";
7122
7393
  return "";
7123
7394
  }
7124
- var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
7395
+ var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, PORT_RE, BARE_PORT_RE, SOCK_RE, UNIT_RE, PORT_STAGE_RE, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
7125
7396
  var init_stateful = __esm({
7126
7397
  "src/engine/formal-engine/stateful.ts"() {
7127
7398
  "use strict";
@@ -7131,11 +7402,20 @@ var init_stateful = __esm({
7131
7402
  DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
7132
7403
  NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
7133
7404
  CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
7134
- EXFIL_RE2 = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
7405
+ EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
7406
+ PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
7407
+ BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
7408
+ SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
7409
+ UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
7410
+ PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
7135
7411
  STAGES_PATTERNS = null;
7136
7412
  CHAINS = null;
7137
7413
  MAX_NODES = 1e5;
7138
- StatefulChainTracker = class {
7414
+ StatefulChainTracker = class _StatefulChainTracker {
7415
+ /** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
7416
+ static now() {
7417
+ return (/* @__PURE__ */ new Date()).toISOString();
7418
+ }
7139
7419
  agentId;
7140
7420
  nodes_ = [];
7141
7421
  ptr = {};
@@ -7157,10 +7437,14 @@ var init_stateful = __esm({
7157
7437
  for (const chain of chains) {
7158
7438
  const stages = chain.stages || [];
7159
7439
  if (stages.length === 0) continue;
7160
- const matched = this.ptr[String(chain.id || "")] || 0;
7440
+ const cid = String(chain.id || "");
7441
+ let matched = this.ptr[cid] || 0;
7442
+ for (const [k, v] of Object.entries(this.ptr)) {
7443
+ if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
7444
+ }
7161
7445
  if (matched > 0 && matched < stages.length) {
7162
7446
  const pf = {
7163
- chainId: String(chain.id || ""),
7447
+ chainId: cid,
7164
7448
  stages,
7165
7449
  matched,
7166
7450
  riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
@@ -7236,8 +7520,10 @@ var init_stateful = __esm({
7236
7520
  actionDetail: ad,
7237
7521
  stage,
7238
7522
  resources: extractResources2(ad),
7523
+ instances: extractInstances(ad, stage),
7239
7524
  isError,
7240
- lifecycle: lifecycleFor(ad, stage, isError)
7525
+ lifecycle: lifecycleFor(ad, stage, isError),
7526
+ timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
7241
7527
  };
7242
7528
  this.nodes_.push(node);
7243
7529
  this.seqNext += 1;
@@ -7245,11 +7531,19 @@ var init_stateful = __esm({
7245
7531
  }
7246
7532
  recordLifecycle(node) {
7247
7533
  if (node.lifecycle === "dead") {
7248
- let hit = false;
7534
+ const clearedInsts = [];
7535
+ for (const inst of node.instances) {
7536
+ const key = `inst:${inst}`;
7537
+ if (key in this.aliveResources) {
7538
+ delete this.aliveResources[key];
7539
+ clearedInsts.push(inst);
7540
+ }
7541
+ }
7542
+ let pathHit = false;
7249
7543
  for (const r of node.resources) {
7250
7544
  if (r in this.aliveResources) {
7251
7545
  delete this.aliveResources[r];
7252
- hit = true;
7546
+ pathHit = true;
7253
7547
  }
7254
7548
  }
7255
7549
  const detail = node.actionDetail;
@@ -7257,16 +7551,30 @@ var init_stateful = __esm({
7257
7551
  const base = r.split("/").pop() || "";
7258
7552
  if (base && detail.includes(base)) {
7259
7553
  delete this.aliveResources[r];
7260
- hit = true;
7554
+ pathHit = true;
7261
7555
  }
7262
7556
  }
7263
- if (hit) this.resetChainsOnDestroy();
7557
+ if (clearedInsts.length > 0 && !pathHit) {
7558
+ for (const inst of clearedInsts) this.resetChainsForInstance(inst);
7559
+ } else {
7560
+ this.resetChainsOnDestroy();
7561
+ }
7264
7562
  return;
7265
7563
  }
7266
7564
  if (node.lifecycle === "alive") {
7267
7565
  for (const r of node.resources) {
7268
7566
  this.aliveResources[r] = node.seq;
7269
7567
  }
7568
+ for (const inst of node.instances) {
7569
+ this.aliveResources[`inst:${inst}`] = node.seq;
7570
+ }
7571
+ }
7572
+ }
7573
+ /** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
7574
+ resetChainsForInstance(inst) {
7575
+ const key = buckKey("", inst).slice(2);
7576
+ for (const k of Object.keys(this.ptr)) {
7577
+ if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
7270
7578
  }
7271
7579
  }
7272
7580
  resetChainsOnDestroy() {
@@ -7274,6 +7582,29 @@ var init_stateful = __esm({
7274
7582
  this.ptr[cid] = 0;
7275
7583
  }
7276
7584
  }
7585
+ /**
7586
+ * 当前节点应推进的链桶集合。
7587
+ * - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
7588
+ * 同时为所有链的该实例建立桶(若首次出现)。
7589
+ * - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
7590
+ * (信息不足时保守推进一切可能,避免漏判)。
7591
+ */
7592
+ targetsForNode(node, chainId) {
7593
+ const insts = node.instances;
7594
+ if (insts.length > 0) {
7595
+ const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
7596
+ if (active.length > 0) return active.map((i) => buckKey(chainId, i));
7597
+ return [chainId];
7598
+ }
7599
+ const targets = [chainId];
7600
+ for (const k of Object.keys(this.aliveResources)) {
7601
+ if (k.startsWith("inst:")) {
7602
+ const inst = k.slice("inst:".length);
7603
+ targets.push(`${chainId}@@${inst}`);
7604
+ }
7605
+ }
7606
+ return targets;
7607
+ }
7277
7608
  advanceChains(node) {
7278
7609
  if (node.lifecycle === "dead") return null;
7279
7610
  if (node.lifecycle === "never_activated") {
@@ -7287,23 +7618,32 @@ var init_stateful = __esm({
7287
7618
  const stages = chain.stages || [];
7288
7619
  if (stages.length === 0) continue;
7289
7620
  const cid = String(chain.id || "");
7290
- const cur = this.ptr[cid] || 0;
7291
- if (cur < stages.length && stages[cur] === stage) {
7292
- this.ptr[cid] = cur + 1;
7293
- if (cur + 1 >= stages.length) {
7294
- return {
7295
- chainId: cid,
7296
- stages,
7297
- riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
7298
- reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
7299
- path: this.chainPath(stages)
7300
- };
7621
+ const targets = this.targetsForNode(node, cid);
7622
+ for (const key of targets) {
7623
+ const cur = this.ptr[key] || 0;
7624
+ if (cur < stages.length && stages[cur] === stage) {
7625
+ this.ptr[key] = cur + 1;
7626
+ if (cur + 1 >= stages.length) {
7627
+ return {
7628
+ chainId: cid,
7629
+ stages,
7630
+ riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
7631
+ reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
7632
+ path: this.chainPath(stages)
7633
+ };
7634
+ }
7301
7635
  }
7302
7636
  }
7303
7637
  }
7304
7638
  return null;
7305
7639
  }
7306
7640
  invalidateChainOnFailure(node) {
7641
+ if (node.instances.length > 0) {
7642
+ for (const inst of node.instances) {
7643
+ this.resetChainsForInstance(inst);
7644
+ }
7645
+ return;
7646
+ }
7307
7647
  const failed = new Set(node.resources);
7308
7648
  if (failed.size === 0) return;
7309
7649
  const { chains } = loadRules();
@@ -7319,7 +7659,7 @@ var init_stateful = __esm({
7319
7659
  let want = stages[0];
7320
7660
  for (const n of this.nodes_) {
7321
7661
  if (n.stage === want) {
7322
- path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
7662
+ path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
7323
7663
  const idx = stages.indexOf(n.stage);
7324
7664
  if (idx + 1 < stages.length) {
7325
7665
  want = stages[idx + 1];
@@ -7355,7 +7695,7 @@ var init_stateful = __esm({
7355
7695
  if (!this.tainted.has(node.seq)) return [];
7356
7696
  if (EXFIL_RE2.test(node.actionDetail)) {
7357
7697
  return [{
7358
- property_id: "sensitive-data-exfil",
7698
+ property_id: "sensitive-data-flow",
7359
7699
  reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
7360
7700
  risk_level: "critical",
7361
7701
  path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
@@ -7397,8 +7737,12 @@ var init_stateful = __esm({
7397
7737
  actionDetail: String(raw.actionDetail || ""),
7398
7738
  stage: String(raw.stage || ""),
7399
7739
  resources: [...raw.resources || []],
7740
+ instances: [...raw.instances || []],
7741
+ // 旧快照无此字段 → 空
7400
7742
  isError: Boolean(raw.isError),
7401
- lifecycle: String(raw.lifecycle || "")
7743
+ lifecycle: String(raw.lifecycle || ""),
7744
+ timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
7745
+ // 旧快照无此字段 → null
7402
7746
  });
7403
7747
  }
7404
7748
  this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
@@ -7591,7 +7935,7 @@ async function runAnalysis(input) {
7591
7935
  riskType: node.l1RiskType,
7592
7936
  confidence: 90,
7593
7937
  source: "heuristic_script_scan",
7594
- violatedLayers: ["1.1-script"]
7938
+ violatedLayers: ["L1.2"]
7595
7939
  };
7596
7940
  }
7597
7941
  }
@@ -7709,7 +8053,7 @@ async function runAnalysis(input) {
7709
8053
  riskType: inlineJudgment.riskType,
7710
8054
  confidence: 90,
7711
8055
  source: "logic_rule",
7712
- violatedLayers: ["1.2"]
8056
+ violatedLayers: ["L2.1"]
7713
8057
  };
7714
8058
  }
7715
8059
  return {
@@ -7742,7 +8086,7 @@ async function runAnalysis(input) {
7742
8086
  console.warn(
7743
8087
  `[AgentMoss] \u5F62\u5F0F\u5316\u9A8C\u8BC1 observe\uFF08\u53EA\u8BB0\u5F55\u4E0D\u62E6\u622A\uFF09: property=${v?.propertyId ?? "?"} risk=${v?.riskLevel ?? "?"} path_len=${v?.path?.length ?? 0} reason=${(v?.reason ?? "?").slice(0, 200)}`
7744
8088
  );
7745
- observedFormalLayers.push("2.4");
8089
+ observedFormalLayers.push("L2.2");
7746
8090
  } else {
7747
8091
  return {
7748
8092
  allowed: false,
@@ -7751,7 +8095,7 @@ async function runAnalysis(input) {
7751
8095
  riskType: "formal_verification",
7752
8096
  confidence: 90,
7753
8097
  source: "formal_engine",
7754
- violatedLayers: ["2.4"]
8098
+ violatedLayers: ["L2.2"]
7755
8099
  };
7756
8100
  }
7757
8101
  }
@@ -7768,7 +8112,7 @@ async function runAnalysis(input) {
7768
8112
  riskType: node.l2RiskType,
7769
8113
  confidence: 85,
7770
8114
  source: "logic_rule_script_scan",
7771
- violatedLayers: ["1.2-script"]
8115
+ violatedLayers: ["L2.3"]
7772
8116
  };
7773
8117
  }
7774
8118
  }
@@ -7855,7 +8199,7 @@ async function runAnalysis(input) {
7855
8199
  riskType: "",
7856
8200
  confidence: 60,
7857
8201
  source: "llm_failure_fallback",
7858
- violatedLayers: ["3"]
8202
+ violatedLayers: ["L3.1"]
7859
8203
  };
7860
8204
  }
7861
8205
  if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
@@ -8797,7 +9141,7 @@ var AnalyzeRequestSchema = z.object({
8797
9141
  session_history: z.array(z.object({
8798
9142
  session_id: z.string(),
8799
9143
  action_history: z.array(ActionItemSchema).default([])
8800
- })).default([]).describe("Multi-session historical actions (optional): for L2.4 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
9144
+ })).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
8801
9145
  a_next: ActionItemSchema.describe("Next action to be executed"),
8802
9146
  reason: z.string().default("").describe("Reason for executing this action"),
8803
9147
  os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
@@ -8814,7 +9158,9 @@ var AnalyzeResponseSchema = z.object({
8814
9158
  policy: z.string().default(""),
8815
9159
  violated_policy: z.string().default(""),
8816
9160
  confidence: z.number().int().min(0).max(100).default(100),
8817
- analysis_duration_ms: z.number().default(0)
9161
+ analysis_duration_ms: z.number().default(0),
9162
+ // 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
9163
+ violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
8818
9164
  });
8819
9165
  var HealthResponseSchema = z.object({
8820
9166
  status: z.string().default("healthy"),
@@ -9198,7 +9544,7 @@ async function analyzeRoute(c) {
9198
9544
  const judgment = incMatchedToDeny(inc);
9199
9545
  if (judgment) {
9200
9546
  const layers = [...result.violatedLayers];
9201
- if (!layers.includes("2.4-stateful")) layers.push("2.4-stateful");
9547
+ if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
9202
9548
  console.error(
9203
9549
  `[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
9204
9550
  );
@@ -9212,7 +9558,8 @@ async function analyzeRoute(c) {
9212
9558
  policy: "",
9213
9559
  violated_policy: "",
9214
9560
  confidence: 90,
9215
- analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
9561
+ analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
9562
+ violation_chain: judgment.violation_chain
9216
9563
  })
9217
9564
  );
9218
9565
  }
@@ -9251,17 +9598,20 @@ function getChainStateStoreFor(_agentId) {
9251
9598
  }
9252
9599
  function incMatchedToDeny(inc) {
9253
9600
  if (inc.matched) {
9601
+ const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
9254
9602
  return {
9255
9603
  property_id: "cross-" + inc.matched.chainId,
9256
9604
  reason: inc.matched.reason,
9257
- risk_level: inc.matched.riskLevel
9605
+ risk_level: inc.matched.riskLevel,
9606
+ violation_chain: chain
9258
9607
  };
9259
9608
  }
9260
9609
  if (inc.exfil && inc.exfil.length > 0) {
9261
9610
  return {
9262
- property_id: "sensitive-data-exfil",
9611
+ property_id: "sensitive-data-flow",
9263
9612
  reason: inc.exfil[0].reason,
9264
- risk_level: inc.exfil[0].risk_level
9613
+ risk_level: inc.exfil[0].risk_level,
9614
+ violation_chain: []
9265
9615
  };
9266
9616
  }
9267
9617
  return null;
@@ -9387,7 +9737,7 @@ function createConsoleRouter() {
9387
9737
  const rc = updateLayerEnabled(body.layer_key, body.enabled);
9388
9738
  return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
9389
9739
  });
9390
- function getL24View() {
9740
+ function getL22View() {
9391
9741
  const ltlHuman = {
9392
9742
  "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9393
9743
  "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
@@ -9418,9 +9768,9 @@ function createConsoleRouter() {
9418
9768
  }))
9419
9769
  };
9420
9770
  }
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;
9771
+ function withL22View(rc) {
9772
+ if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
9773
+ const cat = rc.L2_2.ltl_rules;
9424
9774
  const rls = cat && cat.rules || [];
9425
9775
  const ltlHumanLocal = {
9426
9776
  "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
@@ -9435,7 +9785,7 @@ function createConsoleRouter() {
9435
9785
  "mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
9436
9786
  "mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
9437
9787
  };
9438
- rc.L2_4 = {
9788
+ rc.L2_2 = {
9439
9789
  enabled_via: "L2_logic_rules",
9440
9790
  description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9441
9791
  ltl_rules: rls.map((r) => ({
@@ -9454,22 +9804,22 @@ function createConsoleRouter() {
9454
9804
  }
9455
9805
  console2.get("/api/rules", (c) => {
9456
9806
  const rc = loadRuntimeConfig();
9457
- return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_4: getL24View() });
9807
+ return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
9458
9808
  });
9459
9809
  console2.put("/api/rules/enabled", async (c) => {
9460
9810
  const b = await c.req.json();
9461
9811
  const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
9462
- return c.json(withL24View(rc));
9812
+ return c.json(withL22View(rc));
9463
9813
  });
9464
9814
  console2.put("/api/rules/deny_mode", async (c) => {
9465
9815
  const b = await c.req.json();
9466
9816
  const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
9467
- return c.json(withL24View(rc));
9817
+ return c.json(withL22View(rc));
9468
9818
  });
9469
9819
  console2.put("/api/rules/skip_l3", async (c) => {
9470
9820
  const b = await c.req.json();
9471
9821
  const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
9472
- return c.json(withL24View(rc));
9822
+ return c.json(withL22View(rc));
9473
9823
  });
9474
9824
  console2.put("/api/categories/enabled", async (c) => {
9475
9825
  const b = await c.req.json();
@@ -9619,7 +9969,7 @@ function createConsoleRouter() {
9619
9969
  });
9620
9970
  console2.get("/api/config", (c) => {
9621
9971
  const rc = loadRuntimeConfig();
9622
- return c.json({ ...rc, L2_4: getL24View() });
9972
+ return c.json({ ...rc, L2_2: getL22View() });
9623
9973
  });
9624
9974
  console2.get("/api/env-overrides", (c) => {
9625
9975
  return c.json({ overrides: getEnvOverrides() });
@@ -9959,4 +10309,14 @@ if (isMain) {
9959
10309
  //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
9960
10310
  //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
9961
10311
  //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
10312
+ //!
10313
+ //! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
10314
+ //! P1 cross-session-chain 跨会话组合链
10315
+ //! P2 sensitive-data-flow 敏感数据传播
10316
+ //! P3 low-integrity-reachability 低完整性数据流可达
10317
+ //! P4 no-exfil-after-privesc 提权后禁外传
10318
+ //! P5 no-exfil-after-credential 凭据后禁外传
10319
+ //! P6 mc-ltl-rules LTL 模型检测
10320
+ //! P7 low-integrity-command-exec 低完整性命令执行
10321
+ //! P8 sensitive-data-aggregation 敏感数据聚合外传
9962
10322
  //# sourceMappingURL=cli.js.map