Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/241 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
4.7 KiB
Issue #226:跨 origin 重定向标记泄漏复审修复
更新时间:2026-09-02
关联 Issue:#226 添加客户端特殊标识;交接 Issue:#225 添加客户端埋点统计
1. 修复结论
采纳评审意见:AGC 主站 Client 的默认重定向策略不能继续使用 reqwest 的“任意 origin 默认跟随”,否则 default_headers 中的 X-Genarrative-Client: agc 可能被复制到 OSS 或第三方 origin。
本次修复将工厂默认策略调整为:
- 目标 URL 与原始请求 URL 的完整 origin(scheme + host + effective port)相同:委托 reqwest 默认 redirect policy。
- origin 不同:
stop,返回当前 3xx,不发起下一跳请求。 - 无法取得原始 URL 或 origin 无法确认:按 fail-closed 处理,停止重定向。
这不是把 factory 改成全局 Policy::none()。同 origin 的 redirect 仍保留原有默认限制、方法和请求体处理;调用点显式设置的 Policy::none() 继续有效。
另针对评审发现的同名 Header 覆盖缺口,补充了主站请求终结器:default_headers 继续负责普通请求的默认注入;所有已审计的 AGC 主站请求在发送前统一调用 with_agc_main_site_marker,使用 RequestBuilder::headers 替换调用方可能传入的同名 Header,确保最终值固定为 agc。
2. 修改范围
修改 AGC Rust HTTP Client factory、主站请求终结器、已审计主站直发调用点和定向测试:
apps/ai-game-creator-shell/src-tauri/src/http_client.rs- 已审计主站请求所在的
assets.rs、commands.rs、canvas_generation.rs、direct_runtime.rs、direct_tool_bridge.rs、project/asset_canvas/generation.rs、project/resource_editor.rs - 本地实施方案、阶段计划和本复审记录
不修改:
- #225 的主站 tracking、数据库和后台代码。
- OSS/签名下载、Provider、受控搜索、loopback、更新下载等第三方 Client。
- 各业务调用点的 timeout、connect timeout、认证、幂等或请求体逻辑。
3. 重定向行为契约
| 场景 | 处理 | 标记是否到达下一跳 |
|---|---|---|
| 同 scheme、host、effective port | 继续按 reqwest 默认策略 | 是,仍是主站 origin |
| scheme、host 或端口任一不同 | 返回当前 3xx,不 follow | 否,不发起请求 |
调用点显式 Policy::none() |
继续不 follow | 否 |
使用 Url::origin() 比较,不比较 URL 字符串前缀;路径、查询参数和 fragment 不参与 origin 判断。
4. 同名 Header 覆盖契约
reqwest 的 ClientBuilder::default_headers 只会为请求补充缺失字段,请求级同名 Header 默认优先。因此不能把 default_headers 单独当作“不可伪造”的约束。
当前实现由两层组成:
- factory 设置
default_headers,覆盖普通未显式设置 Header 的请求; with_agc_main_site_marker(request)在主站请求发送前用RequestBuilder::headers替换同名字段。external_editor_json_request已内置该终结器,直接.send()的主站路径也显式经过该终结器。
这样既保留方案一的 Client factory 形态,又满足“调用方传入同名 Header 时最终仍为 X-Genarrative-Client: agc”的冻结契约。
5. 测试证据
新增或调整 http_client 定向测试,覆盖:
- 完整 origin 比较:默认端口等价,scheme/host/非默认端口变化视为跨 origin。
- 同 origin 302:下一跳实际收到
X-Genarrative-Client: agc,最终响应成功。 - 跨 origin 302:当前响应为 302,外部 listener 没有收到连接。
- 显式
Policy::none():仍然覆盖 factory 默认策略。 - 请求级伪造同名 Header:终结器覆盖调用方值,服务端只收到
agc。
验证命令及结果:
cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml http_client -- --nocapture
8 tests passed
6. 对 #225 的交接
本修复不改变 Header 契约:
X-Genarrative-Client: agc
主站只会收到客户端实际发出的主站请求;跨 origin 3xx 不会产生第二个第三方请求,因此不会出现客户端把该标记发送到 OSS/第三方 origin 的情况。#225 不需要回改 tracking、数据库或后台设计。
7. 后续限制
agc_main_site_client_builder() 仍返回原始 reqwest::ClientBuilder,理论上调用方可以再次覆盖 redirect policy,或新增请求时绕过终结器。当前已审计生产调用点均已经过终结器,只有显式 Policy::none() 的 redirect 覆盖,没有重新启用任意 origin follow 的调用。若未来需要类型级不可绕过,再单独评估 AgcMainSiteClient wrapper,不在本次复审修复中扩大范围。