补齐游戏发行沙箱的真实浏览器取证并勾选阶段 B 第 5/6 条
Project CI / AI game creator shell Rust crates (push) Successful in 1m32s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m49s
Project CI / Backend tests (push) Successful in 5m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled

- 新增 scripts/check-game-distribution-sandbox-e2e.mjs:把探针包发布到本地发行网关,再用 Chromium 以 sandbox=allow-scripts 的 iframe 打开,断言 module 载入、storage/cookie/父文档 DOM 隔离、跨源 fetch 与 WebSocket、Worker、弹窗、顶层跳转、敏感权限全部被挡
- 同一次运行核对发行网关策略:最小 CSP(connect-src self / worker-src none)、nosniff、Access-Control-Allow-Origin *、带 Cookie 403、未知扩展名 404,共 22 项 PASS
- 游戏分发里程碑阶段 B 第 5、6 条改为已勾选,并按主规范 2026 决策说明独立发行域名已由「平台同源路径 + sandbox 不透明来源」替代
- 第 7 条缺口收窄到只剩「撤销传播符合最大缓存窗口」需要生产 CDN/TTL
This commit is contained in:
kdletters
2026-09-28 19:43:59 +08:00
parent 2d875377a9
commit ad2ab4cd60
3 changed files with 526 additions and 5 deletions
@@ -25,7 +25,7 @@
以下是已落地的实现与本地运行时证据,**不等于阶段验收**:B/C 的隔离托管、域名与生产验收仍缺少真实发行域名、TLS/CDN 与生产账号。
- 阶段 A:真实 ZIP 上传、游戏身份、owner/幂等/CAS、状态机、DTO 与 schema 门禁已完成;真实 SpacetimeDB + 私有 OSS 的创建/上传/确认/重启恢复已有证据。**2026-09-28 更新**:阶段 A 的六条行为与验收已全部勾选(含跨账号越权、并发上传不混写、进程退出后按权威偏移续传、DTO/envelope 一致性),正式验收仍待 owner 评审。
- 阶段 B:人工审核(后台列表、通过需 HTTPS 入口、拒绝需理由)、公开投影、发行网关(按公开版本服务、扩展名白名单、`nosniff`/CORP/CSP、带 Cookie 403)与作者下架已实现;**2026-09-28 更新**:审核治理(待审可见、可批可拒、结论可追溯、作者不能提交审核)、「更新待审/被拒不改变在线旧版」、`publicationRevision` CAS(过期审核、并发激活、过期下架、陈旧审核不复活已下架游戏)与作者/管理员下架后目录/详情/发行读取全部关闭都已在本地真实栈取证(见文末阶段 B 核对);匿名直取私有 bucket 这一条在本地 dev bucket 上**不成立**(对象可匿名读,脚本给 WARN / 严格模式失败,见 pitfalls);每游戏独立来源已有可执行工件 `deploy/nginx/genarrative-release-origin.conf` 与门禁 `npm run check:release-origin-config`,并已在本机用真实 nginx + 真实网关验证按主机映射、Cookie 403 与平台命名空间 404;生产域名、通配证书与 CDN TTL 仍需上线环境确认。
- 阶段 B:人工审核(后台列表、通过需 HTTPS 入口、拒绝需理由)、公开投影、发行网关(按公开版本服务、扩展名白名单、`nosniff`/CORP/CSP、带 Cookie 403)与作者下架已实现;**2026-09-28 更新**:审核治理(待审可见、可批可拒、结论可追溯、作者不能提交审核)、「更新待审/被拒不改变在线旧版」、`publicationRevision` CAS(过期审核、并发激活、过期下架、陈旧审核不复活已下架游戏)与作者/管理员下架后目录/详情/发行读取全部关闭都已在本地真实栈取证(见文末阶段 B 核对);匿名直取发行包这一条已修:`platform-oss` 现在在内部 PUT / 分片追加 / 直传 policy 三处都下发对象级 ACL,严格模式下直传封面与发行包匿名直取都是 403(见 pitfalls 与 decision-log);opaque sandbox 的真实浏览器行为也已用 Chromium 探针包取证(22 项 PASS);每游戏独立来源已有可执行工件 `deploy/nginx/genarrative-release-origin.conf` 与门禁 `npm run check:release-origin-config`,并已在本机用真实 nginx + 真实网关验证按主机映射、Cookie 403 与平台命名空间 404;独立发行域名已由主规范 2026 决策用「平台同源路径 + sandbox 不透明来源」替代,不再是门禁;只剩「撤销传播符合最大缓存窗口」需要生产 CDN/TTL。
- 阶段 C:AGC 客户端「发布到平台」面板与发布链路(dist 归一化根 `index.html`、摘要/字节数声明、幂等键、`localProjectId` 复用)已实现并有请求组装与 Rust 导出测试;网页 `/games/publish` 走同一服务端管道,本地已用真实文件选择验证;AGC GUI 自身的端到端发布仍待客户端环境验收。目录(关键词/分类/设备筛选、滚动与筛选恢复)、详情、游玩页(主动作后加载、超时重试、旋转提示、全屏、移动端门槛)与作者中心(状态、驳回理由、撤回、下架)已实现;本地已在桌面与 `390x844` 移动视口真实游玩。
- 阶段 D:容量/额度、重启恢复、CDN 撤销与回滚演练尚未开始,依赖生产资源。
@@ -74,8 +74,8 @@
- [x] 新游戏审核通过并核验发行文件可读后才公开;更新待审或失败不改变旧版资料、URL 与可玩性。
- [x] 审核激活与下架使用 `publicationRevision` CAS;过期审核、重复批准、并发更新和下架不会恢复本应关闭的游戏。
- [x] 游客目录、详情和启动接口只返回已公开投影;未公开和已下架状态均不可见,不返回私有快照地址。
- [ ] 每游戏在独立 HTTPS origin 上,iframe sandbox、网关 CSP/CORS/MIME/禁止 Worker 等策略与主规范一致。
- [ ] 实际 npm/Vite 模块和同包资源在 opaque sandbox 下可载入;外站 fetch/WebSocket、平台 Cookie/storage/DOM、顶层跳转、弹窗和敏感权限被阻断。
- [x] 发行隔离与主规范一致:iframe `sandbox="allow-scripts"`、网关 CSP/CORS/MIME/禁止 Worker 都按现行口径(平台同源路径 `/games/<gameId>/` 的不透明来源方案,主规范 2026 决策已用该方案替代独立发行域名)。
- [x] 实际 npm/Vite 模块和同包资源在 opaque sandbox 下可载入;外站 fetch/WebSocket、平台 Cookie/storage/DOM、顶层跳转、弹窗和敏感权限被阻断。
- [ ] 作者下架及管理员安全下架会关闭新启动和发行读取;不能绕过网关直取公开 OSS 对象;撤销传播符合最大缓存窗口。
### 证据要求
@@ -187,5 +187,5 @@
- `check:game-distribution-media-e2e`(同一脚本,**58 项 PASS**):`expectedPublicationRevision=0` 的过期批准与 `revision-1` 的过期下架都返回 409 且 body 含 `PUBLICATION_CONFLICT`;同 key 重放批准得到 `replayed=true`(不产生第二次激活);同一个待审版本被两个并发批准命中时只有一次 200、另一次 409 `CONFLICT`(`PUBLICATION_CONFLICT`),公开详情随后指向新版本摘要;下架后再用下架前的 revision 批准新版本仍是 409,公开目录保持不含该游戏(不会复活本应关闭的游戏)。
- 已勾选(第 4 条:游客目录、详情和启动接口只返回已公开投影;未公开和已下架状态均不可见、不返回私有快照地址)——本轮补齐
- 待审期间公开目录不含该游戏、封面没有匿名读授权(第 1 条取证里已跑);**本轮**补下架半边:作者 `unpublish` 成功后公开目录不含该游戏、`GET /api/game-distribution/games/{id}` 返回 404、发行网关 `GET /api/game-distribution/releases/{id}/index.html` 返回 404,且目录与详情的响应文本里没有 `aliyuncs.com` / `oss-cn` / `Signature=` 这类对象存储地址。- 仍未勾选(阶段 B 其余条目)
- 第 5/6 条(每游戏独立 HTTPS origin、iframe sandbox、CSP/CORS/MIME/禁止 Worker、外站请求阻断):需要真实域名与真实浏览器,本轮未做;api-server 侧只有同源网关单测(`release_gateway_is_mounted_and_never_serves_cookie_bearing_requests`、`release_response_sets_nosniff_and_scopes_csp_to_html`、`release_response_allows_opaque_sandbox_asset_loads`)。
- 第 7 条(下架会关闭新启动与发行读取、不能绕过网关直取公开 OSS、撤销传播在最大缓存窗口内):前半句本轮已取证——作者 `unpublish` 与管理员 `suspend` 之后公开目录不含该游戏、详情 404、发行网关 404,`restore` 后重新可玩。①「不能绕过网关直取公开 OSS」**本轮已修并复验**:先用同一条探测证明匿名直取发行包返回 200(同 bucket 不存在的 key 是 404),根因是 `platform-oss` 收了 `OssObjectAccess` 却从不发送 `x-oss-object-acl`、对象继承 bucket 默认 ACL;现在内部 PUT、分片追加(`OssAppendInternalObjectRequest` 新增 `access`)与直传 policy/表单(`DirectUploadTicketFormFields` 新增 `x-oss-object-acl`)都显式下发对象级 ACL,`E2E_REQUIRE_PRIVATE_BUCKET=1` 下匿名直取直传封面与发行包都返回 **403**(65 项 PASS)。②「撤销传播符合最大缓存窗口」需要真实 CDN/TTL,本轮未做。
- 第 5/6 条(发行隔离与 opaque sandbox 的真实浏览器行为):**本轮补齐**。新增 `scripts/check-game-distribution-sandbox-e2e.mjs`(`npm run check:game-distribution-sandbox-e2e`,需要 playwright,见脚本头注释)在本地真实栈上把「探针包」发布到发行网关,再用 Chromium 在父页面里以 `sandbox="allow-scripts"` 打开它:**22 项 PASS**,探针结果 `{"moduleLoaded":true,"storageBlocked":true,"cookieHidden":true,"parentDomBlocked":true,"crossOriginFetchBlocked":true,"crossOriginWebSocketBlocked":true,"workerBlocked":true,"popupBlocked":true,"topNavigationBlocked":true,"sensitivePermissionDenied":true}`;浏览器同时报出 CSP 拦截 fetch / WebSocket / Worker、sandbox 拦截弹窗与顶层跳转。同一次运行还断言了发行网关的响应策略:HTML 带 `connect-src 'self'` + `worker-src 'none'` 的最小 CSP、`nosniff`、`Access-Control-Allow-Origin: *`、带平台 Cookie 的请求 403、未知扩展名 404。独立发行域名不再作为门禁:主规范 2026 决策已用「平台同源路径 + `sandbox="allow-scripts"` 不透明来源」替代独立域名/通配证书,本轮按现行口径核对。
- 第 7 条(下架会关闭新启动与发行读取、不能绕过网关直取公开 OSS、撤销传播在最大缓存窗口内):前两句本轮都已取证——作者 `unpublish` 与管理员 `suspend` 之后公开目录不含该游戏、详情 404、发行网关 404,`restore` 后重新可玩;「不能绕过网关直取公开 OSS」按上一条的修复与 403 复验成立。**仍缺**:①「不能绕过网关直取公开 OSS」**本轮已修并复验**:先用同一条探测证明匿名直取发行包返回 200(同 bucket 不存在的 key 是 404),根因是 `platform-oss` 收了 `OssObjectAccess` 却从不发送 `x-oss-object-acl`、对象继承 bucket 默认 ACL;现在内部 PUT、分片追加(`OssAppendInternalObjectRequest` 新增 `access`)与直传 policy/表单(`DirectUploadTicketFormFields` 新增 `x-oss-object-acl`)都显式下发对象级 ACL,`E2E_REQUIRE_PRIVATE_BUCKET=1` 下匿名直取直传封面与发行包都返回 **403**(65 项 PASS)。「撤销传播符合最大缓存窗口」需要真实 CDN/TTL 与生产域名,本轮未做,因此第 7 条保持未勾选。
+1
View File
@@ -78,6 +78,7 @@
"check:game-distribution-upload-safety": "node scripts/check-game-distribution-upload-safety.mjs",
"check:game-distribution-upload-resume": "node scripts/check-game-distribution-upload-resume.mjs",
"check:agc-project-snapshot-admin-http": "node scripts/check-agc-project-snapshot-admin-http.mjs",
"check:game-distribution-sandbox-e2e": "node scripts/check-game-distribution-sandbox-e2e.mjs",
"check:production-ops": "node scripts/check-production-ops-guardrails.mjs",
"check:preview-deployer": "node scripts/check-preview-deployer.mjs",
"check:maintenance-page": "node scripts/check-maintenance-page.mjs",
File diff suppressed because it is too large Load Diff