# HMS 全系统前端审计报告 > 日期: 2026-04-26 | 审计范围: 全系统(基础模块 + 健康模块 + 插件 + 小程序) > 方法: 代码扫描 + 浏览器操作测试 + 后端日志分析 > 审计人: Claude Code 自动化审计 --- ## 执行摘要 | 指标 | 值 | |------|-----| | 审计覆盖模块 | 12 个(auth/config/workflow/message/plugin/health/ai/settings/小程序) | | 浏览器验证页面 | 18 个 Web 页面 + 通知面板 | | 代码级审计模块 | 10 个(4 个后台 agent + 6 个手动/浏览器) | | **总问题数** | **72 个** | | P0 阻断 | **9 个** | | P1 严重 | **20 个** | | P2 中等 | **22 个** | | P3 轻微 | **15 个** | | P4 优化 | **6 个** | ### 风险矩阵 | 风险域 | 状态 | 关键发现 | |--------|------|---------| | 安全 | 🔴 高风险 | 2 个 P0(文件无认证访问 + SQL 注入)| | 数据完整性 | 🔴 高风险 | 级联删除缺失 × 3,并行网关死锁 | | 功能完整性 | 🟡 中等 | 多个模块 CRUD 不完整(模板/偏好/组织) | | 实时性 | 🟡 中等 | 消息 60 秒轮询,无 WebSocket | | 代码质量 | 🟢 良好 | 动态表 SQL 注入防护好,租户隔离一致 | --- ## P0 阻断问题(8 个) ### P0-1: 上传文件无认证访问 - **模块**: erp-server 静态文件服务 - **类型**: 安全漏洞 - **复现步骤**: 直接访问 `http://localhost:3000/uploads/{任意文件名}` - **预期行为**: 需 JWT 认证才能访问医疗文档 - **实际行为**: 所有上传文件公开可访问,包括医疗文档 - **影响**: 医疗隐私数据泄露,违反 HIPAA 合规 - **相关文件**: `crates/erp-server/src/main.rs:545-546` - **建议修复**: 在 ServeDir 前添加 JWT 认证中间件,或使用签名 URL ### P0-2: analytics/batch 端点公开 - **模块**: erp-server API - **类型**: 安全漏洞 - **复现步骤**: 无需认证 POST `/api/v1/analytics/batch` - **影响**: 任意 JSON 事件注入,可伪造分析数据 - **相关文件**: `crates/erp-server/src/main.rs` 路由注册 - **建议修复**: 添加 JWT 认证守卫 ### P0-3: load_plugin_config SQL 注入 - **模块**: erp-plugin 引擎 - **类型**: 安全漏洞 - **复现步骤**: 恶意 WASM 插件 manifest 中 craft `plugin_id` 包含 SQL 注入 payload - **影响**: 通过 `format!()` 拼接 SQL,`plugin_id` 仅用 `replace('\'', "''")` 转义,不安全 - **相关文件**: `crates/erp-plugin/src/engine.rs:630-637` - **建议修复**: 改用参数化查询 `$N` 占位符 ### P0-4: 组织删除无级联检查 - **模块**: erp-auth 组织管理 - **类型**: 数据完整性 - **复现步骤**: 删除包含子部门/岗位/用户关联的组织 - **影响**: 软删除组织后,子部门、岗位、用户关联成为孤儿数据 - **建议修复**: 删除前检查并阻止存在子记录的删除操作 ### P0-5: 部门删除无级联检查 - **模块**: erp-auth 部门管理 - **类型**: 数据完整性 - **复现步骤**: 删除包含子部门或关联用户的部门 - **影响**: 同 P0-4,部门树结构断裂 - **建议修复**: 递归检查子部门和用户关联 ### P0-6: 岗位删除无级联检查 - **模块**: erp-auth 岗位管理 - **类型**: 数据完整性 - **影响**: 删除岗位后用户-岗位关联悬空 - **建议修复**: 检查用户关联后阻止或级联更新 ### P0-7: 并行网关 Join 逻辑可能导致死锁 - **模块**: erp-workflow 执行引擎 - **类型**: 功能缺陷 - **相关文件**: `crates/erp-workflow/src/engine/executor.rs:369-425` - **影响**: 并行分支汇聚时,token 查询可能匹配到历史迭代的 stale token,导致提前/错误完成或永久等待 - **建议修复**: 添加 token lineage/correlation 机制,区分不同 fork 产生的 token ### P0-8: workflow on_tenant_deleted 是空操作 - **模块**: erp-workflow 租户清理 - **类型**: 数据完整性 - **相关文件**: `crates/erp-workflow/src/module.rs:149-154` - **影响**: 删除租户后,流程定义/实例/任务/Token 残留,跨租户数据泄漏风险 - **建议修复**: 实现级联软删除所有租户相关数据 --- ## P1 严重问题(18 个) ### P1-1: health-data 统计端点 500 错误 - **模块**: erp-health 统计 - **类型**: 功能缺陷 - **根因**: PostgreSQL AVG() 返回 NUMERIC 类型,Rust 代码期望 f64(FLOAT8) - **相关文件**: `crates/erp-health/src/service/stats_service.rs` - **浏览器验证**: 统计报表页面显示"加载统计数据失败 500" - **建议修复**: 使用 `Decimal` 类型或 SQL 中显式 `CAST(AVG(...) AS FLOAT8)` ### P1-2: 行级数据权限未生效 - **模块**: erp-auth 权限 - **类型**: 功能缺陷 - **影响**: `department_ids` 已填充但未用于数据过滤查询 - **建议修复**: 在查询构建器中加入 department_ids 过滤条件 ### P1-3: 消息无实时推送 - **模块**: erp-message - **类型**: 功能缺失 - **影响**: 医疗告警最多延迟 60 秒才能到达医护人员,临床不可接受 - **建议修复**: 实现 WebSocket 或 SSE 推送 ### P1-4: 消息模板 CRUD 不完整 - **模块**: erp-message - **类型**: 功能缺失 - **影响**: 模板只能创建和列表,无法编辑、删除,且 `render()` 方法未接入发送管道 - **相关文件**: `crates/erp-message/src/template_service.rs` ### P1-5: 通知偏好设置无法加载已有配置 - **模块**: erp-message + 前端 - **类型**: 功能缺陷 - **影响**: 后端无 GET `/message-subscriptions` 端点,前端无法加载用户已保存的偏好;第二次保存因缺少 version 字段必然失败 - **相关文件**: `crates/erp-message/src/module.rs`, `NotificationPreferences.tsx` ### P1-6: 工作流 ServiceTask 是空操作 - **模块**: erp-workflow - **类型**: 功能缺失 - **相关文件**: `crates/erp-workflow/src/engine/executor.rs:277` - **影响**: 所有 ServiceTask 被自动跳过,`service_type` 字段无效 ### P1-7: 工作流未注册任何事件处理器 - **模块**: erp-workflow - **类型**: 功能缺失 - **影响**: `register_event_handlers` 为空函数,工作流模块不响应任何外部事件 - **相关文件**: `crates/erp-workflow/src/module.rs:137` ### P1-8: candidate_groups(角色/部门分配)存储但未使用 - **模块**: erp-workflow - **类型**: 功能缺失 - **影响**: 配置了 candidate_groups 但无 assignee 的 UserTask 对所有用户不可见,任务成为孤儿 - **相关文件**: `crates/erp-workflow/src/service/task_service.rs:25-36` ### P1-9: 工作流超时检查仅记录日志 - **模块**: erp-workflow - **类型**: 功能缺失 - **影响**: 超时任务无升级/自动完成/通知,永久停留在 pending 状态 - **相关文件**: `crates/erp-workflow/src/engine/timeout.rs` ### P1-10: 工作流 deprecated 状态不可达 - **模块**: erp-workflow + 前端 - **类型**: 功能缺陷 - **影响**: 前端定义了 `deprecated` 状态样式,但后端无转换路径 - **相关文件**: `ProcessDefinitions.tsx:19` ### P1-11: 工作流定义更新跳过校验 - **模块**: erp-workflow - **类型**: 功能缺陷 - **影响**: 只更新 nodes 而不提供 edges 时,不做图结构验证 - **相关文件**: `crates/erp-workflow/src/service/definition_service.rs:174-181` ### P1-12: 编号序列表名未充分消毒 - **模块**: erp-plugin - **类型**: 安全隐患 - **影响**: `plugin_id` 格式化到 DDL/DML 语句时仅 `replace('-', "_")`,不处理引号/分号 - **相关文件**: `crates/erp-plugin/src/host.rs:339` ### P1-13 ~ P1-18: 组织模块 P1 问题 - **P1-13**: 组织/部门/岗位缺少列表 API 中的树形结构返回 - **P1-14**: 部门树重组操作(拖拽移动父节点)未实现 - **P1-15**: 组织/部门名称唯一性校验缺失 - **P1-16**: 部门详情 GET 端点缺失 - **P1-17**: 岗位分配/取消分配 API 缺失 - **P1-18**: 消息群发(角色/部门/全员)fan-out 未实现 --- ## P2 中等问题(20 个) | ID | 模块 | 问题 | 文件 | |----|------|------|------| | P2-1 | health | 随访任务患者名显示为 UUID 片段 | `FollowUpTaskList.tsx` | | P2-2 | health | 前端测试覆盖率极低(3 个文件) | `apps/web/src/` | | P2-3 | health | 深色模式样式重复 ~19 页内联 isDark | 各健康页面 | | P2-4 | health | useDarkMode 和 useThemeMode 重叠 | hooks/ | | P2-5 | health | AppointmentList 冗余 404 请求 | `AppointmentList.tsx` | | P2-6 | message | 未读计数不即时刷新(等 60s 轮询) | `stores/message.ts` | | P2-7 | message | markAsRead 失败不回滚乐观更新 | `stores/message.ts:57-69` | | P2-8 | message | mark_all_read 不更新 version 字段 | `message_service.rs:298-326` | | P2-9 | message | 通知面板点击不导航到详情 | `NotificationPanel.tsx:81-85` | | P2-10 | message | 轮询间隔 60s 对医疗告警不可接受 | `NotificationPanel.tsx:28-31` | | P2-11 | workflow | N+1 查询问题(实例/任务列表) | `instance_service.rs`, `task_service.rs` | | P2-12 | workflow | 排他网关表达式错误被静默吞掉 | `executor.rs:176` | | P2-13 | workflow | ProcessDesigner 不支持边条件配置 | `ProcessDesigner.tsx` | | P2-14 | workflow | 委派 API 要求手动输入 UUID | `PendingTasks.tsx:207-210` | | P2-15 | workflow | 前端 UpdateDefinitionRequest 缺少 version | `workflowDefinitions.ts:45-51` | | P2-16 | plugin | reconcile_references 表名注入模式 | `data_service.rs:1204-1206` | | P2-17 | plugin | PluginMarket installed 判断字段不匹配 | `PluginMarket.tsx:68` | | P2-18 | plugin | 模板渲染未接入发送管道 | `template_service.rs:92-99` | | P2-19 | plugin | 偏好设置 version 字段未发送导致更新失败 | `NotificationPreferences.tsx:26-37` | | P2-20 | plugin | 偏好设置仅暴露 DND,channel_preferences 隐藏 | `NotificationPreferences.tsx:60` | --- ## P3 轻微问题(14 个) | ID | 模块 | 问题 | |----|------|------| | P3-1 | health | 已完成任务仍显示操作按钮 | | P3-2 | health | ArticleEditor 图片上传未实现 (TODO) | | P3-3 | health | PatientDetail 头部标题显示"页面" | | P3-4 | health | 4 处 any 类型使用 | | P3-5 | health | 登录硬编码默认 tenant_id | | P3-6 | settings | 审计日志操作用户列显示原始 UUID | | P3-7 | settings | 审计日志资源类型过滤列表硬编码 | | P3-8 | settings | 系统参数无列表 API,需手动输入 key | | P3-9 | plugin | Kanban 拖拽 version 硬编码 0 导致锁冲突 | | P3-10 | plugin | CRUD 排序使用 JSONB 文本提取导致字典序 | | P3-11 | workflow | ProcessDesigner 用 destroyOnHidden 而非 destroyOnClose | | P3-12 | workflow | InstanceMonitor 显示 raw node_id 而非名称 | | P3-13 | workflow | 待办任务状态 Tag 不适配深色模式 | | P3-14 | message | 偏好设置 DND 启用但未填时间范围时无效 | --- ## P4 优化建议(6 个) | ID | 模块 | 建议 | |----|------|------| | P4-1 | plugin | PluginAdmin purge 按钮状态与后端不一致 | | P4-2 | plugin | WASM init() 使用 nil UUID | | P4-3 | plugin | recover_plugins 不按 tenant_id 过滤 | | P4-4 | settings | LanguageManager 编辑弹窗无可编辑字段 | | P4-5 | settings | ChangePassword 最小长度仅前端校验 | | P4-6 | settings | Settings API delete/update URL 编码不一致 | --- ## 模块审计摘要 ### 基础模块 | 模块 | 页面数 | 浏览器验证 | 代码审计 | 关键发现 | |------|--------|-----------|---------|---------| | 用户/权限 (B1) | 2 | ✅ | ✅ | email 验证宽松 | | 组织架构 (B2) | 1 | ❌ | ✅ | 3×P0 级联删除缺失 | | 工作流 (B3) | 6 | ✅ 3 页 | ✅ | 3×P0 + 6×P1 功能大量缺失 | | 消息 (B4) | 3+面板 | ✅ 面板 | ✅ | 5×P1 无实时推送 | ### 健康模块 | 模块 | 页面数 | 浏览器验证 | 关键发现 | |------|--------|-----------|---------| | 患者管理 (B5) | 2 | ✅ | 标题"页面"bug | | 医生/排班/预约 (B6) | 3 | ✅ | 冗余 404 请求 | | 随访/咨询 (B7) | 4 | ✅ 部分 | 患者 UUID 片段 | | 积分/文章/AI (B8) | 9 | ✅ 部分 | 统计报表 500 | ### 系统/插件 | 模块 | 页面数 | 浏览器验证 | 代码审计 | 关键发现 | |------|--------|-----------|---------|---------| | 插件 (B9) | 8 | ❌ | ✅ | P1 SQL 注入 | | 统计/仪表盘 (B9) | 2 | ✅ | ✅ | 500 错误 | | 系统设置 (B10) | 8 标签 | ✅ 3 标签 | ✅ | 审计日志 UUID | ### 小程序(B11-B14) | 模块 | 审计方式 | 关键发现 | |------|---------|---------| | 登录/首页 (B11) | 代码审计 | P0 .env 泄露风险 | | 健康管理 (B12) | 代码审计 | P2 错误处理缺失 | | 医患交互 (B13) | 代码审计 | P1 咨询消息轮询无错误恢复 | | 内容/商城 (B14) | 代码审计 | P2 空状态处理缺失 | #### 小程序专项发现 ### P0-9: .env 未加入 .gitignore - **模块**: miniprogram 配置 - **类型**: 安全漏洞 - **相关文件**: `apps/miniprogram/.gitignore`(仅含 node_modules/ 和 dist/) - **影响**: `.env` 文件含 `TARO_APP_ENCRYPTION_KEY=hms_miniprogram_encryption_key_2026`,可能意外提交到 git - **当前状态**: 未被 git 追踪(git ls-files 为空),但风险存在 - **建议修复**: 在 `.gitignore` 中添加 `.env` 和 `.env.*` ### P1-19: 小程序加密密钥为弱密钥 - **模块**: miniprogram secure-storage - **类型**: 安全隐患 - **相关文件**: `apps/miniprogram/.env:2`, `apps/miniprogram/src/utils/secure-storage.ts` - **影响**: `hms_miniprogram_encryption_key_2026` 为可预测字符串,AES 加密形同虚设 - **建议修复**: 使用 `python -c "import secrets; print(secrets.token_hex(32))"` 生成强密钥 ### P1-20: project.config.json urlCheck: false - **模块**: miniprogram 配置 - **类型**: 安全隐患 - **相关文件**: `apps/miniprogram/project.config.json:6` - **影响**: 生产环境允许请求任意域名,应限制为后端 API 域名 - **建议修复**: 生产版本设为 `true` 并配置合法域名白名单 ### P2-21: secure-storage 未设密钥时明文存储 - **模块**: miniprogram secure-storage - **类型**: 安全隐患 - **相关文件**: `apps/miniprogram/src/utils/secure-storage.ts:12` - **影响**: `ENCRYPTION_KEY` 为空时 `encrypt()` 直接返回明文,token 和敏感数据不加密存储 - **建议修复**: 强制要求密钥,未设置时阻止存储敏感数据 ### P2-22: 小程序各页面缺少统一错误边界 - **模块**: miniprogram 全局 - **类型**: 功能缺陷 - **影响**: API 请求失败时页面无友好错误提示,可能白屏 - **建议修复**: 添加全局错误边界组件和网络错误拦截器 ### P3-15: 小程序部分页面缺少空状态处理 - **模块**: miniprogram 健康管理 - **类型**: UX 问题 - **影响**: 健康数据为空时无引导提示 --- ## 浏览器验证发现汇总 以下问题通过实际浏览器操作验证: | 页面 | 操作 | 结果 | 问题 | |------|------|------|------| | Users | 创建用户(无效邮箱) | 成功 | email 验证宽松 | | Users | 删除用户 | 成功 | — | | Users | 分配角色 | 成功 | — | | Patient List | 搜索 | 成功 | — | | Patient Detail | 切换标签 | 成功 | 标题显示"页面" | | Statistics | 加载 | 失败 500 | SQL 类型不匹配 | | Settings-字典 | 新建/添加项/删除 | 全部成功 | — | | Settings-密码 | 错误旧密码 | 正确提示 | — | | Settings-审计日志 | 查看 | 成功 | UUID 而非用户名 | | Settings-菜单 | 查看 | 成功 | — | | Workflow-定义 | 列表 | 成功 | — | | Workflow-设计器 | 编辑草稿 | 成功 | 缺节点属性编辑器 | | Workflow-待办 | 查看 | 成功 | — | | 通知面板 | 打开 | 成功 | 60s 轮询延迟 | --- ## 优先修复建议 ### 第一优先级(P0,必须立即修复) 1. **上传文件认证**(P0-1)— 医疗隐私合规要求 2. **SQL 注入修复**(P0-3)— 安全风险,改用参数化查询 3. **analytics/batch 认证**(P0-2)— 防止数据伪造 4. **级联删除检查**(P0-4/5/6)— 防止数据完整性破坏 5. **并行网关修复**(P0-7)— 核心工作流引擎可靠性 6. **租户清理实现**(P0-8)— 多租户数据隔离 ### 第二优先级(P1,2 周内修复) 1. 统计报表 SQL 类型修复(P1-1)— 面向用户的功能 2. 行级数据权限实现(P1-2)— 安全要求 3. 消息实时推送(P1-3)— 医疗告警时效性 4. 工作流功能补全(P1-6~11)— 模块可用性 5. 消息模板/偏好完善(P1-4/5)— 功能闭环 ### 第三优先级(P2,1 月内修复) 1. 前端测试覆盖率提升(P2-2) 2. 工作流 N+1 查询优化(P2-11) 3. 各模块 UX 修复(UUID 显示、标题、按钮状态等) --- ## 附录:审计方法 - **代码扫描**: Explore agent 深度遍历源码,逐模块检查功能完整性 - **浏览器测试**: Chrome DevTools MCP 实际操作 18 个页面,验证 CRUD 链路 - **后端日志分析**: 解析 Axum tracing 日志定位 500 错误根因 - **网络面板**: 监控 API 请求/响应,发现冗余调用和错误响应