背景:对全部 206 个 HTTP 路由 + SocketIO 事件逐个排查(主审 + 2 个子
智能体初筛后复核),另按用户要求专项审计前端渲染面(无新发现)。
完整报告见 _experiments/security_audit_2026-09-02/REPORT.md 第八章。
新漏洞修复:
- 多智能体角色 role_id 路径穿越(高):save_custom_role 直接拼接
"{role_id}.md",POST body 的 role_id 无任何校验,可 ../ 穿越覆盖
其他用户角色文件(跨用户提示词注入),本地已复现写入成功。
修复:新增 validate_role_id 白名单(^[a-z0-9][a-z0-9_-]{0,63}$),
role_store 存储层 + multi_agent API 层(POST/PUT/DELETE)双保险
- open-in-file-manager 漏 _is_host_mode_request 检查(同文件其余三个
端点都有):docker 模式下任意登录用户可触发宿主 GUI 弹窗+探测路径。
修复:补 host 检查,docker 下 403
- /api/admin/secondary/verify 无限流且 CSRF 豁免:admin 会话前提下
可在线爆破二级密码(纵深防御缺失)。修复:5 次/300s 用户维度限流
- WS client_chunk_log / client_stream_debug_log 无连接认证检查且
无限流(日志写盘放大面)。修复:必须已认证连接 + 30 次/60s 滑窗
+ 桶表万级上限回收
- _get_conversation_file_path 直接拼 "{id}.json"(conv_ 前缀恰好阻碍
直接穿越,属防御深度缺失)。修复:^conv_[A-Za-z0-9_-]+$ 白名单,
已核验全部 5 个调用点与 temp_ 前缀排除路径不受影响
- /api/conversations/media/<id> 采信 entry mime_type 并 inline 返回。
修复:text/html、image/svg+xml、xhtml 强制 octet-stream + attachment
(与 file/content 的 SVG 策略对齐,防存储型 XSS 一致性收口)
- /api/status 每次心跳返回宿主绝对 project_path(泄露系统用户名与
目录布局)。修复:docker 模式脱敏为容器视角 /workspace,host 不变
LLM 成本攻击止血(第一轮发现 5 的端点级落地):
- POST /api/tasks(GUI 发消息主入口):30 次/60s/user
- POST /api/conversations/<id>/compress(调 api_client.chat 做摘要):
5 次/300s/user
- 注:对话回顾 review 端点实为纯本地 Markdown 生成(不调 LLM),
其「发送给模型」模式走 /api/tasks,已被上述限流覆盖;
按 token 计费的完整配额方案仍遗留待产品决策
验证:全部 py_compile 通过;冒烟测试 6/6;validate_role_id 8 个恶意
样本全拒 + 3 个合法样本放行 + 穿越写入拦截回归通过;conv_id 白名单
4 组样本符合预期。端点级行为待服务重启后实测。
Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
314 lines
12 KiB
Python
314 lines
12 KiB
Python
"""API conversation message pack.
|
||
|
||
User-visible messages produced by server/conversation.py (REST/WebSocket responses).
|
||
Pure data only — do not import anything here; merged into modules.i18n at import time.
|
||
Keys are prefixed with ``conversation.`` (the core table already owns
|
||
``conversation.default_title`` — no key below duplicates it).
|
||
"""
|
||
|
||
MESSAGES = {
|
||
# ── _get_conversation_file_path(对话 ID 白名单校验) ──
|
||
"conversation.invalid_conversation_id": {
|
||
"zh-CN": "对话 ID 不合法",
|
||
"en-US": "Invalid conversation ID",
|
||
},
|
||
# ── _build_safe_load_result(安全导航加载结果,返还前端) ──
|
||
"conversation.load_failed_not_found": {
|
||
"zh-CN": "对话不存在或加载失败",
|
||
"en-US": "Conversation not found or failed to load",
|
||
},
|
||
"conversation.load_failed_detail": {
|
||
"zh-CN": "对话加载失败: {conversation_id}",
|
||
"en-US": "Failed to load conversation: {conversation_id}",
|
||
},
|
||
"conversation.unknown_title": {
|
||
"zh-CN": "未知对话",
|
||
"en-US": "Unknown chat",
|
||
},
|
||
"conversation.loaded_detail": {
|
||
"zh-CN": "对话已加载: {conversation_id}",
|
||
"en-US": "Conversation loaded: {conversation_id}",
|
||
},
|
||
|
||
# ── 输入草稿(GET/POST /api/input-draft) ──
|
||
"conversation.read_input_draft_failed": {
|
||
"zh-CN": "读取输入草稿失败: {error}",
|
||
"en-US": "Failed to read input draft: {error}",
|
||
},
|
||
"conversation.input_draft_too_long": {
|
||
"zh-CN": "输入草稿过长(最大 40000 字符)",
|
||
"en-US": "Input draft is too long (max 40000 characters)",
|
||
},
|
||
"conversation.save_input_draft_failed": {
|
||
"zh-CN": "保存输入草稿失败: {error}",
|
||
"en-US": "Failed to save input draft: {error}",
|
||
},
|
||
|
||
# ── _resolve_target_terminal_for_workspace(异常消息经 str(exc) 直达前端 error 字段) ──
|
||
"conversation.workspace_not_found": {
|
||
"zh-CN": "工作区不存在",
|
||
"en-US": "Workspace not found",
|
||
},
|
||
"conversation.project_not_found": {
|
||
"zh-CN": "项目不存在",
|
||
"en-US": "Project not found",
|
||
},
|
||
"conversation.system_not_initialized": {
|
||
"zh-CN": "系统未初始化",
|
||
"en-US": "System not initialized",
|
||
},
|
||
|
||
# ── GET /api/conversations ──
|
||
"conversation.get_list_failed": {
|
||
"zh-CN": "获取对话列表失败",
|
||
"en-US": "Failed to get the conversation list",
|
||
},
|
||
"conversation.get_list_exception": {
|
||
"zh-CN": "获取对话列表时发生异常",
|
||
"en-US": "An error occurred while getting the conversation list",
|
||
},
|
||
|
||
# ── POST /api/conversations ──
|
||
"conversation.manager_not_initialized": {
|
||
"zh-CN": "对话管理器未初始化",
|
||
"en-US": "Conversation manager not initialized",
|
||
},
|
||
"conversation.created_detail": {
|
||
"zh-CN": "已创建新对话: {conversation_id}",
|
||
"en-US": "New conversation created: {conversation_id}",
|
||
},
|
||
"conversation.create_exception": {
|
||
"zh-CN": "创建对话时发生异常",
|
||
"en-US": "An error occurred while creating the conversation",
|
||
},
|
||
|
||
# ── GET /api/conversations/<id> 与其后多处共用 ──
|
||
"conversation.not_found_detail": {
|
||
"zh-CN": "对话 {conversation_id} 不存在",
|
||
"en-US": "Conversation {conversation_id} not found",
|
||
},
|
||
"conversation.get_info_exception": {
|
||
"zh-CN": "获取对话信息时发生异常",
|
||
"en-US": "An error occurred while getting conversation info",
|
||
},
|
||
|
||
# ── PUT /api/conversations/<id>/load ──
|
||
"conversation.load_exception": {
|
||
"zh-CN": "加载对话时发生异常",
|
||
"en-US": "An error occurred while loading the conversation",
|
||
},
|
||
|
||
# ── DELETE /api/conversations/<id> ──
|
||
"conversation.delete_exception": {
|
||
"zh-CN": "删除对话时发生异常",
|
||
"en-US": "An error occurred while deleting the conversation",
|
||
},
|
||
|
||
# ── GET /api/conversations/search ──
|
||
"conversation.search_query_required": {
|
||
"zh-CN": "请提供搜索关键词",
|
||
"en-US": "Please provide a search keyword",
|
||
},
|
||
"conversation.search_exception": {
|
||
"zh-CN": "搜索对话时发生异常",
|
||
"en-US": "An error occurred while searching conversations",
|
||
},
|
||
|
||
# ── GET /api/conversations/<id>/messages ──
|
||
"conversation.get_messages_exception": {
|
||
"zh-CN": "获取对话消息时发生异常",
|
||
"en-US": "An error occurred while getting conversation messages",
|
||
},
|
||
|
||
# ── GET /api/conversations/media/<media_id> ──
|
||
"conversation.media_store_unavailable": {
|
||
"zh-CN": "media_store 不可用",
|
||
"en-US": "media_store is unavailable",
|
||
},
|
||
"conversation.media_id_required": {
|
||
"zh-CN": "media_id 不能为空",
|
||
"en-US": "media_id must not be empty",
|
||
},
|
||
"conversation.media_not_found": {
|
||
"zh-CN": "媒体不存在",
|
||
"en-US": "Media not found",
|
||
},
|
||
"conversation.media_file_not_found": {
|
||
"zh-CN": "媒体文件不存在",
|
||
"en-US": "Media file not found",
|
||
},
|
||
|
||
# ── 版本控制(/api/conversations/<id>/versioning*) ──
|
||
"conversation.update_versioning_failed": {
|
||
"zh-CN": "更新对话版本配置失败",
|
||
"en-US": "Failed to update conversation versioning config",
|
||
},
|
||
"conversation.versioning_workspace_point_unsupported": {
|
||
"zh-CN": "当前模式不支持工作区版本点,请切换到宿主机模式",
|
||
"en-US": "Workspace version points are not supported in this mode; please switch to host mode",
|
||
},
|
||
"conversation.versioning_workspace_detail_unsupported": {
|
||
"zh-CN": "当前模式不支持工作区版本详情,请切换到宿主机模式",
|
||
"en-US": "Workspace version details are not supported in this mode; please switch to host mode",
|
||
},
|
||
"conversation.checkpoint_not_found": {
|
||
"zh-CN": "未找到对应版本点",
|
||
"en-US": "Version point not found",
|
||
},
|
||
"conversation.versioning_workspace_restore_unsupported": {
|
||
"zh-CN": "当前模式不支持工作区回溯,请切换到宿主机模式",
|
||
"en-US": "Workspace restore is not supported in this mode; please switch to host mode",
|
||
},
|
||
"conversation.restore_save_failed": {
|
||
"zh-CN": "保存恢复后的对话失败",
|
||
"en-US": "Failed to save the restored conversation",
|
||
},
|
||
"conversation.seq_required": {
|
||
"zh-CN": "缺少有效 seq",
|
||
"en-US": "A valid seq is required",
|
||
},
|
||
"conversation.versioning_not_enabled": {
|
||
"zh-CN": "当前对话未开启版本管理",
|
||
"en-US": "Versioning is not enabled for this conversation",
|
||
},
|
||
"conversation.duplicate_failed": {
|
||
"zh-CN": "复制对话失败",
|
||
"en-US": "Failed to duplicate conversation",
|
||
},
|
||
"conversation.version_restored_title": {
|
||
"zh-CN": "版本回溯对话",
|
||
"en-US": "Version-restored chat",
|
||
},
|
||
|
||
# ── 压缩(/api/conversations/<id>/compress*) ──
|
||
"conversation.compress_blocked_by_admin": {
|
||
"zh-CN": "压缩对话已被管理员禁用",
|
||
"en-US": "Conversation compression has been disabled by the admin",
|
||
},
|
||
"conversation.compress_exception": {
|
||
"zh-CN": "压缩对话时发生异常",
|
||
"en-US": "An error occurred while compressing the conversation",
|
||
},
|
||
"conversation.compression_cancelled_error": {
|
||
"zh-CN": "用户切换对话导致压缩取消",
|
||
"en-US": "Compression cancelled because the user switched conversations",
|
||
},
|
||
"conversation.not_found_or_cancel_failed": {
|
||
"zh-CN": "对话不存在或取消失败",
|
||
"en-US": "Conversation not found or failed to cancel",
|
||
},
|
||
|
||
# ── 子智能体(/api/sub_agents*) ──
|
||
"conversation.sub_agent_manager_unavailable": {
|
||
"zh-CN": "子智能体管理器不可用",
|
||
"en-US": "Sub-agent manager unavailable",
|
||
},
|
||
"conversation.sub_agent_task_not_found": {
|
||
"zh-CN": "未找到对应子智能体任务",
|
||
"en-US": "Sub-agent task not found",
|
||
},
|
||
"conversation.read_progress_failed": {
|
||
"zh-CN": "读取进度失败: {error}",
|
||
"en-US": "Failed to read progress: {error}",
|
||
},
|
||
"conversation.unsupported_stop_mode": {
|
||
"zh-CN": "mode 只支持 terminate/soft_stop",
|
||
"en-US": "mode only supports terminate/soft_stop",
|
||
},
|
||
"conversation.sub_agent_stop_forbidden": {
|
||
"zh-CN": "无权限停止该子智能体任务",
|
||
"en-US": "Not allowed to stop this sub-agent task",
|
||
},
|
||
"conversation.sub_agent_stop_failed": {
|
||
"zh-CN": "停止子智能体失败",
|
||
"en-US": "Failed to stop sub-agent",
|
||
},
|
||
|
||
# ── 后台命令(/api/background_commands*) ──
|
||
"conversation.background_command_manager_unavailable": {
|
||
"zh-CN": "后台命令管理器不可用",
|
||
"en-US": "Background command manager unavailable",
|
||
},
|
||
"conversation.background_command_not_found": {
|
||
"zh-CN": "未找到对应后台命令",
|
||
"en-US": "Background command not found",
|
||
},
|
||
"conversation.background_command_access_forbidden": {
|
||
"zh-CN": "无权限访问该后台命令",
|
||
"en-US": "Not allowed to access this background command",
|
||
},
|
||
"conversation.background_command_stop_forbidden": {
|
||
"zh-CN": "无权限停止该后台命令",
|
||
"en-US": "Not allowed to stop this background command",
|
||
},
|
||
"conversation.background_command_stop_failed": {
|
||
"zh-CN": "停止后台命令失败",
|
||
"en-US": "Failed to stop background command",
|
||
},
|
||
|
||
# ── 复制(POST /api/conversations/<id>/duplicate) ──
|
||
"conversation.duplicated_title": {
|
||
"zh-CN": "复制的对话",
|
||
"en-US": "Duplicated chat",
|
||
},
|
||
"conversation.duplicate_exception": {
|
||
"zh-CN": "复制对话时发生异常",
|
||
"en-US": "An error occurred while duplicating the conversation",
|
||
},
|
||
|
||
# ── 对话回顾(review_preview / review) ──
|
||
"conversation.review_blocked_by_admin": {
|
||
"zh-CN": "对话引用已被管理员禁用",
|
||
"en-US": "Conversation review has been disabled by the admin",
|
||
},
|
||
"conversation.cannot_review_current_conversation": {
|
||
"zh-CN": "无法引用当前对话",
|
||
"en-US": "Cannot review the current conversation",
|
||
},
|
||
"conversation.review_preview_exception": {
|
||
"zh-CN": "生成预览时发生异常",
|
||
"en-US": "An error occurred while generating the preview",
|
||
},
|
||
"conversation.review_generate_exception": {
|
||
"zh-CN": "生成对话回顾时发生异常",
|
||
"en-US": "An error occurred while generating the conversation review",
|
||
},
|
||
|
||
# ── 统计 / 当前对话 ──
|
||
"conversation.get_statistics_exception": {
|
||
"zh-CN": "获取对话统计时发生异常",
|
||
"en-US": "An error occurred while getting conversation statistics",
|
||
},
|
||
"conversation.not_found": {
|
||
"zh-CN": "对话不存在",
|
||
"en-US": "Conversation not found",
|
||
},
|
||
|
||
# ── 系统命令(WebSocket command_result / POST /api/commands) ──
|
||
"conversation.cleared": {
|
||
"zh-CN": "对话已清除",
|
||
"en-US": "Conversation cleared",
|
||
},
|
||
"conversation.terminal_system_not_initialized": {
|
||
"zh-CN": "终端系统未初始化",
|
||
"en-US": "Terminal system not initialized",
|
||
},
|
||
"conversation.unknown_command": {
|
||
"zh-CN": "未知命令: {command}",
|
||
"en-US": "Unknown command: {command}",
|
||
},
|
||
"conversation.command_required": {
|
||
"zh-CN": "命令不能为空",
|
||
"en-US": "Command must not be empty",
|
||
},
|
||
"conversation.command_execution_exception": {
|
||
"zh-CN": "命令执行异常: {error}",
|
||
"en-US": "An error occurred while executing the command: {error}",
|
||
},
|
||
|
||
# ── token 统计 ──
|
||
"conversation.get_token_statistics_exception": {
|
||
"zh-CN": "获取token统计时发生异常",
|
||
"en-US": "An error occurred while getting token statistics",
|
||
},
|
||
} |