fix: 修复35个安全与功能缺陷,补全知识进化/数字孪生/行为采集模块

## 安全修复 (12项)
- Webhook接口添加全局Token认证,过滤敏感请求头
- 修复JWT Base64 padding公式,防止签名验证绕过
- 数据库密码/飞书Token从源码移除,改为环境变量
- 工作流引擎添加路径遍历防护 (_resolve_safe_path)
- eval()添加模板长度上限检查
- 审批API添加认证依赖
- 前端v-html增强XSS转义,console.log仅开发模式输出
- 500错误不再暴露内部异常详情

## Agent运行时修复 (7项)
- 删除_inject_knowledge_context中未定义db变量的finally块
- 工具执行添加try/except保护,异常不崩溃Agent
- LLM重试计入budget计数器
- self_review异常时passed=False
- max_iterations截断标记success=False
- 工具参数JSON解析失败时记录警告日志
- run()开始时重置_llm_invocations计数器

## 配置与基础设施
- DEBUG默认False,SQL_ECHO独立配置项
- init_db()补全13个缺失模型导入
- 新增WEBHOOK_AUTH_TOKEN/SQL_ECHO配置项
- 新增.env.example模板文件

## 前端修复 (12项)
- 登录改用URLSearchParams替代FormData
- 401拦截器通过Pinia store统一清理状态
- SSE流超时从60s延长至300s
- final/error事件时清除streamTimeout
- localStorage聊天记录添加24h TTL
- safeParseArgCount替代模板中裸JSON.parse
- fetchUser 401时同时清除user对象

## 新增模块
- 知识进化: knowledge_extractor/retriever/tasks
- 数字孪生: shadow_executor/comparison模型
- 行为采集: behavior_middleware/collector/fingerprint_engine
- 代码审查: code_review_agent/document_review_agent
- 反馈学习: feedback_learner
- 瓶颈检测/优化引擎/成本估算/需求估算
- 速率限制器 (rate_limiter)
- Alembic迁移 015-020

## 文档
- 商业化落地计划
- 8篇docs文档 (架构/API/部署/开发/贡献等)
- Docker Compose生产配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-10 19:50:20 +08:00
parent f79dc0b3c6
commit ab1589921a
77 changed files with 9442 additions and 265 deletions

View File

@@ -680,12 +680,22 @@ class WorkflowEngine:
if in_degree[neighbor] == 0:
queue.append(neighbor)
# 检查是否有环(只检查可达节点)
reachable_nodes = set(result)
if len(reachable_nodes) < len(self.nodes):
# 有些节点不可达,这是正常的(条件分支)
pass
# 检查是否有环Kahn 算法结束后仍有非零入度的节点之间存在边 → 环路
remaining = [n for n in self.nodes.keys() if n not in result and in_degree.get(n, 0) > 0]
if remaining:
cycle_nodes = set()
for n in remaining:
for nb in graph.get(n, []):
if nb in remaining:
cycle_nodes.add(n)
cycle_nodes.add(nb)
if cycle_nodes:
logger.error("DAG 环路检测: 涉及节点 %s", list(cycle_nodes))
raise WorkflowExecutionError(
detail=f"工作流存在循环依赖,涉及节点: {list(cycle_nodes)}",
)
logger.debug("不可达节点(非环路,可能被条件分支排除): %s", remaining)
self.execution_graph = result
return result
@@ -949,6 +959,27 @@ class WorkflowEngine:
return result
def _resolve_safe_path(self, file_path: str) -> str:
"""路径遍历防护:解析并验证路径在工作区根目录内。"""
import os
from pathlib import Path
from app.core.config import settings
raw_root = (getattr(settings, "LOCAL_FILE_TOOLS_ROOT", None) or "").strip()
if raw_root:
workspace_root = Path(raw_root).expanduser().resolve()
else:
workspace_root = Path(__file__).resolve().parent.parent.parent.parent
try:
p = Path(file_path).expanduser()
if not p.is_absolute():
p = (workspace_root / p).resolve()
else:
p = p.resolve()
p.relative_to(workspace_root)
return str(p)
except (ValueError, OSError) as e:
raise ValueError(f"路径访问被拒绝: {file_path} (工作区根: {workspace_root})") from e
def _resolve_llm_prompt_placeholder(self, input_data: Dict[str, Any], var_path: str) -> Any:
"""
解析 LLM 提示词中的 {{path}}。
@@ -1385,8 +1416,11 @@ class WorkflowEngine:
merged: Dict[str, Any] = {**root}
if isinstance(input_data, dict):
merged = {**merged, **input_data}
decision = merged.get('__hil_decision')
comment = merged.get('__hil_comment')
# 使用 per-node 决策键,避免多审批节点冲突
decision_key = f'__hil_decision_{node_id}'
comment_key = f'__hil_comment_{node_id}'
decision = merged.get(decision_key) or merged.get('__hil_decision')
comment = merged.get(comment_key) or merged.get('__hil_comment')
if decision == 'approved':
out = {
'approved': True,
@@ -2590,6 +2624,8 @@ class WorkflowEngine:
if file_path:
file_path = replace_variables(file_path, input_data)
# 路径遍历防护:确保路径在允许的工作区内
file_path = self._resolve_safe_path(file_path)
if isinstance(content, str):
content = replace_variables(content, input_data)
@@ -3837,6 +3873,9 @@ class WorkflowEngine:
logger.info(f"[rjb] Cache节点 {node_id} 执行value模板")
logger.info(f"[rjb] value_str前300字符: {value_str[:300]}")
logger.info(f"[rjb] user_input: {user_input[:50]}, output: {str(output)[:50]}, timestamp: {timestamp}")
# 安全校验:禁止过长的模板字符串,防止资源耗尽
if len(value_str) > 10000:
raise ValueError(f"模板表达式过长 ({len(value_str)} 字符),拒绝执行")
value = eval(value_str, {"__builtins__": {}}, safe_dict)
logger.info(f"[rjb] Cache节点 {node_id} value模板执行成功类型: {type(value)}")