Skip to content

fix(sync): 支持大规模 Skills 归档安全恢复#5695

Open
DASungta wants to merge 4 commits into
farion1231:mainfrom
DASungta:agent/large-skills-sync
Open

fix(sync): 支持大规模 Skills 归档安全恢复#5695
DASungta wants to merge 4 commits into
farion1231:mainfrom
DASungta:agent/large-skills-sync

Conversation

@DASungta

Copy link
Copy Markdown

Summary / 概述

这个 PR 修复 WebDAV / S3 Skills 备份“可以上传,但在另一台设备无法恢复”的问题。

主要改动:

  • 将 Skills ZIP 的文件/目录条目上限从 10,000 调整为 100,000。
  • 保留 512 MiB 压缩 artifact 和 512 MiB 逻辑解压内容限制,不取消原有 ZIP Bomb 防护。
  • 上传时按实际写入 ZIP 的条目和文件字节流执行预检;本地快照全部通过后才创建 WebDAV 远端目录或上传 artifact。
  • 恢复时先检查 ZIP 中央目录,再按实际解压字节流执行第二层硬限制;预检失败不会替换本地 Skills。
  • manifest.json 中为 skills.zip 增加可选的 entryCountuncompressedSize,不提升协议版本。
  • WebDAV / S3 恢复确认框展示 Skills 文件/目录条目数和预计解压体积。
  • 兼容没有统计字段的历史 manifest;旧备份下载后仍会通过 ZIP 中央目录执行安全预检。

问题定位与历史分析

原实现的判断位于 Skills ZIP 恢复路径:

const MAX_EXTRACT_ENTRIES: usize = 10_000;

if archive.len() > MAX_EXTRACT_ENTRIES {
    // reject
}

这里的 archive.len() 统计的是 ZIP 中所有文件和目录条目,并不是 CC Switch 数据库中的 Skill 数量。因此,一个 Skill 只要携带较多模板、示例、前端依赖或其他资源文件,就可能单独突破限制。

我对本机的真实备份进行了统计:

  • 数据库中安装的 Skill:21 个
  • 实际 ZIP 条目:12,676 个
  • 其中:12,490 个文件、186 个目录
  • 文件逻辑内容合计:约 61.56 MiB
  • APFS 实际占盘:约 101 MiB

这份备份远低于 512 MiB,却因为条目数超过 10,000 而无法在其他设备恢复。资源型 Skill 已经越来越常见,Skill 数量与 ZIP 条目数之间不再存在稳定比例。

继续查看 Git 历史后确认,10,000 条目和 512 MiB 限制最初由提交 6098fa75 引入,目的在于防止 ZIP Bomb、海量小文件导致的 CPU / inode 消耗以及解压后磁盘占用失控。这个安全目标是合理且必须保留的。

真正的问题有两个:

  1. 10,000 这个固定条目值已无法覆盖现在合法的大型 Skills 集合。
  2. 旧实现主要在恢复端检查条目数,而上传端可以生成并上传超过该限制的备份,破坏了同步应具备的 round-trip 保证。

修改理由与必要性

只删除条目上限会放弃对海量小文件攻击的防护;只提高上限但不调整上传路径,则仍然可能产生“上传成功、恢复失败”的快照。

因此这个 PR 采用有界扩容和上传/恢复对称校验:

边界 限制 目的
ZIP 文件/目录条目 100,000 限制文件系统操作次数和 inode 消耗
Skills 逻辑解压内容 512 MiB 限制恢复后的文件内容规模
单个同步 artifact 512 MiB 限制网络下载和内存占用

100,000 相对当前真实的 12,676 条目约有 7.9 倍余量,能够支持多个资源型 Skill,同时仍然是明确、可测试的资源上界。

实现考量

1. 上传和恢复使用相同安全策略

ZIP 创建过程不再一次性读取单个文件,而是流式复制并累计实际字节数。每写入一个文件或目录条目就更新统计,超限立即终止本地快照构建。

ZIP 完成后,db.sqlskills.zip 的最终产物大小还会使用与下载端相同的 512 MiB artifact 限制。WebDAV 只有在整个本地快照通过预检后才创建远端目录,S3 同样在任何 put_object 之前完成快照构建。

2. 中央目录预检 + 实际字节流硬限制

恢复端先遍历 ZIP 中央目录:

  • 统计实际文件/目录条目数;
  • 使用 checked_add 汇总声明的未压缩大小;
  • 在创建解压目录和替换本地 Skills 前验证两个限制。

中央目录属于不可信输入,因此它只用于快速预检。真正解压时仍按实际读取的字节数再次执行 512 MiB 限制,避免伪造 ZIP 元数据绕过检查。

3. 保持协议向后兼容

统计字段放在 skills.zip 对应的 ArtifactMeta 中,并使用可选字段:

{
  "entryCount": 12676,
  "uncompressedSize": 64554996
}
  • 新版本可以直接读取没有这两个字段的旧 manifest。
  • 旧版本会忽略未知字段。
  • 协议版本、远端目录结构和 artifact 名称均不变。
  • 旧备份没有统计字段时,恢复确认框不显示预估信息,但下载后的安全预检仍然生效。

4. 预估体积的语义

界面展示的是文件内容逻辑大小,即 ZIP 解压后所有普通文件字节数之和。它可跨平台稳定计算,但不等同于最终磁盘占用;大量小文件、目录项和文件系统块分配会使实际占盘更高。

Related Issue / 关联 Issue

未关联 Issue。本 PR 针对可稳定复现的跨设备 Skills 恢复失败。

Screenshots / 截图

无。恢复确认框仅在新 manifest 包含统计字段时增加“Skills 文件/目录条目”和“预计解压体积”两行;旧 manifest 的现有界面保持不变。

Validation / 验证

  • cargo fmt --check
  • cargo clippy --lib -- -D warnings
  • cargo test --lib services::webdav_sync -- --nocapture:8 passed
  • cargo test --lib services::sync_protocol::tests -- --nocapture:25 passed
  • cargo test --lib services::s3_sync::tests -- --nocapture:5 passed
  • node node_modules/typescript/bin/tsc --noEmit
  • node node_modules/prettier/bin/prettier.cjs --check "src/**/*.{js,jsx,ts,tsx,css,json}"
  • node node_modules/vitest/vitest.mjs run tests/components/WebdavSyncSection.test.tsx:21 passed
  • 英文、简体中文、繁体中文、日文 locale JSON 解析通过

本机 pnpm typecheck 会被 pnpm 的依赖构建审批检查提前阻断(ERR_PNPM_IGNORED_BUILDS,涉及 esbuild / msw),尚未进入 tsc;因此使用项目已安装的同一 TypeScript 本地二进制直接执行了等价的 tsc --noEmit,未修改依赖审批配置。

Checklist / 检查清单

  • TypeScript 类型检查通过(使用项目本地 TypeScript 二进制直接执行)
  • 前端代码格式检查通过
  • Rust cargo fmt / cargo clippy 通过
  • WebDAV、S3、协议边界及前端组件测试通过
  • 已同步更新 en / zh / zh-TW / ja 国际化文本
  • 已验证旧 manifest 缺少归档统计字段时仍可反序列化和恢复

@github-actions github-actions Bot added documentation Improvements or additions to documentation frontend Frontend (React/TypeScript) backend Backend (Rust/Tauri) labels Jul 23, 2026
@DASungta
DASungta marked this pull request as ready for review July 23, 2026 15:35
@DASungta
DASungta requested a review from farion1231 as a code owner July 23, 2026 15:35
@farion1231

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: e55ad2a752

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend (Rust/Tauri) documentation Improvements or additions to documentation frontend Frontend (React/TypeScript)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants