--- id: T-616 title: 自动升级安全下载校验与同盘暂存 phase: 8 deps: [T-615] status: DONE created: 2026-07-13 --- ## 问题 / 背景 自动升级不能把远程zip直接交给更新器。客户端必须先完成可信地址限制、流式下载、大小/hash校验、防zip路径穿越、安全解压、包内版本与manifest逐文件校验;任何一步失败都不能碰当前程序文件。 更新临时文件不能混进用户业务数据 `data/`。本任务使用安装目录同级管理区 `<安装目录>/.cmshopee-update/`,既与程序在同一磁盘便于后续目录切换,也与账号、SQLite、图片和密钥完全隔离。 ## 方案 1. 新增 `app/update_installer.py` 纯逻辑模块,定义自动安装元数据、下载结果、暂存结果和中文错误类型;不得依赖Qt控件。 2. 只接受HTTPS下载地址。下载主机采用客户端受信任域名白名单;重定向只能落在同一白名单内,禁止降级HTTP、`file://`、内网地址和任意第三方跳转。 3. 流式下载到 `.cmshopee-update/downloads/.zip.part`,持续校验累计字节数,完成后要求与 `size_bytes` 完全一致、SHA-256严格一致,再原子改名为 `.zip`。包大小硬上限建议512 MiB;超限、磁盘不足、中断或取消时保留当前版本,清理不完整part文件。 4. 安全解压到 `.cmshopee-update/staging/-<随机值>/`。解压前逐项拒绝绝对路径、盘符/UNC、`..`、符号链接、Windows保留名、NTFS ADS冒号、尾随点/空格、大小写不敏感重复目标;限制文件数不超过20000、解压总量不超过2 GiB,并限制异常压缩比。 5. 解压后严格验证 `version.txt`、`package-manifest.json`、`cmshopee.exe` 和 `_internal/`;版本、包格式、更新器协议必须与服务端元数据一致。按manifest逐文件复算大小和SHA-256,拒绝manifest未声明的程序文件和缺失文件。 6. 本地写入 `pending.json` 只记录版本、安装根、暂存根、hash和阶段,不记录URL查询参数、密钥或用户数据路径。下一次启动可识别已经完整校验的暂存包,避免重复下载;无效/不完整pending必须清理后重新下载。 7. 失败消息对用户保持中文概述,远程URL、内部路径、堆栈写入脱敏诊断日志,不直接展示。 ## 验收要点 - 正常zip能下载、校验并暂存,当前 `cmshopee.exe`、`_internal/` 和 `data/` 完全不变。 - hash为空、格式错误、不匹配,大小不匹配,HTTP/非白名单地址和越权重定向均拒绝安装。 - zip slip、符号链接、大小写重复、保留名、ADS、zip bomb和manifest缺失/篡改均被自动化测试阻断。 - 下载中断后不会把 `.part` 当完整包;已验证pending可在重启后复用,不重复下载。 - 更新目录始终位于 `.cmshopee-update/`,不创建 `data/updates`,不读取用户业务数据。 ## 测试要求 - 新增 `tests/test_update_installer.py`,使用临时目录和本地可控响应覆盖成功、取消、网络失败、所有安全拒绝路径及pending恢复。 - 运行ruff、compileall、完整unittest和 `git diff --check`;不依赖真实线上下载地址。 ## 边界(不改什么) - 不启动独立更新器,不替换程序,不自动重启。 - 不修改当前T-544强制升级GUI;GUI接入由T-618完成。 - 不读取、移动、覆盖或清理 `data/`。 ## 执行记录 - 2026-07-13:新增无Qt依赖的 `app/update_installer.py`,实现受信任HTTPS下载、同盘part暂存、大小/hash校验、安全zip解压、manifest逐文件复核及可复用 `pending.json`。 - 2026-07-13:补齐版本接口自动安装元数据透传;所有失败均发生在当前程序替换前,取消与无效pending会清理不完整状态,`data/` 不参与更新。 - 2026-07-13:干净worktree验证通过:ruff、compileall、完整unittest(400项)和 `git diff --check`。