ZCode 内部揭秘:将您的 Git 历史记录静默上传至云端

Hacker News 热门(buzzing.cc 中文翻译)·2026-09-18 22:22·3小时前·csmantle
Hacker News 热门(buzzing.cc 中文翻译)
同事件
77AI 编辑部评分,满分 100

ZCode 内部揭秘:将您的 Git 历史记录静默上传至云端

2026-09-18 22:22· 3小时前· csmantle
正文 · AI 翻译

我不是英语母语者;这篇文章由 AI 翻译。

事情始于一次清理磁盘空间时的例行检查:~/.zcode 占用了超过 700MB。在断断续续地深入排查后,我确认了一件相当离谱的事:

只要你处于登录状态,ZCode(智谱官方的 AI 编程桌面应用)就会悄悄地把你的整个工作区——完整的 .git 历史记录、LFS 资源缓存、reflog 以及全局应用配置——打包、加密,然后直接上传到阿里云 OSS。

更讽刺的是:用于加密的 RSA 公钥是由服务器动态下发的,而私钥只存在于云端。你无法解密就躺在你自己磁盘上的那几百兆密文,ZCode 客户端本身也做不到。

以下是完整的调查记录、证据链,以及一条能永久关闭它的单行防御措施。

起点:一个卡在待处理状态的 313MB 归档文件 #

~/.zcode 是 ZCode 的数据根目录。体积构成大致如下:

  • cli/:约 257MB(会话数据库、执行日志)
  • computer-use/:约 130MB(打包的应用及运行时依赖)
  • v2/checkpoints/:约 303MB(主要嫌疑对象)

v2/checkpoints/ 内部,我发现了一个 313MB 的 .enc 文件,旁边还有一个状态元数据文件:

{
  "workspacePath": "/Users/ferstar/myprojects/<a commercial project>",
  "lastCompressedSize": {
    "encryptedSizeBytes": 313070842,
    "workspaceSizeBytes": 345549173
  },
  "kind": "baseline",
  "failureCount": 564
}

事情的经过很直白:

  1. 客户端扫描了我正在进行的商业项目,排除了 node_modules 和其他几个文件,然后把剩余的 345MB 打包成一个 313MB 的加密归档文件,标记为 baseline(完整快照);
  2. 它记录了 564 次上传失败尝试,最终让它停留在本地 pending/ 目录中,等待下一次重试。

该仓库总计 10GB;减去依赖项后,剩余的 345MB 几乎全是核心知识产权。

它去了哪里:从日志到 asar 逆向工程 #

日志中没有明确的上传 URL,所以我拆开了客户端的 app.asar。重建后的上传流程如下:

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 + max_size + OSS form credentials + callback
    C->>C: tar.gz pack → AES-256-CTR encrypt → RSA-OAEP wrap key
    C->>O: PostObject direct upload of tar.gz.enc
    O->>S: callback confirms receipt

该流水线分两个阶段运行:

  1. 向协调器请求凭证:客户端调用 https://zcode.z.ai(代码中为 VITE_ZCODE_ENDPOINT_ORIGIN)。服务器返回 OSS 表单签名(policyx-oss-signature)、一个动态 Object Key、大小限制,以及本轮加密所用的 RSA 公钥;
  2. 直接以表单 POST 上传至 OSS:在本地完成归档和流式加密后,客户端绕过 ZCode 自己的应用服务器,通过 HTTP POST 表单tar.gz.enc直接上传至阿里云 OSS。随后 OSS 回调智谱后端以登记该快照。

检查活动套接字证实了这一点:运行中的 ZCode 进程维持着到 zcode.z.ai IP 端点以及两个阿里云 OSS 存储节点的持久 HTTPS 连接。

最讽刺的部分:密钥属于服务器#

该加密实现采用的是教科书式的信封加密:

keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)
  • 内容通过 AES-256-CTR 使用临时对称密钥进行加密;
  • 该对称密钥使用服务器提供的公钥,通过 RSA-OAEP-SHA256 进行封装。

关键问题在于那把公钥:它是在凭证协商过程中由服务器下发的,而对应的私钥永远不会触及你的机器。用我系统上所有本地私钥来解封信封密钥都失败了,这在意料之中。

换句话说:你硬盘上那 313MB 的密文,你或客户端都无法打开。只有智谱的后端持有解锁它的密钥。

如果这个功能真的是为用户侧回滚或跨设备同步而构建的,密钥本应保存在本地(就像 Git 或 Time Machine 那样)。一个只有服务器才能使用的密钥只有一个用途:确保服务器随时都能读取你的代码。

打包了什么:近 90% 是 .git #

尽管密文是加密锁定的,但打包过程中生成的 Manifest(文件清单)以明文形式保存在本地。拆解一份包含 42,411 个文件的快照:

内容 大小 占比 包含的信息
.git/lfs/ 196.1 MB 56.8% LFS 缓存——所有曾下载过的二进制资源和大体积媒体文件
.git/objects/ 102.2 MB 29.6% 完整的提交历史对象存储(提交、树、blob)
.git/logs/ 0.6 MB 0.2% reflog——本地分支历史与未推送的操作痕迹
源代码与文档 约 46.2 MB 13.4% src/、配置文件、内部文档

.git 目录就占了整个数据载荷的 86.6%

一旦上传,云端收到的远不止你当前的工作树——它得到的是你仓库自第一天起的完整谱系

  • 在后续提交中已被删除的历史 API 密钥和敏感配置;
  • 未推送的本地分支名称(会泄露尚未发布的功能计划);
  • .git/config 中配置的内部 GitLab 主机名和仓库路径。

此外,还有一个名为 repo_snapshot_extra_manifest 的额外清单文件会对你的全局 ZCode 配置文件(例如 settings.behavior.json)进行哈希处理,并在每次快照时将它们跨工作区打包。

关于开关的真相:UI 开关并不能阻止它 #

自然的反应是去检查设置把它关掉。我将 UI 选项与代码库进行了交叉比对:

开关 你以为它会做什么 它实际做了什么
优化体验optimizeAgentExperienceEnabled 禁用遥测 / 数据收集 仅控制数据是否被授权用于模型训练。快照捕获和上传仍在运行
仓库快照索引repoSnapshotIndexingEnabled 禁用快照功能 仅控制服务器是否对上传的快照建立索引。本地打包和上传仍不间断进行

查看宿主程序集代码就一目了然:捕获/上传 sidecar 在启动时被无条件实例化。没有任何基于用户偏好的门控if检查;唯一的要求是tokenProvider能够返回一个有效的 JWT。

归根结底:只要你处于登录状态,这条后台流水线就永久处于活跃状态,没有任何 UI 设置能将其关闭。

捕获触发发生在两个时间点:captureBeforePrompt(每次提示词之前)以及任务完成并标记为repo-wiki-update时。在会话日志中,单个活跃会话最多产生了 62 次捕获事件。

隐私政策怎么说 #

查阅 ZCode 的隐私政策,其中明确声明会收集“对话过程中提交的文本、文件和代码”——这是为 LLM 提供上下文的常规做法。

然而,纵观整份政策、FAQ 和更新日志,没有任何一处提及会静默打包并上传整个工作区和完整的 Git 历史记录

最接近的表述是那条通用模板声明:“优化程序默认关闭,未经同意输入内容不会被用于训练”。

防御:删除是打地鼠;锁定目录 #

当我第一次发现那个待处理的软件包时,我直接把它删掉了。不到半小时,它又重新捕获了——一个全新的 313MB 归档文件,重试计数器从 564 跳到了 565。当上传方发现文件不见了,它就会重新打包一个新的。手动删除就是打地鼠。

最干净、最有效的解决方案是在文件系统层面设置不可变标志,在内核层面拒绝写入权限:

macOS #

# Wipe and lock the checkpoints directory
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# Verify: should output "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test

Linux #

# Wipe and lock the checkpoints directory
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints
# Verify: should output "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test

影响与回滚 #

  • 结果:每当捕获逻辑尝试磁盘 I/O 时,都会被内核阻止。没有本地产物,上传流水线就没有东西可发送;
  • 权衡:“检查点回滚 / 时间线”UI 功能将无法使用(该功能本来就一直要求上传你的代码)。正常聊天、自动补全和工具执行不受影响。日志中被吞掉的 I/O 错误是无害的;
  • 恢复方法:运行 chflags nouchg ~/.zcode/v2/checkpoints(macOS)或 sudo chattr -i ~/.zcode/v2/checkpoints(Linux)。

结语 #

使用 AI 工具时,模型推理不可避免地需要代码上下文——这一点大家都接受。但这一行为显然在两个方面越了界:

第一,数据范围。推理发送的是与任务相关的上下文;而快照外泄的却是整个代码仓库,连同多年的 Git 提交历史。

第二,架构姿态。如果这真的是为用户侧恢复或同步而设计,解密密钥本应归用户所有。而加密密钥完全由服务器持有、隐私政策中零披露、后台上传无法阻止、删除后仍顽固地重新打包——这看起来远不像备份,更像是数据收集。

工具就是工具,但用户必须自己划定边界。如果软件不让你关掉它,那就用操作系统内核把它锁进笼子里。

相关