Files
erp/docs/audits/audit-2026-04-18-full.md
iven 841766b168
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

15 KiB
Raw Blame History

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_nameemail 等字段
  • 影响:
    • 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. 方案 AJWT 只存角色,权限在服务端 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 警告

  • 现象: Drawerwidth 属性、ModaldestroyOnClose 已废弃
  • 修复: 升级到 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 入库 后端未清理 HTMLReact 前端安全
无权限用户访问 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 信号处理、模块按拓扑逆序关闭