15 KiB
15 KiB
ERP 平台全面深度审计报告
审计日期: 2026-04-18 审计方式: 前端功能链路 + API 接口测试 + 代码静态分析 + 安全渗透测试 审计范围: 全部 5 个业务模块 + 2 个插件 + 前端 SPA + 安全与代码质量
一、审计总结
| 维度 | CRITICAL | HIGH | MEDIUM | LOW | 合计 |
|---|---|---|---|---|---|
| 安全 | 2 | 4 | 3 | 2 | 11 |
| 功能 | 1 | 3 | 3 | 1 | 8 |
| 代码质量 | 0 | 1 | 3 | 6 | 10 |
| 合计 | 3 | 8 | 9 | 9 | 29 |
二、CRITICAL — 必须立即修复
C-01 Redis 凭据硬编码在配置文件中(泄露到 Git)
- 文件:
crates/erp-server/config/default.toml(line 11) - 现象:
url = "redis://:redis_KBCYJk@129.204.154.246:6379"硬编码了远程 Redis 密码和 IP - 影响: 凭据已提交到 Git 仓库,任何有代码访问权限的人都能获取 Redis 密码和服务器 IP
- 修复:
- 立即轮换 Redis 密码
- 将
url改回__MUST_SET_VIA_ENV__占位符 - 使用环境变量
ERP__REDIS__URL传递
C-02 存储型 XSS — 用户输入未做 HTML 清理
- 文件:
crates/erp-auth/src/service/user_service.rs(创建/更新用户) - 现象: 通过
POST /api/v1/users可将<script>alert('xss')</script>和<img src=x onerror=alert(1)>直接存入数据库的display_name、email等字段 - 影响:
- React JSX 自动转义避免了前端直接触发(当前安全)
- 但原始 HTML 已存储在数据库中,在以下场景可触发:
- 邮件模板渲染
- PDF 导出
- OpenAPI 文档中的 schema 示例
- 未来使用非 React 渲染的任何场景
- 验证:
POST /api/v1/users {"display_name":"<img src=x onerror=alert(document.cookie)>"} → 201 Created, 原始 HTML 直接入库 - 修复: 后端入库前对所有用户可编辑字段 strip HTML tags 或 escape HTML entities
C-03 首页工作台统计卡片永久 Loading
- 文件:
apps/web/src/pages/Home.tsx - 现象: 4 个统计卡片(用户总数、角色数量、流程实例、未读消息)始终显示 loading 动画
- 根因:
useCountUp动画依赖数据加载,API 返回格式与前端预期不匹配 - 影响: 工作台页面无法展示核心统计数据,用户体验极差
- 修复: 修正统计 API 的数据格式,确保与
StatisticCard组件预期一致
三、HIGH — 高优先级问题
H-01 用户名唯一性约束未生效
- 文件:
crates/erp-auth/src/service/user_service.rs(创建用户) - 现象: 用相同
username创建两次用户均返回201 Created - 影响: 可能导致身份混淆、审计日志混乱
- 修复: 在创建用户前先查询
username是否已存在(同 tenant_id + 未删除),或添加数据库唯一索引
H-02 消息模板 API 返回空 body
- 文件:
GET /api/v1/messages/templates - 现象: 返回空 HTTP body(非 JSON 格式),前端无法解析
- 影响: 消息中心"模板"tab 无法展示数据
- 修复: 修复空列表的序列化处理,确保返回
{"success":true,"data":{"data":[],"total":0}}
H-03 主题 API 返回空 body
- 文件:
GET /api/v1/config/theme - 现象: 返回空 body 而非 JSON
- 影响: 主题设置页面无法加载当前配置
- 修复: 为新租户初始化默认主题配置,或 API 返回默认值
H-04 JWT Token 体积过大
- 文件:
crates/erp-auth/src/service/token_service.rs - 现象: Access Token 包含 64 个权限字符串,JWT payload 约 2.5KB
- 影响:
- 每次 HTTP 请求都要携带 2.5KB+ 的 Authorization header
- 影响带宽和性能,尤其在高频 API 调用场景
- 权限变更需要等 Token 过期才生效(最长 15 分钟)
- 修复:
- 方案 A:JWT 只存角色,权限在服务端 Redis 缓存实时查询
- 方案 B:使用权限位图/bitmask 压缩
- 方案 C:减少 JWT 中的权限列表,改为中间件实时校验
H-05 字段长度无限制
- 文件:
crates/erp-auth/src/dto.rs - 现象:
display_name可接受 500+ 字符(测试通过 500 个 'A'),无 max length 验证 - 影响: UI 布局破坏、潜在数据库性能问题
- 修复: 添加
#[validate(length(max = 100))]等长度约束
四、MEDIUM — 中优先级问题
M-01 菜单配置前端硬编码,未使用后端 API
- 文件:
apps/web/src/components/AppLayout.tsx或路由配置 - 现象: 后端
GET /api/v1/config/menus返回空数组,侧边栏菜单完全前端硬编码 - 影响: 菜单无法通过管理后台动态配置,插件菜单需要在代码中手动添加
- 修复: 实现前端从 API 动态加载菜单配置,或在后端初始化默认菜单数据
M-02 时间戳未本地化显示
- 文件: 消息中心、审计日志等列表页面
- 现象: 时间显示为原始 ISO 格式
2026-04-14T13:10:59.516776Z,用户不友好 - 影响: 用户体验差
- 修复: 使用 dayjs 格式化为本地时间,如
2026-04-14 21:10:59
M-03 前端路由仅做认证守卫,无权限守卫
- 文件:
apps/web/src/App.tsx - 现象: 路由只检查是否已登录(token 存在),不检查用户是否有权限访问特定页面
- 影响: 无权限用户可以通过直接输入 URL 访问任何页面(虽然 API 层会返回 403)
- 修复: 在路由守卫中增加权限校验,根据 JWT 中的 permissions 控制页面可见性
M-04 消息响应包含内部 tenant_id
- 文件:
crates/erp-message/src/handler/message_handler.rs - 现象:
GET /api/v1/messages返回每条消息的tenant_id字段 - 影响: 泄露内部多租户架构信息
- 修复: 在 DTO 层排除
tenant_id字段
M-05 搜索缺少防抖(Debounce)
- 文件:
apps/web/src/pages/Users.tsx,apps/web/src/components/EntitySelect.tsx - 现象: 用户搜索输入框每次按键都触发 API 请求
- 影响: 高频请求冲击服务器,用户体验差
- 修复: 添加 300ms debounce(已有
useDebouncedValueHook 但未使用)
M-06 Organizations 页面 useCallback/useEffect 循环依赖
- 文件:
apps/web/src/pages/Organizations.tsx - 现象: useCallback 依赖项导致 useEffect 无限循环渲染
- 影响: 性能问题、可能导致浏览器卡顿
- 修复: 重构 useCallback 依赖项,消除循环
M-07 测试数据残留在生产数据库
- 现象: 数据库中存在以下测试用户和数据:
xss_user— display_name 为<script>alert('xss')</script>test_role_api— 测试角色audit_test_user— 审计测试用户testuser01— 测试用户test_user_api— API 测试用户Perf test消息 — 性能测试消息business_key: PERF-TEST-26311的待办任务
- 影响: 数据污染、潜在安全风险
- 修复: 清理所有测试数据,确保数据库只包含有意义的业务数据
M-08 系统设置多个 tab 数据为空
- 现象: 数据字典、编号规则、系统参数、语言管理等 tab 均无种子数据
- 影响: 系统看起来像空的,用户需要手动配置所有基础数据
- 修复: 在
on_tenant_created中初始化默认字典(客户类型、行业、地区等)
M-09 中文 API 响应编码异常
- 现象: 部分中文内容在 API JSON 响应中显示为乱码(如
\u7eef\u8364\u7cba而非"系统管理员") - 影响: 可能是 curl 的显示问题,也可能是后端序列化配置问题
- 修复: 确认后端 JSON 序列化使用
ensure_ascii: false等效配置
五、LOW — 低优先级 / 代码质量
L-01 死代码 — graph 目录
- 文件:
apps/web/src/pages/plugins/graph/(6 个文件) - 现象: 完全未使用的代码
- 修复: 删除或标记为实验性
L-02 死代码 — 未使用的 Hooks
- 文件:
apps/web/src/hooks/ - 现象:
useDarkMode,useDebouncedValue,usePaginatedData,useApiRequest4 个 Hook 未被引用 - 修复: 清理或接入这些 Hook(特别是 useDebouncedValue 在搜索场景很有用)
L-03 重复代码 — useCountUp
- 现象:
useCountUp在 3 处重复定义 - 修复: 提取为共享 Hook
L-04 暗色模式检测逻辑重复
- 现象:
const isDark = token.colorBgContainer === '#111827'在 20+ 组件中重复 - 修复: 用已有的
useDarkModeHook 替换
L-05 i18n 已配置但未使用
- 现象: i18next 已初始化且有 30 个翻译 key,但所有页面组件硬编码中文
- 修复: 逐步替换硬编码中文为 i18n key
L-06 antd 废弃 API 警告
- 现象:
Drawer的width属性、Modal的destroyOnClose已废弃 - 修复: 升级到 antd 6 的新 API 用法
L-07 ErrorBoundary 错误信息泄露
- 文件:
apps/web/src/components/ErrorBoundary.tsx - 现象: 错误边界展示完整的错误堆栈给用户
- 修复: 生产环境只显示友好错误消息,堆栈信息仅记录到控制台
L-08 Home 页面使用 dangerouslySetInnerHTML
- 文件:
apps/web/src/pages/Home.tsx - 现象: 工作台页面使用
dangerouslySetInnerHTML渲染内容 - 影响: 如果内容包含用户输入,可能导致 XSS
- 修复: 改用 React 组件渲染
L-09 插件恢复计数不准确
- 现象:
Plugin recovered: 0但实际 WASM 已加载 - 修复: 修正 recovery 计数逻辑
六、安全测试矩阵
| 测试项 | 结果 | 备注 |
|---|---|---|
| 无 Token 访问受保护端点 | ✅ 401 | 正确拦截 |
| 无效 Token | ✅ 401 | 正确拦截 |
| 篡改 Token payload | ✅ 401 | HMAC 签名校验有效 |
| 错误密码登录 | ✅ 401 | 正确拒绝 |
| 短密码创建用户 | ✅ 400 | 验证 min=6 生效 |
| 空 Token 刷新 | ✅ 401 | 正确拒绝 |
| 旧 Refresh Token 重用 | ✅ 401 | 轮换机制生效 |
| SQL 注入(搜索参数) | ✅ 安全 | SeaORM 参数化查询 |
| SQL 注入(UUID 路径) | ✅ 安全 | UUID 解析拒绝非法字符 |
| 存储型 XSS | ❌ 入库 | 后端未清理 HTML,React 前端安全 |
| 无权限用户访问 API | ✅ 403 | 权限校验正确 |
| 无权限用户提权 | ✅ 403 | 角色分配受权限保护 |
| 限流 | ✅ 生效 | 5 次失败后触发 429 |
| CORS 配置 | ✅ 白名单 | 仅允许 localhost 端口 |
| 凭据泄露 | ❌ Redis 密码硬编码 | 已提交到 Git |
七、功能链路审计结果
7.1 认证链路 ✅ 基本正常
| 环节 | 状态 | 备注 |
|---|---|---|
| 登录 → JWT 签发 | ✅ | access (15min) + refresh (7d) |
| Token 刷新轮换 | ✅ | 旧 Token 使用后立即失效 |
| 密码修改 → Token 吊销 | ✅ | 所有 refresh token 失效 |
| 登出 → Token 吊销 | ✅ | |
| 限流保护 | ✅ | 5 次失败后 429 |
| 审计日志记录 | ✅ | 登录成功/失败均有记录 |
7.2 用户管理 ✅ 基本正常,有缺陷
| 环节 | 状态 | 备注 |
|---|---|---|
| CRUD 操作 | ✅ | |
| 角色分配 | ✅ | |
| 用户名唯一性 | ❌ | 重复用户名可创建 |
| 输入验证 | ⚠️ | 密码有验证,其他字段长度/XSS 无验证 |
| 软删除 | ✅ |
7.3 权限管理 ✅ 正常
| 环节 | 状态 | 备注 |
|---|---|---|
| 角色列表 | ✅ | 3 个角色(admin/viewer/test) |
| 权限分配 | ✅ | 54 个权限可精确分配 |
| 系统角色保护 | ⚠️ | admin 角色权限可被修改 |
| data_scope 配置 | ❌ | 权限对话框中无 data_scope 配置入口 |
7.4 工作流引擎 ✅ 正常
| 环节 | 状态 | 备注 |
|---|---|---|
| 流程定义 CRUD | ✅ | 3 个定义(draft/published) |
| 流程发起 | ✅ | |
| 任务审批 | ✅ | approve/reject |
| 任务委派 | ✅ | |
| 实例监控 | ✅ | running/terminated/suspended |
| 超时检测 | ✅ | 60s 间隔扫描 |
7.5 消息中心 ⚠️ 部分异常
| 环节 | 状态 | 备注 |
|---|---|---|
| 消息列表 | ✅ | 10 条消息 |
| 未读计数 | ✅ | bell 图标显示未读数 |
| 标记已读 | ✅ | |
| 全部已读 | ✅ | |
| 消息模板 | ❌ | API 返回空 body |
| 通知设置 | ⚠️ | 未验证 |
| 工作流事件 → 消息 | ✅ | "流程已启动" 消息自动生成 |
7.6 系统配置 ⚠️ 数据缺失
| 环节 | 状态 | 备注 |
|---|---|---|
| 数据字典 | ❌ | 空数据,无种子 |
| 菜单配置 | ❌ | 后端空,前端硬编码 |
| 编号规则 | ❌ | 空 |
| 系统参数 | ⚠️ | 未验证 |
| 主题设置 | ❌ | API 返回空 |
| 语言管理 | ⚠️ | 未验证 |
| 修改密码 | ✅ | 功能正常 |
| 审计日志 | ✅ |
7.7 插件系统 ✅ 基本正常
| 环节 | 状态 | 备注 |
|---|---|---|
| CRM 插件运行 | ✅ | 状态:运行中 |
| 客户 CRUD | ✅ | 6 条客户数据 |
| 联系人 | ✅ | |
| 沟通记录 | ✅ | |
| 标签管理 | ✅ | |
| 客户关系 | ✅ | |
| 统计概览 | ⚠️ | |
| 销售漏斗 | ⚠️ |
八、修复优先级建议
🔴 立即修复(本周内)
| 编号 | 问题 | 预计工作量 |
|---|---|---|
| C-01 | Redis 凭据从 config 移除,改用环境变量 | 0.5h |
| C-02 | 后端添加 HTML sanitize 中间件 | 2h |
| C-03 | 修复首页统计卡片数据格式 | 1h |
| H-01 | 添加用户名唯一性校验 | 1h |
🟡 本迭代修复(2 周内)
| 编号 | 问题 | 预计工作量 |
|---|---|---|
| H-02 | 修复消息模板空返回 | 0.5h |
| H-03 | 修复主题 API 空返回 | 0.5h |
| H-04 | JWT 权限压缩或改为服务端查询 | 4h |
| H-05 | 添加字段长度验证 | 1h |
| M-03 | 添加前端路由权限守卫 | 2h |
| M-05 | 搜索添加防抖 | 0.5h |
| M-07 | 清理测试数据 | 1h |
| M-08 | 初始化默认系统配置数据 | 2h |
🟢 迭代中逐步修复
| 编号 | 问题 |
|---|---|
| M-01 | 菜单动态加载 |
| M-02 | 时间戳本地化 |
| M-04 | API 响应排除 tenant_id |
| M-06 | Organizations 性能修复 |
| L-01~L-09 | 代码质量清理 |
九、系统亮点(做得好的地方)
- Token 刷新轮换机制 — 旧 Refresh Token 重用被正确拒绝
- 限流保护 — 登录失败 5 次后触发 429 Too Many Requests
- SeaORM 参数化查询 — SQL 注入测试全部被拦截
- 权限校验完整性 — 无权限用户所有操作返回 403
- 多租户架构 — JWT 注入 tenant_id,中间件自动过滤
- 审计日志 — 登录/登出/密码修改等关键操作有完整记录
- WASM 插件沙箱 — CRM 插件运行稳定,6 个实体全部可用
- 工作流引擎 — BPMN 解析、Token 驱动、任务分配完整实现
- 错误处理链 — thiserror → AppError → HTTP 响应的统一错误体系
- 优雅关闭 — CTRL+C 信号处理、模块按拓扑逆序关闭