383 lines
15 KiB
Markdown
383 lines
15 KiB
Markdown
# 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
|
||
- **修复**:
|
||
1. 立即轮换 Redis 密码
|
||
2. 将 `url` 改回 `__MUST_SET_VIA_ENV__` 占位符
|
||
3. 使用环境变量 `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 分钟)
|
||
- **修复**:
|
||
1. 方案 A:JWT 只存角色,权限在服务端 Redis 缓存实时查询
|
||
2. 方案 B:使用权限位图/bitmask 压缩
|
||
3. 方案 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(已有 `useDebouncedValue` Hook 但未使用)
|
||
|
||
### 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`, `useApiRequest` 4 个 Hook 未被引用
|
||
- **修复**: 清理或接入这些 Hook(特别是 useDebouncedValue 在搜索场景很有用)
|
||
|
||
### L-03 重复代码 — useCountUp
|
||
|
||
- **现象**: `useCountUp` 在 3 处重复定义
|
||
- **修复**: 提取为共享 Hook
|
||
|
||
### L-04 暗色模式检测逻辑重复
|
||
|
||
- **现象**: `const isDark = token.colorBgContainer === '#111827'` 在 20+ 组件中重复
|
||
- **修复**: 用已有的 `useDarkMode` Hook 替换
|
||
|
||
### 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 | 代码质量清理 |
|
||
|
||
---
|
||
|
||
## 九、系统亮点(做得好的地方)
|
||
|
||
1. **Token 刷新轮换机制** — 旧 Refresh Token 重用被正确拒绝
|
||
2. **限流保护** — 登录失败 5 次后触发 429 Too Many Requests
|
||
3. **SeaORM 参数化查询** — SQL 注入测试全部被拦截
|
||
4. **权限校验完整性** — 无权限用户所有操作返回 403
|
||
5. **多租户架构** — JWT 注入 tenant_id,中间件自动过滤
|
||
6. **审计日志** — 登录/登出/密码修改等关键操作有完整记录
|
||
7. **WASM 插件沙箱** — CRM 插件运行稳定,6 个实体全部可用
|
||
8. **工作流引擎** — BPMN 解析、Token 驱动、任务分配完整实现
|
||
9. **错误处理链** — thiserror → AppError → HTTP 响应的统一错误体系
|
||
10. **优雅关闭** — CTRL+C 信号处理、模块按拓扑逆序关闭
|