gongxiangp2/docs/存储域名与品牌资源事故复盘-2026-08-11.md
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

7.6 KiB
Raw Blame History

存储域名与品牌资源事故复盘(2026-08-11)

结论

生产 Header Logo 的旧地址不是 2026 年近期修复首次写入的,而是 2025 年遗留配置。近期系统设置安全重构虽然没有制造这条历史数据,但沿用了过期默认值,也没有识别“展示代理地址可能被管理页再次写回数据库”的问题,因此同类故障仍可能复发。

本次治理把存储引用分成两类:

  • LEGACY_READ_ONLY:只允许读取、代理或迁移历史素材。
  • 当前写入:默认值、新上传、恢复默认、系统设置和新数据库记录只能使用本地 /brand/... 或当前 s3.jiansun.vip/gongxiangpan/...。

2026-08-12 13:13(Asia/Shanghai)重新只读核查当前正式数据库,并与公开 /api/system-settings 结果逐字段比对:唯一设置行的 Header / Login Logo 已是 https://s3.jiansun.vip/gongxiangpan/... 当前可信桶地址,用户指出的旧精确 Header URL 已不在现有行中;两个公网对象均返回 HTTP 200、image/png,浏览器实际解码为 800×800,并在正式首页显示为 40×40 图标。因此“现有行仍保存旧地址”已不再是当前事实。

但这不等于数据库治理已经完成:同一只读核查显示 headerLogoUrl / loginLogoUrl 的数据库列默认值仍是退役 http://s3.1080p.hk/...,本轮三条目标 migration 也尚无已完成记录。仓库 migration 会在已有 system_settings 历史基线上补齐列、修正未来默认值,并且只把品牌两列中匹配退役前缀的值改为本地 /brand/...;当前可信桶中的合法自定义 Logo 不会被覆盖。仓库更早的历史 migration 本身不能从空数据库完整重放,本轮没有把这件事包装成“全新建库已修复”。

误导来源

  1. Prisma 的 Logo 默认值仍指向旧域名。
  2. 系统设置行自动创建时仍使用旧默认值。
  3. 后台“恢复默认”按钮会重新保存旧地址。
  4. 管理员 GET 返回展示代理 URL,页面保存其它设置时可能把代理 URL 当原值写回。
  5. favicon 上游失败后返回透明 1×1 图片,接口看似成功但用户看不到 Logo。
  6. Header、登录、注册、完善资料页没有稳定的本地图片回退。
  7. 在线历史恢复接口会把历史桶 URL 直接写入新素材。
  8. 旧项目记忆把已经下线的资源域名描述为新素材正式入口。
  9. 现有存储迁移文档只核对素材预览,没有把 Header、登录 Logo、favicon、后台默认值和 Schema 默认值列入验收。
  10. Git 历史中曾存在包含旧生产事实与敏感配置的报告;工作树删除不能消除历史风险,需单独纳入秘密扫描和凭据风险登记,不能在当前修复中贸然改写远端历史。

Git 时间线证据

  • 3fff87c(2025-10-27)引入 Logo 分离上传;生产旧 Header 文件名中的时间戳也落在这一天。
  • 2b7cd7f(2025-12-27)的历史数据库报告已记录用户本次指出的精确 Header URL,证明它不是 2026-07 的安全修复首次写入。
  • 3cba791 / 7c913b6(2026-07-30/31)的系统设置脱敏修复没有主动更新生产 Logo,但仍复制了旧 Schema 默认值、API 默认值和后台重置值。这是本轮必须承认的遗漏:修复了凭据泄漏,却没有同时清理品牌资源的旧写入路径。
  • d69eb6e(2026-08-07)只处理视频手动预览图,不涉及 Logo,也没有解决视频队列跨部署恢复。

因此,事故来源不是“某一次新提交把当前生产行直接改成旧 Header”,而是历史脏值长期存在,加上后续修复没有铲除所有复发入口。两部分都必须处理,不能只修数据库或只加页面回退。

文件 / 资料 造成的错误判断或复发风险 本次处理
prisma/schema.prisma 新建设置行时可能自动填入旧 Logo 改为本地品牌默认值并新增 migration
app/api/system-settings/route.ts 设置不存在时写旧默认;管理员读取展示代理地址后可能再写回 canonical 与 preview 分离,PUT 严格校验
app/admin/settings/page.tsx “恢复默认”重新选择旧域名 改为 /brand/...
app/api/favicon/route.ts 旧地址失败后用透明 1×1 冒充成功 改为真实本地 favicon
components/Header.tsx、登录/注册/完善资料页 远端图坏时没有稳定回退,alt 被挤成竖排坏图文字 统一使用一次性回退组件
app/api/system-settings/upload-logo/route.ts 只信浏览器 MIME、未限制大小、上传后未确认对象已持久化且元数据一致,权限口径也不一致 限制字节/像素/尺寸并完整解码、核对 MIME、带凭据 HEAD 核对大小和类型后再写库、统一系统管理员权限
app/api/restore-data/route.ts 在线流程直接把旧桶 URL 写进新素材 永久停用在线写入入口
.kiro/steering/项目记忆库.md 把已下线资源域名写成新素材正式入口,并用“老素材不改”覆盖了系统 Logo 更新当前拓扑,明确 Logo 不属于老素材
.kiro/specs/production-system-analysis.md 2025 年快照外观像当前正式说明 顶部标记为历史快照并指向当前文档
存储迁移文档 只验收素材,遗漏站点品牌资源和设置往返 补齐迁移门禁
Git 历史中的旧生产报告 既会误导当前判断,也存在历史秘密风险 登记风险;当前不贸然改写远端历史

已采取的代码门禁

  • 构建前执行 npm run storage:legacy-boundary。
  • 旧域名只允许出现在明确列出的历史只读兼容代码、兼容性测试和不可修改的历史 migration 中。
  • 在线历史恢复 API 永久停用;历史恢复改走显式来源、显式目标、默认 dry-run、先迁移对象再写库的离线流程。
  • 品牌资源使用镜像内本地文件作为最终回退。

该门禁由两层组成:普通构建会扫描进入 Docker 构建上下文的源码、数据库 migration 和常见配置;正式增量发布会在工作站完整仓库上先扫描全部受支持源码/脚本,再允许联网构建。普通说明文档、未知扩展名及 Git 历史仍需独立审阅与秘密扫描,不能把规则匹配当成语义证明或历史清理工具。

合法的历史兼容范围

以下用途仍可识别旧域名,但不得生成新的旧域名数据:

  • 媒体代理与 URL 改写
  • 工作审核和考核调研中的历史素材展示
  • 历史资源网络探测
  • 旧对象迁移到当前桶的离线工具
  • 对应的兼容性测试与不可变历史 migration

新增允许项必须在代码评审中说明用途并加入显式 allowlist,不能用“老素材兼容”作为默认值、上传目标、重置值或新写入的理由。

后续验收

  1. 当前行值和首页显示已验收;仍须执行受控 migration,并确认三个目标 migration 均有成功记录、Logo 两列默认值已改为 /brand/...,再逐行复核只存在本地或当前可信桶地址。
  2. Logo 配置 URL 404、超时或返回 HTML 时,所有入口仍显示可见本地品牌图。
  3. 管理员只改颜色/维护通知并保存,不得改变数据库中的 canonical Logo URL。
  4. 新建系统设置行和“恢复默认”均不得出现旧域名。
  5. 构建门禁能阻止旧域名重新进入非只读代码;唯一受控写引用只能是本 migration 的精确品牌数据清洗 SQL。
  6. 当前桶切换或存储迁移必须一并核对素材预览、品牌资源、favicon、设置 API、默认值和重置值。
  7. Logo 上传验收必须再从公网 URL 实际 GET,确认 HTTP 200、图片 Content-Type 和浏览器可解码;带凭据 HEAD 成功不能替代用户侧可见性验收。