@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.
- package/dist/cli.js +630 -150
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +1 -1
- package/dist/engine/coordinator.js +554 -197
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +28 -6
- package/dist/rules/l2_rules.json +242 -214
- package/dist/rules/mc_rules.json +258 -26
- package/dist/server.js +716 -246
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +22 -8
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -632,10 +632,15 @@ 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
|
|
635
|
+
const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
|
|
636
|
+
const rtRules = ltl.rules || [];
|
|
636
637
|
const enabledById = {};
|
|
637
638
|
for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
|
|
638
|
-
|
|
639
|
+
const catEnabled = ltl.category_enabled !== false;
|
|
640
|
+
for (const r of rules) {
|
|
641
|
+
if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
|
|
642
|
+
if (!catEnabled) r.enabled = false;
|
|
643
|
+
}
|
|
639
644
|
}
|
|
640
645
|
} catch {
|
|
641
646
|
}
|
|
@@ -680,7 +685,8 @@ function _loadSensitivePathScope(scope) {
|
|
|
680
685
|
path: sp.path,
|
|
681
686
|
riskLevel: sp.risk_level,
|
|
682
687
|
desc: sp.desc,
|
|
683
|
-
credential: sp.deny_mode === "deny_both" ? true : void 0
|
|
688
|
+
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
689
|
+
dirDelOnly: sp.dir_del_only === true ? true : void 0
|
|
684
690
|
}));
|
|
685
691
|
}
|
|
686
692
|
function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
|
|
@@ -778,7 +784,7 @@ function runLogicCheck(input) {
|
|
|
778
784
|
riskType: "intent_deviation",
|
|
779
785
|
confidence: 85,
|
|
780
786
|
source: "logic_rule",
|
|
781
|
-
violatedLayers: ["
|
|
787
|
+
violatedLayers: ["L2.1"]
|
|
782
788
|
};
|
|
783
789
|
}
|
|
784
790
|
}
|
|
@@ -802,7 +808,7 @@ function runLogicCheck(input) {
|
|
|
802
808
|
riskType: "privilege_escalation",
|
|
803
809
|
confidence: 85,
|
|
804
810
|
source: "logic_rule",
|
|
805
|
-
violatedLayers: ["
|
|
811
|
+
violatedLayers: ["L2.1"]
|
|
806
812
|
};
|
|
807
813
|
}
|
|
808
814
|
}
|
|
@@ -854,7 +860,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
854
860
|
riskType: "combo_chain",
|
|
855
861
|
confidence: 85,
|
|
856
862
|
source: "logic_rule",
|
|
857
|
-
violatedLayers: ["
|
|
863
|
+
violatedLayers: ["L2.1"]
|
|
858
864
|
};
|
|
859
865
|
}
|
|
860
866
|
}
|
|
@@ -979,7 +985,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
979
985
|
riskType: "read_before_write",
|
|
980
986
|
confidence: 85,
|
|
981
987
|
source: "logic_rule",
|
|
982
|
-
violatedLayers: ["
|
|
988
|
+
violatedLayers: ["L2.1"]
|
|
983
989
|
};
|
|
984
990
|
}
|
|
985
991
|
return null;
|
|
@@ -999,7 +1005,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
999
1005
|
riskType: "consent_missing",
|
|
1000
1006
|
confidence: 85,
|
|
1001
1007
|
source: "logic_rule",
|
|
1002
|
-
violatedLayers: ["
|
|
1008
|
+
violatedLayers: ["L2.1"]
|
|
1003
1009
|
};
|
|
1004
1010
|
}
|
|
1005
1011
|
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
@@ -1017,7 +1023,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
1017
1023
|
riskType: "consent_missing",
|
|
1018
1024
|
confidence: 85,
|
|
1019
1025
|
source: "logic_rule",
|
|
1020
|
-
violatedLayers: ["
|
|
1026
|
+
violatedLayers: ["L2.1"]
|
|
1021
1027
|
};
|
|
1022
1028
|
}
|
|
1023
1029
|
function containsPrivilegeEscalation(actionDetail) {
|
|
@@ -1077,7 +1083,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1077
1083
|
riskType: "file_access",
|
|
1078
1084
|
confidence: 95,
|
|
1079
1085
|
source: "logic_rule",
|
|
1080
|
-
violatedLayers: ["
|
|
1086
|
+
violatedLayers: ["L2.1"],
|
|
1081
1087
|
isCredential: true
|
|
1082
1088
|
};
|
|
1083
1089
|
}
|
|
@@ -1093,7 +1099,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1093
1099
|
riskType: "file_access",
|
|
1094
1100
|
confidence: 95,
|
|
1095
1101
|
source: "logic_rule",
|
|
1096
|
-
violatedLayers: ["
|
|
1102
|
+
violatedLayers: ["L2.1"]
|
|
1097
1103
|
};
|
|
1098
1104
|
}
|
|
1099
1105
|
}
|
|
@@ -1107,7 +1113,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1107
1113
|
riskType: "file_access",
|
|
1108
1114
|
confidence: 70,
|
|
1109
1115
|
source: "logic_rule",
|
|
1110
|
-
violatedLayers: ["
|
|
1116
|
+
violatedLayers: ["L2.1"]
|
|
1111
1117
|
};
|
|
1112
1118
|
}
|
|
1113
1119
|
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 +1125,7 @@ var init_logic_rules = __esm({
|
|
|
1119
1125
|
init_rules_loader();
|
|
1120
1126
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1121
1127
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1122
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
|
|
1128
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
|
|
1123
1129
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1124
1130
|
"ask_user_question",
|
|
1125
1131
|
"glob",
|
|
@@ -1432,6 +1438,7 @@ __export(runtime_config_exports, {
|
|
|
1432
1438
|
isLayerEnabled: () => isLayerEnabled,
|
|
1433
1439
|
loadRuntimeConfig: () => loadRuntimeConfig,
|
|
1434
1440
|
mergeConfig: () => mergeConfig,
|
|
1441
|
+
pickDeletableRule: () => pickDeletableRule,
|
|
1435
1442
|
saveRuntimeConfig: () => saveRuntimeConfig,
|
|
1436
1443
|
updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
|
|
1437
1444
|
updateAgentRuleEnabled: () => updateAgentRuleEnabled,
|
|
@@ -1470,7 +1477,7 @@ function emptyRuntimeConfig() {
|
|
|
1470
1477
|
L1_rules: {},
|
|
1471
1478
|
L2_rules: {},
|
|
1472
1479
|
L3_skills: {},
|
|
1473
|
-
|
|
1480
|
+
L2_2: {},
|
|
1474
1481
|
agent_rules: {},
|
|
1475
1482
|
scenario_rules: {}
|
|
1476
1483
|
};
|
|
@@ -1498,7 +1505,7 @@ function loadRuntimeConfig() {
|
|
|
1498
1505
|
L1_rules: merged.L1_rules || {},
|
|
1499
1506
|
L2_rules: merged.L2_rules || {},
|
|
1500
1507
|
L3_skills: merged.L3_skills || {},
|
|
1501
|
-
|
|
1508
|
+
L2_2: merged.L2_2 || {},
|
|
1502
1509
|
agent_rules: merged.agent_rules || {},
|
|
1503
1510
|
scenario_rules: merged.scenario_rules || {}
|
|
1504
1511
|
};
|
|
@@ -1551,6 +1558,7 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1551
1558
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1552
1559
|
if (r.credential) out.credential = true;
|
|
1553
1560
|
if (r.read_only) out.read_only = true;
|
|
1561
|
+
if (r.dir_del_only) out.dir_del_only = true;
|
|
1554
1562
|
return out;
|
|
1555
1563
|
});
|
|
1556
1564
|
}
|
|
@@ -1596,8 +1604,16 @@ function getDisabledL2RulesWithSkipL3() {
|
|
|
1596
1604
|
function _slugify(text) {
|
|
1597
1605
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
|
|
1598
1606
|
}
|
|
1607
|
+
function _fnv1a32(text) {
|
|
1608
|
+
let h = 2166136261;
|
|
1609
|
+
for (const b of Buffer.from(text, "utf-8")) {
|
|
1610
|
+
h ^= b;
|
|
1611
|
+
h = h * 16777619 >>> 0;
|
|
1612
|
+
}
|
|
1613
|
+
return h.toString(16).padStart(8, "0");
|
|
1614
|
+
}
|
|
1599
1615
|
function _generatePatternId(pattern) {
|
|
1600
|
-
return `pat_${_slugify(pattern)}`;
|
|
1616
|
+
return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
|
|
1601
1617
|
}
|
|
1602
1618
|
function _deriveDenyMode(rule) {
|
|
1603
1619
|
if (rule.deny_mode) return rule.deny_mode;
|
|
@@ -1659,6 +1675,7 @@ function _l2SensitivePathsToRules() {
|
|
|
1659
1675
|
builtin: true
|
|
1660
1676
|
};
|
|
1661
1677
|
_syncDenyModeFields(rule);
|
|
1678
|
+
if (sp.dirDelOnly) rule.dir_del_only = true;
|
|
1662
1679
|
return rule;
|
|
1663
1680
|
});
|
|
1664
1681
|
}
|
|
@@ -1875,17 +1892,20 @@ function generateSourceDefaults() {
|
|
|
1875
1892
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1876
1893
|
},
|
|
1877
1894
|
L3_skills: _l3SkillsToRules(),
|
|
1878
|
-
|
|
1895
|
+
L2_2: _ltlRulesToRuntime(),
|
|
1879
1896
|
agent_rules: _agentRulesToDefaults(),
|
|
1880
1897
|
scenario_rules: _scenarioRulesToDefaults()
|
|
1881
1898
|
};
|
|
1882
1899
|
}
|
|
1883
1900
|
function _ltlRulesToRuntime() {
|
|
1884
1901
|
const cands = [
|
|
1885
|
-
join3(
|
|
1886
|
-
join3(__dirname4, "
|
|
1887
|
-
join3(
|
|
1888
|
-
|
|
1902
|
+
getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
|
|
1903
|
+
join3(__dirname4, "../rules"),
|
|
1904
|
+
join3(__dirname4, "rules"),
|
|
1905
|
+
join3(__dirname4, "../../../agent_moss/rules"),
|
|
1906
|
+
join3(__dirname4, "../../../../agent_moss/rules"),
|
|
1907
|
+
join3(process.cwd(), "agent_moss/rules")
|
|
1908
|
+
].filter(Boolean);
|
|
1889
1909
|
let mc = {};
|
|
1890
1910
|
for (const dir of cands) {
|
|
1891
1911
|
const p = join3(dir, "mc_rules.json");
|
|
@@ -2063,7 +2083,11 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
2063
2083
|
);
|
|
2064
2084
|
_mergeRuleCategories(cfg, srcDefaults, "L1_rules");
|
|
2065
2085
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
2066
|
-
|
|
2086
|
+
if (!cfg.L2_2 && cfg.L2_4) {
|
|
2087
|
+
cfg.L2_2 = cfg.L2_4;
|
|
2088
|
+
delete cfg.L2_4;
|
|
2089
|
+
}
|
|
2090
|
+
_mergeRuleCategories(cfg, srcDefaults, "L2_2");
|
|
2067
2091
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
2068
2092
|
_mergeAgentRules(cfg, srcDefaults);
|
|
2069
2093
|
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
@@ -2241,16 +2265,20 @@ function addCustomRule(layer, category, rule) {
|
|
|
2241
2265
|
saveRuntimeConfig(runtime);
|
|
2242
2266
|
return runtime;
|
|
2243
2267
|
}
|
|
2244
|
-
function
|
|
2245
|
-
const
|
|
2246
|
-
const rules = runtime[layer]?.[category]?.rules || [];
|
|
2247
|
-
const idx = rules.findIndex((r) => r.id === ruleId);
|
|
2268
|
+
function pickDeletableRule(rules, ruleId) {
|
|
2269
|
+
const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
|
|
2248
2270
|
if (idx >= 0) {
|
|
2249
|
-
const r = rules[idx];
|
|
2250
|
-
if (r.builtin && !r.source_removed) return null;
|
|
2251
2271
|
rules.splice(idx, 1);
|
|
2252
|
-
|
|
2272
|
+
return rules;
|
|
2253
2273
|
}
|
|
2274
|
+
if (rules.some((r) => r.id === ruleId)) return null;
|
|
2275
|
+
return rules;
|
|
2276
|
+
}
|
|
2277
|
+
function deleteCustomRule(layer, category, ruleId) {
|
|
2278
|
+
const runtime = loadRuntimeConfig();
|
|
2279
|
+
const rules = runtime[layer]?.[category]?.rules || [];
|
|
2280
|
+
if (pickDeletableRule(rules, ruleId) === null) return null;
|
|
2281
|
+
saveRuntimeConfig(runtime);
|
|
2254
2282
|
return runtime;
|
|
2255
2283
|
}
|
|
2256
2284
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
@@ -2683,7 +2711,7 @@ function runHeuristicCheck(input) {
|
|
|
2683
2711
|
riskType: "sensitive_action",
|
|
2684
2712
|
confidence: 90,
|
|
2685
2713
|
source: "heuristic",
|
|
2686
|
-
violatedLayers: ["
|
|
2714
|
+
violatedLayers: ["L1.1"]
|
|
2687
2715
|
};
|
|
2688
2716
|
}
|
|
2689
2717
|
}
|
|
@@ -2693,8 +2721,10 @@ function runHeuristicCheck(input) {
|
|
|
2693
2721
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2694
2722
|
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
2695
2723
|
let topRiskType = "";
|
|
2724
|
+
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2696
2725
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
2697
2726
|
if (rule.pattern.test(scanCommand)) {
|
|
2727
|
+
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2698
2728
|
matched.push(rule.description);
|
|
2699
2729
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
2700
2730
|
highestSeverity = rule.severity;
|
|
@@ -2736,10 +2766,26 @@ function runHeuristicCheck(input) {
|
|
|
2736
2766
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2737
2767
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2738
2768
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2769
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2770
|
+
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2771
|
+
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2772
|
+
if (mergeResults.length > 0) {
|
|
2773
|
+
let best = mergeResults[0];
|
|
2774
|
+
for (const r of mergeResults) {
|
|
2775
|
+
if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
|
|
2776
|
+
}
|
|
2777
|
+
if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
|
|
2778
|
+
return {
|
|
2779
|
+
allowed: false,
|
|
2780
|
+
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
2781
|
+
riskLevel: highestSeverity,
|
|
2782
|
+
riskType: highestRiskType,
|
|
2783
|
+
confidence: 95,
|
|
2784
|
+
source: "heuristic",
|
|
2785
|
+
violatedLayers: ["L1.1"]
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
return best;
|
|
2743
2789
|
}
|
|
2744
2790
|
if (matched.length > 0) {
|
|
2745
2791
|
return {
|
|
@@ -2749,8 +2795,7 @@ function runHeuristicCheck(input) {
|
|
|
2749
2795
|
riskType: highestRiskType,
|
|
2750
2796
|
confidence: 95,
|
|
2751
2797
|
source: "heuristic",
|
|
2752
|
-
violatedLayers: ["
|
|
2753
|
-
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2798
|
+
violatedLayers: ["L1.1"]
|
|
2754
2799
|
};
|
|
2755
2800
|
}
|
|
2756
2801
|
const lateralResult = checkLateralMovement(detailL);
|
|
@@ -2878,6 +2923,7 @@ function isSearchActionType(actionType) {
|
|
|
2878
2923
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2879
2924
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2880
2925
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2926
|
+
const keygenGenerate = isSshKeygenGenerate(command);
|
|
2881
2927
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2882
2928
|
const pathMatches = (text, path6) => {
|
|
2883
2929
|
const parts = path6.split(/(\(\?!.*?\))/);
|
|
@@ -2890,6 +2936,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2890
2936
|
};
|
|
2891
2937
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2892
2938
|
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2939
|
+
if (keygenGenerate) continue;
|
|
2893
2940
|
if (sp.path === "/dev/random") {
|
|
2894
2941
|
if (isWriteOpFlag) {
|
|
2895
2942
|
continue;
|
|
@@ -2901,7 +2948,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2901
2948
|
riskType: "file_access",
|
|
2902
2949
|
confidence: 90,
|
|
2903
2950
|
source: "heuristic",
|
|
2904
|
-
violatedLayers: ["
|
|
2951
|
+
violatedLayers: ["L1.1"]
|
|
2905
2952
|
};
|
|
2906
2953
|
}
|
|
2907
2954
|
if (!isWriteOpFlag && !sp.credential) {
|
|
@@ -2914,16 +2961,51 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2914
2961
|
riskType: "file_access",
|
|
2915
2962
|
confidence: 90,
|
|
2916
2963
|
source: "heuristic",
|
|
2917
|
-
violatedLayers: ["
|
|
2964
|
+
violatedLayers: ["L1.1"],
|
|
2918
2965
|
isCredential: !!sp.credential
|
|
2919
2966
|
};
|
|
2920
2967
|
}
|
|
2921
2968
|
}
|
|
2922
2969
|
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
2923
2970
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2971
|
+
let pPath = sp.path;
|
|
2972
|
+
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
2973
|
+
let dirPrefixMatch = null;
|
|
2974
|
+
if (sp.dirDelOnly) {
|
|
2975
|
+
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
2976
|
+
dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
|
|
2977
|
+
}
|
|
2978
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
|
|
2979
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
2980
|
+
if (pathReMatch || expandedReMatch) {
|
|
2981
|
+
if (keygenGenerate) continue;
|
|
2982
|
+
if (sp.dirDelOnly) {
|
|
2983
|
+
if (isDeleteActionForDir(command, sp.path)) {
|
|
2984
|
+
return {
|
|
2985
|
+
allowed: false,
|
|
2986
|
+
reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2987
|
+
riskLevel: sp.riskLevel,
|
|
2988
|
+
riskType: "file_access",
|
|
2989
|
+
confidence: 90,
|
|
2990
|
+
source: "heuristic",
|
|
2991
|
+
violatedLayers: ["L1.1"],
|
|
2992
|
+
isCredential: !!sp.credential
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
if (isArchiveReadOfDir(command, sp.path)) {
|
|
2996
|
+
return {
|
|
2997
|
+
allowed: false,
|
|
2998
|
+
reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2999
|
+
riskLevel: sp.riskLevel,
|
|
3000
|
+
riskType: "file_access",
|
|
3001
|
+
confidence: 90,
|
|
3002
|
+
source: "heuristic",
|
|
3003
|
+
violatedLayers: ["L1.1"],
|
|
3004
|
+
isCredential: !!sp.credential
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
2927
3009
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2928
3010
|
continue;
|
|
2929
3011
|
}
|
|
@@ -2934,7 +3016,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2934
3016
|
riskType: "file_access",
|
|
2935
3017
|
confidence: 90,
|
|
2936
3018
|
source: "heuristic",
|
|
2937
|
-
violatedLayers: ["
|
|
3019
|
+
violatedLayers: ["L1.1"],
|
|
2938
3020
|
isCredential: !!sp.credential
|
|
2939
3021
|
};
|
|
2940
3022
|
}
|
|
@@ -2957,7 +3039,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2957
3039
|
riskType: "file_access",
|
|
2958
3040
|
confidence: 90,
|
|
2959
3041
|
source: "heuristic",
|
|
2960
|
-
violatedLayers: ["
|
|
3042
|
+
violatedLayers: ["L1.1"],
|
|
2961
3043
|
isCredential: !!sp.credential
|
|
2962
3044
|
};
|
|
2963
3045
|
}
|
|
@@ -2974,7 +3056,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2974
3056
|
riskType: "file_access",
|
|
2975
3057
|
confidence: 90,
|
|
2976
3058
|
source: "heuristic",
|
|
2977
|
-
violatedLayers: ["
|
|
3059
|
+
violatedLayers: ["L1.1"],
|
|
2978
3060
|
isCredential: true
|
|
2979
3061
|
};
|
|
2980
3062
|
}
|
|
@@ -2987,18 +3069,142 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2987
3069
|
riskType: "file_access",
|
|
2988
3070
|
confidence: 90,
|
|
2989
3071
|
source: "heuristic",
|
|
2990
|
-
violatedLayers: ["
|
|
3072
|
+
violatedLayers: ["L1.1"]
|
|
2991
3073
|
};
|
|
2992
3074
|
}
|
|
2993
3075
|
return null;
|
|
2994
3076
|
}
|
|
3077
|
+
function isHomeValue(path6) {
|
|
3078
|
+
let p = path6.trim().toLowerCase();
|
|
3079
|
+
if (p.startsWith("$(") && p.endsWith(")")) {
|
|
3080
|
+
const inner = p.slice(2, -1).trim();
|
|
3081
|
+
return HOME_TARGET_RE.test(" " + inner);
|
|
3082
|
+
}
|
|
3083
|
+
if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
|
|
3084
|
+
return HOME_TARGET_RE.test(" " + p);
|
|
3085
|
+
}
|
|
3086
|
+
function isDeleteCommand(seg) {
|
|
3087
|
+
const s = seg.trimStart();
|
|
3088
|
+
if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
|
|
3089
|
+
return /删除|移除/.test(s);
|
|
3090
|
+
}
|
|
3091
|
+
function homeEscapeTarget(seg) {
|
|
3092
|
+
const low = seg.toLowerCase();
|
|
3093
|
+
const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
|
|
3094
|
+
let m;
|
|
3095
|
+
while ((m = re.exec(low)) !== null) {
|
|
3096
|
+
const endIdx = m.index + m[0].length;
|
|
3097
|
+
const tail = low.slice(endIdx);
|
|
3098
|
+
if (!tail) continue;
|
|
3099
|
+
let token = tail.trimStart().split(/\s+/)[0] || "";
|
|
3100
|
+
token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
|
|
3101
|
+
token = token.replace(/^['"]|['"]$/g, "");
|
|
3102
|
+
const segments = token.split("/").filter((s) => s && s !== ".");
|
|
3103
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
3104
|
+
let depth = 0;
|
|
3105
|
+
let minDepth = 0;
|
|
3106
|
+
for (const s of segments) {
|
|
3107
|
+
depth += s === ".." ? -1 : 1;
|
|
3108
|
+
minDepth = Math.min(minDepth, depth);
|
|
3109
|
+
}
|
|
3110
|
+
if (minDepth < 0) return true;
|
|
3111
|
+
if (depth <= 0) return true;
|
|
3112
|
+
}
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
function checkHomeDeletion(actionType, actionDetail) {
|
|
3116
|
+
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
|
|
3117
|
+
const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
3118
|
+
for (const seg of segs) {
|
|
3119
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3120
|
+
if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
|
|
3121
|
+
return {
|
|
3122
|
+
allowed: false,
|
|
3123
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
|
|
3124
|
+
riskLevel: "high",
|
|
3125
|
+
riskType: "script_execution",
|
|
3126
|
+
confidence: 90,
|
|
3127
|
+
source: "heuristic",
|
|
3128
|
+
violatedLayers: ["L1.1"]
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
const assigned = {};
|
|
3133
|
+
for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
|
|
3134
|
+
for (const [varName, path6] of Object.entries(assigned)) {
|
|
3135
|
+
if (!isHomeValue(path6)) continue;
|
|
3136
|
+
const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
|
|
3137
|
+
for (const seg of segs) {
|
|
3138
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3139
|
+
if (varRefRe.test(seg)) {
|
|
3140
|
+
return {
|
|
3141
|
+
allowed: false,
|
|
3142
|
+
reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
|
|
3143
|
+
riskLevel: "high",
|
|
3144
|
+
riskType: "script_execution",
|
|
3145
|
+
confidence: 90,
|
|
3146
|
+
source: "heuristic",
|
|
3147
|
+
violatedLayers: ["L1.1"]
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
function escapeRegExp(s) {
|
|
3155
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3156
|
+
}
|
|
3157
|
+
function isDeleteActionForDir(command, dirPath) {
|
|
3158
|
+
const low = command.toLowerCase();
|
|
3159
|
+
const dpath = dirPath.toLowerCase();
|
|
3160
|
+
if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
|
|
3161
|
+
return true;
|
|
3162
|
+
}
|
|
3163
|
+
if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3167
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3168
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
|
|
3169
|
+
return true;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return false;
|
|
3173
|
+
}
|
|
3174
|
+
function isArchiveReadOfDir(command, dirPath) {
|
|
3175
|
+
const low = command.toLowerCase();
|
|
3176
|
+
const dpath = dirPath.toLowerCase();
|
|
3177
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3178
|
+
const s = seg.trim();
|
|
3179
|
+
if (!s || !s.includes(dpath)) continue;
|
|
3180
|
+
const parts = s.split(/\s+/);
|
|
3181
|
+
const fw = parts[0] || "";
|
|
3182
|
+
if (fw === "zip") return true;
|
|
3183
|
+
if (fw === "tar") {
|
|
3184
|
+
if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
|
|
3185
|
+
return true;
|
|
3186
|
+
}
|
|
3187
|
+
if (fw === "cp" || fw === "rsync") {
|
|
3188
|
+
const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
|
|
3189
|
+
if (segPaths.length && segPaths[0].includes(dpath)) return true;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return false;
|
|
3193
|
+
}
|
|
3194
|
+
function isSshKeygenGenerate(command) {
|
|
3195
|
+
const low = command.toLowerCase();
|
|
3196
|
+
if (!low.includes("ssh-keygen")) return false;
|
|
3197
|
+
if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
|
|
3198
|
+
if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
|
|
3199
|
+
return true;
|
|
3200
|
+
}
|
|
2995
3201
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2996
3202
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2997
3203
|
const scan = stripHeredocContent(actionDetail);
|
|
2998
3204
|
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2999
3205
|
const isTempPath = TEMP_DIRS.some(
|
|
3000
3206
|
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
3001
|
-
);
|
|
3207
|
+
) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
|
|
3002
3208
|
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
3209
|
if (!isTempPath && wildcardDeleteRe.test(scan)) {
|
|
3004
3210
|
return {
|
|
@@ -3008,7 +3214,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3008
3214
|
riskType: "script_execution",
|
|
3009
3215
|
confidence: 90,
|
|
3010
3216
|
source: "heuristic",
|
|
3011
|
-
violatedLayers: ["
|
|
3217
|
+
violatedLayers: ["L1.1"]
|
|
3012
3218
|
};
|
|
3013
3219
|
}
|
|
3014
3220
|
const findWriteMatch = actionDetail.match(
|
|
@@ -3023,10 +3229,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3023
3229
|
riskType: "script_execution",
|
|
3024
3230
|
confidence: 90,
|
|
3025
3231
|
source: "heuristic",
|
|
3026
|
-
violatedLayers: ["
|
|
3232
|
+
violatedLayers: ["L1.1"]
|
|
3027
3233
|
};
|
|
3028
3234
|
}
|
|
3029
|
-
if (/>\s
|
|
3235
|
+
if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
|
|
3030
3236
|
return {
|
|
3031
3237
|
allowed: false,
|
|
3032
3238
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -3034,7 +3240,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3034
3240
|
riskType: "file_access",
|
|
3035
3241
|
confidence: 95,
|
|
3036
3242
|
source: "heuristic",
|
|
3037
|
-
violatedLayers: ["
|
|
3243
|
+
violatedLayers: ["L1.1"]
|
|
3038
3244
|
};
|
|
3039
3245
|
}
|
|
3040
3246
|
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
@@ -3045,7 +3251,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3045
3251
|
riskType: "file_access",
|
|
3046
3252
|
confidence: 90,
|
|
3047
3253
|
source: "heuristic",
|
|
3048
|
-
violatedLayers: ["
|
|
3254
|
+
violatedLayers: ["L1.1"]
|
|
3049
3255
|
};
|
|
3050
3256
|
}
|
|
3051
3257
|
return null;
|
|
@@ -3136,13 +3342,13 @@ function checkLateralMovement(actionDetail) {
|
|
|
3136
3342
|
riskType: "lateral_movement",
|
|
3137
3343
|
confidence: 80,
|
|
3138
3344
|
source: "heuristic",
|
|
3139
|
-
violatedLayers: ["
|
|
3345
|
+
violatedLayers: ["L1.1"]
|
|
3140
3346
|
};
|
|
3141
3347
|
}
|
|
3142
3348
|
}
|
|
3143
3349
|
return null;
|
|
3144
3350
|
}
|
|
3145
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
|
|
3351
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3146
3352
|
var init_heuristic = __esm({
|
|
3147
3353
|
"src/engine/heuristic.ts"() {
|
|
3148
3354
|
"use strict";
|
|
@@ -3172,6 +3378,8 @@ var init_heuristic = __esm({
|
|
|
3172
3378
|
})();
|
|
3173
3379
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3174
3380
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3381
|
+
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
3382
|
+
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
3383
|
}
|
|
3176
3384
|
});
|
|
3177
3385
|
|
|
@@ -3350,112 +3558,6 @@ var init_token_stats = __esm({
|
|
|
3350
3558
|
}
|
|
3351
3559
|
});
|
|
3352
3560
|
|
|
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
3561
|
// node_modules/better-sqlite3/lib/util.js
|
|
3460
3562
|
var require_util = __commonJS({
|
|
3461
3563
|
"node_modules/better-sqlite3/lib/util.js"(exports) {
|
|
@@ -4283,7 +4385,7 @@ var AnalyzeRequestSchema = z.object({
|
|
|
4283
4385
|
session_history: z.array(z.object({
|
|
4284
4386
|
session_id: z.string(),
|
|
4285
4387
|
action_history: z.array(ActionItemSchema).default([])
|
|
4286
|
-
})).default([]).describe("Multi-session historical actions (optional): for L2.
|
|
4388
|
+
})).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
4389
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
4288
4390
|
reason: z.string().default("").describe("Reason for executing this action"),
|
|
4289
4391
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
@@ -4300,7 +4402,9 @@ var AnalyzeResponseSchema = z.object({
|
|
|
4300
4402
|
policy: z.string().default(""),
|
|
4301
4403
|
violated_policy: z.string().default(""),
|
|
4302
4404
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
4303
|
-
analysis_duration_ms: z.number().default(0)
|
|
4405
|
+
analysis_duration_ms: z.number().default(0),
|
|
4406
|
+
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
4407
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
4304
4408
|
});
|
|
4305
4409
|
var HealthResponseSchema = z.object({
|
|
4306
4410
|
status: z.string().default("healthy"),
|
|
@@ -4563,6 +4667,7 @@ ${result.scriptContent}
|
|
|
4563
4667
|
init_esm_shims();
|
|
4564
4668
|
init_heuristic();
|
|
4565
4669
|
init_logic_rules();
|
|
4670
|
+
init_inline_analyzer();
|
|
4566
4671
|
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4567
4672
|
import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
4568
4673
|
import { homedir as homedir4 } from "os";
|
|
@@ -6177,6 +6282,13 @@ function extractScriptPath2(command) {
|
|
|
6177
6282
|
return null;
|
|
6178
6283
|
}
|
|
6179
6284
|
function extractScriptPathWithArgs(command) {
|
|
6285
|
+
const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
|
|
6286
|
+
if (inline) {
|
|
6287
|
+
const inner = inline[2];
|
|
6288
|
+
const realPath = extractScriptPath2(inner);
|
|
6289
|
+
if (realPath) return { path: realPath, args: [] };
|
|
6290
|
+
return { path: null, args: [] };
|
|
6291
|
+
}
|
|
6180
6292
|
const scriptPath = extractScriptPath2(command);
|
|
6181
6293
|
if (!scriptPath) return { path: null, args: [] };
|
|
6182
6294
|
const idx = command.indexOf(scriptPath);
|
|
@@ -6190,11 +6302,59 @@ function extractScriptPathWithArgs(command) {
|
|
|
6190
6302
|
}
|
|
6191
6303
|
return { path: scriptPath, args };
|
|
6192
6304
|
}
|
|
6305
|
+
function looksLikeScriptPath(tok) {
|
|
6306
|
+
const t = tok.trim().replace(/^['"]|['"]$/g, "");
|
|
6307
|
+
if (!t) return false;
|
|
6308
|
+
const low = t.toLowerCase();
|
|
6309
|
+
if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
|
|
6310
|
+
if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
|
|
6311
|
+
if (t.includes("/") && !t.startsWith("-")) return true;
|
|
6312
|
+
return false;
|
|
6313
|
+
}
|
|
6193
6314
|
function extractInlineScriptPath(actionDetail) {
|
|
6315
|
+
const code = extractInlineCode(actionDetail);
|
|
6316
|
+
if (code) {
|
|
6317
|
+
const candidates = [];
|
|
6318
|
+
const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
|
|
6319
|
+
let m;
|
|
6320
|
+
while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6321
|
+
const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
|
|
6322
|
+
while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
|
|
6323
|
+
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
6324
|
+
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6325
|
+
for (const cand of candidates) {
|
|
6326
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
|
|
6327
|
+
for (const tok of tokens) {
|
|
6328
|
+
if (looksLikeScriptPath(tok)) {
|
|
6329
|
+
try {
|
|
6330
|
+
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
6331
|
+
if (existsSync9(resolved)) {
|
|
6332
|
+
const stat = statSync2(resolved);
|
|
6333
|
+
if (stat.isFile()) return resolved;
|
|
6334
|
+
}
|
|
6335
|
+
} catch {
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
6341
|
+
while ((m = openRe.exec(code)) !== null) {
|
|
6342
|
+
const p = m[1];
|
|
6343
|
+
try {
|
|
6344
|
+
const resolved = resolvePath2(p);
|
|
6345
|
+
if (existsSync9(resolved)) {
|
|
6346
|
+
const stat = statSync2(resolved);
|
|
6347
|
+
if (stat.isFile()) return resolved;
|
|
6348
|
+
}
|
|
6349
|
+
} catch {
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6352
|
+
return null;
|
|
6353
|
+
}
|
|
6194
6354
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
6195
|
-
let
|
|
6196
|
-
while ((
|
|
6197
|
-
const p =
|
|
6355
|
+
let m2;
|
|
6356
|
+
while ((m2 = re.exec(actionDetail)) !== null) {
|
|
6357
|
+
const p = m2[1];
|
|
6198
6358
|
let resolved;
|
|
6199
6359
|
try {
|
|
6200
6360
|
resolved = resolvePath2(p);
|
|
@@ -6579,7 +6739,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
6579
6739
|
riskType: riskType || "",
|
|
6580
6740
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
6581
6741
|
source: "llm",
|
|
6582
|
-
violatedLayers: ["
|
|
6742
|
+
violatedLayers: ["L3.1"]
|
|
6583
6743
|
};
|
|
6584
6744
|
} catch (err) {
|
|
6585
6745
|
if (abortController.signal.aborted) {
|
|
@@ -7140,7 +7300,139 @@ init_esm_shims();
|
|
|
7140
7300
|
// src/engine/formal-engine/builder.ts
|
|
7141
7301
|
init_esm_shims();
|
|
7142
7302
|
init_rules_loader();
|
|
7143
|
-
|
|
7303
|
+
|
|
7304
|
+
// src/engine/formal-engine/decode.ts
|
|
7305
|
+
init_esm_shims();
|
|
7306
|
+
var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
7307
|
+
function isValidBase64Token(tok) {
|
|
7308
|
+
if (tok.length < 8) return false;
|
|
7309
|
+
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
7310
|
+
return true;
|
|
7311
|
+
}
|
|
7312
|
+
function isPrintable(s) {
|
|
7313
|
+
for (let i = 0; i < s.length; i++) {
|
|
7314
|
+
const c = s.charCodeAt(i);
|
|
7315
|
+
if (c < 32 || c > 126) return false;
|
|
7316
|
+
}
|
|
7317
|
+
return true;
|
|
7318
|
+
}
|
|
7319
|
+
function decodeBase64Literals(text) {
|
|
7320
|
+
const out = [];
|
|
7321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7322
|
+
for (const m of text.matchAll(B64_TOKEN_RE)) {
|
|
7323
|
+
const tok = m[0];
|
|
7324
|
+
if (!isValidBase64Token(tok)) continue;
|
|
7325
|
+
if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
|
|
7326
|
+
try {
|
|
7327
|
+
const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
|
|
7328
|
+
const bin = Buffer.from(padded, "base64");
|
|
7329
|
+
const s = bin.toString("utf8");
|
|
7330
|
+
if (s && isPrintable(s) && !seen.has(s)) {
|
|
7331
|
+
seen.add(s);
|
|
7332
|
+
out.push(s);
|
|
7333
|
+
}
|
|
7334
|
+
} catch {
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
return out;
|
|
7338
|
+
}
|
|
7339
|
+
var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
|
|
7340
|
+
var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
|
|
7341
|
+
function stripStringLiterals(text) {
|
|
7342
|
+
const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
|
|
7343
|
+
if (!m) return text;
|
|
7344
|
+
const cmd = m[1].split("/").pop().toLowerCase();
|
|
7345
|
+
if (!OUTPUT_CMDS.has(cmd)) return text;
|
|
7346
|
+
return text.replace(QUOTED_STR_RE, "");
|
|
7347
|
+
}
|
|
7348
|
+
function decodeUrlAndNumeric(text) {
|
|
7349
|
+
const out = [];
|
|
7350
|
+
let s = text;
|
|
7351
|
+
for (let i = 0; i < 3; i++) {
|
|
7352
|
+
let nxt = s;
|
|
7353
|
+
try {
|
|
7354
|
+
nxt = decodeURIComponent(s);
|
|
7355
|
+
} catch {
|
|
7356
|
+
break;
|
|
7357
|
+
}
|
|
7358
|
+
if (nxt === s) break;
|
|
7359
|
+
s = nxt;
|
|
7360
|
+
}
|
|
7361
|
+
if (s !== text) out.push(s);
|
|
7362
|
+
if (/\\[0-7]{3}/.test(text)) {
|
|
7363
|
+
let expanded = "";
|
|
7364
|
+
try {
|
|
7365
|
+
expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
|
|
7366
|
+
} catch {
|
|
7367
|
+
}
|
|
7368
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
7369
|
+
}
|
|
7370
|
+
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
7371
|
+
let expanded = "";
|
|
7372
|
+
try {
|
|
7373
|
+
expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
|
|
7374
|
+
} catch {
|
|
7375
|
+
}
|
|
7376
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
7377
|
+
}
|
|
7378
|
+
return out;
|
|
7379
|
+
}
|
|
7380
|
+
function allCandidates(detail, max = 8) {
|
|
7381
|
+
const shell = stripStringLiterals(detail);
|
|
7382
|
+
const cands = [shell];
|
|
7383
|
+
cands.push(...decodeBase64Literals(shell));
|
|
7384
|
+
cands.push(...decodeUrlAndNumeric(shell));
|
|
7385
|
+
cands.push(...decodeBase64Literals(detail));
|
|
7386
|
+
cands.push(...decodeUrlAndNumeric(detail));
|
|
7387
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7388
|
+
const out = [];
|
|
7389
|
+
for (const c of cands) {
|
|
7390
|
+
if (seen.has(c) || c.length > 4096) continue;
|
|
7391
|
+
seen.add(c);
|
|
7392
|
+
out.push(c);
|
|
7393
|
+
if (out.length >= max) break;
|
|
7394
|
+
}
|
|
7395
|
+
return out;
|
|
7396
|
+
}
|
|
7397
|
+
var STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
|
|
7398
|
+
var PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
|
|
7399
|
+
var LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
|
|
7400
|
+
var LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
|
|
7401
|
+
function matchStageAny(detail, stages) {
|
|
7402
|
+
const cands = allCandidates(detail);
|
|
7403
|
+
if (cands.length === 0) return "";
|
|
7404
|
+
const shell = cands[0];
|
|
7405
|
+
const hits = [];
|
|
7406
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
7407
|
+
for (const p of pats) {
|
|
7408
|
+
p.lastIndex = 0;
|
|
7409
|
+
if (p.test(shell)) {
|
|
7410
|
+
hits.push(name);
|
|
7411
|
+
break;
|
|
7412
|
+
}
|
|
7413
|
+
}
|
|
7414
|
+
}
|
|
7415
|
+
if (hits.length > 0) {
|
|
7416
|
+
if (hits.length === 1) return hits[0];
|
|
7417
|
+
if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
|
|
7418
|
+
if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
|
|
7419
|
+
return "LISTEN";
|
|
7420
|
+
}
|
|
7421
|
+
for (const s of STAGE_PRIORITY) {
|
|
7422
|
+
if (hits.includes(s)) return s;
|
|
7423
|
+
}
|
|
7424
|
+
if (hits.includes("EXEC")) return "EXEC";
|
|
7425
|
+
}
|
|
7426
|
+
for (const cand of cands.slice(1)) {
|
|
7427
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
7428
|
+
for (const p of pats) {
|
|
7429
|
+
p.lastIndex = 0;
|
|
7430
|
+
if (p.test(cand)) return name;
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
return "";
|
|
7435
|
+
}
|
|
7144
7436
|
|
|
7145
7437
|
// src/engine/formal-engine/ltl.ts
|
|
7146
7438
|
init_esm_shims();
|
|
@@ -7174,11 +7466,7 @@ function compileAps(apPatterns) {
|
|
|
7174
7466
|
}
|
|
7175
7467
|
function labelEvent(actionDetail) {
|
|
7176
7468
|
if (!AP_RE) compileAps(loadMC().ap_patterns || {});
|
|
7177
|
-
|
|
7178
|
-
try {
|
|
7179
|
-
candidates = (init_decode(), __toCommonJS(decode_exports)).allCandidates(actionDetail);
|
|
7180
|
-
} catch {
|
|
7181
|
-
}
|
|
7469
|
+
const candidates = allCandidates(actionDetail);
|
|
7182
7470
|
const hits = /* @__PURE__ */ new Set();
|
|
7183
7471
|
for (const [name, pats] of Object.entries(AP_RE || {})) {
|
|
7184
7472
|
for (const c of candidates) {
|
|
@@ -7416,7 +7704,7 @@ function loadCrossSessionChains() {
|
|
|
7416
7704
|
return CROSS_SESSION_CHAINS;
|
|
7417
7705
|
}
|
|
7418
7706
|
var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
7419
|
-
var EXFIL_RE = /https
|
|
7707
|
+
var EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
|
|
7420
7708
|
function checkCrossSessionChain(graph) {
|
|
7421
7709
|
const violations = [];
|
|
7422
7710
|
const nodes = allNodes(graph);
|
|
@@ -7505,7 +7793,7 @@ function checkSensitiveDataFlow(graph) {
|
|
|
7505
7793
|
const n = nodes[i];
|
|
7506
7794
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
7507
7795
|
violations.push({
|
|
7508
|
-
propertyId: "sensitive-data-
|
|
7796
|
+
propertyId: "sensitive-data-flow",
|
|
7509
7797
|
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
7798
|
riskLevel: "critical",
|
|
7511
7799
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -7517,9 +7805,67 @@ function checkSensitiveDataFlow(graph) {
|
|
|
7517
7805
|
}
|
|
7518
7806
|
var EBPF_NET_RE = /connect|sendto|network_connect/i;
|
|
7519
7807
|
var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
|
|
7520
|
-
var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
|
|
7521
7808
|
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;
|
|
7522
7809
|
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;
|
|
7810
|
+
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;
|
|
7811
|
+
function extractInlineShell(s) {
|
|
7812
|
+
const out = [];
|
|
7813
|
+
let m;
|
|
7814
|
+
INLINE_SHELL_RE.lastIndex = 0;
|
|
7815
|
+
while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
|
|
7816
|
+
let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
|
|
7817
|
+
if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
|
|
7818
|
+
g = g.replace(/['"\[\],]+/g, " ");
|
|
7819
|
+
}
|
|
7820
|
+
g = g.trim();
|
|
7821
|
+
if (g) out.push(g);
|
|
7822
|
+
}
|
|
7823
|
+
return out;
|
|
7824
|
+
}
|
|
7825
|
+
var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
|
|
7826
|
+
var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
|
|
7827
|
+
function containsPrivesc(shell) {
|
|
7828
|
+
if (!shell) return false;
|
|
7829
|
+
for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
|
|
7830
|
+
const s = seg.trim();
|
|
7831
|
+
if (!s) continue;
|
|
7832
|
+
const toks = s.split(/\s+/);
|
|
7833
|
+
const head = (toks[0] || "").toLowerCase();
|
|
7834
|
+
const rest = toks.slice(1).join(" ");
|
|
7835
|
+
if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
|
|
7836
|
+
if (head === "setcap" && /cap_/.test(rest)) return true;
|
|
7837
|
+
if (head === "pkexec" || head === "doas") return true;
|
|
7838
|
+
if (head === "sudo") {
|
|
7839
|
+
if (/^-u\s+/.test(rest)) {
|
|
7840
|
+
if (/^-u\s+root\b/.test(rest)) return true;
|
|
7841
|
+
continue;
|
|
7842
|
+
}
|
|
7843
|
+
const m = /^(\S+)(.*)$/.exec(rest);
|
|
7844
|
+
if (m) {
|
|
7845
|
+
const nxt = m[1].toLowerCase();
|
|
7846
|
+
const tail = m[2];
|
|
7847
|
+
if (PKG_MGR.has(nxt)) continue;
|
|
7848
|
+
if (SHELLS.has(nxt)) return true;
|
|
7849
|
+
if (nxt === "-i" || nxt === "-s") return true;
|
|
7850
|
+
if (nxt.startsWith("su")) return true;
|
|
7851
|
+
if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
|
|
7852
|
+
}
|
|
7853
|
+
if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
|
|
7854
|
+
if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
|
|
7855
|
+
if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
|
|
7856
|
+
if (/\bvisudo\b/.test(rest)) return true;
|
|
7857
|
+
if (/\bpasswd\s+root\b/.test(rest)) return true;
|
|
7858
|
+
if (/\buseradd\b/.test(rest)) return true;
|
|
7859
|
+
}
|
|
7860
|
+
if (head === "su") {
|
|
7861
|
+
if (!rest) return true;
|
|
7862
|
+
if (/^-l?\s*$/.test(rest)) return true;
|
|
7863
|
+
if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
|
|
7867
|
+
return false;
|
|
7868
|
+
}
|
|
7523
7869
|
function isExfilNode(n) {
|
|
7524
7870
|
if (n.stage === "EXFIL") return true;
|
|
7525
7871
|
if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
|
|
@@ -7534,8 +7880,12 @@ function isCredNode(n) {
|
|
|
7534
7880
|
return CRED_STRICT_RE.test(n.actionDetail);
|
|
7535
7881
|
}
|
|
7536
7882
|
function isPrivescNode(n) {
|
|
7537
|
-
|
|
7538
|
-
|
|
7883
|
+
const shell = stripStringLiterals(n.actionDetail);
|
|
7884
|
+
if (containsPrivesc(shell)) return true;
|
|
7885
|
+
for (const cmd of extractInlineShell(shell)) {
|
|
7886
|
+
if (containsPrivesc(cmd)) return true;
|
|
7887
|
+
}
|
|
7888
|
+
return false;
|
|
7539
7889
|
}
|
|
7540
7890
|
function checkNeverExfilAfterPrivesc(graph) {
|
|
7541
7891
|
const violations = [];
|
|
@@ -7556,7 +7906,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
7556
7906
|
if (pre) {
|
|
7557
7907
|
const post = nodes[firstEv];
|
|
7558
7908
|
return [{
|
|
7559
|
-
propertyId: "no-
|
|
7909
|
+
propertyId: "no-exfil-after-privesc",
|
|
7560
7910
|
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
7911
|
riskLevel: "critical",
|
|
7562
7912
|
path: [
|
|
@@ -7572,7 +7922,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
7572
7922
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7573
7923
|
if (isExfilNode(nodes[j])) {
|
|
7574
7924
|
violations.push({
|
|
7575
|
-
propertyId: "no-
|
|
7925
|
+
propertyId: "no-exfil-after-privesc",
|
|
7576
7926
|
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
7927
|
riskLevel: "critical",
|
|
7578
7928
|
path: [
|
|
@@ -7605,7 +7955,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7605
7955
|
if (pre) {
|
|
7606
7956
|
const post = nodes[firstEv];
|
|
7607
7957
|
return [{
|
|
7608
|
-
propertyId: "no-
|
|
7958
|
+
propertyId: "no-exfil-after-credential",
|
|
7609
7959
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7610
7960
|
riskLevel: "critical",
|
|
7611
7961
|
path: [
|
|
@@ -7621,7 +7971,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7621
7971
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7622
7972
|
if (isExfilNode(nodes[j])) {
|
|
7623
7973
|
violations.push({
|
|
7624
|
-
propertyId: "no-
|
|
7974
|
+
propertyId: "no-exfil-after-credential",
|
|
7625
7975
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7626
7976
|
riskLevel: "critical",
|
|
7627
7977
|
path: [
|
|
@@ -7691,7 +8041,7 @@ function checkLowIntegrityCommandExec(graph) {
|
|
|
7691
8041
|
const n = nodes[i];
|
|
7692
8042
|
if (lowAt.has(i) && isComputeNode(n)) {
|
|
7693
8043
|
violations.push({
|
|
7694
|
-
propertyId: "low-integrity-
|
|
8044
|
+
propertyId: "low-integrity-command-exec",
|
|
7695
8045
|
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
8046
|
riskLevel: "critical",
|
|
7697
8047
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -7738,7 +8088,7 @@ function checkCrossSessionReachability(graph) {
|
|
|
7738
8088
|
const srcIdx = Math.min(...sources);
|
|
7739
8089
|
const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
|
|
7740
8090
|
violations.push({
|
|
7741
|
-
propertyId: "
|
|
8091
|
+
propertyId: "low-integrity-reachability",
|
|
7742
8092
|
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
8093
|
riskLevel: "high",
|
|
7744
8094
|
path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
|
|
@@ -7790,17 +8140,17 @@ function checkSensitiveDataAggregation(graph) {
|
|
|
7790
8140
|
return violations;
|
|
7791
8141
|
}
|
|
7792
8142
|
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-
|
|
8143
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
|
|
8144
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
|
|
8145
|
+
{ id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
|
|
8146
|
+
{ id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
|
|
8147
|
+
{ id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
|
|
7798
8148
|
// 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 }
|
|
8149
|
+
{ id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
|
|
8150
|
+
// P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
|
|
8151
|
+
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
|
|
8152
|
+
// P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
|
|
8153
|
+
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
|
|
7804
8154
|
];
|
|
7805
8155
|
|
|
7806
8156
|
// src/engine/formal-engine/engine.ts
|
|
@@ -7863,11 +8213,41 @@ var FormalEngine = class {
|
|
|
7863
8213
|
// src/engine/formal-engine/stateful.ts
|
|
7864
8214
|
init_esm_shims();
|
|
7865
8215
|
init_rules_loader();
|
|
7866
|
-
init_decode();
|
|
7867
8216
|
var DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
|
|
7868
8217
|
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
8218
|
var CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
7870
|
-
var EXFIL_RE2 = /https
|
|
8219
|
+
var EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
|
|
8220
|
+
var PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
|
|
8221
|
+
var BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
|
|
8222
|
+
var SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
|
|
8223
|
+
var UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
|
|
8224
|
+
var PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
|
|
8225
|
+
function extractInstances(actionDetail, stage) {
|
|
8226
|
+
const out = [];
|
|
8227
|
+
if (PORT_STAGE_RE.test(stage)) {
|
|
8228
|
+
const d = actionDetail;
|
|
8229
|
+
let m;
|
|
8230
|
+
const re = new RegExp(PORT_RE.source, "gi");
|
|
8231
|
+
while ((m = re.exec(d)) !== null) {
|
|
8232
|
+
const p = m[1] || m[2] || m[3];
|
|
8233
|
+
if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
|
|
8234
|
+
}
|
|
8235
|
+
if (/SCAN/.test(stage)) {
|
|
8236
|
+
const bareRe = new RegExp(BARE_PORT_RE.source, "g");
|
|
8237
|
+
while ((m = bareRe.exec(d)) !== null) {
|
|
8238
|
+
if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
m = SOCK_RE.exec(d);
|
|
8242
|
+
if (m && m[1]) out.push(`sock:${m[1]}`);
|
|
8243
|
+
m = UNIT_RE.exec(d);
|
|
8244
|
+
if (m && m[1]) out.push(`unit:${m[1]}`);
|
|
8245
|
+
}
|
|
8246
|
+
return out;
|
|
8247
|
+
}
|
|
8248
|
+
function buckKey(chainId, inst) {
|
|
8249
|
+
return inst ? `${chainId}@@${inst}` : chainId;
|
|
8250
|
+
}
|
|
7871
8251
|
var STAGES_PATTERNS = null;
|
|
7872
8252
|
var CHAINS = null;
|
|
7873
8253
|
function loadRules() {
|
|
@@ -7910,7 +8290,11 @@ function lifecycleFor(detail, stage, isError) {
|
|
|
7910
8290
|
return "";
|
|
7911
8291
|
}
|
|
7912
8292
|
var MAX_NODES = 1e5;
|
|
7913
|
-
var StatefulChainTracker = class {
|
|
8293
|
+
var StatefulChainTracker = class _StatefulChainTracker {
|
|
8294
|
+
/** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
|
|
8295
|
+
static now() {
|
|
8296
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8297
|
+
}
|
|
7914
8298
|
agentId;
|
|
7915
8299
|
nodes_ = [];
|
|
7916
8300
|
ptr = {};
|
|
@@ -7932,10 +8316,14 @@ var StatefulChainTracker = class {
|
|
|
7932
8316
|
for (const chain of chains) {
|
|
7933
8317
|
const stages = chain.stages || [];
|
|
7934
8318
|
if (stages.length === 0) continue;
|
|
7935
|
-
const
|
|
8319
|
+
const cid = String(chain.id || "");
|
|
8320
|
+
let matched = this.ptr[cid] || 0;
|
|
8321
|
+
for (const [k, v] of Object.entries(this.ptr)) {
|
|
8322
|
+
if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
|
|
8323
|
+
}
|
|
7936
8324
|
if (matched > 0 && matched < stages.length) {
|
|
7937
8325
|
const pf = {
|
|
7938
|
-
chainId:
|
|
8326
|
+
chainId: cid,
|
|
7939
8327
|
stages,
|
|
7940
8328
|
matched,
|
|
7941
8329
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
@@ -8011,8 +8399,10 @@ var StatefulChainTracker = class {
|
|
|
8011
8399
|
actionDetail: ad,
|
|
8012
8400
|
stage,
|
|
8013
8401
|
resources: extractResources2(ad),
|
|
8402
|
+
instances: extractInstances(ad, stage),
|
|
8014
8403
|
isError,
|
|
8015
|
-
lifecycle: lifecycleFor(ad, stage, isError)
|
|
8404
|
+
lifecycle: lifecycleFor(ad, stage, isError),
|
|
8405
|
+
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
8016
8406
|
};
|
|
8017
8407
|
this.nodes_.push(node);
|
|
8018
8408
|
this.seqNext += 1;
|
|
@@ -8020,11 +8410,19 @@ var StatefulChainTracker = class {
|
|
|
8020
8410
|
}
|
|
8021
8411
|
recordLifecycle(node) {
|
|
8022
8412
|
if (node.lifecycle === "dead") {
|
|
8023
|
-
|
|
8413
|
+
const clearedInsts = [];
|
|
8414
|
+
for (const inst of node.instances) {
|
|
8415
|
+
const key = `inst:${inst}`;
|
|
8416
|
+
if (key in this.aliveResources) {
|
|
8417
|
+
delete this.aliveResources[key];
|
|
8418
|
+
clearedInsts.push(inst);
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8421
|
+
let pathHit = false;
|
|
8024
8422
|
for (const r of node.resources) {
|
|
8025
8423
|
if (r in this.aliveResources) {
|
|
8026
8424
|
delete this.aliveResources[r];
|
|
8027
|
-
|
|
8425
|
+
pathHit = true;
|
|
8028
8426
|
}
|
|
8029
8427
|
}
|
|
8030
8428
|
const detail = node.actionDetail;
|
|
@@ -8032,16 +8430,30 @@ var StatefulChainTracker = class {
|
|
|
8032
8430
|
const base = r.split("/").pop() || "";
|
|
8033
8431
|
if (base && detail.includes(base)) {
|
|
8034
8432
|
delete this.aliveResources[r];
|
|
8035
|
-
|
|
8433
|
+
pathHit = true;
|
|
8036
8434
|
}
|
|
8037
8435
|
}
|
|
8038
|
-
if (
|
|
8436
|
+
if (clearedInsts.length > 0 && !pathHit) {
|
|
8437
|
+
for (const inst of clearedInsts) this.resetChainsForInstance(inst);
|
|
8438
|
+
} else {
|
|
8439
|
+
this.resetChainsOnDestroy();
|
|
8440
|
+
}
|
|
8039
8441
|
return;
|
|
8040
8442
|
}
|
|
8041
8443
|
if (node.lifecycle === "alive") {
|
|
8042
8444
|
for (const r of node.resources) {
|
|
8043
8445
|
this.aliveResources[r] = node.seq;
|
|
8044
8446
|
}
|
|
8447
|
+
for (const inst of node.instances) {
|
|
8448
|
+
this.aliveResources[`inst:${inst}`] = node.seq;
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8452
|
+
/** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
|
|
8453
|
+
resetChainsForInstance(inst) {
|
|
8454
|
+
const key = buckKey("", inst).slice(2);
|
|
8455
|
+
for (const k of Object.keys(this.ptr)) {
|
|
8456
|
+
if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
|
|
8045
8457
|
}
|
|
8046
8458
|
}
|
|
8047
8459
|
resetChainsOnDestroy() {
|
|
@@ -8049,6 +8461,29 @@ var StatefulChainTracker = class {
|
|
|
8049
8461
|
this.ptr[cid] = 0;
|
|
8050
8462
|
}
|
|
8051
8463
|
}
|
|
8464
|
+
/**
|
|
8465
|
+
* 当前节点应推进的链桶集合。
|
|
8466
|
+
* - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
|
|
8467
|
+
* 同时为所有链的该实例建立桶(若首次出现)。
|
|
8468
|
+
* - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
|
|
8469
|
+
* (信息不足时保守推进一切可能,避免漏判)。
|
|
8470
|
+
*/
|
|
8471
|
+
targetsForNode(node, chainId) {
|
|
8472
|
+
const insts = node.instances;
|
|
8473
|
+
if (insts.length > 0) {
|
|
8474
|
+
const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
|
|
8475
|
+
if (active.length > 0) return active.map((i) => buckKey(chainId, i));
|
|
8476
|
+
return [chainId];
|
|
8477
|
+
}
|
|
8478
|
+
const targets = [chainId];
|
|
8479
|
+
for (const k of Object.keys(this.aliveResources)) {
|
|
8480
|
+
if (k.startsWith("inst:")) {
|
|
8481
|
+
const inst = k.slice("inst:".length);
|
|
8482
|
+
targets.push(`${chainId}@@${inst}`);
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
return targets;
|
|
8486
|
+
}
|
|
8052
8487
|
advanceChains(node) {
|
|
8053
8488
|
if (node.lifecycle === "dead") return null;
|
|
8054
8489
|
if (node.lifecycle === "never_activated") {
|
|
@@ -8062,23 +8497,32 @@ var StatefulChainTracker = class {
|
|
|
8062
8497
|
const stages = chain.stages || [];
|
|
8063
8498
|
if (stages.length === 0) continue;
|
|
8064
8499
|
const cid = String(chain.id || "");
|
|
8065
|
-
const
|
|
8066
|
-
|
|
8067
|
-
this.ptr[
|
|
8068
|
-
if (cur
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8500
|
+
const targets = this.targetsForNode(node, cid);
|
|
8501
|
+
for (const key of targets) {
|
|
8502
|
+
const cur = this.ptr[key] || 0;
|
|
8503
|
+
if (cur < stages.length && stages[cur] === stage) {
|
|
8504
|
+
this.ptr[key] = cur + 1;
|
|
8505
|
+
if (cur + 1 >= stages.length) {
|
|
8506
|
+
return {
|
|
8507
|
+
chainId: cid,
|
|
8508
|
+
stages,
|
|
8509
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
8510
|
+
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
8511
|
+
path: this.chainPath(stages)
|
|
8512
|
+
};
|
|
8513
|
+
}
|
|
8076
8514
|
}
|
|
8077
8515
|
}
|
|
8078
8516
|
}
|
|
8079
8517
|
return null;
|
|
8080
8518
|
}
|
|
8081
8519
|
invalidateChainOnFailure(node) {
|
|
8520
|
+
if (node.instances.length > 0) {
|
|
8521
|
+
for (const inst of node.instances) {
|
|
8522
|
+
this.resetChainsForInstance(inst);
|
|
8523
|
+
}
|
|
8524
|
+
return;
|
|
8525
|
+
}
|
|
8082
8526
|
const failed = new Set(node.resources);
|
|
8083
8527
|
if (failed.size === 0) return;
|
|
8084
8528
|
const { chains } = loadRules();
|
|
@@ -8094,7 +8538,7 @@ var StatefulChainTracker = class {
|
|
|
8094
8538
|
let want = stages[0];
|
|
8095
8539
|
for (const n of this.nodes_) {
|
|
8096
8540
|
if (n.stage === want) {
|
|
8097
|
-
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
|
|
8541
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
|
|
8098
8542
|
const idx = stages.indexOf(n.stage);
|
|
8099
8543
|
if (idx + 1 < stages.length) {
|
|
8100
8544
|
want = stages[idx + 1];
|
|
@@ -8130,7 +8574,7 @@ var StatefulChainTracker = class {
|
|
|
8130
8574
|
if (!this.tainted.has(node.seq)) return [];
|
|
8131
8575
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
8132
8576
|
return [{
|
|
8133
|
-
property_id: "sensitive-data-
|
|
8577
|
+
property_id: "sensitive-data-flow",
|
|
8134
8578
|
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
8579
|
risk_level: "critical",
|
|
8136
8580
|
path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
|
|
@@ -8172,8 +8616,12 @@ var StatefulChainTracker = class {
|
|
|
8172
8616
|
actionDetail: String(raw.actionDetail || ""),
|
|
8173
8617
|
stage: String(raw.stage || ""),
|
|
8174
8618
|
resources: [...raw.resources || []],
|
|
8619
|
+
instances: [...raw.instances || []],
|
|
8620
|
+
// 旧快照无此字段 → 空
|
|
8175
8621
|
isError: Boolean(raw.isError),
|
|
8176
|
-
lifecycle: String(raw.lifecycle || "")
|
|
8622
|
+
lifecycle: String(raw.lifecycle || ""),
|
|
8623
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
|
|
8624
|
+
// 旧快照无此字段 → null
|
|
8177
8625
|
});
|
|
8178
8626
|
}
|
|
8179
8627
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
@@ -8402,7 +8850,7 @@ async function runAnalysis(input) {
|
|
|
8402
8850
|
riskType: node.l1RiskType,
|
|
8403
8851
|
confidence: 90,
|
|
8404
8852
|
source: "heuristic_script_scan",
|
|
8405
|
-
violatedLayers: ["
|
|
8853
|
+
violatedLayers: ["L1.2"]
|
|
8406
8854
|
};
|
|
8407
8855
|
}
|
|
8408
8856
|
}
|
|
@@ -8520,7 +8968,7 @@ async function runAnalysis(input) {
|
|
|
8520
8968
|
riskType: inlineJudgment.riskType,
|
|
8521
8969
|
confidence: 90,
|
|
8522
8970
|
source: "logic_rule",
|
|
8523
|
-
violatedLayers: ["1
|
|
8971
|
+
violatedLayers: ["L2.1"]
|
|
8524
8972
|
};
|
|
8525
8973
|
}
|
|
8526
8974
|
return {
|
|
@@ -8553,7 +9001,7 @@ async function runAnalysis(input) {
|
|
|
8553
9001
|
console.warn(
|
|
8554
9002
|
`[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
9003
|
);
|
|
8556
|
-
observedFormalLayers.push("2
|
|
9004
|
+
observedFormalLayers.push("L2.2");
|
|
8557
9005
|
} else {
|
|
8558
9006
|
return {
|
|
8559
9007
|
allowed: false,
|
|
@@ -8562,7 +9010,7 @@ async function runAnalysis(input) {
|
|
|
8562
9010
|
riskType: "formal_verification",
|
|
8563
9011
|
confidence: 90,
|
|
8564
9012
|
source: "formal_engine",
|
|
8565
|
-
violatedLayers: ["2
|
|
9013
|
+
violatedLayers: ["L2.2"]
|
|
8566
9014
|
};
|
|
8567
9015
|
}
|
|
8568
9016
|
}
|
|
@@ -8579,7 +9027,7 @@ async function runAnalysis(input) {
|
|
|
8579
9027
|
riskType: node.l2RiskType,
|
|
8580
9028
|
confidence: 85,
|
|
8581
9029
|
source: "logic_rule_script_scan",
|
|
8582
|
-
violatedLayers: ["
|
|
9030
|
+
violatedLayers: ["L2.3"]
|
|
8583
9031
|
};
|
|
8584
9032
|
}
|
|
8585
9033
|
}
|
|
@@ -8666,7 +9114,7 @@ async function runAnalysis(input) {
|
|
|
8666
9114
|
riskType: "",
|
|
8667
9115
|
confidence: 60,
|
|
8668
9116
|
source: "llm_failure_fallback",
|
|
8669
|
-
violatedLayers: ["
|
|
9117
|
+
violatedLayers: ["L3.1"]
|
|
8670
9118
|
};
|
|
8671
9119
|
}
|
|
8672
9120
|
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
@@ -9035,7 +9483,7 @@ async function analyzeRoute(c) {
|
|
|
9035
9483
|
const judgment = incMatchedToDeny(inc);
|
|
9036
9484
|
if (judgment) {
|
|
9037
9485
|
const layers = [...result.violatedLayers];
|
|
9038
|
-
if (!layers.includes("2
|
|
9486
|
+
if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
|
|
9039
9487
|
console.error(
|
|
9040
9488
|
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
9041
9489
|
);
|
|
@@ -9049,7 +9497,8 @@ async function analyzeRoute(c) {
|
|
|
9049
9497
|
policy: "",
|
|
9050
9498
|
violated_policy: "",
|
|
9051
9499
|
confidence: 90,
|
|
9052
|
-
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
|
|
9500
|
+
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
9501
|
+
violation_chain: judgment.violation_chain
|
|
9053
9502
|
})
|
|
9054
9503
|
);
|
|
9055
9504
|
}
|
|
@@ -9088,17 +9537,20 @@ function getChainStateStoreFor(_agentId) {
|
|
|
9088
9537
|
}
|
|
9089
9538
|
function incMatchedToDeny(inc) {
|
|
9090
9539
|
if (inc.matched) {
|
|
9540
|
+
const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
|
|
9091
9541
|
return {
|
|
9092
9542
|
property_id: "cross-" + inc.matched.chainId,
|
|
9093
9543
|
reason: inc.matched.reason,
|
|
9094
|
-
risk_level: inc.matched.riskLevel
|
|
9544
|
+
risk_level: inc.matched.riskLevel,
|
|
9545
|
+
violation_chain: chain
|
|
9095
9546
|
};
|
|
9096
9547
|
}
|
|
9097
9548
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
9098
9549
|
return {
|
|
9099
|
-
property_id: "sensitive-data-
|
|
9550
|
+
property_id: "sensitive-data-flow",
|
|
9100
9551
|
reason: inc.exfil[0].reason,
|
|
9101
|
-
risk_level: inc.exfil[0].risk_level
|
|
9552
|
+
risk_level: inc.exfil[0].risk_level,
|
|
9553
|
+
violation_chain: []
|
|
9102
9554
|
};
|
|
9103
9555
|
}
|
|
9104
9556
|
return null;
|
|
@@ -9224,7 +9676,7 @@ function createConsoleRouter() {
|
|
|
9224
9676
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
9225
9677
|
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
9226
9678
|
});
|
|
9227
|
-
function
|
|
9679
|
+
function getL22View() {
|
|
9228
9680
|
const ltlHuman = {
|
|
9229
9681
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9230
9682
|
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
@@ -9240,9 +9692,16 @@ function createConsoleRouter() {
|
|
|
9240
9692
|
};
|
|
9241
9693
|
const mc = loadMC();
|
|
9242
9694
|
const rules = mc.rules || [];
|
|
9695
|
+
let catEnabled = true;
|
|
9696
|
+
try {
|
|
9697
|
+
const rt = loadRuntimeConfig();
|
|
9698
|
+
catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
|
|
9699
|
+
} catch {
|
|
9700
|
+
}
|
|
9243
9701
|
return {
|
|
9244
9702
|
enabled_via: "L2_logic_rules",
|
|
9245
9703
|
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9704
|
+
category_enabled: catEnabled,
|
|
9246
9705
|
ltl_rules: rules.map((r) => ({
|
|
9247
9706
|
id: r.id,
|
|
9248
9707
|
type: r.type,
|
|
@@ -9255,10 +9714,10 @@ function createConsoleRouter() {
|
|
|
9255
9714
|
}))
|
|
9256
9715
|
};
|
|
9257
9716
|
}
|
|
9258
|
-
function
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
const rls =
|
|
9717
|
+
function withL22View(rc) {
|
|
9718
|
+
const l22 = rc && rc.L2_2;
|
|
9719
|
+
if (l22 && typeof l22 === "object" && !Array.isArray(l22) && l22.ltl_rules && typeof l22.ltl_rules === "object" && !Array.isArray(l22.ltl_rules)) {
|
|
9720
|
+
const rls = l22.ltl_rules.rules || [];
|
|
9262
9721
|
const ltlHumanLocal = {
|
|
9263
9722
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9264
9723
|
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
@@ -9272,9 +9731,10 @@ function createConsoleRouter() {
|
|
|
9272
9731
|
"mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
|
|
9273
9732
|
"mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
|
|
9274
9733
|
};
|
|
9275
|
-
rc.
|
|
9734
|
+
rc.L2_2 = {
|
|
9276
9735
|
enabled_via: "L2_logic_rules",
|
|
9277
9736
|
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9737
|
+
category_enabled: l22.ltl_rules.category_enabled !== false,
|
|
9278
9738
|
ltl_rules: rls.map((r) => ({
|
|
9279
9739
|
id: r.id,
|
|
9280
9740
|
type: "ltl",
|
|
@@ -9291,27 +9751,27 @@ function createConsoleRouter() {
|
|
|
9291
9751
|
}
|
|
9292
9752
|
console2.get("/api/rules", (c) => {
|
|
9293
9753
|
const rc = loadRuntimeConfig();
|
|
9294
|
-
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules,
|
|
9754
|
+
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
|
|
9295
9755
|
});
|
|
9296
9756
|
console2.put("/api/rules/enabled", async (c) => {
|
|
9297
9757
|
const b = await c.req.json();
|
|
9298
9758
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
9299
|
-
return c.json(
|
|
9759
|
+
return c.json(withL22View(rc));
|
|
9300
9760
|
});
|
|
9301
9761
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
9302
9762
|
const b = await c.req.json();
|
|
9303
9763
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
9304
|
-
return c.json(
|
|
9764
|
+
return c.json(withL22View(rc));
|
|
9305
9765
|
});
|
|
9306
9766
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
9307
9767
|
const b = await c.req.json();
|
|
9308
9768
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
9309
|
-
return c.json(
|
|
9769
|
+
return c.json(withL22View(rc));
|
|
9310
9770
|
});
|
|
9311
9771
|
console2.put("/api/categories/enabled", async (c) => {
|
|
9312
9772
|
const b = await c.req.json();
|
|
9313
9773
|
const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
|
|
9314
|
-
return c.json(rc);
|
|
9774
|
+
return c.json(withL22View(rc));
|
|
9315
9775
|
});
|
|
9316
9776
|
console2.post("/api/rules", async (c) => {
|
|
9317
9777
|
const b = await c.req.json();
|
|
@@ -9327,13 +9787,13 @@ function createConsoleRouter() {
|
|
|
9327
9787
|
credential: b.credential,
|
|
9328
9788
|
deny_mode: b.deny_mode
|
|
9329
9789
|
});
|
|
9330
|
-
return c.json(rc);
|
|
9790
|
+
return c.json(withL22View(rc));
|
|
9331
9791
|
});
|
|
9332
9792
|
console2.delete("/api/rules", async (c) => {
|
|
9333
9793
|
const b = await c.req.json();
|
|
9334
9794
|
deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
9335
9795
|
const rc = loadRuntimeConfig();
|
|
9336
|
-
return c.json(rc);
|
|
9796
|
+
return c.json(withL22View(rc));
|
|
9337
9797
|
});
|
|
9338
9798
|
console2.get("/api/skills", (c) => {
|
|
9339
9799
|
const rc = loadRuntimeConfig();
|
|
@@ -9342,23 +9802,23 @@ function createConsoleRouter() {
|
|
|
9342
9802
|
console2.put("/api/skills/enabled", async (c) => {
|
|
9343
9803
|
const b = await c.req.json();
|
|
9344
9804
|
const rc = updateSkillEnabled(b.skill_id, b.enabled);
|
|
9345
|
-
return c.json(rc);
|
|
9805
|
+
return c.json(withL22View(rc));
|
|
9346
9806
|
});
|
|
9347
9807
|
console2.put("/api/skill-categories/enabled", async (c) => {
|
|
9348
9808
|
const b = await c.req.json();
|
|
9349
9809
|
const rc = updateSkillCategoryEnabled(b.category, b.enabled);
|
|
9350
|
-
return c.json(rc);
|
|
9810
|
+
return c.json(withL22View(rc));
|
|
9351
9811
|
});
|
|
9352
9812
|
console2.post("/api/skills", async (c) => {
|
|
9353
9813
|
const b = await c.req.json();
|
|
9354
9814
|
const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
|
|
9355
|
-
return c.json(rc);
|
|
9815
|
+
return c.json(withL22View(rc));
|
|
9356
9816
|
});
|
|
9357
9817
|
console2.delete("/api/skills", async (c) => {
|
|
9358
9818
|
const b = await c.req.json();
|
|
9359
9819
|
deleteCustomSkill(b.skill_id, b.category);
|
|
9360
9820
|
const rc = loadRuntimeConfig();
|
|
9361
|
-
return c.json(rc);
|
|
9821
|
+
return c.json(withL22View(rc));
|
|
9362
9822
|
});
|
|
9363
9823
|
console2.get("/api/skills/:skill_id/content", async (c) => {
|
|
9364
9824
|
const skillId = c.req.param("skill_id");
|
|
@@ -9456,7 +9916,7 @@ function createConsoleRouter() {
|
|
|
9456
9916
|
});
|
|
9457
9917
|
console2.get("/api/config", (c) => {
|
|
9458
9918
|
const rc = loadRuntimeConfig();
|
|
9459
|
-
return c.json({ ...rc,
|
|
9919
|
+
return c.json({ ...rc, L2_2: getL22View() });
|
|
9460
9920
|
});
|
|
9461
9921
|
console2.get("/api/env-overrides", (c) => {
|
|
9462
9922
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -9464,7 +9924,7 @@ function createConsoleRouter() {
|
|
|
9464
9924
|
console2.post("/api/reset", (c) => {
|
|
9465
9925
|
const defaults = generateSourceDefaults();
|
|
9466
9926
|
saveRuntimeConfig(defaults);
|
|
9467
|
-
return c.json(defaults);
|
|
9927
|
+
return c.json(withL22View(defaults));
|
|
9468
9928
|
});
|
|
9469
9929
|
console2.get("/api/token-stats", (c) => {
|
|
9470
9930
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
@@ -9536,4 +9996,14 @@ export {
|
|
|
9536
9996
|
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
9537
9997
|
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|
|
9538
9998
|
//! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
|
|
9999
|
+
//!
|
|
10000
|
+
//! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
|
|
10001
|
+
//! P1 cross-session-chain 跨会话组合链
|
|
10002
|
+
//! P2 sensitive-data-flow 敏感数据传播
|
|
10003
|
+
//! P3 low-integrity-reachability 低完整性数据流可达
|
|
10004
|
+
//! P4 no-exfil-after-privesc 提权后禁外传
|
|
10005
|
+
//! P5 no-exfil-after-credential 凭据后禁外传
|
|
10006
|
+
//! P6 mc-ltl-rules LTL 模型检测
|
|
10007
|
+
//! P7 low-integrity-command-exec 低完整性命令执行
|
|
10008
|
+
//! P8 sensitive-data-aggregation 敏感数据聚合外传
|
|
9539
10009
|
//# sourceMappingURL=server.js.map
|