Skip to content

feat(service-storage): S3 适配器没有 key 前缀选项 —— 多租户共享 bucket 时对象存储层没有第二道隔离防线(cloud#1969 已裁形状的落点) #17571

Description

@hotlong

框架 pin a5eccf92(cloud .objectstack-sha;本卡行号以 ~/Documents/GitHub/objectstack 工作副本为准)。下游实例:cloud#1969,其 2026-09-11 产品裁决选定了「平台对象存储 + 每环境 key 前缀」,而该前缀今天在框架侧没有落点。

缺口

S3StorageAdapterOptionspackages/services/service-storage/src/s3-storage-adapter.ts:36-51)有 bucket / region / endpoint / 凭据 / forcePathStyle / metrics没有任何 key 前缀选项。适配器把调用方给的 key 原样写下去:Key: key:169:183:193:203:344:370)。

key 由 buildKey 生成(storage-routes.ts:962):

function buildKey(scope: string, fileId: string, filename: string): string {
  const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';
  return `${scope}/${fileId}${ext}`;
}

scope 默认 'user'fileIdrandomUUID()key 里没有环境维度。

为什么这在多租户宿主上是隔离问题

准确说法,不夸大:不是「会撞 key」——randomUUID() 让意外碰撞不具现实风险。问题是没有第二道防线。一个 bucket + 无前缀 = 所有租户共享一个 key 命名空间,跨租户访问被挡住的唯一原因是每环境 sys_file 元数据检查。files/:fileId_local/raw/:token 这类从请求里取标识符的路径上,一次漏掉的元数据检查就是一次跨租户读,而对象存储层本身不会拒绝——它看到的是一个合法 key。

前缀把隔离从「每处检查都别写错」变成「注入一次,调用方无法越出」。cloud#1969 的裁决正是按这条选的 B 而不是 A(A 的隔离靠一个路径字符串,写错时静默泄露而不是响亮拒绝)。

建议形状(未定,请按框架口径裁)

S3StorageAdapterOptionskeyPrefix?: string,适配器在所有出入口统一施加:写入时前置,list()Prefix 参数上前置,读出的 key 去除,使前缀对调用方完全不可见。判据是「调用方拿不到未加前缀的写入口」,而不是「调用方记得加前缀」。

LocalStorageAdapterOptions 是否同步加,请一并裁——本地适配器的 rootDir 已经承担了同类作用,可能不需要第二套。

与相邻卡的关系

  • objectstack#17354(宿主 dispatcher 上的 /api/v1/storage/* 门)自述「⛔ 不需要真 S3/R2,本卡与对象存储后端无关」,并预测 cloud#1969 在它落地后收窄为「pin 一次框架 + R2 运维」。那个预测少算了本卡:没有前缀,cloud 侧无法实现已裁的隔离形状。两张卡不冲突,是同一条链的两段。
  • cloud 侧还另有一处缺口,属 cloud 自己:capability-loader.tsstorage 条目没有 configKeysettingssettingsOptions),所以内核工厂今天根本无法把适配器选项注入租户内核。那条在 cloud#1969 里处理,不劳本仓。

验收(建议)

同一个 bucket、两个不同 keyPrefix 的适配器实例:A 写入的对象,B 用同一个 key 读不到、删不到、list() 列不出;A 自己读写正常;调用方全程只见未加前缀的 key。

立卡人:cloud epic #2131 的 PM 座位(ad2f7ae1-dac1-4c5f-a4b1-69e1c5b1ad8a),因裁决执行需要而立。派发归本仓车道,我不认领、不派。

Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions