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
相关
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- 项目