Skip to content

同族第三处组装:share-link 路由把授权信封裁成 4 个字段后直接当 enforcement context 喂给 engine.find —— group 租户姿态下 Layer 0 墙恒判否 #6206

Description

@qq9340100

来源:#6071(REST 面 principalKind)实施时枚举「授权信封 → ExecutionContext」组装家族的越界发现,未在该 PR 修复(#6071 分诊把范围钉死在 rest-server 那一处)。未评级 —— 交分诊,下面只给实读到的事实与方向。

事实(已核 origin/main 80f7dc6a3)

resolveAuthzContext 之后的组装,除 #6071 点名的两处(rest-server.ts / resolve-execution-context.ts)之外,还有第三处:

packages/plugins/plugin-sharing/src/sharing-plugin.ts:633-639 —— share-link 路由的 contextFromRequest:

const authz = await resolveAuthzContext({ ql, headers, getSession });
return {
userId: authz.userId,
tenantId: authz.tenantId,
positions: authz.positions,
permissions: authz.permissions,
};

信封里被丢掉的:accessible_org_idsorg_user_idssystemPermissionsposturetabPermissions

它不是只用来做路由自己的 401 判断 —— 这个对象被原样当作 enforcement context 喂进数据引擎:
packages/plugins/plugin-sharing/src/share-link-service.ts:288-293

const exists = await this.engine.find(input.object, {
where: { id: input.recordId }, fields: ['id'], limit: 1,
context: context.isSystem ? SYSTEM_CTX : context,
} as any);

即 [Finding-2] 那条「只能为你自己看得见的记录建链接」的可见性校验,跑在这个被裁过的上下文上。

方向(实读消费方,未跑复现)

packages/plugins/plugin-security/src/security-plugin.ts:3169-3175context?.accessible_org_ids 交给 Layer 0 租户墙,而 packages/plugins/plugin-security/src/tenant-layer.ts:49-55 对该入参写得很明确:

Read ONLY under the group posture, where it is the wall. An empty/absent set there denies (fail closed)

所以方向是判否,不是放行:

  • single 姿态(默认):accessible_org_ids 不被读,今天无差别;
  • group 姿态:集合恒缺席 ⇒ Layer 0 恒判否 ⇒ engine.find 查不到那一行 ⇒ 路由抛 403 FORBIDDEN: Not permitted to share <object>/<id> —— 即便调用者在正常 REST 面上看得见这条记录。建链接这条路在 group 姿态下恒不可用

posture 缺席是同一处的次要一条(ADR-0095 D2 把它定为「解析一次、随上下文下发,enforcement 侧不得重推」),这里没有下发;我没有单独测量它的差量,一并交分诊。

值得注意的一点:契约本身是「窄」的

ShareLinkExecutionContext(packages/spec/src/contracts/share-link-service.ts:100-112)声明的就是这 5 个字段(userId/tenantId/isSystem/positions/permissions)。所以组装侧照契约填,并不算违约 —— 缝在于:一个声明为「窄」的上下文契约,被当作完整 ExecutionContext 送进了 engine.find 的 enforcement 路径。两种收法方向不同,请分诊裁决而不是按本文语气推断:

  • A:承认这条路需要完整主体,ShareLinkExecutionContext 收敛/替换为 ExecutionContext,组装侧下发全字段;
  • B:保留窄契约,但可见性校验不要用它 —— 由调用方传入完整上下文,或该校验改走一个显式的「可见性服务」入口。

按 PD #12(contract-first),我倾向 A 的方向感更正:声明窄 ≠ 消费窄,今天是消费侧默默拿窄契约当宽的用;但这要定公开面形状,不该由实施座位顺手决定。

查重

三仓 open issue 无影子单。关联而非重复:#6071(本族,REST 面 principalKind,已修)、#5852(同在 plugin-sharing,但缝在 sharing serviceorganizationId 而传输写 tenantId,与本单的「路由组装裁字段」不同点)、ADR-0105 D2、ADR-0095 D2。

Refs:#6071#5852#5859、ADR-0105 D2、ADR-0095 D2


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions