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.
- backend/__init__.py +1 -0
- backend/__main__.py +3 -0
- backend/_assets/config.yaml +195 -0
- backend/_assets/frontend/dist/assets/AgentsView-DFqRplI8.css +1 -0
- backend/_assets/frontend/dist/assets/AgentsView-DJTpfQ87.js +1 -0
- backend/_assets/frontend/dist/assets/AttackChainView-BVfI-SC6.js +1 -0
- backend/_assets/frontend/dist/assets/ChatView-Bg4nIlQ0.css +1 -0
- backend/_assets/frontend/dist/assets/ChatView-CRndtmEC.js +38 -0
- backend/_assets/frontend/dist/assets/DashboardView-BXs4AWv8.js +1 -0
- backend/_assets/frontend/dist/assets/DashboardView-WnHWiqBW.css +1 -0
- backend/_assets/frontend/dist/assets/EventDetailView-DQk6Qp7-.js +1 -0
- backend/_assets/frontend/dist/assets/EventDetailView-Dj-a-zWr.css +1 -0
- backend/_assets/frontend/dist/assets/EventsView-8xh8uKO0.js +1 -0
- backend/_assets/frontend/dist/assets/EventsView-BHfFn66K.css +1 -0
- backend/_assets/frontend/dist/assets/KnowledgeView-C2N3v9bF.css +1 -0
- backend/_assets/frontend/dist/assets/KnowledgeView-D54ODoC9.js +11 -0
- backend/_assets/frontend/dist/assets/LoginView-CjvFmo7C.js +1 -0
- backend/_assets/frontend/dist/assets/LoginView-D14FzcsP.css +1 -0
- backend/_assets/frontend/dist/assets/SettingsView-JOJuoABi.js +1 -0
- backend/_assets/frontend/dist/assets/UsersView-DhMXZhir.js +1 -0
- backend/_assets/frontend/dist/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
- backend/_assets/frontend/dist/assets/index-D9Su3fYY.css +1 -0
- backend/_assets/frontend/dist/assets/index-DBEjbyJR.js +4 -0
- backend/_assets/frontend/dist/assets/rolldown-runtime-DK3Fl9T5.js +1 -0
- backend/_assets/frontend/dist/assets/vendor-charts-Dh1nChzO.js +39 -0
- backend/_assets/frontend/dist/assets/vendor-icons-BTNgi7vU.js +1 -0
- backend/_assets/frontend/dist/assets/vendor-ui-Cdd65KTN.js +7367 -0
- backend/_assets/frontend/dist/assets/vendor-vue-CXKcZAmz.js +1 -0
- backend/_assets/frontend/dist/index.html +51 -0
- backend/_assets/frontend/index.html +46 -0
- backend/_assets/frontend/package-lock.json +2983 -0
- backend/_assets/frontend/package.json +37 -0
- backend/_assets/frontend/src/App.vue +231 -0
- backend/_assets/frontend/src/main.js +12 -0
- backend/_assets/frontend/src/router/index.js +117 -0
- backend/_assets/frontend/src/stores/chat.js +671 -0
- backend/_assets/frontend/src/styles/tokens.css +282 -0
- backend/_assets/frontend/src/utils/http.js +191 -0
- backend/_assets/frontend/src/views/AgentsView.vue +227 -0
- backend/_assets/frontend/src/views/AttackChainView.vue +210 -0
- backend/_assets/frontend/src/views/ChatView.vue +1107 -0
- backend/_assets/frontend/src/views/DashboardView.vue +514 -0
- backend/_assets/frontend/src/views/EventDetailView.vue +387 -0
- backend/_assets/frontend/src/views/EventsView.vue +253 -0
- backend/_assets/frontend/src/views/KnowledgeView.vue +883 -0
- backend/_assets/frontend/src/views/LoginView.vue +314 -0
- backend/_assets/frontend/src/views/SettingsView.vue +226 -0
- backend/_assets/frontend/src/views/UsersView.vue +235 -0
- backend/_assets/frontend/vite.config.js +55 -0
- backend/_assets/knowledge_data/compliance/regulations.json +383 -0
- backend/_assets/knowledge_data/cve/vulnerabilities.json +70720 -0
- backend/_assets/knowledge_data/mitre_attack/techniques.json +14097 -0
- backend/_assets/knowledge_data/remediation/remediation.json +574 -0
- backend/_assets/knowledge_data/threat_intel/actors.json +1602 -0
- backend/_assets/knowledge_data/threat_intel/malware.json +971 -0
- backend/agents/__init__.py +1 -0
- backend/agents/alert_filter/__init__.py +1 -0
- backend/agents/alert_filter/alert_filter_agent.py +88 -0
- backend/agents/analyst.py +331 -0
- backend/agents/base.py +553 -0
- backend/agents/classifier_agent.py +134 -0
- backend/agents/cot_engine.py +578 -0
- backend/agents/intel.py +46 -0
- backend/agents/knowledge/__init__.py +2 -0
- backend/agents/knowledge/agentic_rag.py +928 -0
- backend/agents/knowledge/knowledge_agent.py +47 -0
- backend/agents/registry.py +148 -0
- backend/agents/responder.py +51 -0
- backend/agents/summary_agent.py +219 -0
- backend/auto_ingestor.py +948 -0
- backend/auto_patrol.py +452 -0
- backend/cli_app.py +523 -0
- backend/config/__init__.py +20 -0
- backend/config/provider_profiles.py +416 -0
- backend/config/runtime_settings.py +84 -0
- backend/decision_fusion/__init__.py +32 -0
- backend/decision_fusion/base.py +127 -0
- backend/decision_fusion/dempster_shafer.py +419 -0
- backend/decision_fusion/weighted_average.py +141 -0
- backend/email_ingestor.py +588 -0
- backend/errors.py +243 -0
- backend/escalation.py +453 -0
- backend/evaluation/__init__.py +7 -0
- backend/evaluation/evaluator.py +197 -0
- backend/evaluation/metrics.py +210 -0
- backend/federation/__init__.py +22 -0
- backend/federation/core.py +743 -0
- backend/interface/__init__.py +1 -0
- backend/interface/api_server.py +1830 -0
- backend/interface/cli.py +671 -0
- backend/knowledge/__init__.py +11 -0
- backend/knowledge/compliance.py +101 -0
- backend/knowledge/cve_db.py +100 -0
- backend/knowledge/mitre_attack.py +457 -0
- backend/knowledge/remediation.py +84 -0
- backend/knowledge/threat_intel_kb.py +246 -0
- backend/llm/__init__.py +1 -0
- backend/llm/anthropic_compatible.py +191 -0
- backend/llm/base.py +81 -0
- backend/llm/deepseek.py +221 -0
- backend/llm/fallback.py +268 -0
- backend/llm/mock.py +204 -0
- backend/llm/openai_compatible.py +51 -0
- backend/llm/provider.py +217 -0
- backend/llm/qwen.py +218 -0
- backend/main.py +421 -0
- backend/memory/__init__.py +1 -0
- backend/memory/episodic_memory.py +132 -0
- backend/memory/manager.py +204 -0
- backend/memory/semantic_memory.py +156 -0
- backend/memory/session_memory.py +102 -0
- backend/ml_model/__init__.py +23 -0
- backend/ml_model/pipeline.py +191 -0
- backend/ml_model/trainer.py +627 -0
- backend/models/__init__.py +1 -0
- backend/models/event.py +62 -0
- backend/models/message.py +58 -0
- backend/models/output.py +1216 -0
- backend/monitoring/metrics.py +184 -0
- backend/orchestrator/__init__.py +17 -0
- backend/orchestrator/agent_router.py +145 -0
- backend/orchestrator/core.py +159 -0
- backend/orchestrator/dynamic_weights.py +287 -0
- backend/orchestrator/react_loop.py +1895 -0
- backend/orchestrator/risk_model.py +494 -0
- backend/orchestrator/risk_scorer.py +356 -0
- backend/reasoner/__init__.py +19 -0
- backend/reasoner/reasoner.py +316 -0
- backend/runtime_assets.py +45 -0
- backend/security/__init__.py +24 -0
- backend/security/adversarial/__init__.py +13 -0
- backend/security/adversarial/guard.py +121 -0
- backend/security/adversarial/redteam.py +121 -0
- backend/security/adversarial/scanner.py +208 -0
- backend/security/audit.py +118 -0
- backend/security/auth.py +283 -0
- backend/security/circuit_breaker.py +212 -0
- backend/security/dispatch.py +18 -0
- backend/security/ratelimit.py +237 -0
- backend/security/rbac.py +431 -0
- backend/security/refresh_sessions.py +193 -0
- backend/security/sanitizer.py +32 -0
- backend/storage/__init__.py +1 -0
- backend/storage/chroma_store.py +79 -0
- backend/storage/database.py +296 -0
- backend/storage/migrations/env.py +65 -0
- backend/storage/migrations/versions/0001_initial_schema.py +101 -0
- backend/storage/migrations/versions/0002_add_agent_logs_experiences.py +53 -0
- backend/storage/migrations/versions/0003_add_conversation_owner.py +30 -0
- backend/storage/models.py +194 -0
- backend/storage/repositories/__init__.py +1 -0
- backend/storage/repositories/asset_repo.py +86 -0
- backend/storage/repositories/conversation_repo.py +216 -0
- backend/storage/repositories/event_repo.py +76 -0
- backend/storage/repositories/trajectory_repo.py +324 -0
- backend/storage/retention.py +117 -0
- backend/tools/__init__.py +16 -0
- backend/tools/alert_filter.py +208 -0
- backend/tools/aliyun_firewall.py +237 -0
- backend/tools/aws_firewall.py +369 -0
- backend/tools/base.py +445 -0
- backend/tools/calling.py +207 -0
- backend/tools/cve_search.py +77 -0
- backend/tools/execution_engine.py +202 -0
- backend/tools/firewall.py +387 -0
- backend/tools/geoip.py +168 -0
- backend/tools/log_analyzer.py +185 -0
- backend/tools/ml_detector.py +226 -0
- backend/tools/registry.py +38 -0
- backend/tools/tencent_firewall.py +300 -0
- backend/tools/threat_intel.py +732 -0
- backend/tools/trust_checker.py +76 -0
- backend/utils/__init__.py +5 -0
- backend/utils/config_loader.py +164 -0
- backend/utils/env.py +75 -0
- backend/utils/logger.py +150 -0
- backend/utils/text.py +32 -0
- secagentx-3.0.0.dist-info/METADATA +325 -0
- secagentx-3.0.0.dist-info/RECORD +182 -0
- secagentx-3.0.0.dist-info/WHEEL +5 -0
- secagentx-3.0.0.dist-info/entry_points.txt +2 -0
- secagentx-3.0.0.dist-info/top_level.txt +1 -0
backend/main.py
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# 统一环境初始化 (加载 .env + 设置项目根目录)
|
|
7
|
+
from backend.utils.env import init_environment
|
|
8
|
+
from backend.runtime_assets import config_path
|
|
9
|
+
init_environment()
|
|
10
|
+
|
|
11
|
+
# 数据模型(单数据源,Schema 定义以 models.py 为准)
|
|
12
|
+
from backend.storage.models import SCHEMA_SQL
|
|
13
|
+
|
|
14
|
+
from backend.llm.provider import LLMFactory
|
|
15
|
+
from backend.tools.registry import ToolRegistry
|
|
16
|
+
from backend.tools.threat_intel import ThreatIntelTool
|
|
17
|
+
from backend.tools.firewall import FirewallTool
|
|
18
|
+
from backend.tools.log_analyzer import LogAnalyzerTool
|
|
19
|
+
from backend.tools.alert_filter import AlertFilterTool
|
|
20
|
+
from backend.tools.cve_search import CVESearchTool
|
|
21
|
+
from backend.tools.geoip import GeoIPTool
|
|
22
|
+
from backend.agents.analyst import AnalystAgent
|
|
23
|
+
from backend.agents.intel import IntelAgent
|
|
24
|
+
from backend.agents.responder import ResponderAgent
|
|
25
|
+
from backend.agents.knowledge.knowledge_agent import KnowledgeAgent
|
|
26
|
+
from backend.agents.alert_filter.alert_filter_agent import AlertFilterAgent
|
|
27
|
+
from backend.agents.knowledge.agentic_rag import AgenticRAGEngine
|
|
28
|
+
from backend.orchestrator.core import Orchestrator
|
|
29
|
+
from backend.knowledge.mitre_attack import MitreAttackKnowledge
|
|
30
|
+
from backend.knowledge.compliance import ComplianceKnowledge
|
|
31
|
+
from backend.knowledge.cve_db import CVEDatabase
|
|
32
|
+
from backend.knowledge.threat_intel_kb import ActorKnowledge, MalwareKnowledge
|
|
33
|
+
|
|
34
|
+
# 统一日志
|
|
35
|
+
import logging
|
|
36
|
+
logger = logging.getLogger("secagentx")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_config(path: str = None) -> dict:
|
|
40
|
+
if path is None:
|
|
41
|
+
path = config_path()
|
|
42
|
+
path = Path(path)
|
|
43
|
+
if not path.exists():
|
|
44
|
+
logger.info(f"[config] config file not found: {path}")
|
|
45
|
+
return {}
|
|
46
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
47
|
+
return yaml.safe_load(f)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _run_alembic_migrations():
|
|
51
|
+
"""启动时自动执行 Alembic 迁移(PostgreSQL 模式)。"""
|
|
52
|
+
try:
|
|
53
|
+
from alembic.config import Config
|
|
54
|
+
from alembic import command
|
|
55
|
+
migrations_dir = os.path.join(os.path.dirname(__file__), "storage", "migrations")
|
|
56
|
+
alembic_cfg = Config(os.path.join(migrations_dir, "alembic.ini"))
|
|
57
|
+
alembic_cfg.set_main_option("script_location", migrations_dir)
|
|
58
|
+
command.upgrade(alembic_cfg, "head")
|
|
59
|
+
logger.info("[init] Alembic 迁移完成: head")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.warning(f"[init] Alembic 迁移执行失败: {e}")
|
|
62
|
+
logger.warning("[init] 请确认已安装 alembic (pip install alembic) 且数据库连接正常")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def init_db(db_path: str = None, force_sqlite: bool = False):
|
|
66
|
+
"""
|
|
67
|
+
初始化数据库 Schema
|
|
68
|
+
|
|
69
|
+
- PostgreSQL 模式: 自动执行 Alembic 迁移到最新版本
|
|
70
|
+
- SQLite 模式: 直接执行 CREATE TABLE DDL
|
|
71
|
+
|
|
72
|
+
参数:
|
|
73
|
+
db_path: SQLite 数据库文件路径(仅在 SQLite 模式下使用)
|
|
74
|
+
force_sqlite: 强制使用 SQLite 初始化(测试用)
|
|
75
|
+
"""
|
|
76
|
+
from backend.storage.database import _is_postgres
|
|
77
|
+
|
|
78
|
+
# PostgreSQL 模式:自动运行 Alembic 迁移
|
|
79
|
+
if _is_postgres() and not force_sqlite:
|
|
80
|
+
try:
|
|
81
|
+
import alembic
|
|
82
|
+
_run_alembic_migrations()
|
|
83
|
+
except ImportError:
|
|
84
|
+
logger.warning("[init] alembic 未安装 (pip install alembic),跳过 Schema 自动化管理")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"[init] Alembic 迁移失败: {e}")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# SQLite 模式:使用 models.py 中的单数据源 DDL
|
|
90
|
+
import sqlite3
|
|
91
|
+
if db_path is None:
|
|
92
|
+
db_path = os.getenv("SECAGENTX_DB_PATH", "data/secagentx.db")
|
|
93
|
+
os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
|
|
94
|
+
conn = sqlite3.connect(db_path)
|
|
95
|
+
conn.executescript(SCHEMA_SQL)
|
|
96
|
+
_migrate_sqlite_schema(conn)
|
|
97
|
+
conn.commit()
|
|
98
|
+
conn.close()
|
|
99
|
+
logger.info(f"[init] SQLite 数据库初始化: {db_path}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _migrate_sqlite_schema(conn):
|
|
103
|
+
"""SQLite 增量迁移:为已存在的旧表补齐缺失列(幂等,可重复执行)。
|
|
104
|
+
|
|
105
|
+
历史库(早期版本创建)可能缺少后续版本新增的列,
|
|
106
|
+
这里检测并 ALTER TABLE 补齐,避免 'no such column' 运行时错误。
|
|
107
|
+
"""
|
|
108
|
+
import sqlite3
|
|
109
|
+
|
|
110
|
+
def _ensure_column(table: str, column: str, ddl: str) -> bool:
|
|
111
|
+
try:
|
|
112
|
+
cols = {r[1] for r in conn.execute(f"PRAGMA table_info({table})")}
|
|
113
|
+
except sqlite3.Error:
|
|
114
|
+
return False
|
|
115
|
+
if column not in cols:
|
|
116
|
+
try:
|
|
117
|
+
conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {ddl}")
|
|
118
|
+
logger.info(f"[migrate] 表 {table} 新增列 {column}")
|
|
119
|
+
return True
|
|
120
|
+
except sqlite3.Error as e:
|
|
121
|
+
logger.warning(f"[migrate] 表 {table} 添加列 {column} 失败: {e}")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
# events 表历史版本缺少处置字段
|
|
125
|
+
_ensure_column("events", "resolution", "TEXT DEFAULT ''")
|
|
126
|
+
_ensure_column("events", "resolved_by", "TEXT DEFAULT ''")
|
|
127
|
+
owner_added = _ensure_column(
|
|
128
|
+
"conversations", "owner_id", "TEXT NOT NULL DEFAULT ''"
|
|
129
|
+
)
|
|
130
|
+
if owner_added:
|
|
131
|
+
legacy_owner = os.getenv("SECAGENTX_LEGACY_CONVERSATION_OWNER", "admin")
|
|
132
|
+
conn.execute(
|
|
133
|
+
"UPDATE conversations SET owner_id = ? WHERE owner_id = ''",
|
|
134
|
+
(legacy_owner,),
|
|
135
|
+
)
|
|
136
|
+
conn.execute(
|
|
137
|
+
"CREATE INDEX IF NOT EXISTS idx_conversations_owner "
|
|
138
|
+
"ON conversations(owner_id)"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _check_api_keys():
|
|
143
|
+
"""启动时检查核心模型与可选威胁情报配置。"""
|
|
144
|
+
runtime_provider = os.getenv("SECAGENTX_ACTIVE_PROVIDER", "")
|
|
145
|
+
legacy_model = bool(os.getenv("DEEPSEEK_API_KEY") or os.getenv("QWEN_API_KEY"))
|
|
146
|
+
model_ready = bool(runtime_provider or legacy_model)
|
|
147
|
+
intel = {
|
|
148
|
+
"VirusTotal": bool(os.getenv("VT_API_KEY")),
|
|
149
|
+
"AbuseIPDB": bool(os.getenv("ABUSEIPDB_API_KEY")),
|
|
150
|
+
"AlienVault OTX": bool(os.getenv("OTX_API_KEY")),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
logger.info("\n" + "=" * 50)
|
|
154
|
+
logger.info(" SecAgentX API Key 检查")
|
|
155
|
+
logger.info("=" * 50)
|
|
156
|
+
if model_ready:
|
|
157
|
+
logger.info(" [OK] 活动模型 Provider 已配置")
|
|
158
|
+
else:
|
|
159
|
+
logger.warning(" [MISSING] 模型 Provider 未配置,请运行 secagentx onboard")
|
|
160
|
+
enabled_intel = [name for name, ready in intel.items() if ready]
|
|
161
|
+
if enabled_intel:
|
|
162
|
+
logger.info(" [OK] 外部威胁情报: %s", ", ".join(enabled_intel))
|
|
163
|
+
else:
|
|
164
|
+
logger.info(" [OPTIONAL] 外部威胁情报未配置;使用本地知识与离线情报适配器")
|
|
165
|
+
logger.info("=" * 50 + "\n")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def init_application(config: dict = None) -> Orchestrator:
|
|
169
|
+
_check_api_keys()
|
|
170
|
+
|
|
171
|
+
if config is None:
|
|
172
|
+
config = load_config()
|
|
173
|
+
|
|
174
|
+
from backend.storage.database import _is_postgres, get_sqlite_path
|
|
175
|
+
if _is_postgres():
|
|
176
|
+
init_db()
|
|
177
|
+
logger.info("[init] database: PostgreSQL")
|
|
178
|
+
else:
|
|
179
|
+
db_path = get_sqlite_path()
|
|
180
|
+
init_db(db_path)
|
|
181
|
+
logger.info(f"[init] database: {db_path}")
|
|
182
|
+
|
|
183
|
+
mitre = MitreAttackKnowledge()
|
|
184
|
+
mitre_count = mitre.count()
|
|
185
|
+
logger.info(f"[init] MITRE ATT&CK: {mitre_count['techniques']} techniques, {mitre_count['sub_techniques']} sub-techniques, {mitre_count['tactics']} tactics")
|
|
186
|
+
compliance = ComplianceKnowledge()
|
|
187
|
+
logger.info(f"[init] Compliance knowledge: {compliance.count()} regulations/standards")
|
|
188
|
+
cve_db = CVEDatabase()
|
|
189
|
+
logger.info(f"[init] CVE database: {cve_db.count()} vulnerabilities")
|
|
190
|
+
actor_kb = ActorKnowledge()
|
|
191
|
+
actor_count = actor_kb.count()
|
|
192
|
+
logger.info(f"[init] Threat actors: {actor_count['actors']} ({', '.join(f'{k}:{v}' for k,v in actor_count.get('countries',{}).items())})")
|
|
193
|
+
malware_kb = MalwareKnowledge()
|
|
194
|
+
logger.info(f"[init] Malware: {malware_kb.count()} samples")
|
|
195
|
+
|
|
196
|
+
tools = ToolRegistry()
|
|
197
|
+
tools.register(ThreatIntelTool())
|
|
198
|
+
# 读取 config 白名单,注入 FirewallTool(支持适配器模式)
|
|
199
|
+
block_cfg = config.get("auto_operation", {}).get("block_protection", {})
|
|
200
|
+
whitelist = block_cfg.get("whitelist_ips", None)
|
|
201
|
+
fw_backend = os.getenv("FIREWALL_BACKEND", "disabled")
|
|
202
|
+
fw_tool = FirewallTool(whitelist=whitelist, backend=fw_backend)
|
|
203
|
+
fw_tool.agent_id = "orch-firewall" # 审计日志标识
|
|
204
|
+
tools.register(fw_tool)
|
|
205
|
+
logger.info(f"[init] 防火墙后端: {fw_backend}")
|
|
206
|
+
tools.register(LogAnalyzerTool())
|
|
207
|
+
tools.register(AlertFilterTool())
|
|
208
|
+
tools.register(CVESearchTool())
|
|
209
|
+
tools.register(GeoIPTool())
|
|
210
|
+
# ML 为可选能力:只有部署了真实训练模型时才注册,避免把未验证模型暴露给 Agent。
|
|
211
|
+
try:
|
|
212
|
+
from backend.tools.ml_detector import MLThreatDetectorTool
|
|
213
|
+
ml_tool = MLThreatDetectorTool()
|
|
214
|
+
if os.path.isfile(ml_tool._model_path):
|
|
215
|
+
tools.register(ml_tool)
|
|
216
|
+
logger.info("[init] ML 威胁检测模型已发现,将在首次调用时加载")
|
|
217
|
+
else:
|
|
218
|
+
logger.info("[init] ML 威胁检测未启用:未部署真实训练模型")
|
|
219
|
+
except ImportError as e:
|
|
220
|
+
logger.warning(f"[init] ML 威胁检测模块加载失败: {e}")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.warning(f"[init] ML 威胁检测初始化失败: {e}")
|
|
223
|
+
logger.info(f"[init] tools: {tools.count()}")
|
|
224
|
+
|
|
225
|
+
llm_configs = config.get("llm", {})
|
|
226
|
+
deepseek_cfg = dict(llm_configs.get("deepseek", {}))
|
|
227
|
+
qwen_cfg = dict(llm_configs.get("qwen", {}))
|
|
228
|
+
# LLM fallback 配置(主 LLM 超时/失败时自动切换备用 LLM)
|
|
229
|
+
_orchestrator_fb = dict(llm_configs.get("fallback", {})) if isinstance(llm_configs.get("fallback"), dict) else None
|
|
230
|
+
|
|
231
|
+
for cfg in [deepseek_cfg, qwen_cfg]:
|
|
232
|
+
key = cfg.get("api_key", "")
|
|
233
|
+
if key.startswith("${") and key.endswith("}"):
|
|
234
|
+
cfg["api_key"] = os.getenv(key[2:-1], "")
|
|
235
|
+
|
|
236
|
+
# ═══ 修复:fallback 段的嵌套 config 也要做 env 插值 ═══
|
|
237
|
+
# 否则 config.yaml fallback.config.api_key 的 "${QWEN_API_KEY}" 会以字面量传给 LLM,
|
|
238
|
+
# 导致 fallback 请求 401/403。
|
|
239
|
+
if _orchestrator_fb and isinstance(_orchestrator_fb, dict):
|
|
240
|
+
_fb_cfg = _orchestrator_fb.get("config")
|
|
241
|
+
if isinstance(_fb_cfg, dict):
|
|
242
|
+
_fb_key = _fb_cfg.get("api_key", "")
|
|
243
|
+
if isinstance(_fb_key, str) and _fb_key.startswith("${") and _fb_key.endswith("}"):
|
|
244
|
+
_fb_cfg["api_key"] = os.getenv(_fb_key[2:-1], "")
|
|
245
|
+
|
|
246
|
+
# 根据 LLM_PROVIDER 环境变量选择 LLM 提供商
|
|
247
|
+
llm_provider = os.getenv(
|
|
248
|
+
"SECAGENTX_ACTIVE_PROVIDER", os.getenv("LLM_PROVIDER", "deepseek")
|
|
249
|
+
).lower()
|
|
250
|
+
orchestrator_llm_cfg = deepseek_cfg.copy()
|
|
251
|
+
if _orchestrator_fb:
|
|
252
|
+
orchestrator_llm_cfg["fallback"] = _orchestrator_fb
|
|
253
|
+
logger.info(f"[init] LLM 提供商: {llm_provider}")
|
|
254
|
+
|
|
255
|
+
# Agent 也使用同一 fallback 配置(主 LLM 超时/失败时自动切换备用 LLM)
|
|
256
|
+
analyst = AnalystAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
257
|
+
intel = IntelAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
258
|
+
responder = ResponderAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
259
|
+
knowledge = KnowledgeAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
260
|
+
alert_filter = AlertFilterAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
261
|
+
# v2.5: 报告生成员(Summary Agent)— 汇总所有专业 Agent 结果生成模板化最终报告
|
|
262
|
+
from backend.agents.summary_agent import SummaryAgent
|
|
263
|
+
summary = SummaryAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
264
|
+
# v2.5: 事件分类器(Classifier Agent)— 判断安全事件模板分类
|
|
265
|
+
from backend.agents.classifier_agent import ClassifierAgent
|
|
266
|
+
classifier = ClassifierAgent(tools, llm_fallback_config=_orchestrator_fb)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
from backend.storage.chroma_store import VectorStore
|
|
270
|
+
vector_store = VectorStore()
|
|
271
|
+
# Qwen RAG 也传入 fallback 配置
|
|
272
|
+
qwen = LLMFactory.get_qwen(qwen_cfg, fallback_config=_orchestrator_fb)
|
|
273
|
+
max_rounds = config.get("agents", {}).get("knowledge", {}).get("max_retrieval_rounds", 3)
|
|
274
|
+
rag_engine = AgenticRAGEngine(llm=qwen, vector_store=vector_store, max_rounds=max_rounds)
|
|
275
|
+
# BGE 模型改为懒加载:首次查询时自动加载(模型已缓存,无需启动时预载)
|
|
276
|
+
knowledge.set_rag_engine(rag_engine)
|
|
277
|
+
logger.info(f"[init] RAG engine: enabled (max {max_rounds} rounds)")
|
|
278
|
+
except Exception as e:
|
|
279
|
+
rag_engine = AgenticRAGEngine(max_rounds=1)
|
|
280
|
+
knowledge.set_rag_engine(rag_engine)
|
|
281
|
+
logger.warning(f"[WARN] [init] RAG engine: FALLBACK MODE - ChromaDB 初始化失败")
|
|
282
|
+
logger.info(f" 原因: {e}")
|
|
283
|
+
logger.info(f" 影响: 知识检索将降级为关键词搜索,语义相关性下降")
|
|
284
|
+
logger.info(f" 修复: 检查 chromadb 安装和 data/chromadb/ 目录权限")
|
|
285
|
+
|
|
286
|
+
orchestrator = Orchestrator({
|
|
287
|
+
**config.get("orchestrator", {}),
|
|
288
|
+
"llm": orchestrator_llm_cfg,
|
|
289
|
+
}, tools=tools)
|
|
290
|
+
orchestrator.register_agent("analyst-001", "安全分析师", analyst,
|
|
291
|
+
"告警分析、日志分析、攻击溯源")
|
|
292
|
+
orchestrator.register_agent("intel-001", "威胁情报员", intel,
|
|
293
|
+
"IOC查询、威胁情报关联")
|
|
294
|
+
orchestrator.register_agent("responder-001", "应急响应员", responder,
|
|
295
|
+
"封禁IP、策略管理")
|
|
296
|
+
orchestrator.register_agent("knowledge-001", "知识智能体", knowledge,
|
|
297
|
+
"MITRE ATT&CK、CVE查询")
|
|
298
|
+
orchestrator.register_agent("alert-filter-001", "告警误报剔除专家", alert_filter,
|
|
299
|
+
"告警误报过滤:规则引擎+AI双层研判,将误报率从90%降至10%以下")
|
|
300
|
+
orchestrator.register_agent("summary-001", "报告生成员", summary,
|
|
301
|
+
"汇总所有专业Agent分析结果,生成模板化最终综合报告")
|
|
302
|
+
orchestrator.register_agent("classifier-001", "事件分类器", classifier,
|
|
303
|
+
"判断安全事件模板分类(漏洞分析/攻击检测/安全配置/威胁情报/应急响应)")
|
|
304
|
+
|
|
305
|
+
stats = orchestrator.get_stats()
|
|
306
|
+
logger.info(f"[init] SecAgentX ready: {stats['agents_count']} agents, {tools.count()} tools")
|
|
307
|
+
|
|
308
|
+
# ═══════════════════════ 初始化跨区域联邦模块 ═══════════════════════
|
|
309
|
+
orchestrator._federation = None
|
|
310
|
+
try:
|
|
311
|
+
from backend.federation import Federation
|
|
312
|
+
federation = Federation(config.get("federation", {}))
|
|
313
|
+
if federation.enabled:
|
|
314
|
+
logger.info(f"[init] 跨区域联邦: 已启用 (region={federation.region_id}, peers={len(federation._peers)})")
|
|
315
|
+
else:
|
|
316
|
+
logger.info("[init] 跨区域联邦: 未启用")
|
|
317
|
+
orchestrator._federation = federation
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.warning(f"[init] 跨区域联邦: 加载失败 ({e})")
|
|
320
|
+
|
|
321
|
+
# ═══════════════════════ 初始化零人工干预模块 ═══════════════════════
|
|
322
|
+
_auto_modules = {}
|
|
323
|
+
_background_tasks: list[asyncio.Task] = []
|
|
324
|
+
auto_op_config = config.get("auto_operation", {})
|
|
325
|
+
auto_enabled = auto_op_config.get("enabled", False)
|
|
326
|
+
|
|
327
|
+
if auto_enabled:
|
|
328
|
+
logger.info("\n[auto] 零人工干预模式已启用")
|
|
329
|
+
|
|
330
|
+
# 1. 自动升级通知引擎
|
|
331
|
+
try:
|
|
332
|
+
from backend.escalation import AutoEscalation
|
|
333
|
+
escalator = AutoEscalation(auto_op_config)
|
|
334
|
+
_auto_modules["escalator"] = escalator
|
|
335
|
+
esc_status = escalator.get_status()
|
|
336
|
+
active_channels = [s["type"] for s in esc_status if s["enabled"]]
|
|
337
|
+
logger.info(f"[auto] 升级通知引擎: {', '.join(active_channels) if active_channels else '仅控制台'}")
|
|
338
|
+
|
|
339
|
+
# 将熔断器的自动恢复失败通知接入升级通知引擎
|
|
340
|
+
try:
|
|
341
|
+
from backend.security.circuit_breaker import circuit_breaker
|
|
342
|
+
async def _cb_escalate(msg: str):
|
|
343
|
+
await escalator.escalate(
|
|
344
|
+
incident_id="circuit-breaker-auto-recovery",
|
|
345
|
+
summary=msg,
|
|
346
|
+
confidence=0.0,
|
|
347
|
+
reason="熔断器自动恢复失败,需人工介入",
|
|
348
|
+
)
|
|
349
|
+
circuit_breaker.set_escalate_callback(_cb_escalate)
|
|
350
|
+
logger.info("[auto] 熔断器自动恢复通知: 已接入升级引擎")
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.warning(f"[auto] 熔断器通知接入失败: {e}")
|
|
353
|
+
except Exception as e:
|
|
354
|
+
escalator = None
|
|
355
|
+
logger.info(f"[auto] 升级通知引擎: 加载失败 ({e})")
|
|
356
|
+
|
|
357
|
+
# 2. 自动告警接入器
|
|
358
|
+
ingestor = None
|
|
359
|
+
try:
|
|
360
|
+
from backend.auto_ingestor import AutoIngestor
|
|
361
|
+
ingestor = AutoIngestor(orchestrator, escalator, auto_op_config)
|
|
362
|
+
_auto_modules["ingestor"] = ingestor
|
|
363
|
+
logger.info(f"[auto] 告警接入器: 已就绪 (在 api_server 启动时激活)")
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.info(f"[auto] 告警接入器: 加载失败 ({e})")
|
|
366
|
+
|
|
367
|
+
# 2.5 邮件安全接入器(可选,依赖 IMAP 配置)
|
|
368
|
+
try:
|
|
369
|
+
from backend.email_ingestor import EmailIngestor
|
|
370
|
+
if ingestor is not None:
|
|
371
|
+
email_ingestor = EmailIngestor(ingestor, auto_op_config)
|
|
372
|
+
_auto_modules["email_ingestor"] = email_ingestor
|
|
373
|
+
if email_ingestor._configured:
|
|
374
|
+
logger.info(f"[auto] 邮件接入器: 已就绪 ({email_ingestor.username}@{email_ingestor.imap_server})")
|
|
375
|
+
else:
|
|
376
|
+
logger.info("[auto] 邮件接入器: 未配置 (设置 EMAIL_IMAP_SERVER/USERNAME/PASSWORD 后启用)")
|
|
377
|
+
else:
|
|
378
|
+
logger.info("[auto] 邮件接入器: 跳过 (告警接入器未就绪)")
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.info(f"[auto] 邮件接入器: 加载失败 ({e})")
|
|
381
|
+
|
|
382
|
+
# 3. 自动安全巡检器
|
|
383
|
+
try:
|
|
384
|
+
from backend.auto_patrol import AutoPatrol
|
|
385
|
+
patrol = AutoPatrol(orchestrator, escalator, auto_op_config)
|
|
386
|
+
_auto_modules["patrol"] = patrol
|
|
387
|
+
logger.info(f"[auto] 安全巡检器: 已就绪 (间隔 {auto_op_config.get('patrol', {}).get('interval_seconds', 1800)}s)")
|
|
388
|
+
except Exception as e:
|
|
389
|
+
logger.info(f"[auto] 安全巡检器: 加载失败 ({e})")
|
|
390
|
+
|
|
391
|
+
# 4. 防火墙工具注入置信度阈值
|
|
392
|
+
try:
|
|
393
|
+
block_threshold = auto_op_config.get("thresholds", {}).get("auto_block", 0.70)
|
|
394
|
+
fw_tool = tools.get("firewall_manage")
|
|
395
|
+
if fw_tool and hasattr(fw_tool, "_block_threshold"):
|
|
396
|
+
fw_tool._block_threshold = block_threshold
|
|
397
|
+
logger.info(f"[auto] 防火墙门控阈值: {block_threshold:.0%}")
|
|
398
|
+
except Exception as e:
|
|
399
|
+
logger.info(f"[auto] 防火墙门控: 配置失败 ({e})")
|
|
400
|
+
|
|
401
|
+
logger.info("[auto] 初始化完成\n")
|
|
402
|
+
else:
|
|
403
|
+
logger.info("\n[auto] 零人工干预模式已禁用 (config.yaml: auto_operation.enabled=false)\n")
|
|
404
|
+
|
|
405
|
+
# 将自动模块和后台任务挂载到 orchestrator 上,供 api_server 访问
|
|
406
|
+
orchestrator._auto_modules = _auto_modules
|
|
407
|
+
orchestrator._background_tasks = _background_tasks
|
|
408
|
+
orchestrator._config = config
|
|
409
|
+
|
|
410
|
+
stats = orchestrator.get_stats()
|
|
411
|
+
logger.info(f"[init] SecAgentX ready: {stats['agents_count']} agents, {tools.count()} tools")
|
|
412
|
+
return orchestrator
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
async def quick_query(orchestrator: Orchestrator, text: str) -> str:
|
|
416
|
+
"""快速查询 — 使用统一 TrueReAct 入口"""
|
|
417
|
+
results = []
|
|
418
|
+
async for chunk in orchestrator.process(text):
|
|
419
|
+
if chunk["type"] in ("orchestrator_complete", "true_react_complete"):
|
|
420
|
+
results.append(chunk.get("summary", chunk.get("content", "")))
|
|
421
|
+
return "\n".join(results)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EpisodicMemory — 情景记忆(v2.4 M4)
|
|
3
|
+
|
|
4
|
+
沉淀已完成的安全研判案例(复用 experiences 表):
|
|
5
|
+
- record_episode: 完成一次威胁研判后写入案例
|
|
6
|
+
- recall_similar: 按场景关键词/向量检索召回相似案例
|
|
7
|
+
- 每次召回自增 times_used
|
|
8
|
+
|
|
9
|
+
字段(对齐 experiences 表):
|
|
10
|
+
scenario / input_summary / actions_taken / outcome / lessons / vector_id / times_used
|
|
11
|
+
"""
|
|
12
|
+
import uuid
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from ..storage.database import Repository
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EpisodicMemory:
|
|
21
|
+
"""情景记忆 — 历史研判案例沉淀与召回。"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, db=None):
|
|
24
|
+
self.db = db if db is not None else Repository()
|
|
25
|
+
|
|
26
|
+
async def _execute(self, sql: str, params: tuple = ()):
|
|
27
|
+
if isinstance(self.db, Repository):
|
|
28
|
+
return await self.db.execute(sql, params)
|
|
29
|
+
return self.db.execute(sql, params)
|
|
30
|
+
|
|
31
|
+
async def _fetch_all(self, sql: str, params: tuple = ()) -> list[dict]:
|
|
32
|
+
if isinstance(self.db, Repository):
|
|
33
|
+
return await self.db.fetch_all(sql, params)
|
|
34
|
+
return self.db.fetch_all(sql, params)
|
|
35
|
+
|
|
36
|
+
async def _fetch_one(self, sql: str, params: tuple = ()) -> Optional[dict]:
|
|
37
|
+
if isinstance(self.db, Repository):
|
|
38
|
+
return await self.db.fetch_one(sql, params)
|
|
39
|
+
return self.db.fetch_one(sql, params)
|
|
40
|
+
|
|
41
|
+
async def record_episode(self, scenario: str, input_summary: str = "",
|
|
42
|
+
actions_taken: list = None, outcome: str = "",
|
|
43
|
+
lessons: str = "") -> str:
|
|
44
|
+
"""沉淀一个研判案例。返回案例 ID。"""
|
|
45
|
+
eid = uuid.uuid4().hex[:12]
|
|
46
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
47
|
+
await self._execute(
|
|
48
|
+
"INSERT INTO experiences (id, scenario, input_summary, actions_taken, outcome, lessons, created_at, times_used) "
|
|
49
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, 0)",
|
|
50
|
+
(
|
|
51
|
+
eid, scenario, input_summary,
|
|
52
|
+
json.dumps(actions_taken or [], ensure_ascii=False),
|
|
53
|
+
outcome, lessons, now,
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
return eid
|
|
57
|
+
|
|
58
|
+
async def recall_similar(self, query: str, limit: int = 5) -> list[dict]:
|
|
59
|
+
"""按场景关键词召回相似案例(关键词重叠 + 时间倒序)。
|
|
60
|
+
|
|
61
|
+
关键词提取策略:
|
|
62
|
+
1. 按分隔符拆词(空格/逗号/句号)
|
|
63
|
+
2. 整段无分隔时按 2-gram 拆分
|
|
64
|
+
"""
|
|
65
|
+
import re
|
|
66
|
+
terms = [t for t in re.split(r'[\s,,。;;、]+', query) if len(t) >= 2]
|
|
67
|
+
if len(terms) <= 1 and len(query) >= 4:
|
|
68
|
+
terms = [query[i:i + 2] for i in range(len(query) - 1)]
|
|
69
|
+
query_terms = list(dict.fromkeys(terms))[:12]
|
|
70
|
+
|
|
71
|
+
rows = await self._fetch_all(
|
|
72
|
+
"SELECT * FROM experiences ORDER BY created_at DESC LIMIT 100", (),
|
|
73
|
+
)
|
|
74
|
+
scored = []
|
|
75
|
+
for r in rows:
|
|
76
|
+
scenario = r.get("scenario") or ""
|
|
77
|
+
input_summary = r.get("input_summary") or ""
|
|
78
|
+
score = 0
|
|
79
|
+
for t in query_terms:
|
|
80
|
+
if len(t) >= 2 and t in scenario:
|
|
81
|
+
score += 2
|
|
82
|
+
elif len(t) >= 2 and t in input_summary:
|
|
83
|
+
score += 1
|
|
84
|
+
if score > 0:
|
|
85
|
+
scored.append((score, r))
|
|
86
|
+
scored.sort(key=lambda x: -x[0])
|
|
87
|
+
|
|
88
|
+
out = []
|
|
89
|
+
for _, r in scored[:limit]:
|
|
90
|
+
eid = r["id"]
|
|
91
|
+
# 自增 times_used(失败静默,不影响召回)
|
|
92
|
+
try:
|
|
93
|
+
await self._execute(
|
|
94
|
+
"UPDATE experiences SET times_used = times_used + 1 WHERE id = ?",
|
|
95
|
+
(eid,),
|
|
96
|
+
)
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
out.append({
|
|
100
|
+
"id": eid,
|
|
101
|
+
"scenario": r.get("scenario", ""),
|
|
102
|
+
"input_summary": r.get("input_summary", ""),
|
|
103
|
+
"actions_taken": json.loads(r.get("actions_taken") or "[]") or [],
|
|
104
|
+
"outcome": r.get("outcome", ""),
|
|
105
|
+
"lessons": r.get("lessons", ""),
|
|
106
|
+
"times_used": (r.get("times_used") or 0) + 1,
|
|
107
|
+
"created_at": r.get("created_at", ""),
|
|
108
|
+
})
|
|
109
|
+
return out
|
|
110
|
+
|
|
111
|
+
async def list_episodes(self, limit: int = 20) -> list[dict]:
|
|
112
|
+
rows = await self._fetch_all(
|
|
113
|
+
"SELECT * FROM experiences ORDER BY times_used DESC, created_at DESC LIMIT ?",
|
|
114
|
+
(limit,),
|
|
115
|
+
)
|
|
116
|
+
return [{
|
|
117
|
+
"id": r.get("id"),
|
|
118
|
+
"scenario": r.get("scenario"),
|
|
119
|
+
"input_summary": r.get("input_summary", ""),
|
|
120
|
+
"outcome": r.get("outcome", ""),
|
|
121
|
+
"lessons": r.get("lessons", ""),
|
|
122
|
+
"times_used": r.get("times_used", 0),
|
|
123
|
+
"created_at": r.get("created_at", ""),
|
|
124
|
+
} for r in rows]
|
|
125
|
+
|
|
126
|
+
async def count(self) -> int:
|
|
127
|
+
row = await self._fetch_one("SELECT COUNT(*) AS n FROM experiences", ())
|
|
128
|
+
return row.get("n", 0) if row else 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = ["EpisodicMemory"]
|
|
132
|
+
|