框架 pin a5eccf92(cloud .objectstack-sha;本卡行号以 ~/Documents/GitHub/objectstack 工作副本为准)。下游实例:cloud#1969,其 2026-09-11 产品裁决选定了「平台对象存储 + 每环境 key 前缀」,而该前缀今天在框架侧没有落点。
缺口
S3StorageAdapterOptions(packages/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',fileId 是 randomUUID()。key 里没有环境维度。
为什么这在多租户宿主上是隔离问题
准确说法,不夸大:不是「会撞 key」——randomUUID() 让意外碰撞不具现实风险。问题是没有第二道防线。一个 bucket + 无前缀 = 所有租户共享一个 key 命名空间,跨租户访问被挡住的唯一原因是每环境 sys_file 元数据检查。files/:fileId 与 _local/raw/:token 这类从请求里取标识符的路径上,一次漏掉的元数据检查就是一次跨租户读,而对象存储层本身不会拒绝——它看到的是一个合法 key。
前缀把隔离从「每处检查都别写错」变成「注入一次,调用方无法越出」。cloud#1969 的裁决正是按这条选的 B 而不是 A(A 的隔离靠一个路径字符串,写错时静默泄露而不是响亮拒绝)。
建议形状(未定,请按框架口径裁)
给 S3StorageAdapterOptions 加 keyPrefix?: string,适配器在所有出入口统一施加:写入时前置,list() 的 Prefix 参数上前置,读出的 key 去除,使前缀对调用方完全不可见。判据是「调用方拿不到未加前缀的写入口」,而不是「调用方记得加前缀」。
LocalStorageAdapterOptions 是否同步加,请一并裁——本地适配器的 rootDir 已经承担了同类作用,可能不需要第二套。
与相邻卡的关系
- objectstack#17354(宿主 dispatcher 上的
/api/v1/storage/* 门)自述「⛔ 不需要真 S3/R2,本卡与对象存储后端无关」,并预测 cloud#1969 在它落地后收窄为「pin 一次框架 + R2 运维」。那个预测少算了本卡:没有前缀,cloud 侧无法实现已裁的隔离形状。两张卡不冲突,是同一条链的两段。
- cloud 侧还另有一处缺口,属 cloud 自己:
capability-loader.ts 的 storage 条目没有 configKey(settings 有 settingsOptions),所以内核工厂今天根本无法把适配器选项注入租户内核。那条在 cloud#1969 里处理,不劳本仓。
验收(建议)
同一个 bucket、两个不同 keyPrefix 的适配器实例:A 写入的对象,B 用同一个 key 读不到、删不到、list() 列不出;A 自己读写正常;调用方全程只见未加前缀的 key。
立卡人:cloud epic #2131 的 PM 座位(ad2f7ae1-dac1-4c5f-a4b1-69e1c5b1ad8a),因裁决执行需要而立。派发归本仓车道,我不认领、不派。
Generated by Claude Code
框架 pin
a5eccf92(cloud.objectstack-sha;本卡行号以~/Documents/GitHub/objectstack工作副本为准)。下游实例:cloud#1969,其 2026-09-11 产品裁决选定了「平台对象存储 + 每环境 key 前缀」,而该前缀今天在框架侧没有落点。缺口
S3StorageAdapterOptions(packages/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):scope默认'user',fileId是randomUUID()。key 里没有环境维度。为什么这在多租户宿主上是隔离问题
准确说法,不夸大:不是「会撞 key」——
randomUUID()让意外碰撞不具现实风险。问题是没有第二道防线。一个 bucket + 无前缀 = 所有租户共享一个 key 命名空间,跨租户访问被挡住的唯一原因是每环境sys_file元数据检查。files/:fileId与_local/raw/:token这类从请求里取标识符的路径上,一次漏掉的元数据检查就是一次跨租户读,而对象存储层本身不会拒绝——它看到的是一个合法 key。前缀把隔离从「每处检查都别写错」变成「注入一次,调用方无法越出」。cloud#1969 的裁决正是按这条选的 B 而不是 A(A 的隔离靠一个路径字符串,写错时静默泄露而不是响亮拒绝)。
建议形状(未定,请按框架口径裁)
给
S3StorageAdapterOptions加keyPrefix?: string,适配器在所有出入口统一施加:写入时前置,list()的Prefix参数上前置,读出的 key 去除,使前缀对调用方完全不可见。判据是「调用方拿不到未加前缀的写入口」,而不是「调用方记得加前缀」。LocalStorageAdapterOptions是否同步加,请一并裁——本地适配器的rootDir已经承担了同类作用,可能不需要第二套。与相邻卡的关系
/api/v1/storage/*门)自述「⛔ 不需要真 S3/R2,本卡与对象存储后端无关」,并预测 cloud#1969 在它落地后收窄为「pin 一次框架 + R2 运维」。那个预测少算了本卡:没有前缀,cloud 侧无法实现已裁的隔离形状。两张卡不冲突,是同一条链的两段。capability-loader.ts的storage条目没有configKey(settings有settingsOptions),所以内核工厂今天根本无法把适配器选项注入租户内核。那条在 cloud#1969 里处理,不劳本仓。验收(建议)
同一个 bucket、两个不同
keyPrefix的适配器实例:A 写入的对象,B 用同一个 key 读不到、删不到、list()列不出;A 自己读写正常;调用方全程只见未加前缀的 key。立卡人:cloud epic #2131 的 PM 座位(
ad2f7ae1-dac1-4c5f-a4b1-69e1c5b1ad8a),因裁决执行需要而立。派发归本仓车道,我不认领、不派。Generated by Claude Code