@kenhkl/agent-moss 0.13.0 → 0.15.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.
@@ -628,10 +628,15 @@ function loadMC() {
628
628
  try {
629
629
  const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
630
630
  if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
631
- const rtRules = rt.loadRuntimeConfig()?.L2_4?.ltl_rules?.rules || [];
631
+ const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
632
+ const rtRules = ltl.rules || [];
632
633
  const enabledById = {};
633
634
  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
+ const catEnabled = ltl.category_enabled !== false;
636
+ for (const r of rules) {
637
+ if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
638
+ if (!catEnabled) r.enabled = false;
639
+ }
635
640
  }
636
641
  } catch {
637
642
  }
@@ -676,7 +681,8 @@ function _loadSensitivePathScope(scope) {
676
681
  path: sp.path,
677
682
  riskLevel: sp.risk_level,
678
683
  desc: sp.desc,
679
- 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
680
686
  }));
681
687
  }
682
688
  function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
@@ -774,7 +780,7 @@ function runLogicCheck(input) {
774
780
  riskType: "intent_deviation",
775
781
  confidence: 85,
776
782
  source: "logic_rule",
777
- violatedLayers: ["2"]
783
+ violatedLayers: ["L2.1"]
778
784
  };
779
785
  }
780
786
  }
@@ -798,7 +804,7 @@ function runLogicCheck(input) {
798
804
  riskType: "privilege_escalation",
799
805
  confidence: 85,
800
806
  source: "logic_rule",
801
- violatedLayers: ["2"]
807
+ violatedLayers: ["L2.1"]
802
808
  };
803
809
  }
804
810
  }
@@ -850,7 +856,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
850
856
  riskType: "combo_chain",
851
857
  confidence: 85,
852
858
  source: "logic_rule",
853
- violatedLayers: ["2"]
859
+ violatedLayers: ["L2.1"]
854
860
  };
855
861
  }
856
862
  }
@@ -975,7 +981,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
975
981
  riskType: "read_before_write",
976
982
  confidence: 85,
977
983
  source: "logic_rule",
978
- violatedLayers: ["2"]
984
+ violatedLayers: ["L2.1"]
979
985
  };
980
986
  }
981
987
  return null;
@@ -995,7 +1001,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
995
1001
  riskType: "consent_missing",
996
1002
  confidence: 85,
997
1003
  source: "logic_rule",
998
- violatedLayers: ["2"]
1004
+ violatedLayers: ["L2.1"]
999
1005
  };
1000
1006
  }
1001
1007
  function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
@@ -1013,7 +1019,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1013
1019
  riskType: "consent_missing",
1014
1020
  confidence: 85,
1015
1021
  source: "logic_rule",
1016
- violatedLayers: ["2"]
1022
+ violatedLayers: ["L2.1"]
1017
1023
  };
1018
1024
  }
1019
1025
  function containsPrivilegeEscalation(actionDetail) {
@@ -1073,7 +1079,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1073
1079
  riskType: "file_access",
1074
1080
  confidence: 95,
1075
1081
  source: "logic_rule",
1076
- violatedLayers: ["2"],
1082
+ violatedLayers: ["L2.1"],
1077
1083
  isCredential: true
1078
1084
  };
1079
1085
  }
@@ -1089,7 +1095,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1089
1095
  riskType: "file_access",
1090
1096
  confidence: 95,
1091
1097
  source: "logic_rule",
1092
- violatedLayers: ["2"]
1098
+ violatedLayers: ["L2.1"]
1093
1099
  };
1094
1100
  }
1095
1101
  }
@@ -1103,7 +1109,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1103
1109
  riskType: "file_access",
1104
1110
  confidence: 70,
1105
1111
  source: "logic_rule",
1106
- violatedLayers: ["2"]
1112
+ violatedLayers: ["L2.1"]
1107
1113
  };
1108
1114
  }
1109
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;
@@ -1115,7 +1121,7 @@ var init_logic_rules = __esm({
1115
1121
  init_rules_loader();
1116
1122
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1117
1123
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1118
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc }) => ({ path: path5, riskLevel, desc, credential: true }));
1124
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1119
1125
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1120
1126
  "ask_user_question",
1121
1127
  "glob",
@@ -1428,6 +1434,7 @@ __export(runtime_config_exports, {
1428
1434
  isLayerEnabled: () => isLayerEnabled,
1429
1435
  loadRuntimeConfig: () => loadRuntimeConfig,
1430
1436
  mergeConfig: () => mergeConfig,
1437
+ pickDeletableRule: () => pickDeletableRule,
1431
1438
  saveRuntimeConfig: () => saveRuntimeConfig,
1432
1439
  updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
1433
1440
  updateAgentRuleEnabled: () => updateAgentRuleEnabled,
@@ -1466,7 +1473,7 @@ function emptyRuntimeConfig() {
1466
1473
  L1_rules: {},
1467
1474
  L2_rules: {},
1468
1475
  L3_skills: {},
1469
- L2_4: {},
1476
+ L2_2: {},
1470
1477
  agent_rules: {},
1471
1478
  scenario_rules: {}
1472
1479
  };
@@ -1494,7 +1501,7 @@ function loadRuntimeConfig() {
1494
1501
  L1_rules: merged.L1_rules || {},
1495
1502
  L2_rules: merged.L2_rules || {},
1496
1503
  L3_skills: merged.L3_skills || {},
1497
- L2_4: merged.L2_4 || {},
1504
+ L2_2: merged.L2_2 || {},
1498
1505
  agent_rules: merged.agent_rules || {},
1499
1506
  scenario_rules: merged.scenario_rules || {}
1500
1507
  };
@@ -1547,6 +1554,7 @@ function getEnabledL2SensitivePaths(runtime) {
1547
1554
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1548
1555
  if (r.credential) out.credential = true;
1549
1556
  if (r.read_only) out.read_only = true;
1557
+ if (r.dir_del_only) out.dir_del_only = true;
1550
1558
  return out;
1551
1559
  });
1552
1560
  }
@@ -1592,8 +1600,16 @@ function getDisabledL2RulesWithSkipL3() {
1592
1600
  function _slugify(text) {
1593
1601
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1594
1602
  }
1603
+ function _fnv1a32(text) {
1604
+ let h = 2166136261;
1605
+ for (const b of Buffer.from(text, "utf-8")) {
1606
+ h ^= b;
1607
+ h = h * 16777619 >>> 0;
1608
+ }
1609
+ return h.toString(16).padStart(8, "0");
1610
+ }
1595
1611
  function _generatePatternId(pattern) {
1596
- return `pat_${_slugify(pattern)}`;
1612
+ return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
1597
1613
  }
1598
1614
  function _deriveDenyMode(rule) {
1599
1615
  if (rule.deny_mode) return rule.deny_mode;
@@ -1655,6 +1671,7 @@ function _l2SensitivePathsToRules() {
1655
1671
  builtin: true
1656
1672
  };
1657
1673
  _syncDenyModeFields(rule);
1674
+ if (sp.dirDelOnly) rule.dir_del_only = true;
1658
1675
  return rule;
1659
1676
  });
1660
1677
  }
@@ -1871,17 +1888,20 @@ function generateSourceDefaults() {
1871
1888
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1872
1889
  },
1873
1890
  L3_skills: _l3SkillsToRules(),
1874
- L2_4: _ltlRulesToRuntime(),
1891
+ L2_2: _ltlRulesToRuntime(),
1875
1892
  agent_rules: _agentRulesToDefaults(),
1876
1893
  scenario_rules: _scenarioRulesToDefaults()
1877
1894
  };
1878
1895
  }
1879
1896
  function _ltlRulesToRuntime() {
1880
1897
  const cands = [
1881
- join3(__dirname4, "..", "rules"),
1882
- join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1883
- join3(process.cwd(), "agent_moss", "rules")
1884
- ];
1898
+ getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
1899
+ join3(__dirname4, "../rules"),
1900
+ join3(__dirname4, "rules"),
1901
+ join3(__dirname4, "../../../agent_moss/rules"),
1902
+ join3(__dirname4, "../../../../agent_moss/rules"),
1903
+ join3(process.cwd(), "agent_moss/rules")
1904
+ ].filter(Boolean);
1885
1905
  let mc = {};
1886
1906
  for (const dir of cands) {
1887
1907
  const p = join3(dir, "mc_rules.json");
@@ -2059,7 +2079,11 @@ function mergeConfig(userCfg, srcDefaults) {
2059
2079
  );
2060
2080
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
2061
2081
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2062
- _mergeRuleCategories(cfg, srcDefaults, "L2_4");
2082
+ if (!cfg.L2_2 && cfg.L2_4) {
2083
+ cfg.L2_2 = cfg.L2_4;
2084
+ delete cfg.L2_4;
2085
+ }
2086
+ _mergeRuleCategories(cfg, srcDefaults, "L2_2");
2063
2087
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
2064
2088
  _mergeAgentRules(cfg, srcDefaults);
2065
2089
  _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
@@ -2237,16 +2261,20 @@ function addCustomRule(layer, category, rule) {
2237
2261
  saveRuntimeConfig(runtime);
2238
2262
  return runtime;
2239
2263
  }
2240
- function deleteCustomRule(layer, category, ruleId) {
2241
- const runtime = loadRuntimeConfig();
2242
- const rules = runtime[layer]?.[category]?.rules || [];
2243
- const idx = rules.findIndex((r) => r.id === ruleId);
2264
+ function pickDeletableRule(rules, ruleId) {
2265
+ const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
2244
2266
  if (idx >= 0) {
2245
- const r = rules[idx];
2246
- if (r.builtin && !r.source_removed) return null;
2247
2267
  rules.splice(idx, 1);
2248
- saveRuntimeConfig(runtime);
2268
+ return rules;
2249
2269
  }
2270
+ if (rules.some((r) => r.id === ruleId)) return null;
2271
+ return rules;
2272
+ }
2273
+ function deleteCustomRule(layer, category, ruleId) {
2274
+ const runtime = loadRuntimeConfig();
2275
+ const rules = runtime[layer]?.[category]?.rules || [];
2276
+ if (pickDeletableRule(rules, ruleId) === null) return null;
2277
+ saveRuntimeConfig(runtime);
2250
2278
  return runtime;
2251
2279
  }
2252
2280
  function addCustomSkill(skillId, category, keywords, content) {
@@ -2679,7 +2707,7 @@ function runHeuristicCheck(input) {
2679
2707
  riskType: "sensitive_action",
2680
2708
  confidence: 90,
2681
2709
  source: "heuristic",
2682
- violatedLayers: ["1.1"]
2710
+ violatedLayers: ["L1.1"]
2683
2711
  };
2684
2712
  }
2685
2713
  }
@@ -2689,8 +2717,10 @@ function runHeuristicCheck(input) {
2689
2717
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2690
2718
  const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
2691
2719
  let topRiskType = "";
2720
+ const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2692
2721
  for (const rule of DANGEROUS_PATTERNS) {
2693
2722
  if (rule.pattern.test(scanCommand)) {
2723
+ if (exemptKeygen && rule.riskType === "file_access") continue;
2694
2724
  matched.push(rule.description);
2695
2725
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
2696
2726
  highestSeverity = rule.severity;
@@ -2732,10 +2762,26 @@ function runHeuristicCheck(input) {
2732
2762
  const detailL = input.aNext.actionDetail.toLowerCase();
2733
2763
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2734
2764
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2735
- if (dangerousResult) return dangerousResult;
2736
- if (!isExcludedType) {
2737
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
2738
- if (pathResult) return pathResult;
2765
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
2766
+ const homeResult = checkHomeDeletion(actionTypeL, detailL);
2767
+ const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2768
+ if (mergeResults.length > 0) {
2769
+ let best = mergeResults[0];
2770
+ for (const r of mergeResults) {
2771
+ if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
2772
+ }
2773
+ if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
2774
+ return {
2775
+ allowed: false,
2776
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
2777
+ riskLevel: highestSeverity,
2778
+ riskType: highestRiskType,
2779
+ confidence: 95,
2780
+ source: "heuristic",
2781
+ violatedLayers: ["L1.1"]
2782
+ };
2783
+ }
2784
+ return best;
2739
2785
  }
2740
2786
  if (matched.length > 0) {
2741
2787
  return {
@@ -2745,8 +2791,7 @@ function runHeuristicCheck(input) {
2745
2791
  riskType: highestRiskType,
2746
2792
  confidence: 95,
2747
2793
  source: "heuristic",
2748
- violatedLayers: ["1.1"]
2749
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
2794
+ violatedLayers: ["L1.1"]
2750
2795
  };
2751
2796
  }
2752
2797
  const lateralResult = checkLateralMovement(detailL);
@@ -2874,6 +2919,7 @@ function isSearchActionType(actionType) {
2874
2919
  function checkSensitivePath(command, cwd, agentExt) {
2875
2920
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2876
2921
  const isWriteOpFlag = isWriteOperation("", command);
2922
+ const keygenGenerate = isSshKeygenGenerate(command);
2877
2923
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2878
2924
  const pathMatches = (text, path5) => {
2879
2925
  const parts = path5.split(/(\(\?!.*?\))/);
@@ -2886,6 +2932,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2886
2932
  };
2887
2933
  for (const sp of ROOT_SENSITIVE_PATHS) {
2888
2934
  if (pathMatches(pathScanText, sp.path.toLowerCase())) {
2935
+ if (keygenGenerate) continue;
2889
2936
  if (sp.path === "/dev/random") {
2890
2937
  if (isWriteOpFlag) {
2891
2938
  continue;
@@ -2897,7 +2944,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2897
2944
  riskType: "file_access",
2898
2945
  confidence: 90,
2899
2946
  source: "heuristic",
2900
- violatedLayers: ["1.1"]
2947
+ violatedLayers: ["L1.1"]
2901
2948
  };
2902
2949
  }
2903
2950
  if (!isWriteOpFlag && !sp.credential) {
@@ -2910,16 +2957,51 @@ function checkSensitivePath(command, cwd, agentExt) {
2910
2957
  riskType: "file_access",
2911
2958
  confidence: 90,
2912
2959
  source: "heuristic",
2913
- violatedLayers: ["1.1"],
2960
+ violatedLayers: ["L1.1"],
2914
2961
  isCredential: !!sp.credential
2915
2962
  };
2916
2963
  }
2917
2964
  }
2918
2965
  for (const sp of HOME_SENSITIVE_PATHS) {
2919
2966
  const expanded = `${expandedCwd}/${sp.path}`;
2920
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2921
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
2922
- if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
2967
+ let pPath = sp.path;
2968
+ if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
2969
+ let dirPrefixMatch = null;
2970
+ if (sp.dirDelOnly) {
2971
+ const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
2972
+ dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
2973
+ }
2974
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
2975
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
2976
+ if (pathReMatch || expandedReMatch) {
2977
+ if (keygenGenerate) continue;
2978
+ if (sp.dirDelOnly) {
2979
+ if (isDeleteActionForDir(command, sp.path)) {
2980
+ return {
2981
+ allowed: false,
2982
+ reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2983
+ riskLevel: sp.riskLevel,
2984
+ riskType: "file_access",
2985
+ confidence: 90,
2986
+ source: "heuristic",
2987
+ violatedLayers: ["L1.1"],
2988
+ isCredential: !!sp.credential
2989
+ };
2990
+ }
2991
+ if (isArchiveReadOfDir(command, sp.path)) {
2992
+ return {
2993
+ allowed: false,
2994
+ reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
2995
+ riskLevel: sp.riskLevel,
2996
+ riskType: "file_access",
2997
+ confidence: 90,
2998
+ source: "heuristic",
2999
+ violatedLayers: ["L1.1"],
3000
+ isCredential: !!sp.credential
3001
+ };
3002
+ }
3003
+ continue;
3004
+ }
2923
3005
  if (!isWriteOpFlag && !sp.credential) {
2924
3006
  continue;
2925
3007
  }
@@ -2930,7 +3012,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2930
3012
  riskType: "file_access",
2931
3013
  confidence: 90,
2932
3014
  source: "heuristic",
2933
- violatedLayers: ["1.1"],
3015
+ violatedLayers: ["L1.1"],
2934
3016
  isCredential: !!sp.credential
2935
3017
  };
2936
3018
  }
@@ -2953,7 +3035,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2953
3035
  riskType: "file_access",
2954
3036
  confidence: 90,
2955
3037
  source: "heuristic",
2956
- violatedLayers: ["1.1"],
3038
+ violatedLayers: ["L1.1"],
2957
3039
  isCredential: !!sp.credential
2958
3040
  };
2959
3041
  }
@@ -2970,7 +3052,7 @@ function checkSensitivePath(command, cwd, agentExt) {
2970
3052
  riskType: "file_access",
2971
3053
  confidence: 90,
2972
3054
  source: "heuristic",
2973
- violatedLayers: ["1.1"],
3055
+ violatedLayers: ["L1.1"],
2974
3056
  isCredential: true
2975
3057
  };
2976
3058
  }
@@ -2983,18 +3065,142 @@ function checkSensitivePath(command, cwd, agentExt) {
2983
3065
  riskType: "file_access",
2984
3066
  confidence: 90,
2985
3067
  source: "heuristic",
2986
- violatedLayers: ["1.1"]
3068
+ violatedLayers: ["L1.1"]
2987
3069
  };
2988
3070
  }
2989
3071
  return null;
2990
3072
  }
3073
+ function isHomeValue(path5) {
3074
+ let p = path5.trim().toLowerCase();
3075
+ if (p.startsWith("$(") && p.endsWith(")")) {
3076
+ const inner = p.slice(2, -1).trim();
3077
+ return HOME_TARGET_RE.test(" " + inner);
3078
+ }
3079
+ if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
3080
+ return HOME_TARGET_RE.test(" " + p);
3081
+ }
3082
+ function isDeleteCommand(seg) {
3083
+ const s = seg.trimStart();
3084
+ if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
3085
+ return /删除|移除/.test(s);
3086
+ }
3087
+ function homeEscapeTarget(seg) {
3088
+ const low = seg.toLowerCase();
3089
+ const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
3090
+ let m;
3091
+ while ((m = re.exec(low)) !== null) {
3092
+ const endIdx = m.index + m[0].length;
3093
+ const tail = low.slice(endIdx);
3094
+ if (!tail) continue;
3095
+ let token = tail.trimStart().split(/\s+/)[0] || "";
3096
+ token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
3097
+ token = token.replace(/^['"]|['"]$/g, "");
3098
+ const segments = token.split("/").filter((s) => s && s !== ".");
3099
+ if (!segments.some((s) => s === "..")) continue;
3100
+ let depth = 0;
3101
+ let minDepth = 0;
3102
+ for (const s of segments) {
3103
+ depth += s === ".." ? -1 : 1;
3104
+ minDepth = Math.min(minDepth, depth);
3105
+ }
3106
+ if (minDepth < 0) return true;
3107
+ if (depth <= 0) return true;
3108
+ }
3109
+ return false;
3110
+ }
3111
+ function checkHomeDeletion(actionType, actionDetail) {
3112
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
3113
+ const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
3114
+ for (const seg of segs) {
3115
+ if (!isDeleteCommand(seg)) continue;
3116
+ if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
3117
+ return {
3118
+ allowed: false,
3119
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
3120
+ riskLevel: "high",
3121
+ riskType: "script_execution",
3122
+ confidence: 90,
3123
+ source: "heuristic",
3124
+ violatedLayers: ["L1.1"]
3125
+ };
3126
+ }
3127
+ }
3128
+ const assigned = {};
3129
+ for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
3130
+ for (const [varName, path5] of Object.entries(assigned)) {
3131
+ if (!isHomeValue(path5)) continue;
3132
+ const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
3133
+ for (const seg of segs) {
3134
+ if (!isDeleteCommand(seg)) continue;
3135
+ if (varRefRe.test(seg)) {
3136
+ return {
3137
+ allowed: false,
3138
+ reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path5}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
3139
+ riskLevel: "high",
3140
+ riskType: "script_execution",
3141
+ confidence: 90,
3142
+ source: "heuristic",
3143
+ violatedLayers: ["L1.1"]
3144
+ };
3145
+ }
3146
+ }
3147
+ }
3148
+ return null;
3149
+ }
3150
+ function escapeRegExp(s) {
3151
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3152
+ }
3153
+ function isDeleteActionForDir(command, dirPath) {
3154
+ const low = command.toLowerCase();
3155
+ const dpath = dirPath.toLowerCase();
3156
+ if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
3157
+ return true;
3158
+ }
3159
+ if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
3160
+ return true;
3161
+ }
3162
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3163
+ const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3164
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
3165
+ return true;
3166
+ }
3167
+ }
3168
+ return false;
3169
+ }
3170
+ function isArchiveReadOfDir(command, dirPath) {
3171
+ const low = command.toLowerCase();
3172
+ const dpath = dirPath.toLowerCase();
3173
+ for (const seg of low.split(/&&|\|\||;|\n/)) {
3174
+ const s = seg.trim();
3175
+ if (!s || !s.includes(dpath)) continue;
3176
+ const parts = s.split(/\s+/);
3177
+ const fw = parts[0] || "";
3178
+ if (fw === "zip") return true;
3179
+ if (fw === "tar") {
3180
+ if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
3181
+ return true;
3182
+ }
3183
+ if (fw === "cp" || fw === "rsync") {
3184
+ const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3185
+ if (segPaths.length && segPaths[0].includes(dpath)) return true;
3186
+ }
3187
+ }
3188
+ return false;
3189
+ }
3190
+ function isSshKeygenGenerate(command) {
3191
+ const low = command.toLowerCase();
3192
+ if (!low.includes("ssh-keygen")) return false;
3193
+ if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
3194
+ if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
3195
+ return true;
3196
+ }
2991
3197
  function checkDangerousPatterns(actionType, actionDetail) {
2992
3198
  if (actionType === "file_write" || actionType === "file_edit") return null;
2993
3199
  const scan = stripHeredocContent(actionDetail);
2994
3200
  const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
2995
3201
  const isTempPath = TEMP_DIRS.some(
2996
3202
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
2997
- );
3203
+ ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
2998
3204
  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;
2999
3205
  if (!isTempPath && wildcardDeleteRe.test(scan)) {
3000
3206
  return {
@@ -3004,7 +3210,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3004
3210
  riskType: "script_execution",
3005
3211
  confidence: 90,
3006
3212
  source: "heuristic",
3007
- violatedLayers: ["1.1"]
3213
+ violatedLayers: ["L1.1"]
3008
3214
  };
3009
3215
  }
3010
3216
  const findWriteMatch = actionDetail.match(
@@ -3019,10 +3225,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
3019
3225
  riskType: "script_execution",
3020
3226
  confidence: 90,
3021
3227
  source: "heuristic",
3022
- violatedLayers: ["1.1"]
3228
+ violatedLayers: ["L1.1"]
3023
3229
  };
3024
3230
  }
3025
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
3231
+ if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
3026
3232
  return {
3027
3233
  allowed: false,
3028
3234
  reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
@@ -3030,7 +3236,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3030
3236
  riskType: "file_access",
3031
3237
  confidence: 95,
3032
3238
  source: "heuristic",
3033
- violatedLayers: ["1.1"]
3239
+ violatedLayers: ["L1.1"]
3034
3240
  };
3035
3241
  }
3036
3242
  if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
@@ -3041,7 +3247,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
3041
3247
  riskType: "file_access",
3042
3248
  confidence: 90,
3043
3249
  source: "heuristic",
3044
- violatedLayers: ["1.1"]
3250
+ violatedLayers: ["L1.1"]
3045
3251
  };
3046
3252
  }
3047
3253
  return null;
@@ -3132,13 +3338,13 @@ function checkLateralMovement(actionDetail) {
3132
3338
  riskType: "lateral_movement",
3133
3339
  confidence: 80,
3134
3340
  source: "heuristic",
3135
- violatedLayers: ["1.1"]
3341
+ violatedLayers: ["L1.1"]
3136
3342
  };
3137
3343
  }
3138
3344
  }
3139
3345
  return null;
3140
3346
  }
3141
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
3347
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3142
3348
  var init_heuristic = __esm({
3143
3349
  "src/engine/heuristic.ts"() {
3144
3350
  "use strict";
@@ -3168,6 +3374,8 @@ var init_heuristic = __esm({
3168
3374
  })();
3169
3375
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3170
3376
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3377
+ HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
3378
+ 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;
3171
3379
  }
3172
3380
  });
3173
3381
 
@@ -3346,112 +3554,6 @@ var init_token_stats = __esm({
3346
3554
  }
3347
3555
  });
3348
3556
 
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
-
3455
3557
  // src/engine/coordinator.ts
3456
3558
  init_esm_shims();
3457
3559
  init_heuristic();
@@ -3684,6 +3786,7 @@ ${result.scriptContent}
3684
3786
  init_esm_shims();
3685
3787
  init_heuristic();
3686
3788
  init_logic_rules();
3789
+ init_inline_analyzer();
3687
3790
  import { statSync as statSync2, existsSync as existsSync9 } from "fs";
3688
3791
  import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
3689
3792
  import { homedir as homedir4 } from "os";
@@ -5298,6 +5401,13 @@ function extractScriptPath2(command) {
5298
5401
  return null;
5299
5402
  }
5300
5403
  function extractScriptPathWithArgs(command) {
5404
+ const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
5405
+ if (inline) {
5406
+ const inner = inline[2];
5407
+ const realPath = extractScriptPath2(inner);
5408
+ if (realPath) return { path: realPath, args: [] };
5409
+ return { path: null, args: [] };
5410
+ }
5301
5411
  const scriptPath = extractScriptPath2(command);
5302
5412
  if (!scriptPath) return { path: null, args: [] };
5303
5413
  const idx = command.indexOf(scriptPath);
@@ -5311,11 +5421,59 @@ function extractScriptPathWithArgs(command) {
5311
5421
  }
5312
5422
  return { path: scriptPath, args };
5313
5423
  }
5424
+ function looksLikeScriptPath(tok) {
5425
+ const t = tok.trim().replace(/^['"]|['"]$/g, "");
5426
+ if (!t) return false;
5427
+ const low = t.toLowerCase();
5428
+ if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
5429
+ if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
5430
+ if (t.includes("/") && !t.startsWith("-")) return true;
5431
+ return false;
5432
+ }
5314
5433
  function extractInlineScriptPath(actionDetail) {
5434
+ const code = extractInlineCode(actionDetail);
5435
+ if (code) {
5436
+ const candidates = [];
5437
+ const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
5438
+ let m;
5439
+ while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
5440
+ const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
5441
+ while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
5442
+ const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5443
+ while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5444
+ for (const cand of candidates) {
5445
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
5446
+ for (const tok of tokens) {
5447
+ if (looksLikeScriptPath(tok)) {
5448
+ try {
5449
+ const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5450
+ if (existsSync9(resolved)) {
5451
+ const stat = statSync2(resolved);
5452
+ if (stat.isFile()) return resolved;
5453
+ }
5454
+ } catch {
5455
+ }
5456
+ }
5457
+ }
5458
+ }
5459
+ const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5460
+ while ((m = openRe.exec(code)) !== null) {
5461
+ const p = m[1];
5462
+ try {
5463
+ const resolved = resolvePath2(p);
5464
+ if (existsSync9(resolved)) {
5465
+ const stat = statSync2(resolved);
5466
+ if (stat.isFile()) return resolved;
5467
+ }
5468
+ } catch {
5469
+ }
5470
+ }
5471
+ return null;
5472
+ }
5315
5473
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5316
- let m;
5317
- while ((m = re.exec(actionDetail)) !== null) {
5318
- const p = m[1];
5474
+ let m2;
5475
+ while ((m2 = re.exec(actionDetail)) !== null) {
5476
+ const p = m2[1];
5319
5477
  let resolved;
5320
5478
  try {
5321
5479
  resolved = resolvePath2(p);
@@ -5700,7 +5858,7 @@ async function analyzeWithLLM(prompt, config) {
5700
5858
  riskType: riskType || "",
5701
5859
  confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
5702
5860
  source: "llm",
5703
- violatedLayers: ["3"]
5861
+ violatedLayers: ["L3.1"]
5704
5862
  };
5705
5863
  } catch (err) {
5706
5864
  if (abortController.signal.aborted) {
@@ -6261,7 +6419,139 @@ init_esm_shims();
6261
6419
  // src/engine/formal-engine/builder.ts
6262
6420
  init_esm_shims();
6263
6421
  init_rules_loader();
6264
- init_decode();
6422
+
6423
+ // src/engine/formal-engine/decode.ts
6424
+ init_esm_shims();
6425
+ var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6426
+ function isValidBase64Token(tok) {
6427
+ if (tok.length < 8) return false;
6428
+ if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
6429
+ return true;
6430
+ }
6431
+ function isPrintable(s) {
6432
+ for (let i = 0; i < s.length; i++) {
6433
+ const c = s.charCodeAt(i);
6434
+ if (c < 32 || c > 126) return false;
6435
+ }
6436
+ return true;
6437
+ }
6438
+ function decodeBase64Literals(text) {
6439
+ const out = [];
6440
+ const seen = /* @__PURE__ */ new Set();
6441
+ for (const m of text.matchAll(B64_TOKEN_RE)) {
6442
+ const tok = m[0];
6443
+ if (!isValidBase64Token(tok)) continue;
6444
+ if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
6445
+ try {
6446
+ const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
6447
+ const bin = Buffer.from(padded, "base64");
6448
+ const s = bin.toString("utf8");
6449
+ if (s && isPrintable(s) && !seen.has(s)) {
6450
+ seen.add(s);
6451
+ out.push(s);
6452
+ }
6453
+ } catch {
6454
+ }
6455
+ }
6456
+ return out;
6457
+ }
6458
+ var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
6459
+ var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
6460
+ function stripStringLiterals(text) {
6461
+ const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
6462
+ if (!m) return text;
6463
+ const cmd = m[1].split("/").pop().toLowerCase();
6464
+ if (!OUTPUT_CMDS.has(cmd)) return text;
6465
+ return text.replace(QUOTED_STR_RE, "");
6466
+ }
6467
+ function decodeUrlAndNumeric(text) {
6468
+ const out = [];
6469
+ let s = text;
6470
+ for (let i = 0; i < 3; i++) {
6471
+ let nxt = s;
6472
+ try {
6473
+ nxt = decodeURIComponent(s);
6474
+ } catch {
6475
+ break;
6476
+ }
6477
+ if (nxt === s) break;
6478
+ s = nxt;
6479
+ }
6480
+ if (s !== text) out.push(s);
6481
+ if (/\\[0-7]{3}/.test(text)) {
6482
+ let expanded = "";
6483
+ try {
6484
+ expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
6485
+ } catch {
6486
+ }
6487
+ if (expanded && expanded !== text) out.push(expanded);
6488
+ }
6489
+ if (/\\x[0-9a-fA-F]{2}/.test(text)) {
6490
+ let expanded = "";
6491
+ try {
6492
+ expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
6493
+ } catch {
6494
+ }
6495
+ if (expanded && expanded !== text) out.push(expanded);
6496
+ }
6497
+ return out;
6498
+ }
6499
+ function allCandidates(detail, max = 8) {
6500
+ const shell = stripStringLiterals(detail);
6501
+ const cands = [shell];
6502
+ cands.push(...decodeBase64Literals(shell));
6503
+ cands.push(...decodeUrlAndNumeric(shell));
6504
+ cands.push(...decodeBase64Literals(detail));
6505
+ cands.push(...decodeUrlAndNumeric(detail));
6506
+ const seen = /* @__PURE__ */ new Set();
6507
+ const out = [];
6508
+ for (const c of cands) {
6509
+ if (seen.has(c) || c.length > 4096) continue;
6510
+ seen.add(c);
6511
+ out.push(c);
6512
+ if (out.length >= max) break;
6513
+ }
6514
+ return out;
6515
+ }
6516
+ var STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
6517
+ var PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
6518
+ var LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
6519
+ var LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
6520
+ function matchStageAny(detail, stages) {
6521
+ const cands = allCandidates(detail);
6522
+ if (cands.length === 0) return "";
6523
+ const shell = cands[0];
6524
+ const hits = [];
6525
+ for (const [name, pats] of Object.entries(stages)) {
6526
+ for (const p of pats) {
6527
+ p.lastIndex = 0;
6528
+ if (p.test(shell)) {
6529
+ hits.push(name);
6530
+ break;
6531
+ }
6532
+ }
6533
+ }
6534
+ if (hits.length > 0) {
6535
+ if (hits.length === 1) return hits[0];
6536
+ if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
6537
+ if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
6538
+ return "LISTEN";
6539
+ }
6540
+ for (const s of STAGE_PRIORITY) {
6541
+ if (hits.includes(s)) return s;
6542
+ }
6543
+ if (hits.includes("EXEC")) return "EXEC";
6544
+ }
6545
+ for (const cand of cands.slice(1)) {
6546
+ for (const [name, pats] of Object.entries(stages)) {
6547
+ for (const p of pats) {
6548
+ p.lastIndex = 0;
6549
+ if (p.test(cand)) return name;
6550
+ }
6551
+ }
6552
+ }
6553
+ return "";
6554
+ }
6265
6555
 
6266
6556
  // src/engine/formal-engine/ltl.ts
6267
6557
  init_esm_shims();
@@ -6295,11 +6585,7 @@ function compileAps(apPatterns) {
6295
6585
  }
6296
6586
  function labelEvent(actionDetail) {
6297
6587
  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
- }
6588
+ const candidates = allCandidates(actionDetail);
6303
6589
  const hits = /* @__PURE__ */ new Set();
6304
6590
  for (const [name, pats] of Object.entries(AP_RE || {})) {
6305
6591
  for (const c of candidates) {
@@ -6537,7 +6823,7 @@ function loadCrossSessionChains() {
6537
6823
  return CROSS_SESSION_CHAINS;
6538
6824
  }
6539
6825
  var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6540
- var EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
6826
+ var EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
6541
6827
  function checkCrossSessionChain(graph) {
6542
6828
  const violations = [];
6543
6829
  const nodes = allNodes(graph);
@@ -6626,7 +6912,7 @@ function checkSensitiveDataFlow(graph) {
6626
6912
  const n = nodes[i];
6627
6913
  if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6628
6914
  violations.push({
6629
- propertyId: "sensitive-data-exfil",
6915
+ propertyId: "sensitive-data-flow",
6630
6916
  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`,
6631
6917
  riskLevel: "critical",
6632
6918
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
@@ -6638,9 +6924,67 @@ function checkSensitiveDataFlow(graph) {
6638
6924
  }
6639
6925
  var EBPF_NET_RE = /connect|sendto|network_connect/i;
6640
6926
  var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
6641
- var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6642
6927
  var EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
6643
6928
  var CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
6929
+ var INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
6930
+ function extractInlineShell(s) {
6931
+ const out = [];
6932
+ let m;
6933
+ INLINE_SHELL_RE.lastIndex = 0;
6934
+ while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
6935
+ let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
6936
+ if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
6937
+ g = g.replace(/['"\[\],]+/g, " ");
6938
+ }
6939
+ g = g.trim();
6940
+ if (g) out.push(g);
6941
+ }
6942
+ return out;
6943
+ }
6944
+ var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
6945
+ var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
6946
+ function containsPrivesc(shell) {
6947
+ if (!shell) return false;
6948
+ for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
6949
+ const s = seg.trim();
6950
+ if (!s) continue;
6951
+ const toks = s.split(/\s+/);
6952
+ const head = (toks[0] || "").toLowerCase();
6953
+ const rest = toks.slice(1).join(" ");
6954
+ if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
6955
+ if (head === "setcap" && /cap_/.test(rest)) return true;
6956
+ if (head === "pkexec" || head === "doas") return true;
6957
+ if (head === "sudo") {
6958
+ if (/^-u\s+/.test(rest)) {
6959
+ if (/^-u\s+root\b/.test(rest)) return true;
6960
+ continue;
6961
+ }
6962
+ const m = /^(\S+)(.*)$/.exec(rest);
6963
+ if (m) {
6964
+ const nxt = m[1].toLowerCase();
6965
+ const tail = m[2];
6966
+ if (PKG_MGR.has(nxt)) continue;
6967
+ if (SHELLS.has(nxt)) return true;
6968
+ if (nxt === "-i" || nxt === "-s") return true;
6969
+ if (nxt.startsWith("su")) return true;
6970
+ if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
6971
+ }
6972
+ if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
6973
+ if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
6974
+ if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
6975
+ if (/\bvisudo\b/.test(rest)) return true;
6976
+ if (/\bpasswd\s+root\b/.test(rest)) return true;
6977
+ if (/\buseradd\b/.test(rest)) return true;
6978
+ }
6979
+ if (head === "su") {
6980
+ if (!rest) return true;
6981
+ if (/^-l?\s*$/.test(rest)) return true;
6982
+ if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
6983
+ }
6984
+ }
6985
+ if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
6986
+ return false;
6987
+ }
6644
6988
  function isExfilNode(n) {
6645
6989
  if (n.stage === "EXFIL") return true;
6646
6990
  if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
@@ -6655,8 +6999,12 @@ function isCredNode(n) {
6655
6999
  return CRED_STRICT_RE.test(n.actionDetail);
6656
7000
  }
6657
7001
  function isPrivescNode(n) {
6658
- if (n.stage === "PRIVESC") return true;
6659
- return PRIVESC_RE.test(n.actionDetail);
7002
+ const shell = stripStringLiterals(n.actionDetail);
7003
+ if (containsPrivesc(shell)) return true;
7004
+ for (const cmd of extractInlineShell(shell)) {
7005
+ if (containsPrivesc(cmd)) return true;
7006
+ }
7007
+ return false;
6660
7008
  }
6661
7009
  function checkNeverExfilAfterPrivesc(graph) {
6662
7010
  const violations = [];
@@ -6677,7 +7025,7 @@ function checkNeverExfilAfterPrivesc(graph) {
6677
7025
  if (pre) {
6678
7026
  const post = nodes[firstEv];
6679
7027
  return [{
6680
- propertyId: "no-privesc-then-exfil",
7028
+ propertyId: "no-exfil-after-privesc",
6681
7029
  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
7030
  riskLevel: "critical",
6683
7031
  path: [
@@ -6693,7 +7041,7 @@ function checkNeverExfilAfterPrivesc(graph) {
6693
7041
  for (let j = i + 1; j < nodes.length; j++) {
6694
7042
  if (isExfilNode(nodes[j])) {
6695
7043
  violations.push({
6696
- propertyId: "no-privesc-then-exfil",
7044
+ propertyId: "no-exfil-after-privesc",
6697
7045
  reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6698
7046
  riskLevel: "critical",
6699
7047
  path: [
@@ -6726,7 +7074,7 @@ function checkNeverExfilAfterCred(graph) {
6726
7074
  if (pre) {
6727
7075
  const post = nodes[firstEv];
6728
7076
  return [{
6729
- propertyId: "no-cred-then-exfil",
7077
+ propertyId: "no-exfil-after-credential",
6730
7078
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6731
7079
  riskLevel: "critical",
6732
7080
  path: [
@@ -6742,7 +7090,7 @@ function checkNeverExfilAfterCred(graph) {
6742
7090
  for (let j = i + 1; j < nodes.length; j++) {
6743
7091
  if (isExfilNode(nodes[j])) {
6744
7092
  violations.push({
6745
- propertyId: "no-cred-then-exfil",
7093
+ propertyId: "no-exfil-after-credential",
6746
7094
  reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6747
7095
  riskLevel: "critical",
6748
7096
  path: [
@@ -6812,7 +7160,7 @@ function checkLowIntegrityCommandExec(graph) {
6812
7160
  const n = nodes[i];
6813
7161
  if (lowAt.has(i) && isComputeNode(n)) {
6814
7162
  violations.push({
6815
- propertyId: "low-integrity-controls-command-exec",
7163
+ propertyId: "low-integrity-command-exec",
6816
7164
  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
7165
  riskLevel: "critical",
6818
7166
  path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
@@ -6859,7 +7207,7 @@ function checkCrossSessionReachability(graph) {
6859
7207
  const srcIdx = Math.min(...sources);
6860
7208
  const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
6861
7209
  violations.push({
6862
- propertyId: "cross-session-reachability",
7210
+ propertyId: "low-integrity-reachability",
6863
7211
  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
7212
  riskLevel: "high",
6865
7213
  path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
@@ -6911,17 +7259,17 @@ function checkSensitiveDataAggregation(graph) {
6911
7259
  return violations;
6912
7260
  }
6913
7261
  var ALL_PROPERTIES = [
6914
- { id: "cross-session-chain", check: checkCrossSessionChain },
6915
- { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6916
- { id: "cross-session-reachability", check: checkCrossSessionReachability },
6917
- { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6918
- { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred },
7262
+ { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
7263
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
7264
+ { id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
7265
+ { id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
7266
+ { id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
6919
7267
  // 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 }
7268
+ { id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
7269
+ // P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
7270
+ { id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
7271
+ // P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
7272
+ { id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
6925
7273
  ];
6926
7274
 
6927
7275
  // src/engine/formal-engine/engine.ts
@@ -6984,7 +7332,6 @@ var FormalEngine = class {
6984
7332
  // src/engine/formal-engine/stateful.ts
6985
7333
  init_esm_shims();
6986
7334
  init_rules_loader();
6987
- init_decode();
6988
7335
 
6989
7336
  // src/engine/formal-engine/chain_state.ts
6990
7337
  init_esm_shims();
@@ -7123,7 +7470,7 @@ async function runAnalysis(input) {
7123
7470
  riskType: node.l1RiskType,
7124
7471
  confidence: 90,
7125
7472
  source: "heuristic_script_scan",
7126
- violatedLayers: ["1.1-script"]
7473
+ violatedLayers: ["L1.2"]
7127
7474
  };
7128
7475
  }
7129
7476
  }
@@ -7241,7 +7588,7 @@ async function runAnalysis(input) {
7241
7588
  riskType: inlineJudgment.riskType,
7242
7589
  confidence: 90,
7243
7590
  source: "logic_rule",
7244
- violatedLayers: ["1.2"]
7591
+ violatedLayers: ["L2.1"]
7245
7592
  };
7246
7593
  }
7247
7594
  return {
@@ -7274,7 +7621,7 @@ async function runAnalysis(input) {
7274
7621
  console.warn(
7275
7622
  `[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
7623
  );
7277
- observedFormalLayers.push("2.4");
7624
+ observedFormalLayers.push("L2.2");
7278
7625
  } else {
7279
7626
  return {
7280
7627
  allowed: false,
@@ -7283,7 +7630,7 @@ async function runAnalysis(input) {
7283
7630
  riskType: "formal_verification",
7284
7631
  confidence: 90,
7285
7632
  source: "formal_engine",
7286
- violatedLayers: ["2.4"]
7633
+ violatedLayers: ["L2.2"]
7287
7634
  };
7288
7635
  }
7289
7636
  }
@@ -7300,7 +7647,7 @@ async function runAnalysis(input) {
7300
7647
  riskType: node.l2RiskType,
7301
7648
  confidence: 85,
7302
7649
  source: "logic_rule_script_scan",
7303
- violatedLayers: ["1.2-script"]
7650
+ violatedLayers: ["L2.3"]
7304
7651
  };
7305
7652
  }
7306
7653
  }
@@ -7387,7 +7734,7 @@ async function runAnalysis(input) {
7387
7734
  riskType: "",
7388
7735
  confidence: 60,
7389
7736
  source: "llm_failure_fallback",
7390
- violatedLayers: ["3"]
7737
+ violatedLayers: ["L3.1"]
7391
7738
  };
7392
7739
  }
7393
7740
  if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
@@ -7417,4 +7764,14 @@ export {
7417
7764
  //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
7418
7765
  //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
7419
7766
  //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
7767
+ //!
7768
+ //! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
7769
+ //! P1 cross-session-chain 跨会话组合链
7770
+ //! P2 sensitive-data-flow 敏感数据传播
7771
+ //! P3 low-integrity-reachability 低完整性数据流可达
7772
+ //! P4 no-exfil-after-privesc 提权后禁外传
7773
+ //! P5 no-exfil-after-credential 凭据后禁外传
7774
+ //! P6 mc-ltl-rules LTL 模型检测
7775
+ //! P7 low-integrity-command-exec 低完整性命令执行
7776
+ //! P8 sensitive-data-aggregation 敏感数据聚合外传
7420
7777
  //# sourceMappingURL=coordinator.js.map