test(objectql): pin listDrafts header-only 投影 —— #6599 所述字段级泄露经实测不存在 - #7014
Merged
Merged
Conversation
#6599 认为 `GET /meta/_drafts` 会吐出 draft 对象的完整 `fields`。实测不成立: `SysMetadataRepository.listDrafts` 是一个显式六键投影,从不读行上的 body。 两条路由(rest-server.ts / domains/meta.ts)确实是 `protocol.listDrafts()` 的 裸透传 —— 它们今天安全,完全依赖这一个投影。本用例把该依赖钉死:任何人把 item 体加宽进投影,两条路由会立刻开始无掩码地服务对象 schema,而 ADR-0106 的 mask 覆盖不到这条路由。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx
…fts-authoring-gate
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
os-project-manager
marked this pull request as ready for review
August 9, 2026 09:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #6599
本 PR 不修复 #6599 所述缺陷 —— 因为该缺陷经实测不存在
#6599 认为
GET /api/v1/meta/_drafts与GET /metadata/_drafts会把 pending object draft 的完整fields映射吐给任何已认证调用方(label、type、picklist options、formula、requiredPermissions)。实测不成立。SysMetadataRepository.listDrafts(packages/metadata-protocol/src/sys-metadata-repository.ts:848)是一个显式六键投影,行上存 schema 的 body 列从头到尾没有被读过:先写预测、后跑探针(喂进一行 body 载有
salary_grade+requiredPermissions+formula的 draft 行),服务出的载荷:卡片的 repro
jq '.[].item.fields.salary_grade'恒为undefined—— 因为.item本身不存在。全仓仅三处listDrafts定义(repo 投影 / protocol 包一层{ drafts }/ SDK fetch 包装),投影只有一处,没有第二条能带 body 的路径。卡片要求的动作与卡片的前提互相矛盾
#6599 要求「把这条出口加进共享 ADR-0106 用例表」。那张表装不下这条出口:
assertObjectSchemaMaskCase(packages/metadata-core/src/object-schema-fls-contract.ts)在非 fault 分支要求 served body 带fields记录,否则直接抛_drafts一个fields都没有。也就是说,如果当初有人照卡片说的去加那一行,门禁会立刻告诉他前提不对。表自己的形状在拒绝这条路由,而没人注意到这一点。那么本 PR 交付什么
把「header-only 投影」这唯一屏障钉死。
两条路由确实是
protocol.listDrafts()的裸透传 —— 没有applyObjectSchemaMask,没有能力门禁,requireAuth之外什么都没有:packages/rest/src/rest-server.ts——res.json(result)packages/runtime/src/domains/meta.ts——deps.success(data)它们今天安全,完全依赖这一个投影。所以任何人把 item 体加宽进
listDrafts(Studio 想要 diff 视图的字段级细节是最明显的拉力),两条路由会在那一瞬间开始向任何已认证调用方无掩码地服务对象 schema、requiredPermissions、picklist 选项值和formula商业 IP,而 ADR-0106 的 mask 覆盖不到这条路由。本用例在那一瞬间变红。为什么这个屏障此前没被钉住
sys-metadata-repository-list-drafts.test.ts原有 6 条用例的形状断言用的是toMatchObject—— 非穷尽,对多出来的键免疫。它们断言的是名字、org 作用域和 WHERE 子句,没有一条能看见「载荷里多了一个 item 体」。这正是新用例必须用键集相等 + 全载荷字符串扫描(而非键白名单)来写的原因。反向验证
预测(先写后跑):把
listDrafts投影加宽为额外吐出item: row.body(即本用例要防的那次「好心加宽」)后 —— 新用例变红,旧 6 条保持绿,因为toMatchObject对多出来的键免疫。若旧 6 条也红,则我对toMatchObject的判断是错的,照实报。实测方向与预测一致:
还原后
Tests 7 passed (7)。消费方普查(普查是先做的,它决定了 (a)/(b) 分叉怎么答)
在证伪之前先把「谁在调这两条路由」扫实了。结论:除 SDK 外,全部调用方都在创作面(Studio / Setup / AI 建站流),没有一个是面向终端用户的读面。
objectstack
packages/client/src/index.ts:826—— SDKmeta.listDrafts(),纯 fetch 包装。packages/cli/src/):零命中。examples/:零命中。cloud仓可查(本容器未挂载)。objectui —— 三簇
StudioDesignSurface.tsx(4 处 client 调用 + 1 处裸 fetch)、PackageOwdOverviewPanel.tsx、ObjectHooksPanel.tsx、packageSurfaces.ts。PackagesPage.tsx(3 处裸 fetch)、inspectors/ObjectFieldInspector.tsx。preview/DraftPreviewBar.tsx(挂在ConsoleLayout,仅?preview=draft时拉取)、preview/draftStatus.ts(AiChatPage的 pending 计数)、preview/usePublishAllDrafts.ts、console/home/HomePage.tsx的PendingDraftsBanner。第 3 簇里唯一没有作者门禁的是
HomePage的PendingDraftsBanner:它在HomePage上无条件渲染(两处渲染点都在isAdmin ? … : …分叉之外),所以非管理员的 Home 也会打这条路由。但它是一个创作期装置 —— 文档字符串写的是 AI 建站后的 publish 闭环,唯一动作是 Publish,失败处理是.catch(() => { /* 不显示 */ })。这条普查现在服务的是「这条路由今天有谁在用」,而不是原本的 (a)/(b) 分叉。
残余(不在本 PR 处理)
去掉字段级泄露后,剩下的是存在性级别披露:已认证调用方仍能看到「
app.hr包里有一个叫 X 的 pending object draft,ai在 t1 改过」。赌注比卡片描述的小一个量级,是否值得上门禁属产品判断,已交回 #6599 由维护者裁定,本 PR 不做。测试
(原 6 条 + 新增 1 条。)
changeset
无。纯测试改动,不改任何运行时行为、不动任何线上载荷,发布不出东西 ⇒ 走
skip-changeset标签。Generated by Claude Code