security: gate unexpected-error detail to clients (residual low)
Follow-up to the traceback gate: client-facing exception detail is now gated too.
- safe_error(e) (app/utils/security.py): returns the exception string only when
FLASK_DEBUG, otherwise a generic message. Full detail stays in server logs.
- All catch-all `except Exception` handlers in api/{graph,report,simulation}.py now
return safe_error(e) instead of str(e); same for the persisted error fields
(project.error / state.error / task fail messages). Typed `except ValueError`
validation handlers (404/400) keep str(e) — those are intentional, actionable
user messages that echo only user-supplied ids.
- graph_builder no longer puts a full traceback into the task error (logs it
server-side with exc_info, surfaces safe_error to the client); the batch-failure
progress message is gated too.
- README documents the CSP connect-src ↔ VITE_API_BASE_URL coupling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ed840c7a00
commit
392a971270
|
|
@ -163,6 +163,11 @@ API_KEY=your_strong_api_key
|
|||
`.env.example` for defaults.
|
||||
- Run the production server with a **single worker** (`gunicorn -w 1 --threads N`); simulation
|
||||
run-state is held in-process, so multiple workers break stop/status routing.
|
||||
- **CSP / API origin:** the frontend ships a Content-Security-Policy whose `connect-src` allows
|
||||
`'self'` + `http://localhost:5001` (the default API). If you point the UI at a different API
|
||||
host (set `VITE_API_BASE_URL`), you **must** add that origin to `connect-src` in
|
||||
`frontend/index.html` too, or the browser will silently block all API calls. Restrict
|
||||
`ALLOWED_ORIGINS` (backend) to your real frontend origin in production.
|
||||
|
||||
#### 2. Install Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"""
|
||||
|
||||
import os
|
||||
from ..utils.security import safe_traceback, upload_content_ok
|
||||
from ..utils.security import safe_traceback, safe_error, upload_content_ok
|
||||
import threading
|
||||
from flask import request, jsonify
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ def generate_ontology():
|
|||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -499,13 +499,13 @@ def build_graph():
|
|||
build_logger.debug(safe_traceback())
|
||||
|
||||
project.status = ProjectStatus.FAILED
|
||||
project.error = str(e)
|
||||
project.error = safe_error(e)
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.FAILED,
|
||||
message=t('progress.buildFailed', error=str(e)),
|
||||
message=t('progress.buildFailed', error=safe_error(e)),
|
||||
error=safe_traceback()
|
||||
)
|
||||
|
||||
|
|
@ -525,7 +525,7 @@ def build_graph():
|
|||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -590,7 +590,7 @@ def get_graph_data(graph_id: str):
|
|||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -618,6 +618,6 @@ def delete_graph(graph_id: str):
|
|||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Report API路由
|
|||
"""
|
||||
|
||||
import os
|
||||
from ..utils.security import safe_traceback
|
||||
from ..utils.security import safe_traceback, safe_error
|
||||
import threading
|
||||
from flask import request, jsonify, send_file
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ def generate_report():
|
|||
|
||||
except Exception as e:
|
||||
logger.error(f"报告生成失败: {str(e)}")
|
||||
task_manager.fail_task(task_id, str(e))
|
||||
task_manager.fail_task(task_id, safe_error(e))
|
||||
|
||||
# 启动后台线程
|
||||
thread = threading.Thread(target=run_generate, daemon=True)
|
||||
|
|
@ -195,7 +195,7 @@ def generate_report():
|
|||
logger.error(f"启动报告生成任务失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ def get_generate_status():
|
|||
logger.error(f"查询任务状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
"error": safe_error(e)
|
||||
}), 500
|
||||
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ def get_report(report_id: str):
|
|||
logger.error(f"获取报告失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ def get_report_by_simulation(simulation_id: str):
|
|||
logger.error(f"获取报告失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ def list_reports():
|
|||
logger.error(f"列出报告失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -436,7 +436,7 @@ def download_report(report_id: str):
|
|||
logger.error(f"下载报告失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -462,7 +462,7 @@ def delete_report(report_id: str):
|
|||
logger.error(f"删除报告失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -559,7 +559,7 @@ def chat_with_report_agent():
|
|||
logger.error(f"对话失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -602,7 +602,7 @@ def get_report_progress(report_id: str):
|
|||
logger.error(f"获取报告进度失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -653,7 +653,7 @@ def get_report_sections(report_id: str):
|
|||
logger.error(f"获取章节列表失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -697,7 +697,7 @@ def get_single_section(report_id: str, section_index: int):
|
|||
logger.error(f"获取章节内容失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -748,7 +748,7 @@ def check_report_status(simulation_id: str):
|
|||
logger.error(f"检查报告状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -809,7 +809,7 @@ def get_agent_log(report_id: str):
|
|||
logger.error(f"获取Agent日志失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -843,7 +843,7 @@ def stream_agent_log(report_id: str):
|
|||
logger.error(f"获取Agent日志失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -891,7 +891,7 @@ def get_console_log(report_id: str):
|
|||
logger.error(f"获取控制台日志失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -925,7 +925,7 @@ def stream_console_log(report_id: str):
|
|||
logger.error(f"获取控制台日志失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -975,7 +975,7 @@ def search_graph_tool():
|
|||
logger.error(f"图谱搜索失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1015,6 +1015,6 @@ def get_graph_statistics_tool():
|
|||
logger.error(f"获取图谱统计失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Step2: Zep实体读取与过滤、OASIS模拟准备与运行(全程自动化
|
|||
"""
|
||||
|
||||
import os
|
||||
from ..utils.security import safe_traceback
|
||||
from ..utils.security import safe_traceback, safe_error
|
||||
from flask import request, jsonify, send_file
|
||||
|
||||
from . import simulation_bp
|
||||
|
|
@ -85,7 +85,7 @@ def get_graph_entities(graph_id: str):
|
|||
logger.error(f"获取图谱实体失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ def get_entity_detail(graph_id: str, entity_uuid: str):
|
|||
logger.error(f"获取实体详情失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ def get_entities_by_type(graph_id: str, entity_type: str):
|
|||
logger.error(f"获取实体失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -232,7 +232,7 @@ def create_simulation():
|
|||
logger.error(f"创建模拟失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -597,14 +597,14 @@ def prepare_simulation():
|
|||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"准备模拟失败: {str(e)}")
|
||||
task_manager.fail_task(task_id, str(e))
|
||||
logger.error(f"准备模拟失败: {e}", exc_info=True)
|
||||
task_manager.fail_task(task_id, safe_error(e))
|
||||
|
||||
# 更新模拟状态为失败
|
||||
state = manager.get_simulation(simulation_id)
|
||||
if state:
|
||||
state.status = SimulationStatus.FAILED
|
||||
state.error = str(e)
|
||||
state.error = safe_error(e)
|
||||
manager._save_simulation_state(state)
|
||||
|
||||
# 启动后台线程
|
||||
|
|
@ -634,7 +634,7 @@ def prepare_simulation():
|
|||
logger.error(f"启动准备任务失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -748,7 +748,7 @@ def get_prepare_status():
|
|||
logger.error(f"查询任务状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
"error": safe_error(e)
|
||||
}), 500
|
||||
|
||||
|
||||
|
|
@ -780,7 +780,7 @@ def get_simulation(simulation_id: str):
|
|||
logger.error(f"获取模拟状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -809,7 +809,7 @@ def list_simulations():
|
|||
logger.error(f"列出模拟失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -982,7 +982,7 @@ def get_simulation_history():
|
|||
logger.error(f"获取历史模拟失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1020,7 +1020,7 @@ def get_simulation_profiles(simulation_id: str):
|
|||
logger.error(f"获取Profile失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1130,7 +1130,7 @@ def get_simulation_profiles_realtime(simulation_id: str):
|
|||
logger.error(f"实时获取Profile失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1250,7 +1250,7 @@ def get_simulation_config_realtime(simulation_id: str):
|
|||
logger.error(f"实时获取Config失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1286,7 +1286,7 @@ def get_simulation_config(simulation_id: str):
|
|||
logger.error(f"获取配置失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1315,7 +1315,7 @@ def download_simulation_config(simulation_id: str):
|
|||
logger.error(f"下载配置失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1367,7 +1367,7 @@ def download_simulation_script(script_name: str):
|
|||
logger.error(f"下载脚本失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1441,7 +1441,7 @@ def generate_profiles():
|
|||
logger.error(f"生成Profile失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1636,7 +1636,7 @@ def start_simulation():
|
|||
logger.error(f"启动模拟失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1695,7 +1695,7 @@ def stop_simulation():
|
|||
logger.error(f"停止模拟失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1755,7 +1755,7 @@ def get_run_status(simulation_id: str):
|
|||
logger.error(f"获取运行状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1856,7 +1856,7 @@ def get_run_status_detail(simulation_id: str):
|
|||
logger.error(f"获取详细状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1910,7 +1910,7 @@ def get_simulation_actions(simulation_id: str):
|
|||
logger.error(f"获取动作历史失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1950,7 +1950,7 @@ def get_simulation_timeline(simulation_id: str):
|
|||
logger.error(f"获取时间线失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -1977,7 +1977,7 @@ def get_agent_stats(simulation_id: str):
|
|||
logger.error(f"获取Agent统计失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2057,7 +2057,7 @@ def get_simulation_posts(simulation_id: str):
|
|||
logger.error(f"获取帖子失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2132,7 +2132,7 @@ def get_simulation_comments(simulation_id: str):
|
|||
logger.error(f"获取评论失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2263,7 +2263,7 @@ def interview_agent():
|
|||
logger.error(f"Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2401,7 +2401,7 @@ def interview_agents_batch():
|
|||
logger.error(f"批量Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2504,7 +2504,7 @@ def interview_all_agents():
|
|||
logger.error(f"全局Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2576,7 +2576,7 @@ def get_interview_history():
|
|||
logger.error(f"获取Interview历史失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2641,7 +2641,7 @@ def get_env_status():
|
|||
logger.error(f"获取环境状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
||||
|
|
@ -2711,6 +2711,6 @@ def close_simulation_env():
|
|||
logger.error(f"关闭环境失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error": safe_error(e),
|
||||
"traceback": safe_traceback()
|
||||
}), 500
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ from ..models.task import TaskManager, TaskStatus
|
|||
from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges
|
||||
from .text_processor import TextProcessor
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.security import safe_error
|
||||
|
||||
logger = get_logger('mirofish.graph_builder')
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -186,9 +190,8 @@ class GraphBuilderService:
|
|||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_msg = f"{str(e)}\n{traceback.format_exc()}"
|
||||
self.task_manager.fail_task(task_id, error_msg)
|
||||
logger.error(f"graph build failed (task {task_id})", exc_info=True)
|
||||
self.task_manager.fail_task(task_id, safe_error(e))
|
||||
|
||||
def create_graph(self, name: str) -> str:
|
||||
"""创建Zep图谱(公开方法)"""
|
||||
|
|
@ -339,7 +342,7 @@ class GraphBuilderService:
|
|||
|
||||
except Exception as e:
|
||||
if progress_callback:
|
||||
progress_callback(t('progress.batchFailed', batch=batch_num, error=str(e)), 0)
|
||||
progress_callback(t('progress.batchFailed', batch=batch_num, error=safe_error(e)), 0)
|
||||
raise
|
||||
|
||||
return episode_uuids
|
||||
|
|
|
|||
|
|
@ -28,6 +28,16 @@ def safe_traceback() -> str:
|
|||
return tb if Config.DEBUG else 'Internal server error (see server logs)'
|
||||
|
||||
|
||||
def safe_error(e) -> str:
|
||||
"""
|
||||
客户端可见的错误文案:DEBUG 模式返回异常消息,生产环境返回通用提示,
|
||||
避免异常消息本身(如 FileNotFoundError 的路径、ValueError 里的配置值)泄露给客户端。
|
||||
完整异常仍由各调用点的 logger.error / safe_traceback 记到服务端日志。
|
||||
"""
|
||||
from ..config import Config
|
||||
return str(e) if Config.DEBUG else 'Internal server error (see server logs)'
|
||||
|
||||
|
||||
def validate_id(value: str, kind: str = 'id') -> str:
|
||||
"""
|
||||
路径校验:拒绝任何不匹配 _ID_RE 的 id(含 '..'、'/'、空值),
|
||||
|
|
|
|||
Loading…
Reference in New Issue