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 进行逆向工程,揭示了一个两阶段的流水线:

  1. 凭证请求 – 客户端向 https://zcode.z.ai/api/v1/snapshot/upload-credential 发送 POST 请求。服务器返回:
    • 快照 ID,
    • RSA 公钥(用于信封加密),
    • 最大大小限制,
    • 阿里云 OSS 表单凭证(policyx-oss-signature),以及
    • 动态对象键(object key)。
  2. 直接上传 – 客户端创建一个 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

相关