secagentx 3.0.0__py3-none-any.whl

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.
Files changed (182) hide show
  1. backend/__init__.py +1 -0
  2. backend/__main__.py +3 -0
  3. backend/_assets/config.yaml +195 -0
  4. backend/_assets/frontend/dist/assets/AgentsView-DFqRplI8.css +1 -0
  5. backend/_assets/frontend/dist/assets/AgentsView-DJTpfQ87.js +1 -0
  6. backend/_assets/frontend/dist/assets/AttackChainView-BVfI-SC6.js +1 -0
  7. backend/_assets/frontend/dist/assets/ChatView-Bg4nIlQ0.css +1 -0
  8. backend/_assets/frontend/dist/assets/ChatView-CRndtmEC.js +38 -0
  9. backend/_assets/frontend/dist/assets/DashboardView-BXs4AWv8.js +1 -0
  10. backend/_assets/frontend/dist/assets/DashboardView-WnHWiqBW.css +1 -0
  11. backend/_assets/frontend/dist/assets/EventDetailView-DQk6Qp7-.js +1 -0
  12. backend/_assets/frontend/dist/assets/EventDetailView-Dj-a-zWr.css +1 -0
  13. backend/_assets/frontend/dist/assets/EventsView-8xh8uKO0.js +1 -0
  14. backend/_assets/frontend/dist/assets/EventsView-BHfFn66K.css +1 -0
  15. backend/_assets/frontend/dist/assets/KnowledgeView-C2N3v9bF.css +1 -0
  16. backend/_assets/frontend/dist/assets/KnowledgeView-D54ODoC9.js +11 -0
  17. backend/_assets/frontend/dist/assets/LoginView-CjvFmo7C.js +1 -0
  18. backend/_assets/frontend/dist/assets/LoginView-D14FzcsP.css +1 -0
  19. backend/_assets/frontend/dist/assets/SettingsView-JOJuoABi.js +1 -0
  20. backend/_assets/frontend/dist/assets/UsersView-DhMXZhir.js +1 -0
  21. backend/_assets/frontend/dist/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  22. backend/_assets/frontend/dist/assets/index-D9Su3fYY.css +1 -0
  23. backend/_assets/frontend/dist/assets/index-DBEjbyJR.js +4 -0
  24. backend/_assets/frontend/dist/assets/rolldown-runtime-DK3Fl9T5.js +1 -0
  25. backend/_assets/frontend/dist/assets/vendor-charts-Dh1nChzO.js +39 -0
  26. backend/_assets/frontend/dist/assets/vendor-icons-BTNgi7vU.js +1 -0
  27. backend/_assets/frontend/dist/assets/vendor-ui-Cdd65KTN.js +7367 -0
  28. backend/_assets/frontend/dist/assets/vendor-vue-CXKcZAmz.js +1 -0
  29. backend/_assets/frontend/dist/index.html +51 -0
  30. backend/_assets/frontend/index.html +46 -0
  31. backend/_assets/frontend/package-lock.json +2983 -0
  32. backend/_assets/frontend/package.json +37 -0
  33. backend/_assets/frontend/src/App.vue +231 -0
  34. backend/_assets/frontend/src/main.js +12 -0
  35. backend/_assets/frontend/src/router/index.js +117 -0
  36. backend/_assets/frontend/src/stores/chat.js +671 -0
  37. backend/_assets/frontend/src/styles/tokens.css +282 -0
  38. backend/_assets/frontend/src/utils/http.js +191 -0
  39. backend/_assets/frontend/src/views/AgentsView.vue +227 -0
  40. backend/_assets/frontend/src/views/AttackChainView.vue +210 -0
  41. backend/_assets/frontend/src/views/ChatView.vue +1107 -0
  42. backend/_assets/frontend/src/views/DashboardView.vue +514 -0
  43. backend/_assets/frontend/src/views/EventDetailView.vue +387 -0
  44. backend/_assets/frontend/src/views/EventsView.vue +253 -0
  45. backend/_assets/frontend/src/views/KnowledgeView.vue +883 -0
  46. backend/_assets/frontend/src/views/LoginView.vue +314 -0
  47. backend/_assets/frontend/src/views/SettingsView.vue +226 -0
  48. backend/_assets/frontend/src/views/UsersView.vue +235 -0
  49. backend/_assets/frontend/vite.config.js +55 -0
  50. backend/_assets/knowledge_data/compliance/regulations.json +383 -0
  51. backend/_assets/knowledge_data/cve/vulnerabilities.json +70720 -0
  52. backend/_assets/knowledge_data/mitre_attack/techniques.json +14097 -0
  53. backend/_assets/knowledge_data/remediation/remediation.json +574 -0
  54. backend/_assets/knowledge_data/threat_intel/actors.json +1602 -0
  55. backend/_assets/knowledge_data/threat_intel/malware.json +971 -0
  56. backend/agents/__init__.py +1 -0
  57. backend/agents/alert_filter/__init__.py +1 -0
  58. backend/agents/alert_filter/alert_filter_agent.py +88 -0
  59. backend/agents/analyst.py +331 -0
  60. backend/agents/base.py +553 -0
  61. backend/agents/classifier_agent.py +134 -0
  62. backend/agents/cot_engine.py +578 -0
  63. backend/agents/intel.py +46 -0
  64. backend/agents/knowledge/__init__.py +2 -0
  65. backend/agents/knowledge/agentic_rag.py +928 -0
  66. backend/agents/knowledge/knowledge_agent.py +47 -0
  67. backend/agents/registry.py +148 -0
  68. backend/agents/responder.py +51 -0
  69. backend/agents/summary_agent.py +219 -0
  70. backend/auto_ingestor.py +948 -0
  71. backend/auto_patrol.py +452 -0
  72. backend/cli_app.py +523 -0
  73. backend/config/__init__.py +20 -0
  74. backend/config/provider_profiles.py +416 -0
  75. backend/config/runtime_settings.py +84 -0
  76. backend/decision_fusion/__init__.py +32 -0
  77. backend/decision_fusion/base.py +127 -0
  78. backend/decision_fusion/dempster_shafer.py +419 -0
  79. backend/decision_fusion/weighted_average.py +141 -0
  80. backend/email_ingestor.py +588 -0
  81. backend/errors.py +243 -0
  82. backend/escalation.py +453 -0
  83. backend/evaluation/__init__.py +7 -0
  84. backend/evaluation/evaluator.py +197 -0
  85. backend/evaluation/metrics.py +210 -0
  86. backend/federation/__init__.py +22 -0
  87. backend/federation/core.py +743 -0
  88. backend/interface/__init__.py +1 -0
  89. backend/interface/api_server.py +1830 -0
  90. backend/interface/cli.py +671 -0
  91. backend/knowledge/__init__.py +11 -0
  92. backend/knowledge/compliance.py +101 -0
  93. backend/knowledge/cve_db.py +100 -0
  94. backend/knowledge/mitre_attack.py +457 -0
  95. backend/knowledge/remediation.py +84 -0
  96. backend/knowledge/threat_intel_kb.py +246 -0
  97. backend/llm/__init__.py +1 -0
  98. backend/llm/anthropic_compatible.py +191 -0
  99. backend/llm/base.py +81 -0
  100. backend/llm/deepseek.py +221 -0
  101. backend/llm/fallback.py +268 -0
  102. backend/llm/mock.py +204 -0
  103. backend/llm/openai_compatible.py +51 -0
  104. backend/llm/provider.py +217 -0
  105. backend/llm/qwen.py +218 -0
  106. backend/main.py +421 -0
  107. backend/memory/__init__.py +1 -0
  108. backend/memory/episodic_memory.py +132 -0
  109. backend/memory/manager.py +204 -0
  110. backend/memory/semantic_memory.py +156 -0
  111. backend/memory/session_memory.py +102 -0
  112. backend/ml_model/__init__.py +23 -0
  113. backend/ml_model/pipeline.py +191 -0
  114. backend/ml_model/trainer.py +627 -0
  115. backend/models/__init__.py +1 -0
  116. backend/models/event.py +62 -0
  117. backend/models/message.py +58 -0
  118. backend/models/output.py +1216 -0
  119. backend/monitoring/metrics.py +184 -0
  120. backend/orchestrator/__init__.py +17 -0
  121. backend/orchestrator/agent_router.py +145 -0
  122. backend/orchestrator/core.py +159 -0
  123. backend/orchestrator/dynamic_weights.py +287 -0
  124. backend/orchestrator/react_loop.py +1895 -0
  125. backend/orchestrator/risk_model.py +494 -0
  126. backend/orchestrator/risk_scorer.py +356 -0
  127. backend/reasoner/__init__.py +19 -0
  128. backend/reasoner/reasoner.py +316 -0
  129. backend/runtime_assets.py +45 -0
  130. backend/security/__init__.py +24 -0
  131. backend/security/adversarial/__init__.py +13 -0
  132. backend/security/adversarial/guard.py +121 -0
  133. backend/security/adversarial/redteam.py +121 -0
  134. backend/security/adversarial/scanner.py +208 -0
  135. backend/security/audit.py +118 -0
  136. backend/security/auth.py +283 -0
  137. backend/security/circuit_breaker.py +212 -0
  138. backend/security/dispatch.py +18 -0
  139. backend/security/ratelimit.py +237 -0
  140. backend/security/rbac.py +431 -0
  141. backend/security/refresh_sessions.py +193 -0
  142. backend/security/sanitizer.py +32 -0
  143. backend/storage/__init__.py +1 -0
  144. backend/storage/chroma_store.py +79 -0
  145. backend/storage/database.py +296 -0
  146. backend/storage/migrations/env.py +65 -0
  147. backend/storage/migrations/versions/0001_initial_schema.py +101 -0
  148. backend/storage/migrations/versions/0002_add_agent_logs_experiences.py +53 -0
  149. backend/storage/migrations/versions/0003_add_conversation_owner.py +30 -0
  150. backend/storage/models.py +194 -0
  151. backend/storage/repositories/__init__.py +1 -0
  152. backend/storage/repositories/asset_repo.py +86 -0
  153. backend/storage/repositories/conversation_repo.py +216 -0
  154. backend/storage/repositories/event_repo.py +76 -0
  155. backend/storage/repositories/trajectory_repo.py +324 -0
  156. backend/storage/retention.py +117 -0
  157. backend/tools/__init__.py +16 -0
  158. backend/tools/alert_filter.py +208 -0
  159. backend/tools/aliyun_firewall.py +237 -0
  160. backend/tools/aws_firewall.py +369 -0
  161. backend/tools/base.py +445 -0
  162. backend/tools/calling.py +207 -0
  163. backend/tools/cve_search.py +77 -0
  164. backend/tools/execution_engine.py +202 -0
  165. backend/tools/firewall.py +387 -0
  166. backend/tools/geoip.py +168 -0
  167. backend/tools/log_analyzer.py +185 -0
  168. backend/tools/ml_detector.py +226 -0
  169. backend/tools/registry.py +38 -0
  170. backend/tools/tencent_firewall.py +300 -0
  171. backend/tools/threat_intel.py +732 -0
  172. backend/tools/trust_checker.py +76 -0
  173. backend/utils/__init__.py +5 -0
  174. backend/utils/config_loader.py +164 -0
  175. backend/utils/env.py +75 -0
  176. backend/utils/logger.py +150 -0
  177. backend/utils/text.py +32 -0
  178. secagentx-3.0.0.dist-info/METADATA +325 -0
  179. secagentx-3.0.0.dist-info/RECORD +182 -0
  180. secagentx-3.0.0.dist-info/WHEEL +5 -0
  181. secagentx-3.0.0.dist-info/entry_points.txt +2 -0
  182. secagentx-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,101 @@
1
+ import json
2
+ import os
3
+ from typing import Optional
4
+
5
+ KNOWLEDGE_BASE_DIR = os.getenv("KNOWLEDGE_BASE_DIR", "knowledge_data")
6
+
7
+
8
+ class ComplianceKnowledge:
9
+ """行业监管合规知识库(等保2.0、网络安全法、数据安全法、个人信息保护法、GDPR等)"""
10
+
11
+ def __init__(self):
12
+ self._data = self._load()
13
+
14
+ def _load(self) -> dict:
15
+ path = os.path.join(KNOWLEDGE_BASE_DIR, "compliance", "regulations.json")
16
+ if not os.path.exists(path):
17
+ return {"regulations": []}
18
+ try:
19
+ with open(path, "r", encoding="utf-8") as f:
20
+ return json.load(f)
21
+ except (json.JSONDecodeError, FileNotFoundError):
22
+ return {"regulations": []}
23
+
24
+ @property
25
+ def regulations(self) -> list[dict]:
26
+ return self._data.get("regulations", [])
27
+
28
+ def search(self, query: str) -> list[dict]:
29
+ """搜索匹配的法规要求"""
30
+ query_lower = query.lower()
31
+ results = []
32
+ for reg in self.regulations:
33
+ score = 0
34
+ matched_items = []
35
+ name = reg.get("name", "")
36
+ abbr = reg.get("abbr", "")
37
+ desc = reg.get("description", "")
38
+
39
+ if query_lower in name.lower() or query_lower in abbr.lower():
40
+ score += 10
41
+ matched_items.append(f"法规名称匹配")
42
+
43
+ for req in reg.get("key_requirements", []):
44
+ if query_lower in req.lower():
45
+ score += 3
46
+ matched_items.append(req)
47
+
48
+ if query_lower in desc.lower():
49
+ score += 5
50
+
51
+ if score > 0:
52
+ results.append({
53
+ "name": name,
54
+ "abbr": abbr,
55
+ "score": score,
56
+ "matched_items": matched_items[:5],
57
+ "key_requirements": reg.get("key_requirements", []),
58
+ "penalties": reg.get("penalties", ""),
59
+ "description": desc,
60
+ })
61
+
62
+ results.sort(key=lambda x: x["score"], reverse=True)
63
+ return results[:5]
64
+
65
+ def get_regulation(self, name_or_abbr: str) -> Optional[dict]:
66
+ """按名称或简称查询具体法规"""
67
+ query = name_or_abbr.lower()
68
+ for reg in self.regulations:
69
+ if query in reg.get("name", "").lower() or query in reg.get("abbr", "").lower():
70
+ return reg
71
+ return None
72
+
73
+ def check_compliance(self, scenario: str) -> list[dict]:
74
+ """检查某场景涉及哪些合规要求"""
75
+ scenario_lower = scenario.lower()
76
+ related = []
77
+ keywords_map = {
78
+ "数据泄露|数据安全|数据保护|personal data": ["数据安全法", "个人信息保护法", "GDPR", "网络安全法"],
79
+ "等级保护|等保|分级保护": ["网络安全等级保护2.0"],
80
+ "关键信息基础设施|关基|CII": ["关键信息基础设施安全保护条例"],
81
+ "支付|信用卡|银行卡|持卡人": ["PCI DSS v4.0"],
82
+ "日志|审计|audit": ["等级保护安全审计要求", "ISO/IEC 27001:2022"],
83
+ "信息安全管理|ISMS": ["ISO/IEC 27001:2022"],
84
+ "个人信息|隐私|用户信息": ["个人信息保护法", "GDPR"],
85
+ "跨境|出境|数据转移|transfer": ["数据安全法", "GDPR", "个人信息保护法"],
86
+ "密码|口令|身份鉴别|认证": ["等保2.0", "ISO/IEC 27001:2022"],
87
+ "网络安全|事件|应急": ["网络安全法", "关键信息基础设施安全保护条例"],
88
+ }
89
+
90
+ for keywords, reg_names in keywords_map.items():
91
+ if any(kw in scenario_lower for kw in keywords.split("|")):
92
+ for reg_name in reg_names:
93
+ reg = self.get_regulation(reg_name)
94
+ if reg and reg not in related:
95
+ related.append(reg)
96
+
97
+ return related
98
+
99
+ def count(self) -> int:
100
+ return len(self.regulations)
101
+
@@ -0,0 +1,100 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ KNOWLEDGE_BASE_DIR = os.getenv("KNOWLEDGE_BASE_DIR", "knowledge_data")
7
+
8
+
9
+ class CVEDatabase:
10
+ """CVE漏洞知识库(从JSON加载最新/高危漏洞)"""
11
+
12
+ def __init__(self):
13
+ self._data = self._load()
14
+
15
+ def _load(self) -> dict:
16
+ path = os.path.join(KNOWLEDGE_BASE_DIR, "cve", "vulnerabilities.json")
17
+ if not os.path.exists(path):
18
+ return {"cve_database": []}
19
+ try:
20
+ with open(path, "r", encoding="utf-8") as f:
21
+ return json.load(f)
22
+ except (json.JSONDecodeError, FileNotFoundError):
23
+ return {"cve_database": []}
24
+
25
+ @property
26
+ def vulnerabilities(self) -> list[dict]:
27
+ return self._data.get("cve_database", [])
28
+
29
+ def get_by_id(self, cve_id: str) -> Optional[dict]:
30
+ """按CVE编号查询"""
31
+ cve_id_upper = cve_id.upper().strip()
32
+ for vuln in self.vulnerabilities:
33
+ if vuln.get("id", "").upper() == cve_id_upper:
34
+ return vuln
35
+ # 支持部分匹配
36
+ if cve_id_upper.startswith(vuln.get("id", "").upper().split("-")[0]):
37
+ if cve_id_upper in vuln.get("id", "").upper():
38
+ return vuln
39
+ return None
40
+
41
+ def search(self, query: str) -> list[dict]:
42
+ """搜索CVE漏洞"""
43
+ query_lower = query.lower()
44
+ results = []
45
+
46
+ for vuln in self.vulnerabilities:
47
+ score = 0
48
+ cve_id = vuln.get("id", "").lower()
49
+ desc = vuln.get("description", "").lower()
50
+ affected = vuln.get("affected", "").lower()
51
+ mitre_mappings = vuln.get("mitre_techniques", vuln.get("mitre_mapping", []))
52
+
53
+ if query_lower in cve_id:
54
+ score += 10
55
+ if query_lower in desc:
56
+ score += 5
57
+ if query_lower in affected:
58
+ score += 5
59
+ for mt in mitre_mappings:
60
+ if query_lower in mt.lower():
61
+ score += 3
62
+
63
+ if score > 0:
64
+ results.append({
65
+ "id": vuln["id"],
66
+ "severity": vuln.get("severity", "UNKNOWN"),
67
+ "cvss_score": vuln.get("cvss_score", 0),
68
+ "description": vuln["description"],
69
+ "affected": vuln.get("affected", ""),
70
+ "impact": vuln.get("impact", ""),
71
+ "detection": vuln.get("detection", ""),
72
+ "remediation": vuln.get("remediation", ""),
73
+ "score": score,
74
+ "mitre_mapping": mitre_mappings,
75
+ })
76
+
77
+ results.sort(key=lambda x: (x["score"], x["cvss_score"]), reverse=True)
78
+ return results[:10]
79
+
80
+ def get_critical_recent(self, days: int = 365) -> list[dict]:
81
+ """获取近期严重漏洞"""
82
+ return [
83
+ v for v in self.vulnerabilities
84
+ if v.get("severity") in ("CRITICAL", "HIGH")
85
+ ][:10]
86
+
87
+ def get_by_mitre_technique(self, technique_id: str) -> list[dict]:
88
+ """通过MITRE ATT&CK技术ID查找相关CVE"""
89
+ tid = technique_id.upper().split(".")[0]
90
+ results = []
91
+ for vuln in self.vulnerabilities:
92
+ mappings = vuln.get("mitre_techniques", vuln.get("mitre_mapping", []))
93
+ for mt in mappings:
94
+ if mt.upper().startswith(tid):
95
+ results.append(vuln)
96
+ break
97
+ return results
98
+
99
+ def count(self) -> int:
100
+ return len(self.vulnerabilities)
@@ -0,0 +1,457 @@
1
+ import json
2
+ import os
3
+ import threading
4
+ from typing import Optional
5
+ from collections import defaultdict
6
+
7
+ KNOWLEDGE_BASE_DIR = os.getenv("KNOWLEDGE_BASE_DIR", "knowledge_data")
8
+
9
+ # 模块级缓存 + 刷新锁
10
+ _mitre_cache = {"data": None, "lock": threading.Lock()}
11
+
12
+
13
+ def _load_mitre_json(force_reload: bool = False):
14
+ """加载 MITRE 数据,带缓存和线程安全刷新"""
15
+ if _mitre_cache["data"] is not None and not force_reload:
16
+ return _mitre_cache["data"]
17
+ with _mitre_cache["lock"]:
18
+ # 双重检查
19
+ if _mitre_cache["data"] is not None and not force_reload:
20
+ return _mitre_cache["data"]
21
+ path = os.path.join(KNOWLEDGE_BASE_DIR, "mitre_attack", "techniques.json")
22
+ if not os.path.exists(path):
23
+ _mitre_cache["data"] = {"tactics": [], "techniques": []}
24
+ return _mitre_cache["data"]
25
+ try:
26
+ with open(path, "r", encoding="utf-8") as f:
27
+ _mitre_cache["data"] = json.load(f)
28
+ except (json.JSONDecodeError, FileNotFoundError):
29
+ _mitre_cache["data"] = {"tactics": [], "techniques": []}
30
+ return _mitre_cache["data"]
31
+
32
+
33
+ _MITRE_DATA = _load_mitre_json()
34
+ TACTICS_INDEX = {t["id"]: t["name"] for t in _MITRE_DATA.get("tactics", [])}
35
+ TACTICS_DETAIL = {t["id"]: t for t in _MITRE_DATA.get("tactics", [])}
36
+
37
+ # 战术执行顺序(用于攻击链展示)
38
+ TACTIC_KILL_CHAIN_ORDER = [
39
+ "TA0043", # 侦察
40
+ "TA0042", # 资源开发
41
+ "TA0001", # 初始访问
42
+ "TA0002", # 执行
43
+ "TA0003", # 持久化
44
+ "TA0004", # 权限提升
45
+ "TA0005", # 防御绕过
46
+ "TA0006", # 凭据访问
47
+ "TA0007", # 发现
48
+ "TA0008", # 横向移动
49
+ "TA0009", # 收集
50
+ "TA0011", # 命令与控制
51
+ "TA0010", # 数据外传
52
+ "TA0040", # 影响
53
+ ]
54
+
55
+ # ─── phase 归一化映射 ───
56
+ # techniques.json 中 phase 字段混用三种格式:
57
+ # 1. 标准 TA 编号(TA0001~TA0043)→ 直接使用
58
+ # 2. 小写短横线(credential-access、stealth、defense-impairment...)→ 映射到 TA 编号
59
+ # 3. 非标准自定义值(STEALTH、DEFENSE-IMPAIRMENT)→ 映射到最接近的 TA 编号
60
+ # 归一化后保证所有技术都能正确分组、tactic_name 不再为"未知"。
61
+ PHASE_TO_TACTIC = {
62
+ # 标准 TA 编号 → 自身
63
+ "TA0001": "TA0001", "TA0002": "TA0002", "TA0003": "TA0003",
64
+ "TA0004": "TA0004", "TA0005": "TA0005", "TA0006": "TA0006",
65
+ "TA0007": "TA0007", "TA0008": "TA0008", "TA0009": "TA0009",
66
+ "TA0010": "TA0010", "TA0011": "TA0011", "TA0040": "TA0040",
67
+ "TA0042": "TA0042", "TA0043": "TA0043",
68
+ # 小写短横线 → TA 编号
69
+ "reconnaissance": "TA0043", # 侦察
70
+ "resource-development": "TA0042", # 资源开发
71
+ "initial-access": "TA0001", # 初始访问
72
+ "execution": "TA0002", # 执行
73
+ "persistence": "TA0003", # 持久化
74
+ "privilege-escalation": "TA0004", # 权限提升
75
+ "defense-impairment": "TA0005", # 防御绕过
76
+ "stealth": "TA0005", # 防御绕过(旧版字段名)
77
+ "credential-access": "TA0006", # 凭据访问
78
+ "discovery": "TA0007", # 发现
79
+ "lateral-movement": "TA0008", # 横向移动
80
+ "collection": "TA0009", # 收集
81
+ "command-and-control": "TA0011", # 命令与控制
82
+ "exfiltration": "TA0010", # 数据外传
83
+ "impact": "TA0040", # 影响
84
+ # 非标准大写值(数据文件中的自定义值)→ TA 编号
85
+ "STEALTH": "TA0005", # 防御绕过
86
+ "DEFENSE-IMPAIRMENT": "TA0005", # 防御绕过
87
+ }
88
+ # 各战术的中文名(供 tactic_name 展示)
89
+ TACTIC_CN_NAMES = {
90
+ "TA0043": "侦察", "TA0042": "资源开发", "TA0001": "初始访问",
91
+ "TA0002": "执行", "TA0003": "持久化", "TA0004": "权限提升",
92
+ "TA0005": "防御绕过", "TA0006": "凭据访问", "TA0007": "发现",
93
+ "TA0008": "横向移动", "TA0009": "收集", "TA0011": "命令与控制",
94
+ "TA0010": "数据外传", "TA0040": "影响",
95
+ }
96
+
97
+
98
+ def _normalize_phase(phase: str) -> str:
99
+ """将任意格式的 phase 归一化为标准 TA 编号"""
100
+ if not phase:
101
+ return ""
102
+ return PHASE_TO_TACTIC.get(phase, phase)
103
+
104
+
105
+ TECHNIQUES_INDEX: dict[str, dict] = {}
106
+ TECHNIQUES_BY_TACTIC: dict[str, list] = defaultdict(list)
107
+
108
+ for t in _MITRE_DATA.get("techniques", []):
109
+ tid = t["id"]
110
+ raw_phase = t.get("phase", "")
111
+ phase = _normalize_phase(raw_phase)
112
+ subs = t.get("sub_techniques", {})
113
+ sub_info = {}
114
+ if subs and isinstance(subs, dict):
115
+ sub_info = {k: v.get("name", v) if isinstance(v, dict) else v for k, v in subs.items()}
116
+
117
+ entry = {
118
+ "name": t["name"],
119
+ "tactic": phase,
120
+ "raw_phase": raw_phase,
121
+ "tactic_name": TACTIC_CN_NAMES.get(phase, TACTICS_INDEX.get(phase, "未知")),
122
+ "description": t.get("description", ""),
123
+ "detection": t.get("detection", ""),
124
+ "mitigation": t.get("mitigation", ""),
125
+ "scores": t.get("scores", {}),
126
+ "related_cves": t.get("related_cves", []),
127
+ "sub_techniques": sub_info,
128
+ }
129
+ TECHNIQUES_INDEX[tid] = entry
130
+ TECHNIQUES_BY_TACTIC[phase].append(entry)
131
+
132
+
133
+ class MitreAttackKnowledge:
134
+ """
135
+ MITRE ATT&CK 知识库 v2.0
136
+
137
+ 增强功能:
138
+ - 结构化搜索(按战术/等级/复杂度筛选)
139
+ - 攻击链数据(杀伤链阶段排序)
140
+ - 技术评分体系(风险/影响/检测难度)
141
+ - 跨引用(技术→CVE)
142
+ - 战术画像(技术分布/风险等级统计)
143
+ """
144
+
145
+ def __init__(self, data_path: Optional[str] = None):
146
+ self.data_path = data_path or KNOWLEDGE_BASE_DIR
147
+
148
+ # ==================== 基础查询 ====================
149
+
150
+ def get_tactic(self, tactic_id: str) -> Optional[dict]:
151
+ tid = tactic_id.upper()
152
+ tactic = TACTICS_DETAIL.get(tid)
153
+ if not tactic:
154
+ return None
155
+ techniques = [
156
+ {**t, "id": tech_id}
157
+ for tech_id, t in TECHNIQUES_INDEX.items()
158
+ if t.get("tactic") == tid
159
+ ]
160
+ return {
161
+ "id": tid,
162
+ "name": tactic["name"],
163
+ "phase": tactic.get("phase", ""),
164
+ "description": tactic.get("description", ""),
165
+ "kill_chain_position": TACTIC_KILL_CHAIN_ORDER.index(tid) + 1 if tid in TACTIC_KILL_CHAIN_ORDER else 0,
166
+ "techniques_count": len(techniques),
167
+ "techniques": techniques,
168
+ # 战术统计
169
+ "stats": self._tactic_stats(techniques),
170
+ }
171
+
172
+ def get_technique(self, technique_id: str) -> Optional[dict]:
173
+ tid = technique_id.upper()
174
+ if tid in TECHNIQUES_INDEX:
175
+ tech = TECHNIQUES_INDEX[tid]
176
+ result = {
177
+ "id": tid,
178
+ "name": tech["name"],
179
+ "tactic_id": tech.get("tactic", ""),
180
+ "tactic_name": tech.get("tactic_name", "未知"),
181
+ "tactic_position": TACTIC_KILL_CHAIN_ORDER.index(tech.get("tactic", "")) + 1
182
+ if tech.get("tactic", "") in TACTIC_KILL_CHAIN_ORDER else 0,
183
+ "description": tech.get("description", ""),
184
+ "detection": tech.get("detection", ""),
185
+ "mitigation": tech.get("mitigation", ""),
186
+ "has_sub_techniques": bool(tech.get("sub_techniques")),
187
+ "sub_techniques": tech.get("sub_techniques", {}),
188
+ # 评分体系
189
+ "scores": {
190
+ **tech.get("scores", {}),
191
+ "overall_rating": self._overall_rating(tech.get("scores", {})),
192
+ },
193
+ # 跨引用
194
+ "related_cves": tech.get("related_cves", []),
195
+ # 关联合规
196
+ "related_compliance": self._get_compliance_for_technique(tid),
197
+ }
198
+ return result
199
+
200
+ # 子技术查询
201
+ for t_id, t in TECHNIQUES_INDEX.items():
202
+ subs = t.get("sub_techniques", {})
203
+ if tid in subs:
204
+ return {
205
+ "id": tid,
206
+ "name": subs[tid],
207
+ "parent_technique_id": t_id,
208
+ "parent_technique_name": t["name"],
209
+ "tactic_id": t.get("tactic", ""),
210
+ "tactic_name": t.get("tactic_name", "未知"),
211
+ "description": "子技术,父技术描述如上",
212
+ "detection": t.get("detection", ""),
213
+ "mitigation": t.get("mitigation", ""),
214
+ }
215
+ return None
216
+
217
+ # ==================== 增强搜索 ====================
218
+
219
+ def search(self, query: str, filters: Optional[dict] = None) -> list[dict]:
220
+ """
221
+ 增强搜索 — 支持文本匹配 + 多维筛选
222
+
223
+ filters:
224
+ risk_levels: ["紧急", "高危", "中危", "低危"]
225
+ tactics: ["TA0001", "TA0002"]
226
+ has_cve: True/False
227
+ min_risk_score: float
228
+ """
229
+ query_lower = query.lower()
230
+ results = []
231
+
232
+ for tid, tech in TECHNIQUES_INDEX.items():
233
+ score = 0
234
+ matches = []
235
+
236
+ # 文本匹配
237
+ if query_lower in tid.lower() or query_lower in tech["name"].lower():
238
+ score += 10
239
+ matches.append("ID/名称匹配")
240
+ if query_lower in tech.get("description", "").lower():
241
+ score += 5
242
+ matches.append("描述匹配")
243
+ if query_lower in tech.get("detection", "").lower():
244
+ score += 3
245
+ matches.append("检测方法匹配")
246
+ for cve in tech.get("related_cves", []):
247
+ if query_lower in cve.lower():
248
+ score += 6
249
+ matches.append("关联CVE匹配")
250
+
251
+ # 子技术匹配
252
+ for sub_id, sub_name in tech.get("sub_techniques", {}).items():
253
+ if query_lower in sub_id.lower() or query_lower in sub_name.lower():
254
+ score += 3
255
+ matches.append("子技术匹配")
256
+
257
+ if score == 0:
258
+ continue
259
+
260
+ # 多维筛选
261
+ if filters:
262
+ if filters.get("risk_levels"):
263
+ rl = tech.get("scores", {}).get("risk_level", "")
264
+ if rl not in filters["risk_levels"]:
265
+ score = 0
266
+ if filters.get("tactics"):
267
+ if tech.get("tactic", "") not in filters["tactics"]:
268
+ score = 0
269
+ if filters.get("has_cve"):
270
+ if not tech.get("related_cves"):
271
+ score = 0
272
+ if filters.get("min_risk_score"):
273
+ rs = tech.get("scores", {}).get("risk_score", 0)
274
+ if rs < filters["min_risk_score"]:
275
+ score = 0
276
+
277
+ if score <= 0:
278
+ continue
279
+
280
+ results.append({
281
+ "id": tid,
282
+ "name": tech["name"],
283
+ "tactic_id": tech.get("tactic", ""),
284
+ "tactic_name": tech.get("tactic_name", "未知"),
285
+ "type": "technique",
286
+ "description": tech.get("description", "")[:120],
287
+ "risk_level": tech.get("scores", {}).get("risk_level", "未评级"),
288
+ "risk_score": tech.get("scores", {}).get("risk_score", 0),
289
+ "has_cve": bool(tech.get("related_cves")),
290
+ "match_score": score,
291
+ "sub_techniques": list(tech.get("sub_techniques", {}).keys()),
292
+ })
293
+
294
+ # 按匹配度+风险分排序
295
+ results.sort(key=lambda x: (x["match_score"], x["risk_score"]), reverse=True)
296
+ return results[:20]
297
+
298
+ # ==================== 攻击链 ====================
299
+
300
+ def get_kill_chain(self) -> list[dict]:
301
+ """
302
+ 获取完整杀伤链视图
303
+
304
+ 返回战术阶段排序列表,每个战术包含其下的技术和统计
305
+ """
306
+ chain = []
307
+ for tid in TACTIC_KILL_CHAIN_ORDER:
308
+ tactic = TACTICS_DETAIL.get(tid)
309
+ if not tactic:
310
+ continue
311
+ techniques = TECHNIQUES_BY_TACTIC.get(tid, [])
312
+ chain.append({
313
+ "tactic_id": tid,
314
+ "tactic_name": tactic["name"],
315
+ "phase": tactic.get("phase", ""),
316
+ "position": TACTIC_KILL_CHAIN_ORDER.index(tid) + 1,
317
+ "techniques_count": len(techniques),
318
+ "risk_distribution": self._risk_distribution(techniques),
319
+ "top_techniques": [
320
+ {
321
+ "id": tech_id,
322
+ "name": t["name"],
323
+ "risk_level": t.get("scores", {}).get("risk_level", ""),
324
+ "risk_score": t.get("scores", {}).get("risk_score", 0),
325
+ }
326
+ for tech_id, t in TECHNIQUES_INDEX.items()
327
+ if t.get("tactic") == tid
328
+ ][:5],
329
+ })
330
+ return chain
331
+
332
+ def get_attack_flow(self, technique_ids: list[str]) -> list[dict]:
333
+ """
334
+ 获取多技术的攻击链路径
335
+
336
+ 输入: ["T1190", "T1059", "T1003", "T1071", "T1041"]
337
+ 输出: 按杀伤链排序的攻击路径
338
+ """
339
+ flow = []
340
+ for tid in technique_ids:
341
+ tid = tid.upper()
342
+ if tid in TECHNIQUES_INDEX:
343
+ tech = TECHNIQUES_INDEX[tid]
344
+ tactic_pos = TACTIC_KILL_CHAIN_ORDER.index(tech.get("tactic", "")) + 1
345
+ flow.append({
346
+ "id": tid,
347
+ "name": tech["name"],
348
+ "tactic_id": tech.get("tactic", ""),
349
+ "tactic_name": tech.get("tactic_name", "未知"),
350
+ "kill_chain_position": tactic_pos,
351
+ "risk_level": tech.get("scores", {}).get("risk_level", ""),
352
+ "risk_score": tech.get("scores", {}).get("risk_score", 0),
353
+ "description": tech.get("description", "")[:100],
354
+ })
355
+ flow.sort(key=lambda x: x["kill_chain_position"])
356
+ return flow
357
+
358
+ # ==================== 统计报表 ====================
359
+
360
+ def get_dashboard(self) -> dict:
361
+ """获取MITRE知识库仪表盘数据"""
362
+ all_scores = [t.get("scores", {}).get("risk_score", 0) for t in TECHNIQUES_INDEX.values()]
363
+ risk_dist = self._risk_distribution(list(TECHNIQUES_INDEX.values()))
364
+
365
+ # 按战术统计技术分布
366
+ tactic_tech_count = {
367
+ tid: len(TECHNIQUES_BY_TACTIC.get(tid, []))
368
+ for tid in TACTIC_KILL_CHAIN_ORDER
369
+ }
370
+
371
+ return {
372
+ "total_techniques": len(TECHNIQUES_INDEX),
373
+ "total_tactics": len(TACTICS_INDEX),
374
+ "total_sub_techniques": sum(
375
+ len(t.get("sub_techniques", {})) for t in TECHNIQUES_INDEX.values()
376
+ ),
377
+ "risk_distribution": risk_dist,
378
+ "avg_risk_score": round(sum(all_scores) / len(all_scores), 1) if all_scores else 0,
379
+ "max_risk_techniques": self._top_risks(5),
380
+ "tactic_technique_count": tactic_tech_count,
381
+ "technique_with_cve": sum(1 for t in TECHNIQUES_INDEX.values() if t.get("related_cves")),
382
+ }
383
+
384
+ # ==================== 内部方法 ====================
385
+
386
+ def _tactic_stats(self, techniques: list) -> dict:
387
+ """战术级别的统计"""
388
+ scores = [t.get("scores", {}).get("risk_score", 0) for t in techniques]
389
+ return {
390
+ "avg_risk_score": round(sum(scores) / len(scores), 1) if scores else 0,
391
+ "total_techniques": len(techniques),
392
+ "risk_distribution": self._risk_distribution(techniques),
393
+ }
394
+
395
+ def _risk_distribution(self, techniques: list) -> dict:
396
+ """风险等级分布统计"""
397
+ dist = defaultdict(int)
398
+ for t in techniques:
399
+ rl = t.get("scores", {}).get("risk_level", "未评级")
400
+ dist[rl] += 1
401
+ return dict(dist)
402
+
403
+ def _overall_rating(self, scores: dict) -> str:
404
+ """综合评分文字描述"""
405
+ rs = scores.get("risk_score", 0)
406
+ if rs >= 8.0: return " 极高风险 — 优先处置"
407
+ if rs >= 6.5: return " 高风险 — 尽快加固"
408
+ if rs >= 4.5: return " 中风险 — 常规关注"
409
+ return " 低风险 — 持续监控"
410
+
411
+ def _top_risks(self, n: int = 5) -> list[dict]:
412
+ """最高风险技术TOP N"""
413
+ scored = [
414
+ {"id": tid, "name": t["name"], "risk_score": t.get("scores", {}).get("risk_score", 0),
415
+ "risk_level": t.get("scores", {}).get("risk_level", ""),
416
+ "tactic_name": t.get("tactic_name", "")}
417
+ for tid, t in TECHNIQUES_INDEX.items()
418
+ ]
419
+ scored.sort(key=lambda x: x["risk_score"], reverse=True)
420
+ return scored[:n]
421
+
422
+ def _get_compliance_for_technique(self, technique_id: str) -> list[str]:
423
+ """技术关联的合规要求"""
424
+ compliance_map = {
425
+ "T1566": ["等保2.0 身份鉴别", "网络安全法 第二十一条"],
426
+ "T1190": ["等保2.0 漏洞管理", "关基保护条例 安全检测"],
427
+ "T1110": ["等保2.0 身份鉴别", "ISO 27001 A.9.4.2"],
428
+ "T1021": ["等保2.0 访问控制", "ISO 27001 A.9.1.2"],
429
+ "T1003": ["等保2.0 数据安全", "数据安全法 第二十七条"],
430
+ "T1070": ["等保2.0 安全审计", "等保审计要求 三级"],
431
+ "T1041": ["数据安全法 第三十一条", "等保2.0 数据安全"],
432
+ "T1486": ["关基保护条例 应急演练", "网络安全法 第五十五条"],
433
+ "T1071": ["等保2.0 通信安全", "ISO 27001 A.13.1.1"],
434
+ }
435
+ base_id = technique_id.upper().split(".")[0]
436
+ return compliance_map.get(base_id, [])
437
+
438
+ # ==================== 兼容接口 ====================
439
+
440
+ def get_all_tactics(self) -> list[dict]:
441
+ return [{"id": tid, "name": name} for tid, name in TACTICS_INDEX.items()]
442
+
443
+ def count(self) -> dict:
444
+ sub_count = sum(len(t.get("sub_techniques", {})) for t in TECHNIQUES_INDEX.values())
445
+ return {
446
+ "tactics": len(TACTICS_INDEX),
447
+ "techniques": len(TECHNIQUES_INDEX),
448
+ "sub_techniques": sub_count,
449
+ }
450
+
451
+ def get_mitigations(self, technique_id: str) -> list[str]:
452
+ tid = technique_id.upper().split(".")[0]
453
+ tech = TECHNIQUES_INDEX.get(tid)
454
+ if tech and tech.get("mitigation"):
455
+ return [m.strip() for m in tech["mitigation"].split(";") if m.strip()]
456
+ return ["参考MITRE ATT&CK官方缓解建议"]
457
+