越范围发现,记录于 #5189 (E7b,PR #5203 )实现期,不在该 PR 内修 (#5189 的文件面声明为 packages/metadata + packages/spec/src/api/index.ts)。
事实(两条,同一处出水口)
#5189 的正文推断:Studio 写接口对 api 条目「按 ApiEndpointSchema 校验、但不过 E7 的门」。读代码后实情更弱一层 :
1. api 根本不在类型注册表里,所以直写路径一道校验都不跑。
packages/spec/src/kernel/metadata-plugin.zod.ts 的 DEFAULT_METADATA_TYPE_REGISTRY 里没有 { type: 'api', … } 条目;
packages/spec/src/kernel/metadata-type-schemas.ts 的 BUILTIN_METADATA_TYPE_SCHEMAS 里没有 api: ApiEndpointSchema;
于是 packages/metadata-protocol/src/protocol.ts 的 saveMetaItem → resolveOverlaySchema('api', …) → getMetadataTypeSchema('api') 返回 undefined,落进它自己文档写明的那条分支:「未注册 schema 的类型不经校验 直接落库」。
也就是说 PUT /meta/api/{name} 可以把任意 JSON 存成一条 api 条目。同时,因为类型不在注册表里,getMetaTypes() 也枚举不到它 —— Studio 的 metadata-admin 既不显示这个类型,也不为它渲染表单。这是同一个缺失的两面。
2. publishPackageDrafts(Studio「全部发布」的真实入径)没有端点门。
protocol.publishPackageDrafts(ADR-0033 / ADR-0067 D2)在提升 draft 前只跑了对象命名空间前缀检查(validateObjectNamespacePrefix,仅 d.type === 'object')。api 类型的 draft 直接提升为 active,不经 E7 的任何一道门 —— 与 #5189 在 MetadataManager.publishPackage 上修掉的是同一形状,另一条路 。
影响
安全后果已被 #5189 的第二层兜住。 PR #5203 在匹配器装载期(buildEndpointIndex)对每条存量条目重跑免身份门(不支持子集 / 映射 / policy 含 D6),未过门者被排除出索引并 error 点名。所以经本路径写入的「匿名 + 零配额」端点不会被服务 ,它答 404 + 一条响亮日志。
因此本单剩下的是两个非安全 缺口,但都是本程序在消灭的形状:
建议(不预判)
一处修,两步:
给 api 补上 DEFAULT_METADATA_TYPE_REGISTRY 条目与 BUILTIN_METADATA_TYPE_SCHEMAS['api'] = ApiEndpointSchema,让 saveMetaItem 的既有 422 路径自动生效;
在 publishPackageDrafts 里对 api 类型 draft 调用 validateApiEndpointDeclarations —— PR fix(metadata,spec): publish 门兜底 —— publishPackage 接门 + 匹配器装载期拒绝(#5040 E7b) #5203 已把它从 @objectstack/spec/api 导出,命名空间取该 package 已经读到的 pkgNamespace(那段代码就在同一函数里,this.engine?.registry?.getPackage?.(…)?.manifest?.namespace),所以这条路有 身份,可以跑全量门,不像 publishPackage 需要调用方传入。
判据仍然只有一份(E7 的门函数),不产生第二套。
注意第 1 步不是纯加性:给一个此前不校验的类型接上 schema,会让既有的越界存量行在下次写入时被 422 拒绝。是否需要一次性的存量体检 / 宽限,留给裁决。
前置
PR #5203 (#5189 )落 main —— 门函数的公共导出与装载期兜底都在其中。
越范围发现,记录于 #5189(E7b,PR #5203)实现期,不在该 PR 内修(#5189 的文件面声明为
packages/metadata+packages/spec/src/api/index.ts)。事实(两条,同一处出水口)
#5189 的正文推断:Studio 写接口对
api条目「按ApiEndpointSchema校验、但不过 E7 的门」。读代码后实情更弱一层:1.
api根本不在类型注册表里,所以直写路径一道校验都不跑。packages/spec/src/kernel/metadata-plugin.zod.ts的DEFAULT_METADATA_TYPE_REGISTRY里没有{ type: 'api', … }条目;packages/spec/src/kernel/metadata-type-schemas.ts的BUILTIN_METADATA_TYPE_SCHEMAS里没有api: ApiEndpointSchema;packages/metadata-protocol/src/protocol.ts的saveMetaItem→resolveOverlaySchema('api', …)→getMetadataTypeSchema('api')返回undefined,落进它自己文档写明的那条分支:「未注册 schema 的类型不经校验直接落库」。也就是说
PUT /meta/api/{name}可以把任意 JSON 存成一条api条目。同时,因为类型不在注册表里,getMetaTypes()也枚举不到它 —— Studio 的 metadata-admin 既不显示这个类型,也不为它渲染表单。这是同一个缺失的两面。2.
publishPackageDrafts(Studio「全部发布」的真实入径)没有端点门。protocol.publishPackageDrafts(ADR-0033 / ADR-0067 D2)在提升 draft 前只跑了对象命名空间前缀检查(validateObjectNamespacePrefix,仅d.type === 'object')。api类型的 draft 直接提升为 active,不经 E7 的任何一道门 —— 与 #5189 在MetadataManager.publishPackage上修掉的是同一形状,另一条路。影响
安全后果已被 #5189 的第二层兜住。 PR #5203 在匹配器装载期(
buildEndpointIndex)对每条存量条目重跑免身份门(不支持子集 / 映射 / policy 含 D6),未过门者被排除出索引并error点名。所以经本路径写入的「匿名 + 零配额」端点不会被服务,它答 404 + 一条响亮日志。因此本单剩下的是两个非安全缺口,但都是本程序在消灭的形状:
api条目由 artifact ingest 正常产出并被匹配器索引(E2(#5040 执行器):端点匹配器 —— 惰性索引 + 元数据事件失效,精确路径匹配,params恒空 #5089),它是活的类型;却既不在注册表、也没有 schema、也无表单。declared ≠ enforced的镜像:enforced 但 undeclared。建议(不预判)
一处修,两步:
api补上DEFAULT_METADATA_TYPE_REGISTRY条目与BUILTIN_METADATA_TYPE_SCHEMAS['api'] = ApiEndpointSchema,让saveMetaItem的既有 422 路径自动生效;publishPackageDrafts里对api类型 draft 调用validateApiEndpointDeclarations—— PR fix(metadata,spec): publish 门兜底 —— publishPackage 接门 + 匹配器装载期拒绝(#5040 E7b) #5203 已把它从@objectstack/spec/api导出,命名空间取该 package 已经读到的pkgNamespace(那段代码就在同一函数里,this.engine?.registry?.getPackage?.(…)?.manifest?.namespace),所以这条路有身份,可以跑全量门,不像publishPackage需要调用方传入。判据仍然只有一份(E7 的门函数),不产生第二套。
注意第 1 步不是纯加性:给一个此前不校验的类型接上 schema,会让既有的越界存量行在下次写入时被 422 拒绝。是否需要一次性的存量体检 / 宽限,留给裁决。
前置
PR #5203(#5189)落 main —— 门函数的公共导出与装载期兜底都在其中。