"ZCode 静默上传全量 Git 历史"分析
9 月 18 日,博主 Ferstar 发文称,智谱的 AI 编程工具 ZCode 桌面端会在后台把整个工作区打包加密上传到云端,包含完整 Git 提交历史,且解密私钥仅存于服务端。该文在社区引发广泛讨论。
我们分别在两个环境做了验证:一台运行 ZCode 桌面端(macOS,3.12.3)的机器,以及通过 SSH 连接远程 Linux 的 ZCode 会话。取证全程只读。
复测结果可以概括成三句话:
- 爆料属实:桌面端会把包含完整 Git 历史的工作区打包上传,隐私开关关了照传,而且我们找到了上传成功的直接证据——十个工作区里八个成功传出到云端;
- 范围有出入:SSH / WSL 远程项目在当前版本被快照机制跳过,「只要登录就全量上传」的概括并不准确;
- 爆料没提到的:上传的加密包里除了代码,还内嵌了提示词原文——你和 AI 说过的话也在包里。
这三点分别对应下文的机制边界、上传结果和提示词三节。
爆料指控拆解
原文的指控可以拆成五条:
- 登录状态下,ZCode 在每次发送提示词前(
captureBeforePrompt)和任务结束时抓取工作区快照,单个活跃会话最多产生 62 次捕获; - 快照包含
.git目录——原文样本是一份 42,411 个文件的清单,.git占快照体积 86.6%(LFS 缓存 56.8% + Git 对象库 29.6% + reflog 0.2%),源码和配置只占 13.4%; - 打包经信封加密:内容 AES-256-CTR 加密,对称密钥用服务端下发的 RSA-OAEP 公钥包裹(
keyWrapAlgorithm: rsa-oaep-sha256),私钥只在云端,本地密文连客户端自己都解不开; - 设置中的「优化体验」(
optimizeAgentExperienceEnabled)只控制训练数据,「仓库快照索引」(repoSnapshotIndexingEnabled)只控制服务端建不建索引,都不拦上传; - 删除本地快照文件后,客户端会重新打包、持续重试上传,原文样本中一个 313MB 的密文重试了 564 次。
我们在两个环境的代码中逐条核对了上述实现,均能对应。这一层,爆料属实。
复测环境
- 环境 A:ZCode 桌面端,macOS,版本 3.12.3(build 3.12.3.7463),asar 解包后静态分析 + 本地状态文件核查;
- 环境 B:通过 SSH 连接远程 Linux 的 ZCode 会话,以及一台作为多个远程会话宿主机的 Linux 服务器。
全程只读取证:只用了 ls/find/grep/strings/sqlite3 只读模式和 JSON 解析,未修改、未删除任何文件,未触碰任何进程。
快照数据的落盘位置
快照机制的本地痕迹集中在 ~/.zcode/v2/checkpoints/。每个工作区一个子目录,内含状态文件 state.json 和待上传的密文。状态文件的关键字段:
1 | { |
pending/ 子目录下是尚未传成的 .tar.gz.enc 加密包。我们见到的两个大包分别约 122MB 和 183MB,对应两个本地项目工作区——这个体积级别意味着打包内容远超"当前打开的文件",与爆料中"整仓 + Git 历史"的描述一致。
SSH 远程项目不受影响
桌面端代码中,快照捕获入口有一个前置判断。asar 解包后在 out/host/index.js 中可以找到(压缩构建,标识符已改名,逻辑如下):
1 | async captureBeforePrompt(t) { |
workspaceIdentity 仅在 SSH / WSL 远程会话中构造(格式 remote:ssh:host:port:user:path),本地打开的目录不携带该字段。也就是说:远程会话短路跳过,本地会话进入调度。
行为数据与代码互相印证:
- 环境 A 上存在大量远程 SSH 会话(日志中
remote:ssh标识出现数千次),但checkpoints/下十个工作区的快照记录,workspacePath全部是/Users/...本地路径,无一个远程路径; - 环境 B 的服务器作为多个远程会话的宿主机,
~/.zcode/v2/checkpoints/从未生成过任何文件; - 宿主日志中
workspaceIdentity字段只有远程取值、没有空值——本地会话压根不写这个字段,恰好落入抓取分支。
结论:该机制的采集对象是桌面端打开的本地目录,SSH / WSL 远程项目在当前版本被跳过。
两点边界。其一,被跳过的只是快照打包这一条管线;远程会话中的对话内容和被读取的代码片段,本来就会作为推理上下文发给模型,这是所有云端 AI 编程工具的常规行为。其二,跳过逻辑是代码实现,不是隐私政策条款——下个版本改一行代码就能取消,使用者无从感知。
上传确实成功了
原文给出的最醒目证据,是一个重试失败五百余次的加密包。它只说明"有失败的重试",回答不了"有没有成功过"。要回答后者,得先弄清楚客户端怎么记录上传结果。我们还原了它的上传状态机:
1 | attemptUpload: |
关键是 lastAcceptedManifestHash 的写入条件:只有"取到上传凭证 且 OSS 对象上传成功"才会落盘。它是上传成功的结构性证据。
逐个检查十个工作区的状态文件:八个处于 accepted 终态——这八个工作区的完整加密包历史上传成功。旁证是 lastCompressedSize.manifestHash 与 lastAcceptedManifestHash 在八个成功样本中两两相等:最后一次打包的包,就是被服务端接受的包。
时间最近的一次成功上传发生在 9 月 17 日下午。该设备 ~/.zcode/v2/setting.json 中的两个开关此时均为关闭状态:
1 | { |
即"关了开关就不会上传"在该设备上不成立。剩余两个工作区停留在 pending、持续失败重试,属于传输失败,不是没传过——另外八个已经传到了。
提示词也在包里
还原状态机时确认,上传的加密包内除代码与文件清单外,还包含提示词记录:用户输入的原文,以及所用模型、接口地址等配置元数据。也就是说随快照外传的内容不止代码仓库,还包括本地工作区内与 AI 的对话输入。如果你在对话里让 AI 处理过密钥、内网配置、生产环境信息,这些内容同样在打包范围内。
另外,二进制中唯一的快照端点就是 upload-credential——manifest、增量、代码、prompt 全部打进同一个加密包,整包成败,不存在"只传了元数据、代码没传出去"的中间状态。
怎么防
原文给出的阻断方案是文件系统不变锁:手动删除快照是打地鼠,客户端会重新打包,直接从内核层面禁止写入快照目录更有效。
1 | # macOS |
按原文的说明:阻断后快照逻辑写目录时被系统内核拦截,后续上传无从谈起;代价是 checkpoint 回滚 / 时间线功能不可用,日常补全、对话、工具调用不受影响。需要恢复时执行 chflags nouchg(macOS)或 sudo chattr -i(Linux)。
两个待观察项:智谱官方截至成稿未公开回应,事件定性(有意采集或实现缺陷)待官方表态;远程豁免逻辑是否在新版本中延续,值得留意每次更新。
结论
复测结果分三点:
- 机制属实:桌面端会在开关关闭的情况下,把包含完整 Git 历史与提示词记录的工作区打包上传,且十个工作区中有八个存在成功上传记录;
- 范围修正:SSH / WSL 远程项目在当前版本被跳过,"只要登录就全量上传"的概括不准确;
- 重点人群:用桌面端打开本地项目目录的开发者,尤其是项目含凭据或敏感配置的。
参考
- Ferstar:扒一扒 ZCode 静默上传全量 Git 历史的骚操作(2026-09-18)
- V2EX:有用 zcode 的,马上关闭(2026-09-18)
- ZCode 官方隐私政策









