Files
leaudit-platform-backend/docs/权限与地区隔离/新平台主链路租户边界扫描报告.md
T

9.6 KiB

新平台主链路租户边界扫描报告

扫描日期:2026-05-21
范围:新平台主链路
不含:旧 evaluation_points 兼容链路、旧 rules/listrules/new


1. 总结论

当前新平台主链路已经不是“完全没租户化”,而是进入了一个更危险的阶段:

  • 文档主表已经开始写 tenant_code
  • 首页入口模块已经开始按租户过滤
  • 但评查组/规则集/前端上传作用域仍没有完整切到 tenant_code

这意味着系统现在处于一种“半租户化”状态:

  1. 一部分链路按租户工作
  2. 一部分链路仍按 region/area/document_type/entry_module_id
  3. 还有一部分链路默认假设主数据是全局共享

真正的风险不是“功能不能用”,而是:

  • 会把数据落错边界
  • 会把旧脏数据和新租户数据串到同一条版本链
  • 会在多租户复用同入口模块/同文档类型时,把用户带到错误的业务树或规则树

2. 三个最危险的核心问题

2.1 评查组/规则集主数据仍然是全局共享模型

当前:

  • leaudit_evaluation_point_groups 没有 tenant_code
  • leaudit_rule_group_bindings 没有 tenant_code
  • leaudit_rule_sets / rule_type 仍按全局唯一思路工作

结果:

  • 不同租户可能共享同一套分组树
  • 不同租户可能共享同一个 rule_type -> rule_set
  • 一个租户新建规则版本,可能覆盖另一个租户当前使用的规则资产

这不是“显示错了”,而是规则资产归属模型本身还没分租户。

2.2 文档链路仍存在 tenant_code + region 混查

当前:

  • 文档上传虽然开始写 tenant_code
  • 但历史版本命中、列表过滤、详情过滤仍允许 tenant_code 不命中时回退 region

结果:

  • 同地区旧脏数据可能被续成新租户文档的新版本
  • 列表和详情在历史数据上可能出现租户边界漂移
  • 只要旧数据 tenant_code 为空,系统就会继续依赖中文展示名做范围匹配

这是当前最容易造成“查对了表面、查错了真实归属”的问题。

2.3 前端上传和页面作用域仍未显式传递 tenant_code

当前:

  • 上传页提交时主要传 region
  • 首页跳转主要传 entryModuleId + documentTypeIds
  • 子类型分组查询只按 document_type_id
  • 文档列表读范围也主要依赖 URL/sessionStorage 中的模块/文档类型

结果:

  • 当前租户上下文在前端不是一等主语义
  • 很多页面只能“假设后端已经帮你隔离好了”
  • 一旦后端返回混合数据,前端没有第二道确认防线

3. 高风险问题清单

3.1 评查组/规则集后端高风险

3.1.1 RuleController 基本是裸接口

文件:

问题:

  • 缺少统一 verify_access_token
  • 缺少功能权限校验
  • 也没有数据范围/租户上下文透传

影响:

  • 规则集列表、版本内容、发布、回滚、绑定增删改都可能绕过租户边界

3.1.2 CreateVersion 仍按全局 rule_type 工作

文件:

问题:

  • 规则版本创建仍按全局 rule_type 命中 rule_set
  • 没有 tenant_code

影响:

  • 不同租户只要规则类型编码一致,就可能共用同一个规则集和版本链

3.1.3 评查组树所有读写都没有租户谓词

文件:

问题:

  • ListGroups
  • ListAllGroups
  • GetGroup
  • GetChildren
  • ListGroupsByDocumentTypes
  • CreateGroup
  • UpdateGroup
  • DeleteGroup
  • BatchDelete
  • RebindGroup
  • CreateBinding
  • UpdateBinding
  • DeleteBinding

以上全部没有显式 tenant_code 约束。

影响:

  • 管理页看到的可能是跨租户混合分组
  • 修改和删除可能直接作用到其他租户的业务树

3.1.4 RebindGroup 是最高危错挂点

问题:

  • 直接按 pid 批量迁移子分组
  • 只校验层级,不校验租户一致性

影响:

  • 一次操作可能把多个二级子类型整体迁入错误根分组

3.1.5 分组/绑定表结构本身没有 tenant_code

文件:

问题:

  • ensure_rule_group_schema 建表时就没有租户字段

影响:

  • 即使服务层开始传租户,也没有稳定持久化载体

3.2 文档后端主链路高风险

3.2.1 历史版本命中仍会 tenant_code 回退 region

文件:

问题:

  • _find_latest_version_candidate
  • _document_tenant_filter_sql
  • _buildDocumentScopeFilters

都还允许 tenant_code 不足时回退 region

影响:

  • 新租户文档可能接到旧地区文档的历史版本链

3.2.2 group_id / entry_module_id 仍在查询时反推

问题:

  • 列表和详情展示并不完全依赖文档上传时的稳定快照
  • 而是会根据当前分组树状态推断

影响:

  • 配置一变,历史文档显示的业务树就变了
  • 容易造成“文档看起来属于当前子类型,但上传时并不属于”

3.2.3 上传仅校验 group_id 属于 document_type_id

问题:

  • 没有进一步校验:
    • 是否属于当前入口模块
    • 是否属于当前租户允许范围
    • 是否属于当前业务树

影响:

  • 只要知道一个合法分组 ID,就可能落错业务边界

3.2.4 交叉评查链路会绕过原租户范围

问题:

  • GetReviewPoints
  • AuditReviewPoint
  • ConfirmReviewResults

在交叉评查任务成员命中时会 bypass scope

影响:

  • 如果业务上不允许跨租户交叉评查,这就是直接越界读取/更新

3.3 前端主链路高风险

3.3.1 上传页没有显式提交 tenant_code

文件:

问题:

  • 当前前端主要把 tenant_name/arearegion
  • 没有直接传 tenant_code

影响:

  • 后端仍要猜
  • 租户显示名不唯一时最容易落错数据

3.3.2 子类型分组下拉没有按 tenant_code 拉取

问题:

  • /api/v3/evaluation-point-groups/by-document-types
  • 只按 document_type_ids
  • 最多本地再按 entryModuleId 过滤

影响:

  • 不同租户复用同文档类型时,前端会把别的租户二级组混到下拉框

3.3.3 首页跳转/文档列表作用域没有显式携带租户

问题:

  • 首页只传 selectedModuleId/documentTypeIds
  • 文档列表和上传页继续从 sessionStorage/URL 恢复范围

影响:

  • 模块是对的,不代表租户也是对的
  • 最容易在跨页、刷新、缓存残留时串范围

4. 中风险问题清单

4.1 后端中风险

  1. _ensure_entry_module_valid 只校验入口模块存在,不校验租户可用性
  2. _ensure_document_type_valid 仍是全局文种唯一/存在校验
  3. _ensure_code_unique 仍是全局唯一
  4. bootstrap_rule_groupsensure_group_for_doc_type 仍是假设全局分组树
  5. 文档当前用户上下文仍依赖 area 兼容与硬编码管理角色
  6. review_point_audits 没有租户/入口/分组快照

4.2 前端中风险

  1. rule-groups 页面没有当前租户确认层
  2. document-types 页面只围绕入口模块,不围绕租户
  3. 首页系统概览仍用硬编码 documentTypeIds 推断业务

5. 低风险问题清单

  1. 首页仍把 user_area 写入本地缓存
  2. region 仍是多个接口 VO 的正式字段
  3. 上传接口同时暴露 typeId/typeCode/groupId/region/tenant_code 多套口径
  4. 多个管理页缺少“当前租户/当前作用域”显式展示

6. 正确的改造顺序

第一阶段:堵住最容易落错/查错数据的链路

  1. 上传前端显式提交 tenant_code
  2. 后端上传与列表停止 tenant_code -> region 混查
  3. 上传时强校验 group_id + document_type_id + entry_module_id + tenant_code
  4. 文档持久化真实 group_id/root_group_id/entry_module_id/tenant_code

第二阶段:补评查组/规则集的入口防线与读写边界

  1. RuleController 全面加鉴权与权限
  2. 评查组 controller/service 全链路透传租户
  3. 分组树和绑定查询全部加租户过滤
  4. RebindGroupCreateBindingCreateRuleDraft 改成租户内操作

第三阶段:补主数据模型

  1. leaudit_evaluation_point_groups 增加租户归属
  2. leaudit_rule_group_bindings 增加租户归属
  3. 重新定义 rule_set 是否按租户隔离
  4. 重做 bootstrap_rule_groups / ensure_group_for_doc_type

第四阶段:补界面确认层

  1. 首页跳转带上租户上下文
  2. 上传页/文档列表/评查组页显示当前租户
  3. 子类型、规则集、文档列表全部按租户查询

7. 建议接下来直接执行的第一批任务

  1. 后端:收紧文档主链路,去掉危险的 tenant_code + region 混查
  2. 前端:上传显式传 tenant_code,文档列表显式带 tenant_code
  3. 后端:给 RuleController 全量补 token 和权限
  4. 后端:评查组接口开始透传 tenant_code
  5. 文档:补“新平台主链路租户改造实施任务单”