扣费路径的校验问题 #147
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
看了代码确认过关键事实,说明如下。不改代码。
机制
计费入口
execute_billable_asset_operation_with_cost的形状是「先扣、再跑、失败退」:而
should_refund_asset_operation_error无条件返回true——任何失败都退款。问题在于闭包里的执行顺序。以 SFX 为例(generation.rs:328 起):
assetFolderId从 HTTP 入站到这一步没有任何校验,连格式校验都没有。normalize_generated_asset_folder_id只是把"project"和旧版"folder-*"重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的require_owned_asset_folder:这个
Err一路?冒泡回计费闭包,触发退款。用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。攻击面
登录用户拿自己的合法
projectId,配一个随便编的assetFolderId(不存在,或属于别人),反复提交。每次:不需要任何越权——
projectId是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。需要补一句我核查的边界:我在
app.rs没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有backpressure.rs这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。另外还有一个我没有验证的次生问题:
confirm_asset_object和bind_asset_object_to_entity在create_editor_asset之前就已经成功了,失败后大概率留下没有editor_asset行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。波及范围
所有走
execute_billable_asset_operation_with_cost且把asset_folder_id一路透传到create_editor_asset的路径,形状完全一致:所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。
唯一的例外,以及它为什么不算先例
完美像素路径确实做了前置只读预检——
preflight_editor_pixel_art_result是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。但它不扣泥点、不调付费 provider——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。
不改的代价
真要修的时候
两条路,都不用改 schema:
get_editor_asset_library(owner_user_id)拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。preflight_editor_pixel_art_result加一个只读预检 procedure,在execute_billable_asset_operation_with_cost之前调用。跟已有模板一致,也能顺带覆盖projectId。两种都必须和
normalize_generated_asset_folder_id的默认文件夹语义严格对齐("project"/"folder-*"/ 默认 folder 三种特殊值),否则会把合法请求误拒——validate_editor_pixel_art_preflight_asset_folder里已经写过这段对齐逻辑,可以直接抄。要我把这份说明写进
</html> </html>看了代码确认过关键事实,说明如下。**不改代码。**docs/project-memory/shared-memory/pitfalls.md或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。机制
计费入口
[execute_billable_asset_operation_with_cost](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:96)的形状是「先扣、再跑、失败退」:而
[should_refund_asset_operation_error](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:274)无条件返回true——任何失败都退款。问题在于闭包里的执行顺序。以 SFX 为例([generation.rs:328](server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328) 起):
assetFolderId从 HTTP 入站到这一步没有任何校验,连格式校验都没有。normalize_generated_asset_folder_id只是把"project"和旧版"folder-*"重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的[require_owned_asset_folder](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:5345):这个
Err一路?冒泡回计费闭包,触发退款。用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。攻击面
登录用户拿自己的合法
projectId,配一个随便编的assetFolderId(不存在,或属于别人),反复提交。每次:不需要任何越权——
projectId是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。需要补一句我核查的边界:我在
app.rs没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有backpressure.rs这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。另外还有一个我没有验证的次生问题:
confirm_asset_object和bind_asset_object_to_entity在create_editor_asset之前就已经成功了,失败后大概率留下没有editor_asset行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。波及范围
所有走
execute_billable_asset_operation_with_cost且把asset_folder_id一路透传到create_editor_asset的路径,形状完全一致:所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。
唯一的例外,以及它为什么不算先例
完美像素路径确实做了前置只读预检——
[preflight_editor_pixel_art_result](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:2795)是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。但它不扣泥点、不调付费 provider——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。
不改的代价
真要修的时候
两条路,都不用改 schema:
get_editor_asset_library(owner_user_id)拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。preflight_editor_pixel_art_result加一个只读预检 procedure,在execute_billable_asset_operation_with_cost之前调用。跟已有模板一致,也能顺带覆盖projectId。两种都必须和
normalize_generated_asset_folder_id的默认文件夹语义严格对齐("project"/"folder-*"/ 默认 folder 三种特殊值),否则会把合法请求误拒——validate_editor_pixel_art_preflight_asset_folder里已经写过这段对齐逻辑,可以直接抄。要我把这份说明写进
docs/project-memory/shared-memory/pitfalls.md或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。@kdletters