@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/cli.js
CHANGED
|
@@ -633,10 +633,15 @@ function loadMC() {
|
|
|
633
633
|
try {
|
|
634
634
|
const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
635
635
|
if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
|
|
636
|
-
const
|
|
636
|
+
const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
|
|
637
|
+
const rtRules = ltl.rules || [];
|
|
637
638
|
const enabledById = {};
|
|
638
639
|
for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
|
|
639
|
-
|
|
640
|
+
const catEnabled = ltl.category_enabled !== false;
|
|
641
|
+
for (const r of rules) {
|
|
642
|
+
if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
|
|
643
|
+
if (!catEnabled) r.enabled = false;
|
|
644
|
+
}
|
|
640
645
|
}
|
|
641
646
|
} catch {
|
|
642
647
|
}
|
|
@@ -681,7 +686,8 @@ function _loadSensitivePathScope(scope) {
|
|
|
681
686
|
path: sp.path,
|
|
682
687
|
riskLevel: sp.risk_level,
|
|
683
688
|
desc: sp.desc,
|
|
684
|
-
credential: sp.deny_mode === "deny_both" ? true : void 0
|
|
689
|
+
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
690
|
+
dirDelOnly: sp.dir_del_only === true ? true : void 0
|
|
685
691
|
}));
|
|
686
692
|
}
|
|
687
693
|
function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
|
|
@@ -779,7 +785,7 @@ function runLogicCheck(input) {
|
|
|
779
785
|
riskType: "intent_deviation",
|
|
780
786
|
confidence: 85,
|
|
781
787
|
source: "logic_rule",
|
|
782
|
-
violatedLayers: ["
|
|
788
|
+
violatedLayers: ["L2.1"]
|
|
783
789
|
};
|
|
784
790
|
}
|
|
785
791
|
}
|
|
@@ -803,7 +809,7 @@ function runLogicCheck(input) {
|
|
|
803
809
|
riskType: "privilege_escalation",
|
|
804
810
|
confidence: 85,
|
|
805
811
|
source: "logic_rule",
|
|
806
|
-
violatedLayers: ["
|
|
812
|
+
violatedLayers: ["L2.1"]
|
|
807
813
|
};
|
|
808
814
|
}
|
|
809
815
|
}
|
|
@@ -855,7 +861,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
855
861
|
riskType: "combo_chain",
|
|
856
862
|
confidence: 85,
|
|
857
863
|
source: "logic_rule",
|
|
858
|
-
violatedLayers: ["
|
|
864
|
+
violatedLayers: ["L2.1"]
|
|
859
865
|
};
|
|
860
866
|
}
|
|
861
867
|
}
|
|
@@ -980,7 +986,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
980
986
|
riskType: "read_before_write",
|
|
981
987
|
confidence: 85,
|
|
982
988
|
source: "logic_rule",
|
|
983
|
-
violatedLayers: ["
|
|
989
|
+
violatedLayers: ["L2.1"]
|
|
984
990
|
};
|
|
985
991
|
}
|
|
986
992
|
return null;
|
|
@@ -1000,7 +1006,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
1000
1006
|
riskType: "consent_missing",
|
|
1001
1007
|
confidence: 85,
|
|
1002
1008
|
source: "logic_rule",
|
|
1003
|
-
violatedLayers: ["
|
|
1009
|
+
violatedLayers: ["L2.1"]
|
|
1004
1010
|
};
|
|
1005
1011
|
}
|
|
1006
1012
|
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
@@ -1018,7 +1024,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
1018
1024
|
riskType: "consent_missing",
|
|
1019
1025
|
confidence: 85,
|
|
1020
1026
|
source: "logic_rule",
|
|
1021
|
-
violatedLayers: ["
|
|
1027
|
+
violatedLayers: ["L2.1"]
|
|
1022
1028
|
};
|
|
1023
1029
|
}
|
|
1024
1030
|
function containsPrivilegeEscalation(actionDetail) {
|
|
@@ -1078,7 +1084,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1078
1084
|
riskType: "file_access",
|
|
1079
1085
|
confidence: 95,
|
|
1080
1086
|
source: "logic_rule",
|
|
1081
|
-
violatedLayers: ["
|
|
1087
|
+
violatedLayers: ["L2.1"],
|
|
1082
1088
|
isCredential: true
|
|
1083
1089
|
};
|
|
1084
1090
|
}
|
|
@@ -1094,7 +1100,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1094
1100
|
riskType: "file_access",
|
|
1095
1101
|
confidence: 95,
|
|
1096
1102
|
source: "logic_rule",
|
|
1097
|
-
violatedLayers: ["
|
|
1103
|
+
violatedLayers: ["L2.1"]
|
|
1098
1104
|
};
|
|
1099
1105
|
}
|
|
1100
1106
|
}
|
|
@@ -1108,7 +1114,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1108
1114
|
riskType: "file_access",
|
|
1109
1115
|
confidence: 70,
|
|
1110
1116
|
source: "logic_rule",
|
|
1111
|
-
violatedLayers: ["
|
|
1117
|
+
violatedLayers: ["L2.1"]
|
|
1112
1118
|
};
|
|
1113
1119
|
}
|
|
1114
1120
|
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
|
|
@@ -1120,7 +1126,7 @@ var init_logic_rules = __esm({
|
|
|
1120
1126
|
init_rules_loader();
|
|
1121
1127
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1122
1128
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1123
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
|
|
1129
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
|
|
1124
1130
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1125
1131
|
"ask_user_question",
|
|
1126
1132
|
"glob",
|
|
@@ -1433,6 +1439,7 @@ __export(runtime_config_exports, {
|
|
|
1433
1439
|
isLayerEnabled: () => isLayerEnabled,
|
|
1434
1440
|
loadRuntimeConfig: () => loadRuntimeConfig,
|
|
1435
1441
|
mergeConfig: () => mergeConfig,
|
|
1442
|
+
pickDeletableRule: () => pickDeletableRule,
|
|
1436
1443
|
saveRuntimeConfig: () => saveRuntimeConfig,
|
|
1437
1444
|
updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
|
|
1438
1445
|
updateAgentRuleEnabled: () => updateAgentRuleEnabled,
|
|
@@ -1471,7 +1478,7 @@ function emptyRuntimeConfig() {
|
|
|
1471
1478
|
L1_rules: {},
|
|
1472
1479
|
L2_rules: {},
|
|
1473
1480
|
L3_skills: {},
|
|
1474
|
-
|
|
1481
|
+
L2_2: {},
|
|
1475
1482
|
agent_rules: {},
|
|
1476
1483
|
scenario_rules: {}
|
|
1477
1484
|
};
|
|
@@ -1499,7 +1506,7 @@ function loadRuntimeConfig() {
|
|
|
1499
1506
|
L1_rules: merged.L1_rules || {},
|
|
1500
1507
|
L2_rules: merged.L2_rules || {},
|
|
1501
1508
|
L3_skills: merged.L3_skills || {},
|
|
1502
|
-
|
|
1509
|
+
L2_2: merged.L2_2 || {},
|
|
1503
1510
|
agent_rules: merged.agent_rules || {},
|
|
1504
1511
|
scenario_rules: merged.scenario_rules || {}
|
|
1505
1512
|
};
|
|
@@ -1552,6 +1559,7 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1552
1559
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1553
1560
|
if (r.credential) out.credential = true;
|
|
1554
1561
|
if (r.read_only) out.read_only = true;
|
|
1562
|
+
if (r.dir_del_only) out.dir_del_only = true;
|
|
1555
1563
|
return out;
|
|
1556
1564
|
});
|
|
1557
1565
|
}
|
|
@@ -1597,8 +1605,16 @@ function getDisabledL2RulesWithSkipL3() {
|
|
|
1597
1605
|
function _slugify(text) {
|
|
1598
1606
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
|
|
1599
1607
|
}
|
|
1608
|
+
function _fnv1a32(text) {
|
|
1609
|
+
let h = 2166136261;
|
|
1610
|
+
for (const b of Buffer.from(text, "utf-8")) {
|
|
1611
|
+
h ^= b;
|
|
1612
|
+
h = h * 16777619 >>> 0;
|
|
1613
|
+
}
|
|
1614
|
+
return h.toString(16).padStart(8, "0");
|
|
1615
|
+
}
|
|
1600
1616
|
function _generatePatternId(pattern) {
|
|
1601
|
-
return `pat_${_slugify(pattern)}`;
|
|
1617
|
+
return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
|
|
1602
1618
|
}
|
|
1603
1619
|
function _deriveDenyMode(rule) {
|
|
1604
1620
|
if (rule.deny_mode) return rule.deny_mode;
|
|
@@ -1660,6 +1676,7 @@ function _l2SensitivePathsToRules() {
|
|
|
1660
1676
|
builtin: true
|
|
1661
1677
|
};
|
|
1662
1678
|
_syncDenyModeFields(rule);
|
|
1679
|
+
if (sp.dirDelOnly) rule.dir_del_only = true;
|
|
1663
1680
|
return rule;
|
|
1664
1681
|
});
|
|
1665
1682
|
}
|
|
@@ -1876,17 +1893,20 @@ function generateSourceDefaults() {
|
|
|
1876
1893
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1877
1894
|
},
|
|
1878
1895
|
L3_skills: _l3SkillsToRules(),
|
|
1879
|
-
|
|
1896
|
+
L2_2: _ltlRulesToRuntime(),
|
|
1880
1897
|
agent_rules: _agentRulesToDefaults(),
|
|
1881
1898
|
scenario_rules: _scenarioRulesToDefaults()
|
|
1882
1899
|
};
|
|
1883
1900
|
}
|
|
1884
1901
|
function _ltlRulesToRuntime() {
|
|
1885
1902
|
const cands = [
|
|
1886
|
-
join3(
|
|
1887
|
-
join3(__dirname4, "
|
|
1888
|
-
join3(
|
|
1889
|
-
|
|
1903
|
+
getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
|
|
1904
|
+
join3(__dirname4, "../rules"),
|
|
1905
|
+
join3(__dirname4, "rules"),
|
|
1906
|
+
join3(__dirname4, "../../../agent_moss/rules"),
|
|
1907
|
+
join3(__dirname4, "../../../../agent_moss/rules"),
|
|
1908
|
+
join3(process.cwd(), "agent_moss/rules")
|
|
1909
|
+
].filter(Boolean);
|
|
1890
1910
|
let mc = {};
|
|
1891
1911
|
for (const dir of cands) {
|
|
1892
1912
|
const p = join3(dir, "mc_rules.json");
|
|
@@ -2064,7 +2084,11 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
2064
2084
|
);
|
|
2065
2085
|
_mergeRuleCategories(cfg, srcDefaults, "L1_rules");
|
|
2066
2086
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
2067
|
-
|
|
2087
|
+
if (!cfg.L2_2 && cfg.L2_4) {
|
|
2088
|
+
cfg.L2_2 = cfg.L2_4;
|
|
2089
|
+
delete cfg.L2_4;
|
|
2090
|
+
}
|
|
2091
|
+
_mergeRuleCategories(cfg, srcDefaults, "L2_2");
|
|
2068
2092
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
2069
2093
|
_mergeAgentRules(cfg, srcDefaults);
|
|
2070
2094
|
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
@@ -2242,16 +2266,20 @@ function addCustomRule(layer, category, rule) {
|
|
|
2242
2266
|
saveRuntimeConfig(runtime);
|
|
2243
2267
|
return runtime;
|
|
2244
2268
|
}
|
|
2245
|
-
function
|
|
2246
|
-
const
|
|
2247
|
-
const rules = runtime[layer]?.[category]?.rules || [];
|
|
2248
|
-
const idx = rules.findIndex((r) => r.id === ruleId);
|
|
2269
|
+
function pickDeletableRule(rules, ruleId) {
|
|
2270
|
+
const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
|
|
2249
2271
|
if (idx >= 0) {
|
|
2250
|
-
const r = rules[idx];
|
|
2251
|
-
if (r.builtin && !r.source_removed) return null;
|
|
2252
2272
|
rules.splice(idx, 1);
|
|
2253
|
-
|
|
2273
|
+
return rules;
|
|
2254
2274
|
}
|
|
2275
|
+
if (rules.some((r) => r.id === ruleId)) return null;
|
|
2276
|
+
return rules;
|
|
2277
|
+
}
|
|
2278
|
+
function deleteCustomRule(layer, category, ruleId) {
|
|
2279
|
+
const runtime = loadRuntimeConfig();
|
|
2280
|
+
const rules = runtime[layer]?.[category]?.rules || [];
|
|
2281
|
+
if (pickDeletableRule(rules, ruleId) === null) return null;
|
|
2282
|
+
saveRuntimeConfig(runtime);
|
|
2255
2283
|
return runtime;
|
|
2256
2284
|
}
|
|
2257
2285
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
@@ -2684,7 +2712,7 @@ function runHeuristicCheck(input) {
|
|
|
2684
2712
|
riskType: "sensitive_action",
|
|
2685
2713
|
confidence: 90,
|
|
2686
2714
|
source: "heuristic",
|
|
2687
|
-
violatedLayers: ["
|
|
2715
|
+
violatedLayers: ["L1.1"]
|
|
2688
2716
|
};
|
|
2689
2717
|
}
|
|
2690
2718
|
}
|
|
@@ -2694,8 +2722,10 @@ function runHeuristicCheck(input) {
|
|
|
2694
2722
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2695
2723
|
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
2696
2724
|
let topRiskType = "";
|
|
2725
|
+
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2697
2726
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
2698
2727
|
if (rule.pattern.test(scanCommand)) {
|
|
2728
|
+
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2699
2729
|
matched.push(rule.description);
|
|
2700
2730
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
2701
2731
|
highestSeverity = rule.severity;
|
|
@@ -2737,10 +2767,26 @@ function runHeuristicCheck(input) {
|
|
|
2737
2767
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2738
2768
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2739
2769
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2770
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2771
|
+
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2772
|
+
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2773
|
+
if (mergeResults.length > 0) {
|
|
2774
|
+
let best = mergeResults[0];
|
|
2775
|
+
for (const r of mergeResults) {
|
|
2776
|
+
if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
|
|
2777
|
+
}
|
|
2778
|
+
if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
|
|
2779
|
+
return {
|
|
2780
|
+
allowed: false,
|
|
2781
|
+
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
2782
|
+
riskLevel: highestSeverity,
|
|
2783
|
+
riskType: highestRiskType,
|
|
2784
|
+
confidence: 95,
|
|
2785
|
+
source: "heuristic",
|
|
2786
|
+
violatedLayers: ["L1.1"]
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
return best;
|
|
2744
2790
|
}
|
|
2745
2791
|
if (matched.length > 0) {
|
|
2746
2792
|
return {
|
|
@@ -2750,8 +2796,7 @@ function runHeuristicCheck(input) {
|
|
|
2750
2796
|
riskType: highestRiskType,
|
|
2751
2797
|
confidence: 95,
|
|
2752
2798
|
source: "heuristic",
|
|
2753
|
-
violatedLayers: ["
|
|
2754
|
-
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2799
|
+
violatedLayers: ["L1.1"]
|
|
2755
2800
|
};
|
|
2756
2801
|
}
|
|
2757
2802
|
const lateralResult = checkLateralMovement(detailL);
|
|
@@ -2879,6 +2924,7 @@ function isSearchActionType(actionType) {
|
|
|
2879
2924
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2880
2925
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2881
2926
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2927
|
+
const keygenGenerate = isSshKeygenGenerate(command);
|
|
2882
2928
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2883
2929
|
const pathMatches = (text, path6) => {
|
|
2884
2930
|
const parts = path6.split(/(\(\?!.*?\))/);
|
|
@@ -2891,6 +2937,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2891
2937
|
};
|
|
2892
2938
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2893
2939
|
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2940
|
+
if (keygenGenerate) continue;
|
|
2894
2941
|
if (sp.path === "/dev/random") {
|
|
2895
2942
|
if (isWriteOpFlag) {
|
|
2896
2943
|
continue;
|
|
@@ -2902,7 +2949,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2902
2949
|
riskType: "file_access",
|
|
2903
2950
|
confidence: 90,
|
|
2904
2951
|
source: "heuristic",
|
|
2905
|
-
violatedLayers: ["
|
|
2952
|
+
violatedLayers: ["L1.1"]
|
|
2906
2953
|
};
|
|
2907
2954
|
}
|
|
2908
2955
|
if (!isWriteOpFlag && !sp.credential) {
|
|
@@ -2915,16 +2962,51 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2915
2962
|
riskType: "file_access",
|
|
2916
2963
|
confidence: 90,
|
|
2917
2964
|
source: "heuristic",
|
|
2918
|
-
violatedLayers: ["
|
|
2965
|
+
violatedLayers: ["L1.1"],
|
|
2919
2966
|
isCredential: !!sp.credential
|
|
2920
2967
|
};
|
|
2921
2968
|
}
|
|
2922
2969
|
}
|
|
2923
2970
|
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
2924
2971
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2972
|
+
let pPath = sp.path;
|
|
2973
|
+
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
2974
|
+
let dirPrefixMatch = null;
|
|
2975
|
+
if (sp.dirDelOnly) {
|
|
2976
|
+
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
2977
|
+
dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
|
|
2978
|
+
}
|
|
2979
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
|
|
2980
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
2981
|
+
if (pathReMatch || expandedReMatch) {
|
|
2982
|
+
if (keygenGenerate) continue;
|
|
2983
|
+
if (sp.dirDelOnly) {
|
|
2984
|
+
if (isDeleteActionForDir(command, sp.path)) {
|
|
2985
|
+
return {
|
|
2986
|
+
allowed: false,
|
|
2987
|
+
reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2988
|
+
riskLevel: sp.riskLevel,
|
|
2989
|
+
riskType: "file_access",
|
|
2990
|
+
confidence: 90,
|
|
2991
|
+
source: "heuristic",
|
|
2992
|
+
violatedLayers: ["L1.1"],
|
|
2993
|
+
isCredential: !!sp.credential
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
if (isArchiveReadOfDir(command, sp.path)) {
|
|
2997
|
+
return {
|
|
2998
|
+
allowed: false,
|
|
2999
|
+
reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
3000
|
+
riskLevel: sp.riskLevel,
|
|
3001
|
+
riskType: "file_access",
|
|
3002
|
+
confidence: 90,
|
|
3003
|
+
source: "heuristic",
|
|
3004
|
+
violatedLayers: ["L1.1"],
|
|
3005
|
+
isCredential: !!sp.credential
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
2928
3010
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2929
3011
|
continue;
|
|
2930
3012
|
}
|
|
@@ -2935,7 +3017,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2935
3017
|
riskType: "file_access",
|
|
2936
3018
|
confidence: 90,
|
|
2937
3019
|
source: "heuristic",
|
|
2938
|
-
violatedLayers: ["
|
|
3020
|
+
violatedLayers: ["L1.1"],
|
|
2939
3021
|
isCredential: !!sp.credential
|
|
2940
3022
|
};
|
|
2941
3023
|
}
|
|
@@ -2958,7 +3040,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2958
3040
|
riskType: "file_access",
|
|
2959
3041
|
confidence: 90,
|
|
2960
3042
|
source: "heuristic",
|
|
2961
|
-
violatedLayers: ["
|
|
3043
|
+
violatedLayers: ["L1.1"],
|
|
2962
3044
|
isCredential: !!sp.credential
|
|
2963
3045
|
};
|
|
2964
3046
|
}
|
|
@@ -2975,7 +3057,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2975
3057
|
riskType: "file_access",
|
|
2976
3058
|
confidence: 90,
|
|
2977
3059
|
source: "heuristic",
|
|
2978
|
-
violatedLayers: ["
|
|
3060
|
+
violatedLayers: ["L1.1"],
|
|
2979
3061
|
isCredential: true
|
|
2980
3062
|
};
|
|
2981
3063
|
}
|
|
@@ -2988,18 +3070,142 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2988
3070
|
riskType: "file_access",
|
|
2989
3071
|
confidence: 90,
|
|
2990
3072
|
source: "heuristic",
|
|
2991
|
-
violatedLayers: ["
|
|
3073
|
+
violatedLayers: ["L1.1"]
|
|
2992
3074
|
};
|
|
2993
3075
|
}
|
|
2994
3076
|
return null;
|
|
2995
3077
|
}
|
|
3078
|
+
function isHomeValue(path6) {
|
|
3079
|
+
let p = path6.trim().toLowerCase();
|
|
3080
|
+
if (p.startsWith("$(") && p.endsWith(")")) {
|
|
3081
|
+
const inner = p.slice(2, -1).trim();
|
|
3082
|
+
return HOME_TARGET_RE.test(" " + inner);
|
|
3083
|
+
}
|
|
3084
|
+
if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
|
|
3085
|
+
return HOME_TARGET_RE.test(" " + p);
|
|
3086
|
+
}
|
|
3087
|
+
function isDeleteCommand(seg) {
|
|
3088
|
+
const s = seg.trimStart();
|
|
3089
|
+
if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
|
|
3090
|
+
return /删除|移除/.test(s);
|
|
3091
|
+
}
|
|
3092
|
+
function homeEscapeTarget(seg) {
|
|
3093
|
+
const low = seg.toLowerCase();
|
|
3094
|
+
const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
|
|
3095
|
+
let m;
|
|
3096
|
+
while ((m = re.exec(low)) !== null) {
|
|
3097
|
+
const endIdx = m.index + m[0].length;
|
|
3098
|
+
const tail = low.slice(endIdx);
|
|
3099
|
+
if (!tail) continue;
|
|
3100
|
+
let token = tail.trimStart().split(/\s+/)[0] || "";
|
|
3101
|
+
token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
|
|
3102
|
+
token = token.replace(/^['"]|['"]$/g, "");
|
|
3103
|
+
const segments = token.split("/").filter((s) => s && s !== ".");
|
|
3104
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
3105
|
+
let depth = 0;
|
|
3106
|
+
let minDepth = 0;
|
|
3107
|
+
for (const s of segments) {
|
|
3108
|
+
depth += s === ".." ? -1 : 1;
|
|
3109
|
+
minDepth = Math.min(minDepth, depth);
|
|
3110
|
+
}
|
|
3111
|
+
if (minDepth < 0) return true;
|
|
3112
|
+
if (depth <= 0) return true;
|
|
3113
|
+
}
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
function checkHomeDeletion(actionType, actionDetail) {
|
|
3117
|
+
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
|
|
3118
|
+
const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
3119
|
+
for (const seg of segs) {
|
|
3120
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3121
|
+
if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
|
|
3122
|
+
return {
|
|
3123
|
+
allowed: false,
|
|
3124
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
|
|
3125
|
+
riskLevel: "high",
|
|
3126
|
+
riskType: "script_execution",
|
|
3127
|
+
confidence: 90,
|
|
3128
|
+
source: "heuristic",
|
|
3129
|
+
violatedLayers: ["L1.1"]
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
const assigned = {};
|
|
3134
|
+
for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
|
|
3135
|
+
for (const [varName, path6] of Object.entries(assigned)) {
|
|
3136
|
+
if (!isHomeValue(path6)) continue;
|
|
3137
|
+
const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
|
|
3138
|
+
for (const seg of segs) {
|
|
3139
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3140
|
+
if (varRefRe.test(seg)) {
|
|
3141
|
+
return {
|
|
3142
|
+
allowed: false,
|
|
3143
|
+
reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
|
|
3144
|
+
riskLevel: "high",
|
|
3145
|
+
riskType: "script_execution",
|
|
3146
|
+
confidence: 90,
|
|
3147
|
+
source: "heuristic",
|
|
3148
|
+
violatedLayers: ["L1.1"]
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
return null;
|
|
3154
|
+
}
|
|
3155
|
+
function escapeRegExp(s) {
|
|
3156
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3157
|
+
}
|
|
3158
|
+
function isDeleteActionForDir(command, dirPath) {
|
|
3159
|
+
const low = command.toLowerCase();
|
|
3160
|
+
const dpath = dirPath.toLowerCase();
|
|
3161
|
+
if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
|
|
3162
|
+
return true;
|
|
3163
|
+
}
|
|
3164
|
+
if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3168
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3169
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
|
|
3170
|
+
return true;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
return false;
|
|
3174
|
+
}
|
|
3175
|
+
function isArchiveReadOfDir(command, dirPath) {
|
|
3176
|
+
const low = command.toLowerCase();
|
|
3177
|
+
const dpath = dirPath.toLowerCase();
|
|
3178
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3179
|
+
const s = seg.trim();
|
|
3180
|
+
if (!s || !s.includes(dpath)) continue;
|
|
3181
|
+
const parts = s.split(/\s+/);
|
|
3182
|
+
const fw = parts[0] || "";
|
|
3183
|
+
if (fw === "zip") return true;
|
|
3184
|
+
if (fw === "tar") {
|
|
3185
|
+
if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
if (fw === "cp" || fw === "rsync") {
|
|
3189
|
+
const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
|
|
3190
|
+
if (segPaths.length && segPaths[0].includes(dpath)) return true;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3195
|
+
function isSshKeygenGenerate(command) {
|
|
3196
|
+
const low = command.toLowerCase();
|
|
3197
|
+
if (!low.includes("ssh-keygen")) return false;
|
|
3198
|
+
if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
|
|
3199
|
+
if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
|
|
3200
|
+
return true;
|
|
3201
|
+
}
|
|
2996
3202
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2997
3203
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2998
3204
|
const scan = stripHeredocContent(actionDetail);
|
|
2999
3205
|
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
3000
3206
|
const isTempPath = TEMP_DIRS.some(
|
|
3001
3207
|
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
3002
|
-
);
|
|
3208
|
+
) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
|
|
3003
3209
|
const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
|
|
3004
3210
|
if (!isTempPath && wildcardDeleteRe.test(scan)) {
|
|
3005
3211
|
return {
|
|
@@ -3009,7 +3215,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3009
3215
|
riskType: "script_execution",
|
|
3010
3216
|
confidence: 90,
|
|
3011
3217
|
source: "heuristic",
|
|
3012
|
-
violatedLayers: ["
|
|
3218
|
+
violatedLayers: ["L1.1"]
|
|
3013
3219
|
};
|
|
3014
3220
|
}
|
|
3015
3221
|
const findWriteMatch = actionDetail.match(
|
|
@@ -3024,10 +3230,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3024
3230
|
riskType: "script_execution",
|
|
3025
3231
|
confidence: 90,
|
|
3026
3232
|
source: "heuristic",
|
|
3027
|
-
violatedLayers: ["
|
|
3233
|
+
violatedLayers: ["L1.1"]
|
|
3028
3234
|
};
|
|
3029
3235
|
}
|
|
3030
|
-
if (/>\s
|
|
3236
|
+
if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
|
|
3031
3237
|
return {
|
|
3032
3238
|
allowed: false,
|
|
3033
3239
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -3035,7 +3241,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3035
3241
|
riskType: "file_access",
|
|
3036
3242
|
confidence: 95,
|
|
3037
3243
|
source: "heuristic",
|
|
3038
|
-
violatedLayers: ["
|
|
3244
|
+
violatedLayers: ["L1.1"]
|
|
3039
3245
|
};
|
|
3040
3246
|
}
|
|
3041
3247
|
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
@@ -3046,7 +3252,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
3046
3252
|
riskType: "file_access",
|
|
3047
3253
|
confidence: 90,
|
|
3048
3254
|
source: "heuristic",
|
|
3049
|
-
violatedLayers: ["
|
|
3255
|
+
violatedLayers: ["L1.1"]
|
|
3050
3256
|
};
|
|
3051
3257
|
}
|
|
3052
3258
|
return null;
|
|
@@ -3137,13 +3343,13 @@ function checkLateralMovement(actionDetail) {
|
|
|
3137
3343
|
riskType: "lateral_movement",
|
|
3138
3344
|
confidence: 80,
|
|
3139
3345
|
source: "heuristic",
|
|
3140
|
-
violatedLayers: ["
|
|
3346
|
+
violatedLayers: ["L1.1"]
|
|
3141
3347
|
};
|
|
3142
3348
|
}
|
|
3143
3349
|
}
|
|
3144
3350
|
return null;
|
|
3145
3351
|
}
|
|
3146
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
|
|
3352
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3147
3353
|
var init_heuristic = __esm({
|
|
3148
3354
|
"src/engine/heuristic.ts"() {
|
|
3149
3355
|
"use strict";
|
|
@@ -3173,6 +3379,8 @@ var init_heuristic = __esm({
|
|
|
3173
3379
|
})();
|
|
3174
3380
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3175
3381
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3382
|
+
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
3383
|
+
HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
|
|
3176
3384
|
}
|
|
3177
3385
|
});
|
|
3178
3386
|
|
|
@@ -4967,6 +5175,13 @@ function extractScriptPath2(command) {
|
|
|
4967
5175
|
return null;
|
|
4968
5176
|
}
|
|
4969
5177
|
function extractScriptPathWithArgs(command) {
|
|
5178
|
+
const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
|
|
5179
|
+
if (inline) {
|
|
5180
|
+
const inner = inline[2];
|
|
5181
|
+
const realPath = extractScriptPath2(inner);
|
|
5182
|
+
if (realPath) return { path: realPath, args: [] };
|
|
5183
|
+
return { path: null, args: [] };
|
|
5184
|
+
}
|
|
4970
5185
|
const scriptPath = extractScriptPath2(command);
|
|
4971
5186
|
if (!scriptPath) return { path: null, args: [] };
|
|
4972
5187
|
const idx = command.indexOf(scriptPath);
|
|
@@ -4980,11 +5195,59 @@ function extractScriptPathWithArgs(command) {
|
|
|
4980
5195
|
}
|
|
4981
5196
|
return { path: scriptPath, args };
|
|
4982
5197
|
}
|
|
5198
|
+
function looksLikeScriptPath(tok) {
|
|
5199
|
+
const t = tok.trim().replace(/^['"]|['"]$/g, "");
|
|
5200
|
+
if (!t) return false;
|
|
5201
|
+
const low = t.toLowerCase();
|
|
5202
|
+
if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
|
|
5203
|
+
if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
|
|
5204
|
+
if (t.includes("/") && !t.startsWith("-")) return true;
|
|
5205
|
+
return false;
|
|
5206
|
+
}
|
|
4983
5207
|
function extractInlineScriptPath(actionDetail) {
|
|
5208
|
+
const code = extractInlineCode(actionDetail);
|
|
5209
|
+
if (code) {
|
|
5210
|
+
const candidates = [];
|
|
5211
|
+
const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
|
|
5212
|
+
let m;
|
|
5213
|
+
while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
|
|
5214
|
+
const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
|
|
5215
|
+
while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
|
|
5216
|
+
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
5217
|
+
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
5218
|
+
for (const cand of candidates) {
|
|
5219
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
|
|
5220
|
+
for (const tok of tokens) {
|
|
5221
|
+
if (looksLikeScriptPath(tok)) {
|
|
5222
|
+
try {
|
|
5223
|
+
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
5224
|
+
if (existsSync9(resolved)) {
|
|
5225
|
+
const stat = statSync2(resolved);
|
|
5226
|
+
if (stat.isFile()) return resolved;
|
|
5227
|
+
}
|
|
5228
|
+
} catch {
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5234
|
+
while ((m = openRe.exec(code)) !== null) {
|
|
5235
|
+
const p = m[1];
|
|
5236
|
+
try {
|
|
5237
|
+
const resolved = resolvePath2(p);
|
|
5238
|
+
if (existsSync9(resolved)) {
|
|
5239
|
+
const stat = statSync2(resolved);
|
|
5240
|
+
if (stat.isFile()) return resolved;
|
|
5241
|
+
}
|
|
5242
|
+
} catch {
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
return null;
|
|
5246
|
+
}
|
|
4984
5247
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
4985
|
-
let
|
|
4986
|
-
while ((
|
|
4987
|
-
const p =
|
|
5248
|
+
let m2;
|
|
5249
|
+
while ((m2 = re.exec(actionDetail)) !== null) {
|
|
5250
|
+
const p = m2[1];
|
|
4988
5251
|
let resolved;
|
|
4989
5252
|
try {
|
|
4990
5253
|
resolved = resolvePath2(p);
|
|
@@ -5170,6 +5433,7 @@ var init_script_content_analyzer = __esm({
|
|
|
5170
5433
|
init_esm_shims();
|
|
5171
5434
|
init_heuristic();
|
|
5172
5435
|
init_logic_rules();
|
|
5436
|
+
init_inline_analyzer();
|
|
5173
5437
|
init_tracker();
|
|
5174
5438
|
SUSPICIOUS_PATTERNS2 = [
|
|
5175
5439
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -5634,7 +5898,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
5634
5898
|
riskType: riskType || "",
|
|
5635
5899
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
5636
5900
|
source: "llm",
|
|
5637
|
-
violatedLayers: ["
|
|
5901
|
+
violatedLayers: ["L3.1"]
|
|
5638
5902
|
};
|
|
5639
5903
|
} catch (err) {
|
|
5640
5904
|
if (abortController.signal.aborted) {
|
|
@@ -6220,13 +6484,6 @@ var init_brain = __esm({
|
|
|
6220
6484
|
});
|
|
6221
6485
|
|
|
6222
6486
|
// src/engine/formal-engine/decode.ts
|
|
6223
|
-
var decode_exports = {};
|
|
6224
|
-
__export(decode_exports, {
|
|
6225
|
-
allCandidates: () => allCandidates,
|
|
6226
|
-
decodeBase64Literals: () => decodeBase64Literals,
|
|
6227
|
-
decodeUrlAndNumeric: () => decodeUrlAndNumeric,
|
|
6228
|
-
matchStageAny: () => matchStageAny
|
|
6229
|
-
});
|
|
6230
6487
|
function isValidBase64Token(tok) {
|
|
6231
6488
|
if (tok.length < 8) return false;
|
|
6232
6489
|
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
@@ -6259,6 +6516,13 @@ function decodeBase64Literals(text) {
|
|
|
6259
6516
|
}
|
|
6260
6517
|
return out;
|
|
6261
6518
|
}
|
|
6519
|
+
function stripStringLiterals(text) {
|
|
6520
|
+
const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
|
|
6521
|
+
if (!m) return text;
|
|
6522
|
+
const cmd = m[1].split("/").pop().toLowerCase();
|
|
6523
|
+
if (!OUTPUT_CMDS.has(cmd)) return text;
|
|
6524
|
+
return text.replace(QUOTED_STR_RE, "");
|
|
6525
|
+
}
|
|
6262
6526
|
function decodeUrlAndNumeric(text) {
|
|
6263
6527
|
const out = [];
|
|
6264
6528
|
let s = text;
|
|
@@ -6292,7 +6556,10 @@ function decodeUrlAndNumeric(text) {
|
|
|
6292
6556
|
return out;
|
|
6293
6557
|
}
|
|
6294
6558
|
function allCandidates(detail, max = 8) {
|
|
6295
|
-
const
|
|
6559
|
+
const shell = stripStringLiterals(detail);
|
|
6560
|
+
const cands = [shell];
|
|
6561
|
+
cands.push(...decodeBase64Literals(shell));
|
|
6562
|
+
cands.push(...decodeUrlAndNumeric(shell));
|
|
6296
6563
|
cands.push(...decodeBase64Literals(detail));
|
|
6297
6564
|
cands.push(...decodeUrlAndNumeric(detail));
|
|
6298
6565
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6306,7 +6573,31 @@ function allCandidates(detail, max = 8) {
|
|
|
6306
6573
|
return out;
|
|
6307
6574
|
}
|
|
6308
6575
|
function matchStageAny(detail, stages) {
|
|
6309
|
-
|
|
6576
|
+
const cands = allCandidates(detail);
|
|
6577
|
+
if (cands.length === 0) return "";
|
|
6578
|
+
const shell = cands[0];
|
|
6579
|
+
const hits = [];
|
|
6580
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
6581
|
+
for (const p of pats) {
|
|
6582
|
+
p.lastIndex = 0;
|
|
6583
|
+
if (p.test(shell)) {
|
|
6584
|
+
hits.push(name);
|
|
6585
|
+
break;
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
if (hits.length > 0) {
|
|
6590
|
+
if (hits.length === 1) return hits[0];
|
|
6591
|
+
if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
|
|
6592
|
+
if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
|
|
6593
|
+
return "LISTEN";
|
|
6594
|
+
}
|
|
6595
|
+
for (const s of STAGE_PRIORITY) {
|
|
6596
|
+
if (hits.includes(s)) return s;
|
|
6597
|
+
}
|
|
6598
|
+
if (hits.includes("EXEC")) return "EXEC";
|
|
6599
|
+
}
|
|
6600
|
+
for (const cand of cands.slice(1)) {
|
|
6310
6601
|
for (const [name, pats] of Object.entries(stages)) {
|
|
6311
6602
|
for (const p of pats) {
|
|
6312
6603
|
p.lastIndex = 0;
|
|
@@ -6316,12 +6607,18 @@ function matchStageAny(detail, stages) {
|
|
|
6316
6607
|
}
|
|
6317
6608
|
return "";
|
|
6318
6609
|
}
|
|
6319
|
-
var B64_TOKEN_RE;
|
|
6610
|
+
var B64_TOKEN_RE, QUOTED_STR_RE, OUTPUT_CMDS, STAGE_PRIORITY, PURPOSE_EXEC_RE, LISTEN_LEAD_RE, LISTEN_NOISE_RE;
|
|
6320
6611
|
var init_decode = __esm({
|
|
6321
6612
|
"src/engine/formal-engine/decode.ts"() {
|
|
6322
6613
|
"use strict";
|
|
6323
6614
|
init_esm_shims();
|
|
6324
6615
|
B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
6616
|
+
QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
|
|
6617
|
+
OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
|
|
6618
|
+
STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
|
|
6619
|
+
PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
|
|
6620
|
+
LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
|
|
6621
|
+
LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
|
|
6325
6622
|
}
|
|
6326
6623
|
});
|
|
6327
6624
|
|
|
@@ -6357,11 +6654,7 @@ function compileAps(apPatterns) {
|
|
|
6357
6654
|
}
|
|
6358
6655
|
function labelEvent(actionDetail) {
|
|
6359
6656
|
if (!AP_RE) compileAps(loadMC().ap_patterns || {});
|
|
6360
|
-
|
|
6361
|
-
try {
|
|
6362
|
-
candidates = (init_decode(), __toCommonJS(decode_exports)).allCandidates(actionDetail);
|
|
6363
|
-
} catch {
|
|
6364
|
-
}
|
|
6657
|
+
const candidates = allCandidates(actionDetail);
|
|
6365
6658
|
const hits = /* @__PURE__ */ new Set();
|
|
6366
6659
|
for (const [name, pats] of Object.entries(AP_RE || {})) {
|
|
6367
6660
|
for (const c of candidates) {
|
|
@@ -6457,6 +6750,7 @@ var init_ltl = __esm({
|
|
|
6457
6750
|
init_esm_shims();
|
|
6458
6751
|
init_rules_loader();
|
|
6459
6752
|
init_ir();
|
|
6753
|
+
init_decode();
|
|
6460
6754
|
AP_RE = null;
|
|
6461
6755
|
}
|
|
6462
6756
|
});
|
|
@@ -6703,7 +6997,7 @@ function checkSensitiveDataFlow(graph) {
|
|
|
6703
6997
|
const n = nodes[i];
|
|
6704
6998
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
6705
6999
|
violations.push({
|
|
6706
|
-
propertyId: "sensitive-data-
|
|
7000
|
+
propertyId: "sensitive-data-flow",
|
|
6707
7001
|
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
6708
7002
|
riskLevel: "critical",
|
|
6709
7003
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -6713,6 +7007,62 @@ function checkSensitiveDataFlow(graph) {
|
|
|
6713
7007
|
}
|
|
6714
7008
|
return violations;
|
|
6715
7009
|
}
|
|
7010
|
+
function extractInlineShell(s) {
|
|
7011
|
+
const out = [];
|
|
7012
|
+
let m;
|
|
7013
|
+
INLINE_SHELL_RE.lastIndex = 0;
|
|
7014
|
+
while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
|
|
7015
|
+
let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
|
|
7016
|
+
if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
|
|
7017
|
+
g = g.replace(/['"\[\],]+/g, " ");
|
|
7018
|
+
}
|
|
7019
|
+
g = g.trim();
|
|
7020
|
+
if (g) out.push(g);
|
|
7021
|
+
}
|
|
7022
|
+
return out;
|
|
7023
|
+
}
|
|
7024
|
+
function containsPrivesc(shell) {
|
|
7025
|
+
if (!shell) return false;
|
|
7026
|
+
for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
|
|
7027
|
+
const s = seg.trim();
|
|
7028
|
+
if (!s) continue;
|
|
7029
|
+
const toks = s.split(/\s+/);
|
|
7030
|
+
const head = (toks[0] || "").toLowerCase();
|
|
7031
|
+
const rest = toks.slice(1).join(" ");
|
|
7032
|
+
if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
|
|
7033
|
+
if (head === "setcap" && /cap_/.test(rest)) return true;
|
|
7034
|
+
if (head === "pkexec" || head === "doas") return true;
|
|
7035
|
+
if (head === "sudo") {
|
|
7036
|
+
if (/^-u\s+/.test(rest)) {
|
|
7037
|
+
if (/^-u\s+root\b/.test(rest)) return true;
|
|
7038
|
+
continue;
|
|
7039
|
+
}
|
|
7040
|
+
const m = /^(\S+)(.*)$/.exec(rest);
|
|
7041
|
+
if (m) {
|
|
7042
|
+
const nxt = m[1].toLowerCase();
|
|
7043
|
+
const tail = m[2];
|
|
7044
|
+
if (PKG_MGR.has(nxt)) continue;
|
|
7045
|
+
if (SHELLS.has(nxt)) return true;
|
|
7046
|
+
if (nxt === "-i" || nxt === "-s") return true;
|
|
7047
|
+
if (nxt.startsWith("su")) return true;
|
|
7048
|
+
if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
|
|
7049
|
+
}
|
|
7050
|
+
if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
|
|
7051
|
+
if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
|
|
7052
|
+
if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
|
|
7053
|
+
if (/\bvisudo\b/.test(rest)) return true;
|
|
7054
|
+
if (/\bpasswd\s+root\b/.test(rest)) return true;
|
|
7055
|
+
if (/\buseradd\b/.test(rest)) return true;
|
|
7056
|
+
}
|
|
7057
|
+
if (head === "su") {
|
|
7058
|
+
if (!rest) return true;
|
|
7059
|
+
if (/^-l?\s*$/.test(rest)) return true;
|
|
7060
|
+
if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
|
|
7064
|
+
return false;
|
|
7065
|
+
}
|
|
6716
7066
|
function isExfilNode(n) {
|
|
6717
7067
|
if (n.stage === "EXFIL") return true;
|
|
6718
7068
|
if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
|
|
@@ -6727,8 +7077,12 @@ function isCredNode(n) {
|
|
|
6727
7077
|
return CRED_STRICT_RE.test(n.actionDetail);
|
|
6728
7078
|
}
|
|
6729
7079
|
function isPrivescNode(n) {
|
|
6730
|
-
|
|
6731
|
-
|
|
7080
|
+
const shell = stripStringLiterals(n.actionDetail);
|
|
7081
|
+
if (containsPrivesc(shell)) return true;
|
|
7082
|
+
for (const cmd of extractInlineShell(shell)) {
|
|
7083
|
+
if (containsPrivesc(cmd)) return true;
|
|
7084
|
+
}
|
|
7085
|
+
return false;
|
|
6732
7086
|
}
|
|
6733
7087
|
function checkNeverExfilAfterPrivesc(graph) {
|
|
6734
7088
|
const violations = [];
|
|
@@ -6749,7 +7103,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
6749
7103
|
if (pre) {
|
|
6750
7104
|
const post = nodes[firstEv];
|
|
6751
7105
|
return [{
|
|
6752
|
-
propertyId: "no-
|
|
7106
|
+
propertyId: "no-exfil-after-privesc",
|
|
6753
7107
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6754
7108
|
riskLevel: "critical",
|
|
6755
7109
|
path: [
|
|
@@ -6765,7 +7119,7 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
6765
7119
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
6766
7120
|
if (isExfilNode(nodes[j])) {
|
|
6767
7121
|
violations.push({
|
|
6768
|
-
propertyId: "no-
|
|
7122
|
+
propertyId: "no-exfil-after-privesc",
|
|
6769
7123
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6770
7124
|
riskLevel: "critical",
|
|
6771
7125
|
path: [
|
|
@@ -6798,7 +7152,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
6798
7152
|
if (pre) {
|
|
6799
7153
|
const post = nodes[firstEv];
|
|
6800
7154
|
return [{
|
|
6801
|
-
propertyId: "no-
|
|
7155
|
+
propertyId: "no-exfil-after-credential",
|
|
6802
7156
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6803
7157
|
riskLevel: "critical",
|
|
6804
7158
|
path: [
|
|
@@ -6814,7 +7168,7 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
6814
7168
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
6815
7169
|
if (isExfilNode(nodes[j])) {
|
|
6816
7170
|
violations.push({
|
|
6817
|
-
propertyId: "no-
|
|
7171
|
+
propertyId: "no-exfil-after-credential",
|
|
6818
7172
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6819
7173
|
riskLevel: "critical",
|
|
6820
7174
|
path: [
|
|
@@ -6881,7 +7235,7 @@ function checkLowIntegrityCommandExec(graph) {
|
|
|
6881
7235
|
const n = nodes[i];
|
|
6882
7236
|
if (lowAt.has(i) && isComputeNode(n)) {
|
|
6883
7237
|
violations.push({
|
|
6884
|
-
propertyId: "low-integrity-
|
|
7238
|
+
propertyId: "low-integrity-command-exec",
|
|
6885
7239
|
reason: `\u4F4E\u5B8C\u6574\u6027\u6570\u636E\u63A7\u5236\u547D\u4EE4\u6267\u884C\uFF1A${lowAt.get(i)} \u2192 ${n.sessionId}#${n.seq} \u6267\u884C\u4E0D\u53EF\u4FE1\u5185\u5BB9`,
|
|
6886
7240
|
riskLevel: "critical",
|
|
6887
7241
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
@@ -6927,7 +7281,7 @@ function checkCrossSessionReachability(graph) {
|
|
|
6927
7281
|
const srcIdx = Math.min(...sources);
|
|
6928
7282
|
const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
|
|
6929
7283
|
violations.push({
|
|
6930
|
-
propertyId: "
|
|
7284
|
+
propertyId: "low-integrity-reachability",
|
|
6931
7285
|
reason: `\u4F4E\u5B8C\u6574\u6027\u6E90\u7ECF\u8DE8 session \u6570\u636E\u94FE\u591A\u8DF3\u5230\u8FBE\u5F53\u524D\u6267\u884C\uFF08\u6E90: ${srcDesc} \u2192 ${n.sessionId}#${n.seq}\uFF09`,
|
|
6932
7286
|
riskLevel: "high",
|
|
6933
7287
|
path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
|
|
@@ -6978,7 +7332,7 @@ function checkSensitiveDataAggregation(graph) {
|
|
|
6978
7332
|
}
|
|
6979
7333
|
return violations;
|
|
6980
7334
|
}
|
|
6981
|
-
var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE,
|
|
7335
|
+
var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
|
|
6982
7336
|
var init_properties = __esm({
|
|
6983
7337
|
"src/engine/formal-engine/properties.ts"() {
|
|
6984
7338
|
"use strict";
|
|
@@ -6986,30 +7340,33 @@ var init_properties = __esm({
|
|
|
6986
7340
|
init_rules_loader();
|
|
6987
7341
|
init_ir();
|
|
6988
7342
|
init_ltl();
|
|
7343
|
+
init_decode();
|
|
6989
7344
|
CROSS_SESSION_CHAINS = null;
|
|
6990
7345
|
CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
6991
|
-
EXFIL_RE = /https
|
|
7346
|
+
EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
|
|
6992
7347
|
EBPF_NET_RE = /connect|sendto|network_connect/i;
|
|
6993
7348
|
REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
|
|
6994
|
-
PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
|
|
6995
7349
|
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;
|
|
6996
7350
|
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;
|
|
7351
|
+
INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
|
|
7352
|
+
PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
|
|
7353
|
+
SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
|
|
6997
7354
|
TOOL_REGISTRY = null;
|
|
6998
7355
|
COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
|
|
6999
7356
|
STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
|
|
7000
7357
|
DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
|
|
7001
7358
|
ALL_PROPERTIES = [
|
|
7002
|
-
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
7003
|
-
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
7004
|
-
{ id: "
|
|
7005
|
-
{ id: "no-
|
|
7006
|
-
{ id: "no-
|
|
7359
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
|
|
7360
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
|
|
7361
|
+
{ id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
|
|
7362
|
+
{ id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
|
|
7363
|
+
{ id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
|
|
7007
7364
|
// L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
|
|
7008
|
-
{ id: "mc-ltl-rules", check: checkMCRules },
|
|
7009
|
-
//
|
|
7010
|
-
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec },
|
|
7011
|
-
//
|
|
7012
|
-
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation }
|
|
7365
|
+
{ id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
|
|
7366
|
+
// P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
|
|
7367
|
+
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
|
|
7368
|
+
// P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
|
|
7369
|
+
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
|
|
7013
7370
|
];
|
|
7014
7371
|
}
|
|
7015
7372
|
});
|
|
@@ -7082,6 +7439,32 @@ var init_engine = __esm({
|
|
|
7082
7439
|
});
|
|
7083
7440
|
|
|
7084
7441
|
// src/engine/formal-engine/stateful.ts
|
|
7442
|
+
function extractInstances(actionDetail, stage) {
|
|
7443
|
+
const out = [];
|
|
7444
|
+
if (PORT_STAGE_RE.test(stage)) {
|
|
7445
|
+
const d = actionDetail;
|
|
7446
|
+
let m;
|
|
7447
|
+
const re = new RegExp(PORT_RE.source, "gi");
|
|
7448
|
+
while ((m = re.exec(d)) !== null) {
|
|
7449
|
+
const p = m[1] || m[2] || m[3];
|
|
7450
|
+
if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
|
|
7451
|
+
}
|
|
7452
|
+
if (/SCAN/.test(stage)) {
|
|
7453
|
+
const bareRe = new RegExp(BARE_PORT_RE.source, "g");
|
|
7454
|
+
while ((m = bareRe.exec(d)) !== null) {
|
|
7455
|
+
if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7458
|
+
m = SOCK_RE.exec(d);
|
|
7459
|
+
if (m && m[1]) out.push(`sock:${m[1]}`);
|
|
7460
|
+
m = UNIT_RE.exec(d);
|
|
7461
|
+
if (m && m[1]) out.push(`unit:${m[1]}`);
|
|
7462
|
+
}
|
|
7463
|
+
return out;
|
|
7464
|
+
}
|
|
7465
|
+
function buckKey(chainId, inst) {
|
|
7466
|
+
return inst ? `${chainId}@@${inst}` : chainId;
|
|
7467
|
+
}
|
|
7085
7468
|
function loadRules() {
|
|
7086
7469
|
if (STAGES_PATTERNS !== null && CHAINS !== null) {
|
|
7087
7470
|
return { stages: STAGES_PATTERNS, chains: CHAINS };
|
|
@@ -7121,7 +7504,7 @@ function lifecycleFor(detail, stage, isError) {
|
|
|
7121
7504
|
if (detail.includes(">>") || detail.includes(">")) return "alive";
|
|
7122
7505
|
return "";
|
|
7123
7506
|
}
|
|
7124
|
-
var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
|
|
7507
|
+
var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, PORT_RE, BARE_PORT_RE, SOCK_RE, UNIT_RE, PORT_STAGE_RE, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
|
|
7125
7508
|
var init_stateful = __esm({
|
|
7126
7509
|
"src/engine/formal-engine/stateful.ts"() {
|
|
7127
7510
|
"use strict";
|
|
@@ -7131,11 +7514,20 @@ var init_stateful = __esm({
|
|
|
7131
7514
|
DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
|
|
7132
7515
|
NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
|
|
7133
7516
|
CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
7134
|
-
EXFIL_RE2 = /https
|
|
7517
|
+
EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
|
|
7518
|
+
PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
|
|
7519
|
+
BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
|
|
7520
|
+
SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
|
|
7521
|
+
UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
|
|
7522
|
+
PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
|
|
7135
7523
|
STAGES_PATTERNS = null;
|
|
7136
7524
|
CHAINS = null;
|
|
7137
7525
|
MAX_NODES = 1e5;
|
|
7138
|
-
StatefulChainTracker = class {
|
|
7526
|
+
StatefulChainTracker = class _StatefulChainTracker {
|
|
7527
|
+
/** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
|
|
7528
|
+
static now() {
|
|
7529
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7530
|
+
}
|
|
7139
7531
|
agentId;
|
|
7140
7532
|
nodes_ = [];
|
|
7141
7533
|
ptr = {};
|
|
@@ -7157,10 +7549,14 @@ var init_stateful = __esm({
|
|
|
7157
7549
|
for (const chain of chains) {
|
|
7158
7550
|
const stages = chain.stages || [];
|
|
7159
7551
|
if (stages.length === 0) continue;
|
|
7160
|
-
const
|
|
7552
|
+
const cid = String(chain.id || "");
|
|
7553
|
+
let matched = this.ptr[cid] || 0;
|
|
7554
|
+
for (const [k, v] of Object.entries(this.ptr)) {
|
|
7555
|
+
if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
|
|
7556
|
+
}
|
|
7161
7557
|
if (matched > 0 && matched < stages.length) {
|
|
7162
7558
|
const pf = {
|
|
7163
|
-
chainId:
|
|
7559
|
+
chainId: cid,
|
|
7164
7560
|
stages,
|
|
7165
7561
|
matched,
|
|
7166
7562
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
@@ -7236,8 +7632,10 @@ var init_stateful = __esm({
|
|
|
7236
7632
|
actionDetail: ad,
|
|
7237
7633
|
stage,
|
|
7238
7634
|
resources: extractResources2(ad),
|
|
7635
|
+
instances: extractInstances(ad, stage),
|
|
7239
7636
|
isError,
|
|
7240
|
-
lifecycle: lifecycleFor(ad, stage, isError)
|
|
7637
|
+
lifecycle: lifecycleFor(ad, stage, isError),
|
|
7638
|
+
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
7241
7639
|
};
|
|
7242
7640
|
this.nodes_.push(node);
|
|
7243
7641
|
this.seqNext += 1;
|
|
@@ -7245,11 +7643,19 @@ var init_stateful = __esm({
|
|
|
7245
7643
|
}
|
|
7246
7644
|
recordLifecycle(node) {
|
|
7247
7645
|
if (node.lifecycle === "dead") {
|
|
7248
|
-
|
|
7646
|
+
const clearedInsts = [];
|
|
7647
|
+
for (const inst of node.instances) {
|
|
7648
|
+
const key = `inst:${inst}`;
|
|
7649
|
+
if (key in this.aliveResources) {
|
|
7650
|
+
delete this.aliveResources[key];
|
|
7651
|
+
clearedInsts.push(inst);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
let pathHit = false;
|
|
7249
7655
|
for (const r of node.resources) {
|
|
7250
7656
|
if (r in this.aliveResources) {
|
|
7251
7657
|
delete this.aliveResources[r];
|
|
7252
|
-
|
|
7658
|
+
pathHit = true;
|
|
7253
7659
|
}
|
|
7254
7660
|
}
|
|
7255
7661
|
const detail = node.actionDetail;
|
|
@@ -7257,16 +7663,30 @@ var init_stateful = __esm({
|
|
|
7257
7663
|
const base = r.split("/").pop() || "";
|
|
7258
7664
|
if (base && detail.includes(base)) {
|
|
7259
7665
|
delete this.aliveResources[r];
|
|
7260
|
-
|
|
7666
|
+
pathHit = true;
|
|
7261
7667
|
}
|
|
7262
7668
|
}
|
|
7263
|
-
if (
|
|
7669
|
+
if (clearedInsts.length > 0 && !pathHit) {
|
|
7670
|
+
for (const inst of clearedInsts) this.resetChainsForInstance(inst);
|
|
7671
|
+
} else {
|
|
7672
|
+
this.resetChainsOnDestroy();
|
|
7673
|
+
}
|
|
7264
7674
|
return;
|
|
7265
7675
|
}
|
|
7266
7676
|
if (node.lifecycle === "alive") {
|
|
7267
7677
|
for (const r of node.resources) {
|
|
7268
7678
|
this.aliveResources[r] = node.seq;
|
|
7269
7679
|
}
|
|
7680
|
+
for (const inst of node.instances) {
|
|
7681
|
+
this.aliveResources[`inst:${inst}`] = node.seq;
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
/** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
|
|
7686
|
+
resetChainsForInstance(inst) {
|
|
7687
|
+
const key = buckKey("", inst).slice(2);
|
|
7688
|
+
for (const k of Object.keys(this.ptr)) {
|
|
7689
|
+
if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
|
|
7270
7690
|
}
|
|
7271
7691
|
}
|
|
7272
7692
|
resetChainsOnDestroy() {
|
|
@@ -7274,6 +7694,29 @@ var init_stateful = __esm({
|
|
|
7274
7694
|
this.ptr[cid] = 0;
|
|
7275
7695
|
}
|
|
7276
7696
|
}
|
|
7697
|
+
/**
|
|
7698
|
+
* 当前节点应推进的链桶集合。
|
|
7699
|
+
* - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
|
|
7700
|
+
* 同时为所有链的该实例建立桶(若首次出现)。
|
|
7701
|
+
* - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
|
|
7702
|
+
* (信息不足时保守推进一切可能,避免漏判)。
|
|
7703
|
+
*/
|
|
7704
|
+
targetsForNode(node, chainId) {
|
|
7705
|
+
const insts = node.instances;
|
|
7706
|
+
if (insts.length > 0) {
|
|
7707
|
+
const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
|
|
7708
|
+
if (active.length > 0) return active.map((i) => buckKey(chainId, i));
|
|
7709
|
+
return [chainId];
|
|
7710
|
+
}
|
|
7711
|
+
const targets = [chainId];
|
|
7712
|
+
for (const k of Object.keys(this.aliveResources)) {
|
|
7713
|
+
if (k.startsWith("inst:")) {
|
|
7714
|
+
const inst = k.slice("inst:".length);
|
|
7715
|
+
targets.push(`${chainId}@@${inst}`);
|
|
7716
|
+
}
|
|
7717
|
+
}
|
|
7718
|
+
return targets;
|
|
7719
|
+
}
|
|
7277
7720
|
advanceChains(node) {
|
|
7278
7721
|
if (node.lifecycle === "dead") return null;
|
|
7279
7722
|
if (node.lifecycle === "never_activated") {
|
|
@@ -7287,23 +7730,32 @@ var init_stateful = __esm({
|
|
|
7287
7730
|
const stages = chain.stages || [];
|
|
7288
7731
|
if (stages.length === 0) continue;
|
|
7289
7732
|
const cid = String(chain.id || "");
|
|
7290
|
-
const
|
|
7291
|
-
|
|
7292
|
-
this.ptr[
|
|
7293
|
-
if (cur
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7733
|
+
const targets = this.targetsForNode(node, cid);
|
|
7734
|
+
for (const key of targets) {
|
|
7735
|
+
const cur = this.ptr[key] || 0;
|
|
7736
|
+
if (cur < stages.length && stages[cur] === stage) {
|
|
7737
|
+
this.ptr[key] = cur + 1;
|
|
7738
|
+
if (cur + 1 >= stages.length) {
|
|
7739
|
+
return {
|
|
7740
|
+
chainId: cid,
|
|
7741
|
+
stages,
|
|
7742
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
7743
|
+
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
7744
|
+
path: this.chainPath(stages)
|
|
7745
|
+
};
|
|
7746
|
+
}
|
|
7301
7747
|
}
|
|
7302
7748
|
}
|
|
7303
7749
|
}
|
|
7304
7750
|
return null;
|
|
7305
7751
|
}
|
|
7306
7752
|
invalidateChainOnFailure(node) {
|
|
7753
|
+
if (node.instances.length > 0) {
|
|
7754
|
+
for (const inst of node.instances) {
|
|
7755
|
+
this.resetChainsForInstance(inst);
|
|
7756
|
+
}
|
|
7757
|
+
return;
|
|
7758
|
+
}
|
|
7307
7759
|
const failed = new Set(node.resources);
|
|
7308
7760
|
if (failed.size === 0) return;
|
|
7309
7761
|
const { chains } = loadRules();
|
|
@@ -7319,7 +7771,7 @@ var init_stateful = __esm({
|
|
|
7319
7771
|
let want = stages[0];
|
|
7320
7772
|
for (const n of this.nodes_) {
|
|
7321
7773
|
if (n.stage === want) {
|
|
7322
|
-
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
|
|
7774
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
|
|
7323
7775
|
const idx = stages.indexOf(n.stage);
|
|
7324
7776
|
if (idx + 1 < stages.length) {
|
|
7325
7777
|
want = stages[idx + 1];
|
|
@@ -7355,7 +7807,7 @@ var init_stateful = __esm({
|
|
|
7355
7807
|
if (!this.tainted.has(node.seq)) return [];
|
|
7356
7808
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
7357
7809
|
return [{
|
|
7358
|
-
property_id: "sensitive-data-
|
|
7810
|
+
property_id: "sensitive-data-flow",
|
|
7359
7811
|
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
7360
7812
|
risk_level: "critical",
|
|
7361
7813
|
path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
|
|
@@ -7397,8 +7849,12 @@ var init_stateful = __esm({
|
|
|
7397
7849
|
actionDetail: String(raw.actionDetail || ""),
|
|
7398
7850
|
stage: String(raw.stage || ""),
|
|
7399
7851
|
resources: [...raw.resources || []],
|
|
7852
|
+
instances: [...raw.instances || []],
|
|
7853
|
+
// 旧快照无此字段 → 空
|
|
7400
7854
|
isError: Boolean(raw.isError),
|
|
7401
|
-
lifecycle: String(raw.lifecycle || "")
|
|
7855
|
+
lifecycle: String(raw.lifecycle || ""),
|
|
7856
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
|
|
7857
|
+
// 旧快照无此字段 → null
|
|
7402
7858
|
});
|
|
7403
7859
|
}
|
|
7404
7860
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
@@ -7591,7 +8047,7 @@ async function runAnalysis(input) {
|
|
|
7591
8047
|
riskType: node.l1RiskType,
|
|
7592
8048
|
confidence: 90,
|
|
7593
8049
|
source: "heuristic_script_scan",
|
|
7594
|
-
violatedLayers: ["
|
|
8050
|
+
violatedLayers: ["L1.2"]
|
|
7595
8051
|
};
|
|
7596
8052
|
}
|
|
7597
8053
|
}
|
|
@@ -7709,7 +8165,7 @@ async function runAnalysis(input) {
|
|
|
7709
8165
|
riskType: inlineJudgment.riskType,
|
|
7710
8166
|
confidence: 90,
|
|
7711
8167
|
source: "logic_rule",
|
|
7712
|
-
violatedLayers: ["1
|
|
8168
|
+
violatedLayers: ["L2.1"]
|
|
7713
8169
|
};
|
|
7714
8170
|
}
|
|
7715
8171
|
return {
|
|
@@ -7742,7 +8198,7 @@ async function runAnalysis(input) {
|
|
|
7742
8198
|
console.warn(
|
|
7743
8199
|
`[AgentMoss] \u5F62\u5F0F\u5316\u9A8C\u8BC1 observe\uFF08\u53EA\u8BB0\u5F55\u4E0D\u62E6\u622A\uFF09: property=${v?.propertyId ?? "?"} risk=${v?.riskLevel ?? "?"} path_len=${v?.path?.length ?? 0} reason=${(v?.reason ?? "?").slice(0, 200)}`
|
|
7744
8200
|
);
|
|
7745
|
-
observedFormalLayers.push("2
|
|
8201
|
+
observedFormalLayers.push("L2.2");
|
|
7746
8202
|
} else {
|
|
7747
8203
|
return {
|
|
7748
8204
|
allowed: false,
|
|
@@ -7751,7 +8207,7 @@ async function runAnalysis(input) {
|
|
|
7751
8207
|
riskType: "formal_verification",
|
|
7752
8208
|
confidence: 90,
|
|
7753
8209
|
source: "formal_engine",
|
|
7754
|
-
violatedLayers: ["2
|
|
8210
|
+
violatedLayers: ["L2.2"]
|
|
7755
8211
|
};
|
|
7756
8212
|
}
|
|
7757
8213
|
}
|
|
@@ -7768,7 +8224,7 @@ async function runAnalysis(input) {
|
|
|
7768
8224
|
riskType: node.l2RiskType,
|
|
7769
8225
|
confidence: 85,
|
|
7770
8226
|
source: "logic_rule_script_scan",
|
|
7771
|
-
violatedLayers: ["
|
|
8227
|
+
violatedLayers: ["L2.3"]
|
|
7772
8228
|
};
|
|
7773
8229
|
}
|
|
7774
8230
|
}
|
|
@@ -7855,7 +8311,7 @@ async function runAnalysis(input) {
|
|
|
7855
8311
|
riskType: "",
|
|
7856
8312
|
confidence: 60,
|
|
7857
8313
|
source: "llm_failure_fallback",
|
|
7858
|
-
violatedLayers: ["
|
|
8314
|
+
violatedLayers: ["L3.1"]
|
|
7859
8315
|
};
|
|
7860
8316
|
}
|
|
7861
8317
|
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
@@ -8797,7 +9253,7 @@ var AnalyzeRequestSchema = z.object({
|
|
|
8797
9253
|
session_history: z.array(z.object({
|
|
8798
9254
|
session_id: z.string(),
|
|
8799
9255
|
action_history: z.array(ActionItemSchema).default([])
|
|
8800
|
-
})).default([]).describe("Multi-session historical actions (optional): for L2.
|
|
9256
|
+
})).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
|
|
8801
9257
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
8802
9258
|
reason: z.string().default("").describe("Reason for executing this action"),
|
|
8803
9259
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
@@ -8814,7 +9270,9 @@ var AnalyzeResponseSchema = z.object({
|
|
|
8814
9270
|
policy: z.string().default(""),
|
|
8815
9271
|
violated_policy: z.string().default(""),
|
|
8816
9272
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
8817
|
-
analysis_duration_ms: z.number().default(0)
|
|
9273
|
+
analysis_duration_ms: z.number().default(0),
|
|
9274
|
+
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
9275
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
8818
9276
|
});
|
|
8819
9277
|
var HealthResponseSchema = z.object({
|
|
8820
9278
|
status: z.string().default("healthy"),
|
|
@@ -9198,7 +9656,7 @@ async function analyzeRoute(c) {
|
|
|
9198
9656
|
const judgment = incMatchedToDeny(inc);
|
|
9199
9657
|
if (judgment) {
|
|
9200
9658
|
const layers = [...result.violatedLayers];
|
|
9201
|
-
if (!layers.includes("2
|
|
9659
|
+
if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
|
|
9202
9660
|
console.error(
|
|
9203
9661
|
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
9204
9662
|
);
|
|
@@ -9212,7 +9670,8 @@ async function analyzeRoute(c) {
|
|
|
9212
9670
|
policy: "",
|
|
9213
9671
|
violated_policy: "",
|
|
9214
9672
|
confidence: 90,
|
|
9215
|
-
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
|
|
9673
|
+
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
9674
|
+
violation_chain: judgment.violation_chain
|
|
9216
9675
|
})
|
|
9217
9676
|
);
|
|
9218
9677
|
}
|
|
@@ -9251,17 +9710,20 @@ function getChainStateStoreFor(_agentId) {
|
|
|
9251
9710
|
}
|
|
9252
9711
|
function incMatchedToDeny(inc) {
|
|
9253
9712
|
if (inc.matched) {
|
|
9713
|
+
const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
|
|
9254
9714
|
return {
|
|
9255
9715
|
property_id: "cross-" + inc.matched.chainId,
|
|
9256
9716
|
reason: inc.matched.reason,
|
|
9257
|
-
risk_level: inc.matched.riskLevel
|
|
9717
|
+
risk_level: inc.matched.riskLevel,
|
|
9718
|
+
violation_chain: chain
|
|
9258
9719
|
};
|
|
9259
9720
|
}
|
|
9260
9721
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
9261
9722
|
return {
|
|
9262
|
-
property_id: "sensitive-data-
|
|
9723
|
+
property_id: "sensitive-data-flow",
|
|
9263
9724
|
reason: inc.exfil[0].reason,
|
|
9264
|
-
risk_level: inc.exfil[0].risk_level
|
|
9725
|
+
risk_level: inc.exfil[0].risk_level,
|
|
9726
|
+
violation_chain: []
|
|
9265
9727
|
};
|
|
9266
9728
|
}
|
|
9267
9729
|
return null;
|
|
@@ -9387,7 +9849,7 @@ function createConsoleRouter() {
|
|
|
9387
9849
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
9388
9850
|
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
9389
9851
|
});
|
|
9390
|
-
function
|
|
9852
|
+
function getL22View() {
|
|
9391
9853
|
const ltlHuman = {
|
|
9392
9854
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9393
9855
|
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
@@ -9403,9 +9865,16 @@ function createConsoleRouter() {
|
|
|
9403
9865
|
};
|
|
9404
9866
|
const mc = loadMC();
|
|
9405
9867
|
const rules = mc.rules || [];
|
|
9868
|
+
let catEnabled = true;
|
|
9869
|
+
try {
|
|
9870
|
+
const rt = loadRuntimeConfig();
|
|
9871
|
+
catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
|
|
9872
|
+
} catch {
|
|
9873
|
+
}
|
|
9406
9874
|
return {
|
|
9407
9875
|
enabled_via: "L2_logic_rules",
|
|
9408
9876
|
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9877
|
+
category_enabled: catEnabled,
|
|
9409
9878
|
ltl_rules: rules.map((r) => ({
|
|
9410
9879
|
id: r.id,
|
|
9411
9880
|
type: r.type,
|
|
@@ -9418,10 +9887,10 @@ function createConsoleRouter() {
|
|
|
9418
9887
|
}))
|
|
9419
9888
|
};
|
|
9420
9889
|
}
|
|
9421
|
-
function
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
const rls =
|
|
9890
|
+
function withL22View(rc) {
|
|
9891
|
+
const l22 = rc && rc.L2_2;
|
|
9892
|
+
if (l22 && typeof l22 === "object" && !Array.isArray(l22) && l22.ltl_rules && typeof l22.ltl_rules === "object" && !Array.isArray(l22.ltl_rules)) {
|
|
9893
|
+
const rls = l22.ltl_rules.rules || [];
|
|
9425
9894
|
const ltlHumanLocal = {
|
|
9426
9895
|
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9427
9896
|
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
@@ -9435,9 +9904,10 @@ function createConsoleRouter() {
|
|
|
9435
9904
|
"mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
|
|
9436
9905
|
"mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
|
|
9437
9906
|
};
|
|
9438
|
-
rc.
|
|
9907
|
+
rc.L2_2 = {
|
|
9439
9908
|
enabled_via: "L2_logic_rules",
|
|
9440
9909
|
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9910
|
+
category_enabled: l22.ltl_rules.category_enabled !== false,
|
|
9441
9911
|
ltl_rules: rls.map((r) => ({
|
|
9442
9912
|
id: r.id,
|
|
9443
9913
|
type: "ltl",
|
|
@@ -9454,27 +9924,27 @@ function createConsoleRouter() {
|
|
|
9454
9924
|
}
|
|
9455
9925
|
console2.get("/api/rules", (c) => {
|
|
9456
9926
|
const rc = loadRuntimeConfig();
|
|
9457
|
-
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules,
|
|
9927
|
+
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
|
|
9458
9928
|
});
|
|
9459
9929
|
console2.put("/api/rules/enabled", async (c) => {
|
|
9460
9930
|
const b = await c.req.json();
|
|
9461
9931
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
9462
|
-
return c.json(
|
|
9932
|
+
return c.json(withL22View(rc));
|
|
9463
9933
|
});
|
|
9464
9934
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
9465
9935
|
const b = await c.req.json();
|
|
9466
9936
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
9467
|
-
return c.json(
|
|
9937
|
+
return c.json(withL22View(rc));
|
|
9468
9938
|
});
|
|
9469
9939
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
9470
9940
|
const b = await c.req.json();
|
|
9471
9941
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
9472
|
-
return c.json(
|
|
9942
|
+
return c.json(withL22View(rc));
|
|
9473
9943
|
});
|
|
9474
9944
|
console2.put("/api/categories/enabled", async (c) => {
|
|
9475
9945
|
const b = await c.req.json();
|
|
9476
9946
|
const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
|
|
9477
|
-
return c.json(rc);
|
|
9947
|
+
return c.json(withL22View(rc));
|
|
9478
9948
|
});
|
|
9479
9949
|
console2.post("/api/rules", async (c) => {
|
|
9480
9950
|
const b = await c.req.json();
|
|
@@ -9490,13 +9960,13 @@ function createConsoleRouter() {
|
|
|
9490
9960
|
credential: b.credential,
|
|
9491
9961
|
deny_mode: b.deny_mode
|
|
9492
9962
|
});
|
|
9493
|
-
return c.json(rc);
|
|
9963
|
+
return c.json(withL22View(rc));
|
|
9494
9964
|
});
|
|
9495
9965
|
console2.delete("/api/rules", async (c) => {
|
|
9496
9966
|
const b = await c.req.json();
|
|
9497
9967
|
deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
9498
9968
|
const rc = loadRuntimeConfig();
|
|
9499
|
-
return c.json(rc);
|
|
9969
|
+
return c.json(withL22View(rc));
|
|
9500
9970
|
});
|
|
9501
9971
|
console2.get("/api/skills", (c) => {
|
|
9502
9972
|
const rc = loadRuntimeConfig();
|
|
@@ -9505,23 +9975,23 @@ function createConsoleRouter() {
|
|
|
9505
9975
|
console2.put("/api/skills/enabled", async (c) => {
|
|
9506
9976
|
const b = await c.req.json();
|
|
9507
9977
|
const rc = updateSkillEnabled(b.skill_id, b.enabled);
|
|
9508
|
-
return c.json(rc);
|
|
9978
|
+
return c.json(withL22View(rc));
|
|
9509
9979
|
});
|
|
9510
9980
|
console2.put("/api/skill-categories/enabled", async (c) => {
|
|
9511
9981
|
const b = await c.req.json();
|
|
9512
9982
|
const rc = updateSkillCategoryEnabled(b.category, b.enabled);
|
|
9513
|
-
return c.json(rc);
|
|
9983
|
+
return c.json(withL22View(rc));
|
|
9514
9984
|
});
|
|
9515
9985
|
console2.post("/api/skills", async (c) => {
|
|
9516
9986
|
const b = await c.req.json();
|
|
9517
9987
|
const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
|
|
9518
|
-
return c.json(rc);
|
|
9988
|
+
return c.json(withL22View(rc));
|
|
9519
9989
|
});
|
|
9520
9990
|
console2.delete("/api/skills", async (c) => {
|
|
9521
9991
|
const b = await c.req.json();
|
|
9522
9992
|
deleteCustomSkill(b.skill_id, b.category);
|
|
9523
9993
|
const rc = loadRuntimeConfig();
|
|
9524
|
-
return c.json(rc);
|
|
9994
|
+
return c.json(withL22View(rc));
|
|
9525
9995
|
});
|
|
9526
9996
|
console2.get("/api/skills/:skill_id/content", async (c) => {
|
|
9527
9997
|
const skillId = c.req.param("skill_id");
|
|
@@ -9619,7 +10089,7 @@ function createConsoleRouter() {
|
|
|
9619
10089
|
});
|
|
9620
10090
|
console2.get("/api/config", (c) => {
|
|
9621
10091
|
const rc = loadRuntimeConfig();
|
|
9622
|
-
return c.json({ ...rc,
|
|
10092
|
+
return c.json({ ...rc, L2_2: getL22View() });
|
|
9623
10093
|
});
|
|
9624
10094
|
console2.get("/api/env-overrides", (c) => {
|
|
9625
10095
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -9627,7 +10097,7 @@ function createConsoleRouter() {
|
|
|
9627
10097
|
console2.post("/api/reset", (c) => {
|
|
9628
10098
|
const defaults = generateSourceDefaults();
|
|
9629
10099
|
saveRuntimeConfig(defaults);
|
|
9630
|
-
return c.json(defaults);
|
|
10100
|
+
return c.json(withL22View(defaults));
|
|
9631
10101
|
});
|
|
9632
10102
|
console2.get("/api/token-stats", (c) => {
|
|
9633
10103
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
@@ -9959,4 +10429,14 @@ if (isMain) {
|
|
|
9959
10429
|
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
9960
10430
|
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|
|
9961
10431
|
//! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
|
|
10432
|
+
//!
|
|
10433
|
+
//! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
|
|
10434
|
+
//! P1 cross-session-chain 跨会话组合链
|
|
10435
|
+
//! P2 sensitive-data-flow 敏感数据传播
|
|
10436
|
+
//! P3 low-integrity-reachability 低完整性数据流可达
|
|
10437
|
+
//! P4 no-exfil-after-privesc 提权后禁外传
|
|
10438
|
+
//! P5 no-exfil-after-credential 凭据后禁外传
|
|
10439
|
+
//! P6 mc-ltl-rules LTL 模型检测
|
|
10440
|
+
//! P7 low-integrity-command-exec 低完整性命令执行
|
|
10441
|
+
//! P8 sensitive-data-aggregation 敏感数据聚合外传
|
|
9962
10442
|
//# sourceMappingURL=cli.js.map
|