Skip to content

security 服务 getReadableFields 查询面(export 列投影的长期正解) #3547

Description

@os-zhuang

#3391 的独立 follow-up ④。

背景

P1 让 export 的列投影与 list 的字段级权限(FLS)对齐,方案是「从 masked 行推导」:取首块 masked 行的键并集 ∩ schema 字段,作为导出表头(packages/rest/src/rest-server.ts export 路由,#3498)。这是当时半径可控的方案,但有已知妥协:

  • 首块行里某可读字段若恰好全为 null(且驱动省略 null 键),该列表头会消失(纯外观问题)。
  • 空结果集退回全 schema 表头。

长期正解

给 security 服务加一个 getReadableFields(object, context) 查询面,直接返回该 context 下的可读字段集,export 用它做列投影,而不是从数据行反推。

为什么当时没做

REST 层只有 protocol 句柄,够不到 kernel 的 security 服务;加这个查询面 P1 半径过大。需要:

  • 在 security 服务(plugin-security)暴露 getReadableFields(object, context)(复用 FieldMasker 的可读判定,与读中间件同源,杜绝漂移)。
  • 让 REST export 路由能拿到该服务句柄(kernel 服务解析),用它替换「从 masked 行推导」。
  • 回归:导出列 = list 可读列,且不受 null 值/空结果影响。

关联:#3391、#3498(现「从 masked 行推导」实现 + 阻塞性列投影测试)。

Activity

  1. os-zhuang commented on Jul 27, 2026

    @os-zhuang
    ContributorAuthor

    进展汇总

    本 issue 的三个验收项已由两个 PR 覆盖:

    • PR feat(security,rest): getReadableFields query surface for export column projection (#3547) #3561(已合并) —— 落地 getReadableFields(object, context) 查询面本体:

      • ✅ plugin-security 暴露该服务面,复用与读中间件 FieldMasker 同源的 evaluator + requiredPermissions fold(外加 ADR-0090 D10 委托者掩码交集,悬空委托者 fail-closed),返回值即 maskResults 删除集的精确补集 —— 结构上杜绝漂移。
      • ✅ REST export 路由经 resolveSecurityService(环境 kernel 优先,host provider 回退)拿到句柄,在流式输出前完成列投影,替换「从 masked 行推导」。无 security 服务可达时回退旧路径,零回归。
      • ✅ 回归的 null 值一半:某可读列在每一行都缺失时表头仍保留。
    • PR fix(rest): emit the projected export header on an empty result set (#3547) #3649(本次,draft) —— 补齐回归的 空结果集一半:

      零行时 export 至今写出的是零字节文件(表头只在首个数据块旁写一次,而首块为空就 break)。「导出列不依赖行内容」只有在零行时也成立才算真的成立,而列投影本就由 schema + context 推导,零行时照样知道该出哪些列。

      现在:列集权威时(安全服务的可读投影,或显式 ?fields=)补写表头,空导出因此成为可用的导入模板;fieldsFromSchema && !readableProjected 时保持无表头 —— 该路径没有行可收窄,写出完整 schema 表头会暴露 FLS 隐藏列的列名,正是 跟踪:UI 操作按钮与 apiMethods 白名单一致性契约落地(#3026 设计定稿) #3391 要堵的泄露。header=false 仍然一律抑制。

      测试:5 个新用例(空结果出投影表头 csv/xlsx、无投影保持无表头、显式 fields 回显、header=false 优先);rest 包 399 测试全过。

    #3649 合并后本 issue 自动关闭。

    顺带记录的两个非阻塞观察

    1. 查询面目前只有 export 一个消费者。同样受益的候选:导入模板列生成、UI 列选择器。等消费者变多时,值得把该服务面的契约(入参 context 形状、undefined 语义)在 spec/ADR 里固化一句。
    2. 显式 ?fields= 不与可读集取交集是有意为之(对齐 list $select:列名保留、值掩空)。若未来出现「连列名都不得出现在显式请求的导出里」的合规诉求,这里是开关位。

    两条都不建议现在动,先留档。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions