Skip to content

finding: POST /packages/:id/publish-drafts 整包 draft 批量转正,零能力的已认证调用方即可调用(实测 200 vs 同族 _migrate-stored 403) #7023

Description

@os-project-manager

#6599 的消费方普查里撞到的(那张卡问的是「谁在调 /meta/_drafts」;顺着 usePublishAllDrafts 的 Publish 动作走到了这条写面)。未修,按 PD #10 立卡。

实测:一个零能力的已认证调用方能做什么

不是「没看到能力门禁」这种静态观察 —— 直接驱动 dispatcher 量的。调用方上下文 { userId: 'u_portal', systemPermissions: [] },一个会话、一条能力都没有:

### zero-capability caller context ### {"userId":"u_portal","systemPermissions":[]}
### HTTP status ### 200
### publishPackageDrafts invoked? ### 1
### drafts promoted to ACTIVE ### ["app.hr:account","app.hr:salary_review"]
### body ### {"success":true,"data":{"success":true,"publishedCount":2,"failedCount":0,
              "published":[{"type":"object","name":"account"},
                           {"type":"object","name":"salary_review"}],"failed":[]}}

同一个调用方,打隔壁那条同族路由 POST /metadata/_migrate-stored

### _migrate-stored status for the SAME caller ### 403
### migrateStoredMetadata invoked? ### 0

同一个调用方、同一次运行、同一类操作 —— 一条 200 并真的把整包 draft 转正,一条 403 且被调函数一次都没进。

代码面

POST /packages/:id/publish-draftspackages/runtime/src/domains/packages.ts:160

整个 packages.ts(739 行)里 systemPermissions / isSystem / manage_metadata / FORBIDDEN / 403 的命中数是 0。挂载侧 mountPackagesRoutepackages/runtime/src/dispatcher-plugin.ts:984)也只是 dispatcher.dispatch(...) 的转发,没有包任何门禁。所以这条路由之上除全局 requireAuth 外没有任何判据。

对照物就在同一个 dispatcher 里:_migrate-storeddomains/meta.ts)显式判 ec?.isSystem || systemPermissions.has('manage_metadata'),并且在解析 protocol 之前判,理由写在注释里(不让调用方拿 501/200 的差别去探测)。

这条路由做的事

publishPackageDrafts 把该 package 名下每一条 pending DRAFT 行提升为 activeseed 类型的 draft 被发布即意味着装载数据行(路由里 applyPublishedSeeds 那段);随后还会清掉 ADR-0045 的发布门 —— #4829 / PR #6942 刚把它挪到机器管理键 app._unpublished,由 publish-drafts 置 false。也就是说这一次调用同时是「schema 生效」「数据落库」「半成品应用对真实用户可见」。ADR-0045 自己写过这个门的失败方向判断:门失败开放会把半成品应用静默暴露给真实用户,比过度限制严格更糟(docs/adr/0045-...md:192)。

为什么它值得单独一张卡

它和 #6603 是同一形状 —— 而 #6603 刚被维护者裁为需要 manage_metadata,判词是「能写 schema 的人就该是能看见完整 schema 的人」(评论 5225531464)。#6603 管的是单条 PUT /meta/:type/:name;这条是整包批量转正,按同一条判据赌注更大,却是两条里没有门禁的那条。

同族参照:#6920(匿名 mass assignment)、#6599_drafts 读面,其所述字段级泄露已实测证伪,见 PR #7014)。

不在本卡里替维护者选修法

至少有「照 #6603manage_metadata」和「按 package 所有权/作者身份判」两种读法,成本与语义不同;而且 /packages 域下同样裸奔的还有 discard-drafts / revert / rollback / enable / disable,要不要一起收口是域级决定,不是这条路由的局部决定。留给分诊与维护者。

复现

驱动 HttpDispatcher.handlePackages('/app.hr/publish-drafts', 'POST', {}, {}, { request: {}, executionContext: { userId: 'u_portal', systemPermissions: [] } }),protocol 侧放一个 publishPackageDrafts 双档记录被提升的条目;同一上下文再驱动 handleMetadata('_migrate-stored', ctx, 'POST', {}, {}) 作对照。上面两段输出即为该探针的原始 stdout。

立卡时的查重说明

开卡前的 issue 关键词查重被账号级 REST 速率限制挡住(本班次共享身份,多次退避重试仍 API rate limit already exceeded)。改用本地信号做的查重:git log -i --grep=publish-drafts 与全仓 publish-drafts + 权限关键词交叉搜索,命中的都是发布语义(ADR-0045 可见性、seed 装载、org 作用域、端点发布门),没有一条关于这条路由的授权。若分诊发现已有同族卡,按重复合并即可。

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions