ZCode 静默上传完整 Git 历史记录至阿里云 OSS – 调查与缓解措施

摘要

ZCode 会静默打包您的整个项目——包括完整的 .git 历史记录、LFS 缓存和本地配置——使用服务器提供的 RSA 公钥进行加密,并将密文直接上传至阿里云 OSS。只要您处于登录状态,上传就会执行,且无法在用户界面中关闭。解密密钥仅存在于智谱的后端,这意味着只有服务器能够读取数据。


上传机制 – 客户端步骤

客户端首先从 https://zcode.z.ai 获取一次性 RSA 公钥和 OSS 表单凭证,然后通过 AES-256-CTR 加密和 RSA-OAEP 密钥封装流式传输 tar-gz 归档文件,最后将加密文件直接发布到阿里云 OSS。

sequenceDiagram
    participant C as ZCode client
    participant S as zcode.z.ai
    participant O as Aliyun OSS
    C->>S: POST /api/v1/snapshot/upload-credential
    S-->>C: snapshot_id, RSA public key, OSS policy, signature, object key
    C->>C: tar.gz → AES‑256‑CTR → RSA‑OAEP wrap key
    C->>O: POST multipart/form-data (tar.gz.enc)
    O->>S: callback confirming receipt
  • 阶段 1 – 凭证请求: 客户端联系 ZCode 协调器,接收用于该上传会话的动态对象键、大小限制和 RSA 公钥。
  • 阶段 2 – 直接上传至 OSS: 本地加密完成后,客户端绕过 ZCode 自身的服务器,通过签名表单 POST 将密文(*.enc)直接流式传输至阿里云 OSS。随后 OSS 会通知智谱的后端。

网络抓包显示,在此过程中存在到 zcode.z.ai 和两个阿里云 OSS 节点的持久 HTTPS 连接。


为什么加密对用户来说是单向的

RSA 密钥对在服务器上生成;只有公钥被发送到客户端,因此客户端(以及用户)永远无法解密上传的归档文件。

{
  "keyId": "String(i.encryption.key_version)",
  "keyWrapAlgorithm": "rsa-oaep-sha256",
  "publicKeySpkiPem": "Ylt(i.encryption.public_key)"
}
  • 一个临时的对称密钥使用 AES-256-CTR 加密 tar 包。
  • 该对称密钥使用服务器提供的 RSA-OAEP-SHA256 公钥进行封装。
  • 私钥从未触及用户的机器,因此磁盘上的 300 MB 密文在本地是不可读的。

“使用服务器持有的私钥进行信封加密,将本地备份变成了远程资产提取。” – @xcc3641 (HN comment)

如果该功能旨在用于用户端的恢复或同步,私钥应该像 Git 或 Time Machine 一样存储在本地。私钥的缺失表明其唯一目的是让服务器随时可以读取数据。


实际上传的内容 – 详细分析

上传负载中约有 87% 是 .git 数据,这意味着服务器接收到了完整的提交历史、LFS 对象、引用日志(reflogs),甚至包括已删除的机密信息。

路径 大小 负载占比 内容
.git/lfs/ 196.1 MB 56.8 % 所有 LFS 缓存的二进制文件和媒体
.git/objects/ 102.2 MB 29.6 % 完整的对象存储(提交、树、blob)
.git/logs/ 0.6 MB 0.2 % 引用日志 – 本地分支移动记录
源文件与文档 ~46.2 MB 13.4 % src/、配置文件、文档

本地存储了一个列出所有文件(42,411 个条目)的纯文本清单,尽管归档文件本身是加密的,但这暴露了目录结构。


UI 开关无法阻止上传

“优化体验”和“仓库快照索引”这两个开关仅影响下游的服务器端处理;捕获和上传的伴随进程在启动时无条件运行。

开关 预期效果 实际效果
optimizeAgentExperienceEnabled 禁用遥测 仍然捕获并上传快照;仅禁用模型训练同意
repoSnapshotIndexingEnabled 禁用快照功能 上传继续;仅停止对上传数据的服务器端索引

客户端在每次 LLM 提示之前调用 captureBeforePrompt,并在标记为 repo-wiki-update 的任务完成事件上调用。单个会话可以生成数十个捕获事件(观察到最多 62 个)。


隐私政策的遗漏

ZCode 的官方隐私政策提到了收集“对话中提交的文本、文件和代码”,但未披露自动进行的全工作区快照和 Git 历史记录上传。唯一相关的条款是一个通用声明,即优化默认关闭,且未经同意不会将数据用于训练。


社区反应与官方回应

  • 几位评论者指出其他 AI 代理在读取点文件(dotfiles)和被忽略文件时也有类似行为(例如 @philbo)。
  • @acrispino 分享了智谱的公开道歉,声称该功能是与“代码库索引”功能相关的早期发布 bug,上传的数据在 Wiki 生成后会被销毁,且该问题已修复。
  • 其他用户表示信任受损,并建议采用沙箱或隔离策略(参见 @radio879 关于沙箱的深入讨论)。

如何在您的机器上停止上传

删除待处理的归档文件只会触发新的捕获;可靠的缓解措施是在文件系统层面使 ~/.zcode/v2/checkpoints 目录变为不可变。

macOS

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints   # set immutable flag
# Verify: touch should fail with "Operation not permitted"

Linux

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints   # set immutable attribute
# Verify: touch should fail with "Operation not permitted"

影响: 检查点/回滚 UI 将不再起作用,因为它依赖于上传的快照,但正常的聊天、自动完成和工具执行仍然可用。要恢复该功能,请移除不可变标志(在 macOS 上使用 chflags nouchg,在 Linux 上使用 chattr -i)。


结论

  • ZCode 会自动捕获并加密您的整个工作区,包括完整的 Git 历史记录,并将其上传到云存储桶。
  • 解密密钥仅由智谱的服务器持有,因此只有他们能解密数据。
  • 该功能无法通过 UI 设置禁用;只要您处于登录状态,它就会运行。
  • 隐私政策未披露此行为,引发了严重的信任和合规性问题。
  • 用户可以通过使 checkpoints 目录变为不可变来在本地阻止上传,代价是失去 ZCode 的快照恢复功能。

“如果 Repo Wiki 功能生成 Wiki 页面,它可能会触发仓库数据的上传。在云端生成 Wiki 页面后,上传的数据会立即被销毁,不会被存储。” – Zhipu’s statement quoted by @acrispino

建议: 除非供应商提供透明的、可选的控制措施并在本地存储解密密钥,否则应将 ZCode 视为数据外泄风险。考虑对 AI 代理进行沙箱处理,或使用具有明确、用户可控数据处理策略的工具。

Sources

相关