Files
hms/plans/audit-report-2026-04-26.md
iven 83fe89cbcd
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: 全系统审计问题修复 — 安全/数据完整性/功能缺陷/UX (Phase 1-5)
Phase 1 安全热修复:
- P0-1: /uploads 文件服务添加 JWT 认证中间件(支持 header + query param)
- P0-2: analytics/batch 路由从 public 移到 protected_routes
- P0-3: plugin engine SQL 注入修复(format! → 参数化查询)
- P0-new: stats_service compute_avg_field 字段白名单 + FLOAT8 类型转换

Phase 2 数据完整性:
- P0-4: 组织删除级联检查(添加部门存在性校验)
- P0-5: 部门删除级联检查(添加岗位 + 用户存在性校验)
- P0-8: workflow on_tenant_deleted 实现 5 实体批量删除
- P0-7: 并行网关 race condition 修复(consumed → completed 原子转换)

Phase 3 P1 后端 Bug:
- P1-12: plugin host 表名消毒(使用 sanitize_identifier)
- P1-10: workflow deprecated 状态转换(published → deprecated)
- P1-11: workflow 更新验证条件(nodes/edges 任一变化即验证)
- P0-9: 小程序 .gitignore 添加 .env/.env.*/日志
- P1-19: 小程序加密密钥替换为 64 字符强密钥

Phase 4 消息模块:
- P1-5: 通知偏好 GET 路由 + handler
- P1-4: 消息模板 update/delete CRUD + version
- P2-8: mark_all_read SQL 添加 version + 1
- P2-7: markAsRead 改为乐观更新 + 失败回滚

Phase 5 前端修复:
- P2-9: 通知面板点击导航到 /messages
- P2-1: 随访任务患者名批量 ID 解析(替代 UUID 显示)
- P2-5: AppointmentList 分离 patient_id/doctor_id 分别调用 API
- P2-17: PluginMarket installed 字段修正(name → id)
- P3-3: 路由标题 fallback 改为模式匹配(支持 :id 动态路径)
- P2-15: workflow updateDefinition 添加 version 字段
- P3-9: Kanban 版本使用记录实际 version
- P2-21: secure-storage 生产环境无密钥时阻止存储
- P3-11: destroyOnHidden → destroyOnClose
- P3-13: PendingTasks 深色模式 Tag 颜色适配

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 19:16:23 +08:00

383 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 代码期望 f64FLOAT8
- **相关文件**: `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 | 偏好设置仅暴露 DNDchannel_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— 多租户数据隔离
### 第二优先级P12 周内修复)
1. 统计报表 SQL 类型修复P1-1— 面向用户的功能
2. 行级数据权限实现P1-2— 安全要求
3. 消息实时推送P1-3— 医疗告警时效性
4. 工作流功能补全P1-6~11— 模块可用性
5. 消息模板/偏好完善P1-4/5— 功能闭环
### 第三优先级P21 月内修复)
1. 前端测试覆盖率提升P2-2
2. 工作流 N+1 查询优化P2-11
3. 各模块 UX 修复UUID 显示、标题、按钮状态等)
---
## 附录:审计方法
- **代码扫描**: Explore agent 深度遍历源码,逐模块检查功能完整性
- **浏览器测试**: Chrome DevTools MCP 实际操作 18 个页面,验证 CRUD 链路
- **后端日志分析**: 解析 Axum tracing 日志定位 500 错误根因
- **网络面板**: 监控 API 请求/响应,发现冗余调用和错误响应