@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 +482 -122
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +1 -1
- package/dist/engine/coordinator.js +426 -180
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +28 -6
- package/dist/rules/l2_rules.json +88 -60
- package/dist/rules/mc_rules.json +258 -26
- package/dist/server.js +571 -220
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +6 -6
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -632,7 +632,7 @@ function loadMC() {
|
|
|
632
632
|
try {
|
|
633
633
|
const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
634
634
|
if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
|
|
635
|
-
const rtRules = rt.loadRuntimeConfig()?.
|
|
635
|
+
const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
|
|
636
636
|
const enabledById = {};
|
|
637
637
|
for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
|
|
638
638
|
for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
|
|
@@ -680,7 +680,8 @@ function _loadSensitivePathScope(scope) {
|
|
|
680
680
|
path: sp.path,
|
|
681
681
|
riskLevel: sp.risk_level,
|
|
682
682
|
desc: sp.desc,
|
|
683
|
-
credential: sp.deny_mode === "deny_both" ? true : void 0
|
|
683
|
+
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
684
|
+
dirDelOnly: sp.dir_del_only === true ? true : void 0
|
|
684
685
|
}));
|
|
685
686
|
}
|
|
686
687
|
function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
|
|
@@ -778,7 +779,7 @@ function runLogicCheck(input) {
|
|
|
778
779
|
riskType: "intent_deviation",
|
|
779
780
|
confidence: 85,
|
|
780
781
|
source: "logic_rule",
|
|
781
|
-
violatedLayers: ["
|
|
782
|
+
violatedLayers: ["L2.1"]
|
|
782
783
|
};
|
|
783
784
|
}
|
|
784
785
|
}
|
|
@@ -802,7 +803,7 @@ function runLogicCheck(input) {
|
|
|
802
803
|
riskType: "privilege_escalation",
|
|
803
804
|
confidence: 85,
|
|
804
805
|
source: "logic_rule",
|
|
805
|
-
violatedLayers: ["
|
|
806
|
+
violatedLayers: ["L2.1"]
|
|
806
807
|
};
|
|
807
808
|
}
|
|
808
809
|
}
|
|
@@ -854,7 +855,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
854
855
|
riskType: "combo_chain",
|
|
855
856
|
confidence: 85,
|
|
856
857
|
source: "logic_rule",
|
|
857
|
-
violatedLayers: ["
|
|
858
|
+
violatedLayers: ["L2.1"]
|
|
858
859
|
};
|
|
859
860
|
}
|
|
860
861
|
}
|
|
@@ -979,7 +980,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
979
980
|
riskType: "read_before_write",
|
|
980
981
|
confidence: 85,
|
|
981
982
|
source: "logic_rule",
|
|
982
|
-
violatedLayers: ["
|
|
983
|
+
violatedLayers: ["L2.1"]
|
|
983
984
|
};
|
|
984
985
|
}
|
|
985
986
|
return null;
|
|
@@ -999,7 +1000,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
999
1000
|
riskType: "consent_missing",
|
|
1000
1001
|
confidence: 85,
|
|
1001
1002
|
source: "logic_rule",
|
|
1002
|
-
violatedLayers: ["
|
|
1003
|
+
violatedLayers: ["L2.1"]
|
|
1003
1004
|
};
|
|
1004
1005
|
}
|
|
1005
1006
|
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
@@ -1017,7 +1018,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
1017
1018
|
riskType: "consent_missing",
|
|
1018
1019
|
confidence: 85,
|
|
1019
1020
|
source: "logic_rule",
|
|
1020
|
-
violatedLayers: ["
|
|
1021
|
+
violatedLayers: ["L2.1"]
|
|
1021
1022
|
};
|
|
1022
1023
|
}
|
|
1023
1024
|
function containsPrivilegeEscalation(actionDetail) {
|
|
@@ -1077,7 +1078,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1077
1078
|
riskType: "file_access",
|
|
1078
1079
|
confidence: 95,
|
|
1079
1080
|
source: "logic_rule",
|
|
1080
|
-
violatedLayers: ["
|
|
1081
|
+
violatedLayers: ["L2.1"],
|
|
1081
1082
|
isCredential: true
|
|
1082
1083
|
};
|
|
1083
1084
|
}
|
|
@@ -1093,7 +1094,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1093
1094
|
riskType: "file_access",
|
|
1094
1095
|
confidence: 95,
|
|
1095
1096
|
source: "logic_rule",
|
|
1096
|
-
violatedLayers: ["
|
|
1097
|
+
violatedLayers: ["L2.1"]
|
|
1097
1098
|
};
|
|
1098
1099
|
}
|
|
1099
1100
|
}
|
|
@@ -1107,7 +1108,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1107
1108
|
riskType: "file_access",
|
|
1108
1109
|
confidence: 70,
|
|
1109
1110
|
source: "logic_rule",
|
|
1110
|
-
violatedLayers: ["
|
|
1111
|
+
violatedLayers: ["L2.1"]
|
|
1111
1112
|
};
|
|
1112
1113
|
}
|
|
1113
1114
|
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;
|
|
@@ -1119,7 +1120,7 @@ var init_logic_rules = __esm({
|
|
|
1119
1120
|
init_rules_loader();
|
|
1120
1121
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1121
1122
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1122
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
|
|
1123
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
|
|
1123
1124
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1124
1125
|
"ask_user_question",
|
|
1125
1126
|
"glob",
|
|
@@ -1470,7 +1471,7 @@ function emptyRuntimeConfig() {
|
|
|
1470
1471
|
L1_rules: {},
|
|
1471
1472
|
L2_rules: {},
|
|
1472
1473
|
L3_skills: {},
|
|
1473
|
-
|
|
1474
|
+
L2_2: {},
|
|
1474
1475
|
agent_rules: {},
|
|
1475
1476
|
scenario_rules: {}
|
|
1476
1477
|
};
|
|
@@ -1498,7 +1499,7 @@ function loadRuntimeConfig() {
|
|
|
1498
1499
|
L1_rules: merged.L1_rules || {},
|
|
1499
1500
|
L2_rules: merged.L2_rules || {},
|
|
1500
1501
|
L3_skills: merged.L3_skills || {},
|
|
1501
|
-
|
|
1502
|
+
L2_2: merged.L2_2 || {},
|
|
1502
1503
|
agent_rules: merged.agent_rules || {},
|
|
1503
1504
|
scenario_rules: merged.scenario_rules || {}
|
|
1504
1505
|
};
|
|
@@ -1551,6 +1552,7 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1551
1552
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1552
1553
|
if (r.credential) out.credential = true;
|
|
1553
1554
|
if (r.read_only) out.read_only = true;
|
|
1555
|
+
if (r.dir_del_only) out.dir_del_only = true;
|
|
1554
1556
|
return out;
|
|
1555
1557
|
});
|
|
1556
1558
|
}
|
|
@@ -1659,6 +1661,7 @@ function _l2SensitivePathsToRules() {
|
|
|
1659
1661
|
builtin: true
|
|
1660
1662
|
};
|
|
1661
1663
|
_syncDenyModeFields(rule);
|
|
1664
|
+
if (sp.dirDelOnly) rule.dir_del_only = true;
|
|
1662
1665
|
return rule;
|
|
1663
1666
|
});
|
|
1664
1667
|
}
|
|
@@ -1875,7 +1878,7 @@ function generateSourceDefaults() {
|
|
|
1875
1878
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1876
1879
|
},
|
|
1877
1880
|
L3_skills: _l3SkillsToRules(),
|
|
1878
|
-
|
|
1881
|
+
L2_2: _ltlRulesToRuntime(),
|
|
1879
1882
|
agent_rules: _agentRulesToDefaults(),
|
|
1880
1883
|
scenario_rules: _scenarioRulesToDefaults()
|
|
1881
1884
|
};
|
|
@@ -2063,7 +2066,11 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
2063
2066
|
);
|
|
2064
2067
|
_mergeRuleCategories(cfg, srcDefaults, "L1_rules");
|
|
2065
2068
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
2066
|
-
|
|
2069
|
+
if (!cfg.L2_2 && cfg.L2_4) {
|
|
2070
|
+
cfg.L2_2 = cfg.L2_4;
|
|
2071
|
+
delete cfg.L2_4;
|
|
2072
|
+
}
|
|
2073
|
+
_mergeRuleCategories(cfg, srcDefaults, "L2_2");
|
|
2067
2074
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
2068
2075
|
_mergeAgentRules(cfg, srcDefaults);
|
|
2069
2076
|
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
@@ -2683,7 +2690,7 @@ function runHeuristicCheck(input) {
|
|
|
2683
2690
|
riskType: "sensitive_action",
|
|
2684
2691
|
confidence: 90,
|
|
2685
2692
|
source: "heuristic",
|
|
2686
|
-
violatedLayers: ["
|
|
2693
|
+
violatedLayers: ["L1.1"]
|
|
2687
2694
|
};
|
|
2688
2695
|
}
|
|
2689
2696
|
}
|
|
@@ -2693,8 +2700,10 @@ function runHeuristicCheck(input) {
|
|
|
2693
2700
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2694
2701
|
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
2695
2702
|
let topRiskType = "";
|
|
2703
|
+
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2696
2704
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
2697
2705
|
if (rule.pattern.test(scanCommand)) {
|
|
2706
|
+
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2698
2707
|
matched.push(rule.description);
|
|
2699
2708
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
2700
2709
|
highestSeverity = rule.severity;
|
|
@@ -2736,10 +2745,26 @@ function runHeuristicCheck(input) {
|
|
|
2736
2745
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2737
2746
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2738
2747
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2748
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2749
|
+
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2750
|
+
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2751
|
+
if (mergeResults.length > 0) {
|
|
2752
|
+
let best = mergeResults[0];
|
|
2753
|
+
for (const r of mergeResults) {
|
|
2754
|
+
if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
|
|
2755
|
+
}
|
|
2756
|
+
if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
|
|
2757
|
+
return {
|
|
2758
|
+
allowed: false,
|
|
2759
|
+
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
2760
|
+
riskLevel: highestSeverity,
|
|
2761
|
+
riskType: highestRiskType,
|
|
2762
|
+
confidence: 95,
|
|
2763
|
+
source: "heuristic",
|
|
2764
|
+
violatedLayers: ["L1.1"]
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
return best;
|
|
2743
2768
|
}
|
|
2744
2769
|
if (matched.length > 0) {
|
|
2745
2770
|
return {
|
|
@@ -2749,8 +2774,7 @@ function runHeuristicCheck(input) {
|
|
|
2749
2774
|
riskType: highestRiskType,
|
|
2750
2775
|
confidence: 95,
|
|
2751
2776
|
source: "heuristic",
|
|
2752
|
-
violatedLayers: ["
|
|
2753
|
-
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2777
|
+
violatedLayers: ["L1.1"]
|
|
2754
2778
|
};
|
|
2755
2779
|
}
|
|
2756
2780
|
const lateralResult = checkLateralMovement(detailL);
|
|
@@ -2878,6 +2902,7 @@ function isSearchActionType(actionType) {
|
|
|
2878
2902
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2879
2903
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2880
2904
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2905
|
+
const keygenGenerate = isSshKeygenGenerate(command);
|
|
2881
2906
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2882
2907
|
const pathMatches = (text, path6) => {
|
|
2883
2908
|
const parts = path6.split(/(\(\?!.*?\))/);
|
|
@@ -2890,6 +2915,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2890
2915
|
};
|
|
2891
2916
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2892
2917
|
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2918
|
+
if (keygenGenerate) continue;
|
|
2893
2919
|
if (sp.path === "/dev/random") {
|
|
2894
2920
|
if (isWriteOpFlag) {
|
|
2895
2921
|
continue;
|
|
@@ -2901,7 +2927,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2901
2927
|
riskType: "file_access",
|
|
2902
2928
|
confidence: 90,
|
|
2903
2929
|
source: "heuristic",
|
|
2904
|
-
violatedLayers: ["
|
|
2930
|
+
violatedLayers: ["L1.1"]
|
|
2905
2931
|
};
|
|
2906
2932
|
}
|
|
2907
2933
|
if (!isWriteOpFlag && !sp.credential) {
|
|
@@ -2914,16 +2940,51 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2914
2940
|
riskType: "file_access",
|
|
2915
2941
|
confidence: 90,
|
|
2916
2942
|
source: "heuristic",
|
|
2917
|
-
violatedLayers: ["
|
|
2943
|
+
violatedLayers: ["L1.1"],
|
|
2918
2944
|
isCredential: !!sp.credential
|
|
2919
2945
|
};
|
|
2920
2946
|
}
|
|
2921
2947
|
}
|
|
2922
2948
|
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
2923
2949
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2950
|
+
let pPath = sp.path;
|
|
2951
|
+
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
2952
|
+
let dirPrefixMatch = null;
|
|
2953
|
+
if (sp.dirDelOnly) {
|
|
2954
|
+
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
2955
|
+
dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
|
|
2956
|
+
}
|
|
2957
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
|
|
2958
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
2959
|
+
if (pathReMatch || expandedReMatch) {
|
|
2960
|
+
if (keygenGenerate) continue;
|
|
2961
|
+
if (sp.dirDelOnly) {
|
|
2962
|
+
if (isDeleteActionForDir(command, sp.path)) {
|
|
2963
|
+
return {
|
|
2964
|
+
allowed: false,
|
|
2965
|
+
reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2966
|
+
riskLevel: sp.riskLevel,
|
|
2967
|
+
riskType: "file_access",
|
|
2968
|
+
confidence: 90,
|
|
2969
|
+
source: "heuristic",
|
|
2970
|
+
violatedLayers: ["L1.1"],
|
|
2971
|
+
isCredential: !!sp.credential
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
if (isArchiveReadOfDir(command, sp.path)) {
|
|
2975
|
+
return {
|
|
2976
|
+
allowed: false,
|
|
2977
|
+
reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2978
|
+
riskLevel: sp.riskLevel,
|
|
2979
|
+
riskType: "file_access",
|
|
2980
|
+
confidence: 90,
|
|
2981
|
+
source: "heuristic",
|
|
2982
|
+
violatedLayers: ["L1.1"],
|
|
2983
|
+
isCredential: !!sp.credential
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2927
2988
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2928
2989
|
continue;
|
|
2929
2990
|
}
|
|
@@ -2934,7 +2995,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2934
2995
|
riskType: "file_access",
|
|
2935
2996
|
confidence: 90,
|
|
2936
2997
|
source: "heuristic",
|
|
2937
|
-
violatedLayers: ["
|
|
2998
|
+
violatedLayers: ["L1.1"],
|
|
2938
2999
|
isCredential: !!sp.credential
|
|
2939
3000
|
};
|
|
2940
3001
|
}
|
|
@@ -2957,7 +3018,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2957
3018
|
riskType: "file_access",
|
|
2958
3019
|
confidence: 90,
|
|
2959
3020
|
source: "heuristic",
|
|
2960
|
-
violatedLayers: ["
|
|
3021
|
+
violatedLayers: ["L1.1"],
|
|
2961
3022
|
isCredential: !!sp.credential
|
|
2962
3023
|
};
|
|
2963
3024
|
}
|
|
@@ -2974,7 +3035,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2974
3035
|
riskType: "file_access",
|
|
2975
3036
|
confidence: 90,
|
|
2976
3037
|
source: "heuristic",
|
|
2977
|
-
violatedLayers: ["
|
|
3038
|
+
violatedLayers: ["L1.1"],
|
|
2978
3039
|
isCredential: true
|
|
2979
3040
|
};
|
|
2980
3041
|
}
|
|
@@ -2987,18 +3048,142 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2987
3048
|
riskType: "file_access",
|
|
2988
3049
|
confidence: 90,
|
|
2989
3050
|
source: "heuristic",
|
|
2990
|
-
violatedLayers: ["
|
|
3051
|
+
violatedLayers: ["L1.1"]
|
|
2991
3052
|
};
|
|
2992
3053
|
}
|
|
2993
3054
|
return null;
|
|
2994
3055
|
}
|
|
3056
|
+
function isHomeValue(path6) {
|
|
3057
|
+
let p = path6.trim().toLowerCase();
|
|
3058
|
+
if (p.startsWith("$(") && p.endsWith(")")) {
|
|
3059
|
+
const inner = p.slice(2, -1).trim();
|
|
3060
|
+
return HOME_TARGET_RE.test(" " + inner);
|
|
3061
|
+
}
|
|
3062
|
+
if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
|
|
3063
|
+
return HOME_TARGET_RE.test(" " + p);
|
|
3064
|
+
}
|
|
3065
|
+
function isDeleteCommand(seg) {
|
|
3066
|
+
const s = seg.trimStart();
|
|
3067
|
+
if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
|
|
3068
|
+
return /删除|移除/.test(s);
|
|
3069
|
+
}
|
|
3070
|
+
function homeEscapeTarget(seg) {
|
|
3071
|
+
const low = seg.toLowerCase();
|
|
3072
|
+
const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
|
|
3073
|
+
let m;
|
|
3074
|
+
while ((m = re.exec(low)) !== null) {
|
|
3075
|
+
const endIdx = m.index + m[0].length;
|
|
3076
|
+
const tail = low.slice(endIdx);
|
|
3077
|
+
if (!tail) continue;
|
|
3078
|
+
let token = tail.trimStart().split(/\s+/)[0] || "";
|
|
3079
|
+
token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
|
|
3080
|
+
token = token.replace(/^['"]|['"]$/g, "");
|
|
3081
|
+
const segments = token.split("/").filter((s) => s && s !== ".");
|
|
3082
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
3083
|
+
let depth = 0;
|
|
3084
|
+
let minDepth = 0;
|
|
3085
|
+
for (const s of segments) {
|
|
3086
|
+
depth += s === ".." ? -1 : 1;
|
|
3087
|
+
minDepth = Math.min(minDepth, depth);
|
|
3088
|
+
}
|
|
3089
|
+
if (minDepth < 0) return true;
|
|
3090
|
+
if (depth <= 0) return true;
|
|
3091
|
+
}
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
function checkHomeDeletion(actionType, actionDetail) {
|
|
3095
|
+
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
|
|
3096
|
+
const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
3097
|
+
for (const seg of segs) {
|
|
3098
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3099
|
+
if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
|
|
3100
|
+
return {
|
|
3101
|
+
allowed: false,
|
|
3102
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
|
|
3103
|
+
riskLevel: "high",
|
|
3104
|
+
riskType: "script_execution",
|
|
3105
|
+
confidence: 90,
|
|
3106
|
+
source: "heuristic",
|
|
3107
|
+
violatedLayers: ["L1.1"]
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
const assigned = {};
|
|
3112
|
+
for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
|
|
3113
|
+
for (const [varName, path6] of Object.entries(assigned)) {
|
|
3114
|
+
if (!isHomeValue(path6)) continue;
|
|
3115
|
+
const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
|
|
3116
|
+
for (const seg of segs) {
|
|
3117
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3118
|
+
if (varRefRe.test(seg)) {
|
|
3119
|
+
return {
|
|
3120
|
+
allowed: false,
|
|
3121
|
+
reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
|
|
3122
|
+
riskLevel: "high",
|
|
3123
|
+
riskType: "script_execution",
|
|
3124
|
+
confidence: 90,
|
|
3125
|
+
source: "heuristic",
|
|
3126
|
+
violatedLayers: ["L1.1"]
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
function escapeRegExp(s) {
|
|
3134
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3135
|
+
}
|
|
3136
|
+
function isDeleteActionForDir(command, dirPath) {
|
|
3137
|
+
const low = command.toLowerCase();
|
|
3138
|
+
const dpath = dirPath.toLowerCase();
|
|
3139
|
+
if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
|
|
3140
|
+
return true;
|
|
3141
|
+
}
|
|
3142
|
+
if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
|
|
3143
|
+
return true;
|
|
3144
|
+
}
|
|
3145
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3146
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3147
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
function isArchiveReadOfDir(command, dirPath) {
|
|
3154
|
+
const low = command.toLowerCase();
|
|
3155
|
+
const dpath = dirPath.toLowerCase();
|
|
3156
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3157
|
+
const s = seg.trim();
|
|
3158
|
+
if (!s || !s.includes(dpath)) continue;
|
|
3159
|
+
const parts = s.split(/\s+/);
|
|
3160
|
+
const fw = parts[0] || "";
|
|
3161
|
+
if (fw === "zip") return true;
|
|
3162
|
+
if (fw === "tar") {
|
|
3163
|
+
if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
if (fw === "cp" || fw === "rsync") {
|
|
3167
|
+
const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
|
|
3168
|
+
if (segPaths.length && segPaths[0].includes(dpath)) return true;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
return false;
|
|
3172
|
+
}
|
|
3173
|
+
function isSshKeygenGenerate(command) {
|
|
3174
|
+
const low = command.toLowerCase();
|
|
3175
|
+
if (!low.includes("ssh-keygen")) return false;
|
|
3176
|
+
if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
|
|
3177
|
+
if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
|
|
3178
|
+
return true;
|
|
3179
|
+
}
|
|
2995
3180
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2996
3181
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2997
3182
|
const scan = stripHeredocContent(actionDetail);
|
|
2998
3183
|
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2999
3184
|
const isTempPath = TEMP_DIRS.some(
|
|
3000
3185
|
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
3001
|
-
);
|
|
3186
|
+
) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
|
|
3002
3187
|
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;
|
|
3003
3188
|
if (!isTempPath && wildcardDeleteRe.test(scan)) {
|
|
3004
3189
|
return {
|
|
@@ -3008,7 +3193,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3008
3193
|
riskType: "script_execution",
|
|
3009
3194
|
confidence: 90,
|
|
3010
3195
|
source: "heuristic",
|
|
3011
|
-
violatedLayers: ["
|
|
3196
|
+
violatedLayers: ["L1.1"]
|
|
3012
3197
|
};
|
|
3013
3198
|
}
|
|
3014
3199
|
const findWriteMatch = actionDetail.match(
|
|
@@ -3023,10 +3208,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3023
3208
|
riskType: "script_execution",
|
|
3024
3209
|
confidence: 90,
|
|
3025
3210
|
source: "heuristic",
|
|
3026
|
-
violatedLayers: ["
|
|
3211
|
+
violatedLayers: ["L1.1"]
|
|
3027
3212
|
};
|
|
3028
3213
|
}
|
|
3029
|
-
if (/>\s
|
|
3214
|
+
if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
|
|
3030
3215
|
return {
|
|
3031
3216
|
allowed: false,
|
|
3032
3217
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -3034,7 +3219,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3034
3219
|
riskType: "file_access",
|
|
3035
3220
|
confidence: 95,
|
|
3036
3221
|
source: "heuristic",
|
|
3037
|
-
violatedLayers: ["
|
|
3222
|
+
violatedLayers: ["L1.1"]
|
|
3038
3223
|
};
|
|
3039
3224
|
}
|
|
3040
3225
|
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
@@ -3045,7 +3230,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3045
3230
|
riskType: "file_access",
|
|
3046
3231
|
confidence: 90,
|
|
3047
3232
|
source: "heuristic",
|
|
3048
|
-
violatedLayers: ["
|
|
3233
|
+
violatedLayers: ["L1.1"]
|
|
3049
3234
|
};
|
|
3050
3235
|
}
|
|
3051
3236
|
return null;
|
|
@@ -3136,13 +3321,13 @@ function checkLateralMovement(actionDetail) {
|
|
|
3136
3321
|
riskType: "lateral_movement",
|
|
3137
3322
|
confidence: 80,
|
|
3138
3323
|
source: "heuristic",
|
|
3139
|
-
violatedLayers: ["
|
|
3324
|
+
violatedLayers: ["L1.1"]
|
|
3140
3325
|
};
|
|
3141
3326
|
}
|
|
3142
3327
|
}
|
|
3143
3328
|
return null;
|
|
3144
3329
|
}
|
|
3145
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
|
|
3330
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3146
3331
|
var init_heuristic = __esm({
|
|
3147
3332
|
"src/engine/heuristic.ts"() {
|
|
3148
3333
|
"use strict";
|
|
@@ -3172,6 +3357,8 @@ var init_heuristic = __esm({
|
|
|
3172
3357
|
})();
|
|
3173
3358
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3174
3359
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3360
|
+
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
3361
|
+
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;
|
|
3175
3362
|
}
|
|
3176
3363
|
});
|
|
3177
3364
|
|
|
@@ -3350,112 +3537,6 @@ var init_token_stats = __esm({
|
|
|
3350
3537
|
}
|
|
3351
3538
|
});
|
|
3352
3539
|
|
|
3353
|
-
// src/engine/formal-engine/decode.ts
|
|
3354
|
-
var decode_exports = {};
|
|
3355
|
-
__export(decode_exports, {
|
|
3356
|
-
allCandidates: () => allCandidates,
|
|
3357
|
-
decodeBase64Literals: () => decodeBase64Literals,
|
|
3358
|
-
decodeUrlAndNumeric: () => decodeUrlAndNumeric,
|
|
3359
|
-
matchStageAny: () => matchStageAny
|
|
3360
|
-
});
|
|
3361
|
-
function isValidBase64Token(tok) {
|
|
3362
|
-
if (tok.length < 8) return false;
|
|
3363
|
-
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
3364
|
-
return true;
|
|
3365
|
-
}
|
|
3366
|
-
function isPrintable(s) {
|
|
3367
|
-
for (let i = 0; i < s.length; i++) {
|
|
3368
|
-
const c = s.charCodeAt(i);
|
|
3369
|
-
if (c < 32 || c > 126) return false;
|
|
3370
|
-
}
|
|
3371
|
-
return true;
|
|
3372
|
-
}
|
|
3373
|
-
function decodeBase64Literals(text) {
|
|
3374
|
-
const out = [];
|
|
3375
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3376
|
-
for (const m of text.matchAll(B64_TOKEN_RE)) {
|
|
3377
|
-
const tok = m[0];
|
|
3378
|
-
if (!isValidBase64Token(tok)) continue;
|
|
3379
|
-
if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
|
|
3380
|
-
try {
|
|
3381
|
-
const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
|
|
3382
|
-
const bin = Buffer.from(padded, "base64");
|
|
3383
|
-
const s = bin.toString("utf8");
|
|
3384
|
-
if (s && isPrintable(s) && !seen.has(s)) {
|
|
3385
|
-
seen.add(s);
|
|
3386
|
-
out.push(s);
|
|
3387
|
-
}
|
|
3388
|
-
} catch {
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
return out;
|
|
3392
|
-
}
|
|
3393
|
-
function decodeUrlAndNumeric(text) {
|
|
3394
|
-
const out = [];
|
|
3395
|
-
let s = text;
|
|
3396
|
-
for (let i = 0; i < 3; i++) {
|
|
3397
|
-
let nxt = s;
|
|
3398
|
-
try {
|
|
3399
|
-
nxt = decodeURIComponent(s);
|
|
3400
|
-
} catch {
|
|
3401
|
-
break;
|
|
3402
|
-
}
|
|
3403
|
-
if (nxt === s) break;
|
|
3404
|
-
s = nxt;
|
|
3405
|
-
}
|
|
3406
|
-
if (s !== text) out.push(s);
|
|
3407
|
-
if (/\\[0-7]{3}/.test(text)) {
|
|
3408
|
-
let expanded = "";
|
|
3409
|
-
try {
|
|
3410
|
-
expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
|
|
3411
|
-
} catch {
|
|
3412
|
-
}
|
|
3413
|
-
if (expanded && expanded !== text) out.push(expanded);
|
|
3414
|
-
}
|
|
3415
|
-
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
3416
|
-
let expanded = "";
|
|
3417
|
-
try {
|
|
3418
|
-
expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
|
|
3419
|
-
} catch {
|
|
3420
|
-
}
|
|
3421
|
-
if (expanded && expanded !== text) out.push(expanded);
|
|
3422
|
-
}
|
|
3423
|
-
return out;
|
|
3424
|
-
}
|
|
3425
|
-
function allCandidates(detail, max = 8) {
|
|
3426
|
-
const cands = [detail];
|
|
3427
|
-
cands.push(...decodeBase64Literals(detail));
|
|
3428
|
-
cands.push(...decodeUrlAndNumeric(detail));
|
|
3429
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3430
|
-
const out = [];
|
|
3431
|
-
for (const c of cands) {
|
|
3432
|
-
if (seen.has(c) || c.length > 4096) continue;
|
|
3433
|
-
seen.add(c);
|
|
3434
|
-
out.push(c);
|
|
3435
|
-
if (out.length >= max) break;
|
|
3436
|
-
}
|
|
3437
|
-
return out;
|
|
3438
|
-
}
|
|
3439
|
-
function matchStageAny(detail, stages) {
|
|
3440
|
-
for (const cand of allCandidates(detail)) {
|
|
3441
|
-
for (const [name, pats] of Object.entries(stages)) {
|
|
3442
|
-
for (const p of pats) {
|
|
3443
|
-
p.lastIndex = 0;
|
|
3444
|
-
if (p.test(cand)) return name;
|
|
3445
|
-
}
|
|
3446
|
-
}
|
|
3447
|
-
}
|
|
3448
|
-
return "";
|
|
3449
|
-
}
|
|
3450
|
-
var B64_TOKEN_RE;
|
|
3451
|
-
var init_decode = __esm({
|
|
3452
|
-
"src/engine/formal-engine/decode.ts"() {
|
|
3453
|
-
"use strict";
|
|
3454
|
-
init_esm_shims();
|
|
3455
|
-
B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
3456
|
-
}
|
|
3457
|
-
});
|
|
3458
|
-
|
|
3459
3540
|
// node_modules/better-sqlite3/lib/util.js
|
|
3460
3541
|
var require_util = __commonJS({
|
|
3461
3542
|
"node_modules/better-sqlite3/lib/util.js"(exports) {
|
|
@@ -4283,7 +4364,7 @@ var AnalyzeRequestSchema = z.object({
|
|
|
4283
4364
|
session_history: z.array(z.object({
|
|
4284
4365
|
session_id: z.string(),
|
|
4285
4366
|
action_history: z.array(ActionItemSchema).default([])
|
|
4286
|
-
})).default([]).describe("Multi-session historical actions (optional): for L2.
|
|
4367
|
+
})).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)."),
|
|
4287
4368
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
4288
4369
|
reason: z.string().default("").describe("Reason for executing this action"),
|
|
4289
4370
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
@@ -4300,7 +4381,9 @@ var AnalyzeResponseSchema = z.object({
|
|
|
4300
4381
|
policy: z.string().default(""),
|
|
4301
4382
|
violated_policy: z.string().default(""),
|
|
4302
4383
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
4303
|
-
analysis_duration_ms: z.number().default(0)
|
|
4384
|
+
analysis_duration_ms: z.number().default(0),
|
|
4385
|
+
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
4386
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
4304
4387
|
});
|
|
4305
4388
|
var HealthResponseSchema = z.object({
|
|
4306
4389
|
status: z.string().default("healthy"),
|
|
@@ -4563,6 +4646,7 @@ ${result.scriptContent}
|
|
|
4563
4646
|
init_esm_shims();
|
|
4564
4647
|
init_heuristic();
|
|
4565
4648
|
init_logic_rules();
|
|
4649
|
+
init_inline_analyzer();
|
|
4566
4650
|
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4567
4651
|
import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
4568
4652
|
import { homedir as homedir4 } from "os";
|
|
@@ -6177,6 +6261,13 @@ function extractScriptPath2(command) {
|
|
|
6177
6261
|
return null;
|
|
6178
6262
|
}
|
|
6179
6263
|
function extractScriptPathWithArgs(command) {
|
|
6264
|
+
const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
|
|
6265
|
+
if (inline) {
|
|
6266
|
+
const inner = inline[2];
|
|
6267
|
+
const realPath = extractScriptPath2(inner);
|
|
6268
|
+
if (realPath) return { path: realPath, args: [] };
|
|
6269
|
+
return { path: null, args: [] };
|
|
6270
|
+
}
|
|
6180
6271
|
const scriptPath = extractScriptPath2(command);
|
|
6181
6272
|
if (!scriptPath) return { path: null, args: [] };
|
|
6182
6273
|
const idx = command.indexOf(scriptPath);
|
|
@@ -6190,11 +6281,59 @@ function extractScriptPathWithArgs(command) {
|
|
|
6190
6281
|
}
|
|
6191
6282
|
return { path: scriptPath, args };
|
|
6192
6283
|
}
|
|
6284
|
+
function looksLikeScriptPath(tok) {
|
|
6285
|
+
const t = tok.trim().replace(/^['"]|['"]$/g, "");
|
|
6286
|
+
if (!t) return false;
|
|
6287
|
+
const low = t.toLowerCase();
|
|
6288
|
+
if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
|
|
6289
|
+
if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
|
|
6290
|
+
if (t.includes("/") && !t.startsWith("-")) return true;
|
|
6291
|
+
return false;
|
|
6292
|
+
}
|
|
6193
6293
|
function extractInlineScriptPath(actionDetail) {
|
|
6294
|
+
const code = extractInlineCode(actionDetail);
|
|
6295
|
+
if (code) {
|
|
6296
|
+
const candidates = [];
|
|
6297
|
+
const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
|
|
6298
|
+
let m;
|
|
6299
|
+
while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6300
|
+
const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
|
|
6301
|
+
while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
|
|
6302
|
+
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
6303
|
+
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6304
|
+
for (const cand of candidates) {
|
|
6305
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
|
|
6306
|
+
for (const tok of tokens) {
|
|
6307
|
+
if (looksLikeScriptPath(tok)) {
|
|
6308
|
+
try {
|
|
6309
|
+
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
6310
|
+
if (existsSync9(resolved)) {
|
|
6311
|
+
const stat = statSync2(resolved);
|
|
6312
|
+
if (stat.isFile()) return resolved;
|
|
6313
|
+
}
|
|
6314
|
+
} catch {
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
6320
|
+
while ((m = openRe.exec(code)) !== null) {
|
|
6321
|
+
const p = m[1];
|
|
6322
|
+
try {
|
|
6323
|
+
const resolved = resolvePath2(p);
|
|
6324
|
+
if (existsSync9(resolved)) {
|
|
6325
|
+
const stat = statSync2(resolved);
|
|
6326
|
+
if (stat.isFile()) return resolved;
|
|
6327
|
+
}
|
|
6328
|
+
} catch {
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
return null;
|
|
6332
|
+
}
|
|
6194
6333
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
6195
|
-
let
|
|
6196
|
-
while ((
|
|
6197
|
-
const p =
|
|
6334
|
+
let m2;
|
|
6335
|
+
while ((m2 = re.exec(actionDetail)) !== null) {
|
|
6336
|
+
const p = m2[1];
|
|
6198
6337
|
let resolved;
|
|
6199
6338
|
try {
|
|
6200
6339
|
resolved = resolvePath2(p);
|
|
@@ -6579,7 +6718,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
6579
6718
|
riskType: riskType || "",
|
|
6580
6719
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
6581
6720
|
source: "llm",
|
|
6582
|
-
violatedLayers: ["
|
|
6721
|
+
violatedLayers: ["L3.1"]
|
|
6583
6722
|
};
|
|
6584
6723
|
} catch (err) {
|
|
6585
6724
|
if (abortController.signal.aborted) {
|
|
@@ -7140,7 +7279,111 @@ init_esm_shims();
|
|
|
7140
7279
|
// src/engine/formal-engine/builder.ts
|
|
7141
7280
|
init_esm_shims();
|
|
7142
7281
|
init_rules_loader();
|
|
7143
|
-
|
|
7282
|
+
|
|
7283
|
+
// src/engine/formal-engine/decode.ts
|
|
7284
|
+
init_esm_shims();
|
|
7285
|
+
var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
7286
|
+
function isValidBase64Token(tok) {
|
|
7287
|
+
if (tok.length < 8) return false;
|
|
7288
|
+
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
7289
|
+
return true;
|
|
7290
|
+
}
|
|
7291
|
+
function isPrintable(s) {
|
|
7292
|
+
for (let i = 0; i < s.length; i++) {
|
|
7293
|
+
const c = s.charCodeAt(i);
|
|
7294
|
+
if (c < 32 || c > 126) return false;
|
|
7295
|
+
}
|
|
7296
|
+
return true;
|
|
7297
|
+
}
|
|
7298
|
+
function decodeBase64Literals(text) {
|
|
7299
|
+
const out = [];
|
|
7300
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7301
|
+
for (const m of text.matchAll(B64_TOKEN_RE)) {
|
|
7302
|
+
const tok = m[0];
|
|
7303
|
+
if (!isValidBase64Token(tok)) continue;
|
|
7304
|
+
if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
|
|
7305
|
+
try {
|
|
7306
|
+
const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
|
|
7307
|
+
const bin = Buffer.from(padded, "base64");
|
|
7308
|
+
const s = bin.toString("utf8");
|
|
7309
|
+
if (s && isPrintable(s) && !seen.has(s)) {
|
|
7310
|
+
seen.add(s);
|
|
7311
|
+
out.push(s);
|
|
7312
|
+
}
|
|
7313
|
+
} catch {
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
return out;
|
|
7317
|
+
}
|
|
7318
|
+
var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
|
|
7319
|
+
var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
|
|
7320
|
+
function stripStringLiterals(text) {
|
|
7321
|
+
const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
|
|
7322
|
+
if (!m) return text;
|
|
7323
|
+
const cmd = m[1].split("/").pop().toLowerCase();
|
|
7324
|
+
if (!OUTPUT_CMDS.has(cmd)) return text;
|
|
7325
|
+
return text.replace(QUOTED_STR_RE, "");
|
|
7326
|
+
}
|
|
7327
|
+
function decodeUrlAndNumeric(text) {
|
|
7328
|
+
const out = [];
|
|
7329
|
+
let s = text;
|
|
7330
|
+
for (let i = 0; i < 3; i++) {
|
|
7331
|
+
let nxt = s;
|
|
7332
|
+
try {
|
|
7333
|
+
nxt = decodeURIComponent(s);
|
|
7334
|
+
} catch {
|
|
7335
|
+
break;
|
|
7336
|
+
}
|
|
7337
|
+
if (nxt === s) break;
|
|
7338
|
+
s = nxt;
|
|
7339
|
+
}
|
|
7340
|
+
if (s !== text) out.push(s);
|
|
7341
|
+
if (/\\[0-7]{3}/.test(text)) {
|
|
7342
|
+
let expanded = "";
|
|
7343
|
+
try {
|
|
7344
|
+
expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
|
|
7345
|
+
} catch {
|
|
7346
|
+
}
|
|
7347
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
7348
|
+
}
|
|
7349
|
+
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
7350
|
+
let expanded = "";
|
|
7351
|
+
try {
|
|
7352
|
+
expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
|
|
7353
|
+
} catch {
|
|
7354
|
+
}
|
|
7355
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
7356
|
+
}
|
|
7357
|
+
return out;
|
|
7358
|
+
}
|
|
7359
|
+
function allCandidates(detail, max = 8) {
|
|
7360
|
+
const shell = stripStringLiterals(detail);
|
|
7361
|
+
const cands = [shell];
|
|
7362
|
+
cands.push(...decodeBase64Literals(shell));
|
|
7363
|
+
cands.push(...decodeUrlAndNumeric(shell));
|
|
7364
|
+
cands.push(...decodeBase64Literals(detail));
|
|
7365
|
+
cands.push(...decodeUrlAndNumeric(detail));
|
|
7366
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7367
|
+
const out = [];
|
|
7368
|
+
for (const c of cands) {
|
|
7369
|
+
if (seen.has(c) || c.length > 4096) continue;
|
|
7370
|
+
seen.add(c);
|
|
7371
|
+
out.push(c);
|
|
7372
|
+
if (out.length >= max) break;
|
|
7373
|
+
}
|
|
7374
|
+
return out;
|
|
7375
|
+
}
|
|
7376
|
+
function matchStageAny(detail, stages) {
|
|
7377
|
+
for (const cand of allCandidates(detail)) {
|
|
7378
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
7379
|
+
for (const p of pats) {
|
|
7380
|
+
p.lastIndex = 0;
|
|
7381
|
+
if (p.test(cand)) return name;
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
return "";
|
|
7386
|
+
}
|
|
7144
7387
|
|
|
7145
7388
|
// src/engine/formal-engine/ltl.ts
|
|
7146
7389
|
init_esm_shims();
|
|
@@ -7174,11 +7417,7 @@ function compileAps(apPatterns) {
|
|
|
7174
7417
|
}
|
|
7175
7418
|
function labelEvent(actionDetail) {
|
|
7176
7419
|
if (!AP_RE) compileAps(loadMC().ap_patterns || {});
|
|
7177
|
-
|
|
7178
|
-
try {
|
|
7179
|
-
candidates = (init_decode(), __toCommonJS(decode_exports)).allCandidates(actionDetail);
|
|
7180
|
-
} catch {
|
|
7181
|
-
}
|
|
7420
|
+
const candidates = allCandidates(actionDetail);
|
|
7182
7421
|
const hits = /* @__PURE__ */ new Set();
|
|
7183
7422
|
for (const [name, pats] of Object.entries(AP_RE || {})) {
|
|
7184
7423
|
for (const c of candidates) {
|
|
@@ -7505,7 +7744,7 @@ function checkSensitiveDataFlow(graph) {
|
|
|
7505
7744
|
const n = nodes[i];
|
|
7506
7745
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
7507
7746
|
violations.push({
|
|
7508
|
-
propertyId: "sensitive-data-
|
|
7747
|
+
propertyId: "sensitive-data-flow",
|
|
7509
7748
|
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`,
|
|
7510
7749
|
riskLevel: "critical",
|
|
7511
7750
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -7556,7 +7795,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
7556
7795
|
if (pre) {
|
|
7557
7796
|
const post = nodes[firstEv];
|
|
7558
7797
|
return [{
|
|
7559
|
-
propertyId: "no-
|
|
7798
|
+
propertyId: "no-exfil-after-privesc",
|
|
7560
7799
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
7561
7800
|
riskLevel: "critical",
|
|
7562
7801
|
path: [
|
|
@@ -7572,7 +7811,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
7572
7811
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7573
7812
|
if (isExfilNode(nodes[j])) {
|
|
7574
7813
|
violations.push({
|
|
7575
|
-
propertyId: "no-
|
|
7814
|
+
propertyId: "no-exfil-after-privesc",
|
|
7576
7815
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
7577
7816
|
riskLevel: "critical",
|
|
7578
7817
|
path: [
|
|
@@ -7605,7 +7844,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7605
7844
|
if (pre) {
|
|
7606
7845
|
const post = nodes[firstEv];
|
|
7607
7846
|
return [{
|
|
7608
|
-
propertyId: "no-
|
|
7847
|
+
propertyId: "no-exfil-after-credential",
|
|
7609
7848
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7610
7849
|
riskLevel: "critical",
|
|
7611
7850
|
path: [
|
|
@@ -7621,7 +7860,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7621
7860
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7622
7861
|
if (isExfilNode(nodes[j])) {
|
|
7623
7862
|
violations.push({
|
|
7624
|
-
propertyId: "no-
|
|
7863
|
+
propertyId: "no-exfil-after-credential",
|
|
7625
7864
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7626
7865
|
riskLevel: "critical",
|
|
7627
7866
|
path: [
|
|
@@ -7691,7 +7930,7 @@ function checkLowIntegrityCommandExec(graph) {
|
|
|
7691
7930
|
const n = nodes[i];
|
|
7692
7931
|
if (lowAt.has(i) && isComputeNode(n)) {
|
|
7693
7932
|
violations.push({
|
|
7694
|
-
propertyId: "low-integrity-
|
|
7933
|
+
propertyId: "low-integrity-command-exec",
|
|
7695
7934
|
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`,
|
|
7696
7935
|
riskLevel: "critical",
|
|
7697
7936
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -7738,7 +7977,7 @@ function checkCrossSessionReachability(graph) {
|
|
|
7738
7977
|
const srcIdx = Math.min(...sources);
|
|
7739
7978
|
const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
|
|
7740
7979
|
violations.push({
|
|
7741
|
-
propertyId: "
|
|
7980
|
+
propertyId: "low-integrity-reachability",
|
|
7742
7981
|
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`,
|
|
7743
7982
|
riskLevel: "high",
|
|
7744
7983
|
path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
|
|
@@ -7790,17 +8029,17 @@ function checkSensitiveDataAggregation(graph) {
|
|
|
7790
8029
|
return violations;
|
|
7791
8030
|
}
|
|
7792
8031
|
var ALL_PROPERTIES = [
|
|
7793
|
-
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
7794
|
-
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
7795
|
-
{ id: "
|
|
7796
|
-
{ id: "no-
|
|
7797
|
-
{ id: "no-
|
|
8032
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
|
|
8033
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
|
|
8034
|
+
{ id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
|
|
8035
|
+
{ id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
|
|
8036
|
+
{ id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
|
|
7798
8037
|
// L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
|
|
7799
|
-
{ id: "mc-ltl-rules", check: checkMCRules },
|
|
7800
|
-
//
|
|
7801
|
-
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec },
|
|
7802
|
-
//
|
|
7803
|
-
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation }
|
|
8038
|
+
{ id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
|
|
8039
|
+
// P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
|
|
8040
|
+
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
|
|
8041
|
+
// P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
|
|
8042
|
+
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
|
|
7804
8043
|
];
|
|
7805
8044
|
|
|
7806
8045
|
// src/engine/formal-engine/engine.ts
|
|
@@ -7863,11 +8102,41 @@ var FormalEngine = class {
|
|
|
7863
8102
|
// src/engine/formal-engine/stateful.ts
|
|
7864
8103
|
init_esm_shims();
|
|
7865
8104
|
init_rules_loader();
|
|
7866
|
-
init_decode();
|
|
7867
8105
|
var DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
|
|
7868
8106
|
var 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;
|
|
7869
8107
|
var CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
7870
|
-
var EXFIL_RE2 = /https
|
|
8108
|
+
var EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
|
|
8109
|
+
var PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
|
|
8110
|
+
var BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
|
|
8111
|
+
var SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
|
|
8112
|
+
var UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
|
|
8113
|
+
var PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
|
|
8114
|
+
function extractInstances(actionDetail, stage) {
|
|
8115
|
+
const out = [];
|
|
8116
|
+
if (PORT_STAGE_RE.test(stage)) {
|
|
8117
|
+
const d = actionDetail;
|
|
8118
|
+
let m;
|
|
8119
|
+
const re = new RegExp(PORT_RE.source, "gi");
|
|
8120
|
+
while ((m = re.exec(d)) !== null) {
|
|
8121
|
+
const p = m[1] || m[2] || m[3];
|
|
8122
|
+
if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
|
|
8123
|
+
}
|
|
8124
|
+
if (/SCAN/.test(stage)) {
|
|
8125
|
+
const bareRe = new RegExp(BARE_PORT_RE.source, "g");
|
|
8126
|
+
while ((m = bareRe.exec(d)) !== null) {
|
|
8127
|
+
if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
m = SOCK_RE.exec(d);
|
|
8131
|
+
if (m && m[1]) out.push(`sock:${m[1]}`);
|
|
8132
|
+
m = UNIT_RE.exec(d);
|
|
8133
|
+
if (m && m[1]) out.push(`unit:${m[1]}`);
|
|
8134
|
+
}
|
|
8135
|
+
return out;
|
|
8136
|
+
}
|
|
8137
|
+
function buckKey(chainId, inst) {
|
|
8138
|
+
return inst ? `${chainId}@@${inst}` : chainId;
|
|
8139
|
+
}
|
|
7871
8140
|
var STAGES_PATTERNS = null;
|
|
7872
8141
|
var CHAINS = null;
|
|
7873
8142
|
function loadRules() {
|
|
@@ -7910,7 +8179,11 @@ function lifecycleFor(detail, stage, isError) {
|
|
|
7910
8179
|
return "";
|
|
7911
8180
|
}
|
|
7912
8181
|
var MAX_NODES = 1e5;
|
|
7913
|
-
var StatefulChainTracker = class {
|
|
8182
|
+
var StatefulChainTracker = class _StatefulChainTracker {
|
|
8183
|
+
/** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
|
|
8184
|
+
static now() {
|
|
8185
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8186
|
+
}
|
|
7914
8187
|
agentId;
|
|
7915
8188
|
nodes_ = [];
|
|
7916
8189
|
ptr = {};
|
|
@@ -7932,10 +8205,14 @@ var StatefulChainTracker = class {
|
|
|
7932
8205
|
for (const chain of chains) {
|
|
7933
8206
|
const stages = chain.stages || [];
|
|
7934
8207
|
if (stages.length === 0) continue;
|
|
7935
|
-
const
|
|
8208
|
+
const cid = String(chain.id || "");
|
|
8209
|
+
let matched = this.ptr[cid] || 0;
|
|
8210
|
+
for (const [k, v] of Object.entries(this.ptr)) {
|
|
8211
|
+
if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
|
|
8212
|
+
}
|
|
7936
8213
|
if (matched > 0 && matched < stages.length) {
|
|
7937
8214
|
const pf = {
|
|
7938
|
-
chainId:
|
|
8215
|
+
chainId: cid,
|
|
7939
8216
|
stages,
|
|
7940
8217
|
matched,
|
|
7941
8218
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
@@ -8011,8 +8288,10 @@ var StatefulChainTracker = class {
|
|
|
8011
8288
|
actionDetail: ad,
|
|
8012
8289
|
stage,
|
|
8013
8290
|
resources: extractResources2(ad),
|
|
8291
|
+
instances: extractInstances(ad, stage),
|
|
8014
8292
|
isError,
|
|
8015
|
-
lifecycle: lifecycleFor(ad, stage, isError)
|
|
8293
|
+
lifecycle: lifecycleFor(ad, stage, isError),
|
|
8294
|
+
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
8016
8295
|
};
|
|
8017
8296
|
this.nodes_.push(node);
|
|
8018
8297
|
this.seqNext += 1;
|
|
@@ -8020,11 +8299,19 @@ var StatefulChainTracker = class {
|
|
|
8020
8299
|
}
|
|
8021
8300
|
recordLifecycle(node) {
|
|
8022
8301
|
if (node.lifecycle === "dead") {
|
|
8023
|
-
|
|
8302
|
+
const clearedInsts = [];
|
|
8303
|
+
for (const inst of node.instances) {
|
|
8304
|
+
const key = `inst:${inst}`;
|
|
8305
|
+
if (key in this.aliveResources) {
|
|
8306
|
+
delete this.aliveResources[key];
|
|
8307
|
+
clearedInsts.push(inst);
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
let pathHit = false;
|
|
8024
8311
|
for (const r of node.resources) {
|
|
8025
8312
|
if (r in this.aliveResources) {
|
|
8026
8313
|
delete this.aliveResources[r];
|
|
8027
|
-
|
|
8314
|
+
pathHit = true;
|
|
8028
8315
|
}
|
|
8029
8316
|
}
|
|
8030
8317
|
const detail = node.actionDetail;
|
|
@@ -8032,16 +8319,30 @@ var StatefulChainTracker = class {
|
|
|
8032
8319
|
const base = r.split("/").pop() || "";
|
|
8033
8320
|
if (base && detail.includes(base)) {
|
|
8034
8321
|
delete this.aliveResources[r];
|
|
8035
|
-
|
|
8322
|
+
pathHit = true;
|
|
8036
8323
|
}
|
|
8037
8324
|
}
|
|
8038
|
-
if (
|
|
8325
|
+
if (clearedInsts.length > 0 && !pathHit) {
|
|
8326
|
+
for (const inst of clearedInsts) this.resetChainsForInstance(inst);
|
|
8327
|
+
} else {
|
|
8328
|
+
this.resetChainsOnDestroy();
|
|
8329
|
+
}
|
|
8039
8330
|
return;
|
|
8040
8331
|
}
|
|
8041
8332
|
if (node.lifecycle === "alive") {
|
|
8042
8333
|
for (const r of node.resources) {
|
|
8043
8334
|
this.aliveResources[r] = node.seq;
|
|
8044
8335
|
}
|
|
8336
|
+
for (const inst of node.instances) {
|
|
8337
|
+
this.aliveResources[`inst:${inst}`] = node.seq;
|
|
8338
|
+
}
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
/** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
|
|
8342
|
+
resetChainsForInstance(inst) {
|
|
8343
|
+
const key = buckKey("", inst).slice(2);
|
|
8344
|
+
for (const k of Object.keys(this.ptr)) {
|
|
8345
|
+
if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
|
|
8045
8346
|
}
|
|
8046
8347
|
}
|
|
8047
8348
|
resetChainsOnDestroy() {
|
|
@@ -8049,6 +8350,29 @@ var StatefulChainTracker = class {
|
|
|
8049
8350
|
this.ptr[cid] = 0;
|
|
8050
8351
|
}
|
|
8051
8352
|
}
|
|
8353
|
+
/**
|
|
8354
|
+
* 当前节点应推进的链桶集合。
|
|
8355
|
+
* - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
|
|
8356
|
+
* 同时为所有链的该实例建立桶(若首次出现)。
|
|
8357
|
+
* - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
|
|
8358
|
+
* (信息不足时保守推进一切可能,避免漏判)。
|
|
8359
|
+
*/
|
|
8360
|
+
targetsForNode(node, chainId) {
|
|
8361
|
+
const insts = node.instances;
|
|
8362
|
+
if (insts.length > 0) {
|
|
8363
|
+
const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
|
|
8364
|
+
if (active.length > 0) return active.map((i) => buckKey(chainId, i));
|
|
8365
|
+
return [chainId];
|
|
8366
|
+
}
|
|
8367
|
+
const targets = [chainId];
|
|
8368
|
+
for (const k of Object.keys(this.aliveResources)) {
|
|
8369
|
+
if (k.startsWith("inst:")) {
|
|
8370
|
+
const inst = k.slice("inst:".length);
|
|
8371
|
+
targets.push(`${chainId}@@${inst}`);
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
return targets;
|
|
8375
|
+
}
|
|
8052
8376
|
advanceChains(node) {
|
|
8053
8377
|
if (node.lifecycle === "dead") return null;
|
|
8054
8378
|
if (node.lifecycle === "never_activated") {
|
|
@@ -8062,23 +8386,32 @@ var StatefulChainTracker = class {
|
|
|
8062
8386
|
const stages = chain.stages || [];
|
|
8063
8387
|
if (stages.length === 0) continue;
|
|
8064
8388
|
const cid = String(chain.id || "");
|
|
8065
|
-
const
|
|
8066
|
-
|
|
8067
|
-
this.ptr[
|
|
8068
|
-
if (cur
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8389
|
+
const targets = this.targetsForNode(node, cid);
|
|
8390
|
+
for (const key of targets) {
|
|
8391
|
+
const cur = this.ptr[key] || 0;
|
|
8392
|
+
if (cur < stages.length && stages[cur] === stage) {
|
|
8393
|
+
this.ptr[key] = cur + 1;
|
|
8394
|
+
if (cur + 1 >= stages.length) {
|
|
8395
|
+
return {
|
|
8396
|
+
chainId: cid,
|
|
8397
|
+
stages,
|
|
8398
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
8399
|
+
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
8400
|
+
path: this.chainPath(stages)
|
|
8401
|
+
};
|
|
8402
|
+
}
|
|
8076
8403
|
}
|
|
8077
8404
|
}
|
|
8078
8405
|
}
|
|
8079
8406
|
return null;
|
|
8080
8407
|
}
|
|
8081
8408
|
invalidateChainOnFailure(node) {
|
|
8409
|
+
if (node.instances.length > 0) {
|
|
8410
|
+
for (const inst of node.instances) {
|
|
8411
|
+
this.resetChainsForInstance(inst);
|
|
8412
|
+
}
|
|
8413
|
+
return;
|
|
8414
|
+
}
|
|
8082
8415
|
const failed = new Set(node.resources);
|
|
8083
8416
|
if (failed.size === 0) return;
|
|
8084
8417
|
const { chains } = loadRules();
|
|
@@ -8094,7 +8427,7 @@ var StatefulChainTracker = class {
|
|
|
8094
8427
|
let want = stages[0];
|
|
8095
8428
|
for (const n of this.nodes_) {
|
|
8096
8429
|
if (n.stage === want) {
|
|
8097
|
-
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
|
|
8430
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
|
|
8098
8431
|
const idx = stages.indexOf(n.stage);
|
|
8099
8432
|
if (idx + 1 < stages.length) {
|
|
8100
8433
|
want = stages[idx + 1];
|
|
@@ -8130,7 +8463,7 @@ var StatefulChainTracker = class {
|
|
|
8130
8463
|
if (!this.tainted.has(node.seq)) return [];
|
|
8131
8464
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
8132
8465
|
return [{
|
|
8133
|
-
property_id: "sensitive-data-
|
|
8466
|
+
property_id: "sensitive-data-flow",
|
|
8134
8467
|
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`,
|
|
8135
8468
|
risk_level: "critical",
|
|
8136
8469
|
path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
|
|
@@ -8172,8 +8505,12 @@ var StatefulChainTracker = class {
|
|
|
8172
8505
|
actionDetail: String(raw.actionDetail || ""),
|
|
8173
8506
|
stage: String(raw.stage || ""),
|
|
8174
8507
|
resources: [...raw.resources || []],
|
|
8508
|
+
instances: [...raw.instances || []],
|
|
8509
|
+
// 旧快照无此字段 → 空
|
|
8175
8510
|
isError: Boolean(raw.isError),
|
|
8176
|
-
lifecycle: String(raw.lifecycle || "")
|
|
8511
|
+
lifecycle: String(raw.lifecycle || ""),
|
|
8512
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
|
|
8513
|
+
// 旧快照无此字段 → null
|
|
8177
8514
|
});
|
|
8178
8515
|
}
|
|
8179
8516
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
@@ -8402,7 +8739,7 @@ async function runAnalysis(input) {
|
|
|
8402
8739
|
riskType: node.l1RiskType,
|
|
8403
8740
|
confidence: 90,
|
|
8404
8741
|
source: "heuristic_script_scan",
|
|
8405
|
-
violatedLayers: ["
|
|
8742
|
+
violatedLayers: ["L1.2"]
|
|
8406
8743
|
};
|
|
8407
8744
|
}
|
|
8408
8745
|
}
|
|
@@ -8520,7 +8857,7 @@ async function runAnalysis(input) {
|
|
|
8520
8857
|
riskType: inlineJudgment.riskType,
|
|
8521
8858
|
confidence: 90,
|
|
8522
8859
|
source: "logic_rule",
|
|
8523
|
-
violatedLayers: ["1
|
|
8860
|
+
violatedLayers: ["L2.1"]
|
|
8524
8861
|
};
|
|
8525
8862
|
}
|
|
8526
8863
|
return {
|
|
@@ -8553,7 +8890,7 @@ async function runAnalysis(input) {
|
|
|
8553
8890
|
console.warn(
|
|
8554
8891
|
`[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)}`
|
|
8555
8892
|
);
|
|
8556
|
-
observedFormalLayers.push("2
|
|
8893
|
+
observedFormalLayers.push("L2.2");
|
|
8557
8894
|
} else {
|
|
8558
8895
|
return {
|
|
8559
8896
|
allowed: false,
|
|
@@ -8562,7 +8899,7 @@ async function runAnalysis(input) {
|
|
|
8562
8899
|
riskType: "formal_verification",
|
|
8563
8900
|
confidence: 90,
|
|
8564
8901
|
source: "formal_engine",
|
|
8565
|
-
violatedLayers: ["2
|
|
8902
|
+
violatedLayers: ["L2.2"]
|
|
8566
8903
|
};
|
|
8567
8904
|
}
|
|
8568
8905
|
}
|
|
@@ -8579,7 +8916,7 @@ async function runAnalysis(input) {
|
|
|
8579
8916
|
riskType: node.l2RiskType,
|
|
8580
8917
|
confidence: 85,
|
|
8581
8918
|
source: "logic_rule_script_scan",
|
|
8582
|
-
violatedLayers: ["
|
|
8919
|
+
violatedLayers: ["L2.3"]
|
|
8583
8920
|
};
|
|
8584
8921
|
}
|
|
8585
8922
|
}
|
|
@@ -8666,7 +9003,7 @@ async function runAnalysis(input) {
|
|
|
8666
9003
|
riskType: "",
|
|
8667
9004
|
confidence: 60,
|
|
8668
9005
|
source: "llm_failure_fallback",
|
|
8669
|
-
violatedLayers: ["
|
|
9006
|
+
violatedLayers: ["L3.1"]
|
|
8670
9007
|
};
|
|
8671
9008
|
}
|
|
8672
9009
|
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
@@ -9035,7 +9372,7 @@ async function analyzeRoute(c) {
|
|
|
9035
9372
|
const judgment = incMatchedToDeny(inc);
|
|
9036
9373
|
if (judgment) {
|
|
9037
9374
|
const layers = [...result.violatedLayers];
|
|
9038
|
-
if (!layers.includes("2
|
|
9375
|
+
if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
|
|
9039
9376
|
console.error(
|
|
9040
9377
|
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
9041
9378
|
);
|
|
@@ -9049,7 +9386,8 @@ async function analyzeRoute(c) {
|
|
|
9049
9386
|
policy: "",
|
|
9050
9387
|
violated_policy: "",
|
|
9051
9388
|
confidence: 90,
|
|
9052
|
-
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
|
|
9389
|
+
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
9390
|
+
violation_chain: judgment.violation_chain
|
|
9053
9391
|
})
|
|
9054
9392
|
);
|
|
9055
9393
|
}
|
|
@@ -9088,17 +9426,20 @@ function getChainStateStoreFor(_agentId) {
|
|
|
9088
9426
|
}
|
|
9089
9427
|
function incMatchedToDeny(inc) {
|
|
9090
9428
|
if (inc.matched) {
|
|
9429
|
+
const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
|
|
9091
9430
|
return {
|
|
9092
9431
|
property_id: "cross-" + inc.matched.chainId,
|
|
9093
9432
|
reason: inc.matched.reason,
|
|
9094
|
-
risk_level: inc.matched.riskLevel
|
|
9433
|
+
risk_level: inc.matched.riskLevel,
|
|
9434
|
+
violation_chain: chain
|
|
9095
9435
|
};
|
|
9096
9436
|
}
|
|
9097
9437
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
9098
9438
|
return {
|
|
9099
|
-
property_id: "sensitive-data-
|
|
9439
|
+
property_id: "sensitive-data-flow",
|
|
9100
9440
|
reason: inc.exfil[0].reason,
|
|
9101
|
-
risk_level: inc.exfil[0].risk_level
|
|
9441
|
+
risk_level: inc.exfil[0].risk_level,
|
|
9442
|
+
violation_chain: []
|
|
9102
9443
|
};
|
|
9103
9444
|
}
|
|
9104
9445
|
return null;
|
|
@@ -9224,7 +9565,7 @@ function createConsoleRouter() {
|
|
|
9224
9565
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
9225
9566
|
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
9226
9567
|
});
|
|
9227
|
-
function
|
|
9568
|
+
function getL22View() {
|
|
9228
9569
|
const ltlHuman = {
|
|
9229
9570
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9230
9571
|
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
@@ -9255,9 +9596,9 @@ function createConsoleRouter() {
|
|
|
9255
9596
|
}))
|
|
9256
9597
|
};
|
|
9257
9598
|
}
|
|
9258
|
-
function
|
|
9259
|
-
if (rc && rc.
|
|
9260
|
-
const cat = rc.
|
|
9599
|
+
function withL22View(rc) {
|
|
9600
|
+
if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
|
|
9601
|
+
const cat = rc.L2_2.ltl_rules;
|
|
9261
9602
|
const rls = cat && cat.rules || [];
|
|
9262
9603
|
const ltlHumanLocal = {
|
|
9263
9604
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
@@ -9272,7 +9613,7 @@ function createConsoleRouter() {
|
|
|
9272
9613
|
"mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
|
|
9273
9614
|
"mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
|
|
9274
9615
|
};
|
|
9275
|
-
rc.
|
|
9616
|
+
rc.L2_2 = {
|
|
9276
9617
|
enabled_via: "L2_logic_rules",
|
|
9277
9618
|
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9278
9619
|
ltl_rules: rls.map((r) => ({
|
|
@@ -9291,22 +9632,22 @@ function createConsoleRouter() {
|
|
|
9291
9632
|
}
|
|
9292
9633
|
console2.get("/api/rules", (c) => {
|
|
9293
9634
|
const rc = loadRuntimeConfig();
|
|
9294
|
-
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules,
|
|
9635
|
+
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
|
|
9295
9636
|
});
|
|
9296
9637
|
console2.put("/api/rules/enabled", async (c) => {
|
|
9297
9638
|
const b = await c.req.json();
|
|
9298
9639
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
9299
|
-
return c.json(
|
|
9640
|
+
return c.json(withL22View(rc));
|
|
9300
9641
|
});
|
|
9301
9642
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
9302
9643
|
const b = await c.req.json();
|
|
9303
9644
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
9304
|
-
return c.json(
|
|
9645
|
+
return c.json(withL22View(rc));
|
|
9305
9646
|
});
|
|
9306
9647
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
9307
9648
|
const b = await c.req.json();
|
|
9308
9649
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
9309
|
-
return c.json(
|
|
9650
|
+
return c.json(withL22View(rc));
|
|
9310
9651
|
});
|
|
9311
9652
|
console2.put("/api/categories/enabled", async (c) => {
|
|
9312
9653
|
const b = await c.req.json();
|
|
@@ -9456,7 +9797,7 @@ function createConsoleRouter() {
|
|
|
9456
9797
|
});
|
|
9457
9798
|
console2.get("/api/config", (c) => {
|
|
9458
9799
|
const rc = loadRuntimeConfig();
|
|
9459
|
-
return c.json({ ...rc,
|
|
9800
|
+
return c.json({ ...rc, L2_2: getL22View() });
|
|
9460
9801
|
});
|
|
9461
9802
|
console2.get("/api/env-overrides", (c) => {
|
|
9462
9803
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -9536,4 +9877,14 @@ export {
|
|
|
9536
9877
|
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
9537
9878
|
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|
|
9538
9879
|
//! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
|
|
9880
|
+
//!
|
|
9881
|
+
//! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
|
|
9882
|
+
//! P1 cross-session-chain 跨会话组合链
|
|
9883
|
+
//! P2 sensitive-data-flow 敏感数据传播
|
|
9884
|
+
//! P3 low-integrity-reachability 低完整性数据流可达
|
|
9885
|
+
//! P4 no-exfil-after-privesc 提权后禁外传
|
|
9886
|
+
//! P5 no-exfil-after-credential 凭据后禁外传
|
|
9887
|
+
//! P6 mc-ltl-rules LTL 模型检测
|
|
9888
|
+
//! P7 low-integrity-command-exec 低完整性命令执行
|
|
9889
|
+
//! P8 sensitive-data-aggregation 敏感数据聚合外传
|
|
9539
9890
|
//# sourceMappingURL=server.js.map
|