ZCode 在后台静默将完整的 Git 历史记录上传至阿里云 OSS
ZCode 在后台静默将完整的 Git 历史记录上传至云端
核心结论: ZCode(智谱官方 AI 编程桌面端)会在用户登录时,自动打包整个工作区——包括完整的 .git 历史记录、LFS 缓存、reflogs 以及全局应用配置,并使用服务器提供的 RSA 公钥进行加密,随后将密文直接上传至阿里云 OSS。该上传流程是强制执行的,无法通过 UI 开关关闭,且解密密钥仅存在于智谱的后端,这意味着用户无法在本地解密这些数据。
起点:一个卡在 pending 状态的 313 MB 加密归档文件
~/.zcode目录(ZCode 的数据根目录)占用了超过 700 MB 的空间。- 在
~/.zcode/v2/checkpoints/中发现了一个 313 MB 的baseline.enc文件,元数据表明它包含了一个 345 MB 的工作区快照,并且已经经历了 564 次上传失败。 - 该快照代表了一个商业项目,其完整仓库大小为 10 GB;在排除
node_modules后,打包的有效载荷为 345 MB,即几乎全部是知识产权内容。
上传流程:客户端打包 → 直接 POST 至 OSS
通过对 app.asar 进行逆向工程,揭示了一个两阶段的流水线:
- 凭证请求 – 客户端向
https://zcode.z.ai/api/v1/snapshot/upload-credential发送 POST 请求。服务器返回:- 快照 ID,
- RSA 公钥(用于信封加密),
- 最大大小限制,
- 阿里云 OSS 表单凭证(
policy,x-oss-signature),以及 - 动态对象键(object key)。
- 直接上传 – 客户端创建一个
tar.gz归档文件,使用 AES-256-CTR 进行加密,利用提供的公钥通过 RSA-OAEP-SHA256 封装对称密钥,并将生成的*.enc文件直接 POST 到阿里云 OSS。随后 OSS 会回调智谱后端以确认接收。
网络追踪显示,客户端与 zcode.z.ai 及两个阿里云 OSS 节点之间存在持续的 HTTPS 连接。
加密密钥归服务器独有
该实现遵循了教科书式的信封加密:
keyId: "<version>",
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: "<server‑provided PEM>"
- 内容在本地使用临时的 AES-256-CTR 密钥进行加密。
- AES 密钥使用服务器在凭证请求期间提供的 RSA 公钥进行封装。
- 对应的私钥从未出现在客户端机器上;尝试在本地解封装密钥均会失败。
后果:
- 存储在磁盘上的 313 MB 密文无法被用户或 ZCode 客户端解密。
- 只有智谱的后端能够解密该快照,这实际上赋予了该服务对整个仓库历史的无限制读取权限。
实际上传的内容:86% 为 .git 数据
打包过程中生成的清单(以明文保存)列出了 42,411 个文件。大小分布如下:
| 路径 | 大小 | 载荷占比 | 重要内容 |
|---|---|---|---|
.git/lfs/ |
196.1 MB | 56.8% | 所有下载过的二进制大文件 |
.git/objects/ |
102.2 MB | 29.6% | 完整的提交对象存储(提交、树、数据块) |
.git/logs/ |
0.6 MB | 0.2% | Reflogs,本地分支历史 |
| 源代码及文档 | ~46.2 MB | 13.4% | src/,配置文件,文档 |
因此,86.6% 的上传载荷是原始的 Git 数据,暴露了:
- 历史 API 密钥和后来被删除的机密信息。
- 未推送的分支名称,泄露了未发布的功能。
- 包含内部主机名和仓库 URL 的
.git/config条目。
额外的清单(repo_snapshot_extra_manifest)会对全局 ZCode 配置文件进行哈希处理,并将其与每个快照捆绑在一起。
UI 开关无法阻止捕获行为
UI 中出现了两个设置:
| 设置 | 预期效果 | 实际效果 |
|---|---|---|
优化体验 (optimizeAgentExperienceEnabled) |
禁止遥测 / 模型训练数据 | 仅禁止发送用于模型训练的数据;快照捕获依然运行 |
仓库快照索引 (repoSnapshotIndexingEnabled) |
禁用快照功能 | 仅停止服务器端对已上传快照的索引;本地打包和上传继续进行 |
代码检查显示,快照辅助进程在启动时无条件实例化。唯一的限制条件是来自 tokenProvider 的有效 JWT 令牌。捕获触发器会在每次 LLM 提示词之前(captureBeforePrompt)以及标记为 repo-wiki-update 的任务完成时触发。单个会话可以生成数十次捕获事件。
隐私政策遗漏
ZCode 的隐私政策声明它会收集“对话期间提交的文本、文件和代码”,这对于 LLM 上下文来说是标准的。然而,该政策、常见问题解答(FAQ)和更新日志中完全没有提及自动全工作区打包或 Git 历史记录外泄。唯一相关的条款是一条通用的说明,即“优化计划默认关闭,未经同意不会使用输入内容进行训练”。
防御措施:锁定 checkpoints 目录
删除待处理的归档文件只会触发新的捕获(重试计数器会增加)。可靠的缓解措施是将 ~/.zcode/v2/checkpoints 目录设置为不可变,防止客户端写入新的归档文件。
macOS
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints # 设置不可变标志
# 验证:touch 命令应返回 "Operation not permitted"
Linux
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints # 设置不可变属性
# 验证:touch 命令应返回 "Operation not permitted"
影响: 当捕获流水线无法写入归档文件时会中止,因此不会上传任何数据。代价是失去了 ZCode 的“检查点回滚/时间轴”UI 功能;正常的聊天、自动补全和工具执行功能保持正常。若要恢复,请移除不可变标志(macOS 上使用 chflags nouchg,Linux 上使用 chattr -i)。
社区反应与官方回应
- Hacker News 评论强调了对其他 AI 代理读取点文件(dotfiles)的担忧、沙盒化的徒劳性,以及对中国 AI 厂商的不信任。
- 智谱的声明(评论中链接了截图)声称该行为源于早期发布的一项“代码库索引”功能,旨在进行本地仓库索引和临时 Wiki 生成。声明断言上传的数据在 Wiki 生成后立即销毁,且该问题已修复,并向用户道歉并重置了一周的配额。
- 批评者指出声明(数据是临时的、已销毁)与技术证据(持久化加密上传至 OSS 且密钥由服务器持有)之间存在矛盾。
总结
ZCode 的后台进程会捕获登录用户的整个工作区——包括完整的 Git 历史记录——并将其加密上传至第三方云端,且没有任何用户可控的退出选项。加密密钥仅存在于智谱的服务器上,这意味着该服务可以随意解密数据。无法接受这种数据收集程度的用户应锁定 ZCode 的 checkpoints 目录(或卸载该应用)以防止进一步的数据外泄。
Sources
相关
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch