AIGC标识 ZCode 静默上传 Git 历史:一次逆向取证的完整复盘

写作日期:2026-09-19 事件性质:AI 编程工具隐私 / 供应链信任 信源:ferstar 原始取证博客(2026-09-18)、Tokenstead 深度梳理、AIHOT 聚合(精选池最高分 86) 一句话:Z.ai(智谱)官方 AI 编程桌面应用 ZCode,只要处于登录状态,就会把你的整个工作区——连同完整 .git 历史——加密打包上传到阿里云 OSS,而解密私钥只存在于 Z.ai 的云端。


0. TL;DR

  1. 触机:登录即触发,与是否使用无关。单次活跃会话日志里最多记录了 62 次捕获事件。
  2. 范围:不是"发送给模型的上下文",而是整个仓库快照——一次实测 42,411 个文件、313MB,其中 .git 目录占 86.6%
  3. 最关键的设计:信封加密(AES-256-CTR + RSA-OAEP-SHA256),但 RSA 公钥由服务端随凭证下发、私钥从不下发。用户连自己磁盘上那份 313MB 密文都打不开,ZCode 客户端自己也打不开。
  4. 设置开关是摆设:两个看起来能关掉它的开关,经代码比对均只控制"训练授权"和"服务端索引",本地打包上传照常运行。
  5. 删除无用:删掉待传归档,半小时内重新打包,重试计数器从 564 涨到 565。
  6. 隐私政策只字未提。截至原文发布(09-18),ZCode 团队账号的公开回复是一句 "hey I am sorry to let you find it",更像承认机制存在,而非否认。

1. 事件是怎么被发现的

起点非常日常:ferstar 在清理磁盘空间时发现 ~/.zcode 占了 700MB+。目录结构大致是:

目录 体积 内容
cli/ ~257MB 会话数据库、执行日志
computer-use/ ~130MB 内置应用与运行时依赖
v2/checkpoints/ ~303MB 主嫌疑:待上传快照

v2/checkpoints/ 里躺着一个 313MB 的 .enc 文件和一份状态元数据(明文可读):

{
  "workspacePath": "/Users/ferstar/myprojects/<一个商业项目>",
  "lastCompressedSize": {
    "encryptedSizeBytes": 313070842,
    "workspaceSizeBytes": 345549173
  },
  "kind": "baseline",
  "failureCount": 564
}

三个信息当场定性:

  • kind: "baseline" —— 全量基线快照,不是增量;
  • workspaceSizeBytes: 345549173 —— 仓库总大小 10GB,排除 node_modules 等依赖后剩 345MB,几乎全是核心知识产权
  • failureCount: 564 —— 已经失败重试了 564 次,还在 pending/ 里排队等着下一次。

2. 上传链路还原:从日志到 app.asar

日志里没有明文上传地址,ferstar 拆开了 Electron 客户端的 app.asar,还原出两阶段流水线:

┌─────────────┐  ① 请求凭证   ┌──────────────────┐
│ ZCode 客户端 │ ───────────→ │ zcode.z.ai        │
│ (本地打包加密) │ ←─────────── │ (协调端)           │
└──────┬──────┘  返回: OSS 表单签名 │
       │         policy / x-oss-signature    │
       │         动态 Object Key             │
       │         大小上限                     │
       │         本轮 RSA 公钥 ←─────────────┘
       │ ② 本地 tar.gz 打包 → AES-256-CTR 流式加密
       ▼
┌──────────────┐  ③ 表单 POST 直传   ┌─────────────┐
│  阿里云 OSS   │ ←──────────────── │ 密文归档      │
└──────┬───────┘                    └─────────────┘
       │ ④ 回调注册快照
       ▼
   Z.ai 后端

关键代码中的端点常量是 VITE_ZCODE_ENDPOINT_ORIGIN(指向 https://zcode.z.ai)。检查运行时活跃连接也印证了这一点:ZCode 进程持续保持着到 zcode.z.ai 以及两个阿里云 OSS 存储节点的持久 HTTPS 连接。

值得注意的架构细节:密文是客户端绕过 ZCode 自己的应用服务器、直接表单 POST 到 OSS 的。这种"直传对象存储"的设计本是为了省带宽、抗审计——应用服务器上不会留上传记录,只有 OSS 回调注册时后端才知道有新快照。

3. 最讽刺的部分:钥匙在服务端

加密实现是教科书式的信封加密(envelope encryption):

keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)
  • 内容用一次性对称密钥走 AES-256-CTR 加密;
  • 对称密钥用 RSA-OAEP-SHA256 包裹,包裹所用的公钥由服务端在凭证协商时下发。

问题就出在这把公钥上:对应私钥永远不会落到你的机器上。ferstar 用本机所有私钥尝试解包信封密钥,全部失败——符合预期。

也就是说:你磁盘上那份 313MB 密文,你打不开,ZCode 客户端自己也打不开,只有 Z.ai 的后端能打开

这里有一个可以当作"意图试金石"的判断标准:如果这个功能真是为了用户侧的回滚恢复或跨设备同步,密钥应该像 Git 或 Time Machine 一样留在本地。一把只有服务端能用的钥匙,服务的对象不是用户。用 ferstar 的原话说:

"A key that only the server can use serves exactly one purpose: making sure the server can read your code whenever it wants."

4. 到底打包了什么:86.6% 是 .git

打包生成的 Manifest(文件清单)是明文存在本地的,所以可以精确拆解一份 42,411 文件的快照:

内容 体积 占比 泄露了什么
.git/lfs/ 196.1MB 56.8% LFS 缓存——历史上下载过的所有二进制资产和大文件
.git/objects/ 102.2MB 29.6% 完整提交历史对象库(commits / trees / blobs)
.git/logs/ 0.6MB 0.2% reflogs——本地分支操作轨迹、未推送记录
源码与文档 46.2MB 13.4% src/、配置文件、内部文档

.git 目录合计占载荷 86.6%。

这比"上传了源码"严重一个量级,因为 git 对象库不是工作区的快照,而是仓库自诞生以来的完整谱系

  • 后续提交里已经删掉的历史 API 密钥、敏感配置——都在历史 blob 里;
  • 未推送的本地分支名——直接暴露未发布的功能规划;
  • .git/config 里的内部 GitLab 主机名和仓库路径——等于送了一张内网地图。

另外还有一份名为 repo_snapshot_extra_manifest 的附加清单,会把全局 ZCode 配置文件(如 settings.behavior.json)做哈希并跨工作区塞进每一次快照——不只丢当前项目,还丢你的全局配置习惯。

5. 设置里的开关为什么是摆设

直觉反应是去设置里找开关。ferstar 把 UI 开关和代码逐一比对:

开关 用户以为它做什么 实际做什么
Optimize Experience(optimizeAgentExperienceEnabled 关闭遥测/数据收集 只控制数据是否授权用于模型训练。快照捕获与上传照常运行
Repo Snapshot Indexing(repoSnapshotIndexingEnabled 关闭快照功能 只控制服务端是否对已上传快照建索引。本地打包与上传不受影响

宿主进程代码里,捕获/上传 sidecar 在启动时被无条件实例化——没有任何基于用户偏好的 if 分支,唯一的前置条件是 tokenProvider 能产出有效 JWT。触发点有两个:captureBeforePrompt每次发起 prompt 之前)和任务完成时(打上 repo-wiki-update 标记)。会话日志显示,单次活跃会话最多产生 62 次捕获事件。

一句话:只要登录着,这条后台流水线就常驻,没有任何 UI 设置能关掉它。

6. 为什么 Agent 的权限系统"看不见"它

这是技术上最值得写进安全教材的一点。OrcaPromptVault(一个公开的 AI 工具系统提示词收集项目)存有 ZCode 抓获的 131KB 系统提示词和完整的 31 个工具面:

  • 检查点/回滚功能确实接进了系统提示词——模板 "Workspace rewind applied. rewindId, checkpointId, strategy, restoredFiles" 出现了 5 次。这是快照流水线在用户面前的"正门"(回滚 UI);
  • 但 31 个工具里没有任何快照、上传或遥测工具,131KB 的指令里没有出现 Aliyun、OSS、upload、privacy 中的任何一个词;
  • 整条外传管线是宿主层 sidecar,在工具循环之外实例化。Agent 看不见它,权限系统自然也管不到它;
  • 补刀:ZCode 还有一个 ReadSessionContext 工具,可按 session ID 读取其他持久化的会话内容——会话既在本地持久化,又在云端被快照。

推论:你给 Agent 配置的所有权限约束、所有"工具调用前确认",对宿主层的旁路管道统统无效。 审计一个 AI 编程工具,不能只审它的工具面,必须审宿主进程的网络行为。

7. 隐私政策的空白

ZCode 的隐私政策写的是行业标准表述——收集"对话中提交的文本、文件和代码"。但 ferstar 翻遍了政策全文、FAQ 和更新日志,没有任何一处提到"打包并上传整个工作区与完整 Git 历史"。最接近的一句是模板化的"优化计划默认关闭,未经同意不会将输入用于训练"。

对照事件时间线更显眼:ZCode 2026 年 7 月发布,发布时的卖点恰恰是"信任"——在 Claude Code 隐藏遥测争议数周后,以开源权重 + 一方集成的姿态对标 Claude Code。有高管在 X 上被问及 ZCode 是否会有"任何形式的间谍软件"时回答:不会实现官网列表之外的任何东西。而工作区快照不在官网列表上

8. 防御:删除是打地鼠,锁目录才有效

先说为什么删除没用:ferstar 第一次发现时直接删了 pending 归档,半小时内客户端重新捕获、重新打包出一份 313MB 新归档,重试计数器从 564 涨到 565。上传器发现文件不在,就再打一份。

有效的方案是在文件系统层把 checkpoints 目录锁死(内核级拒绝写入,捕获逻辑无法落盘,上传管线无米下锅):

macOS:

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# 验证:应报 "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test

Linux:

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints
touch ~/.zcode/v2/checkpoints/test

代价与回滚:

  • "检查点回滚 / 时间线" UI 会失效——这个功能本来就需要先上传你的代码;
  • 正常聊天、补全、工具调用不受影响,日志里的 I/O 报错无害;
  • 恢复:chflags nouchg ...(macOS)或 sudo chattr -i ...(Linux)。

Windows(原文未提供,以下为等效思路,未实测请自行验证):Windows 没有直接对应 chattr +i 的用户态命令,可用 NTFS 拒绝 ACL 达到同样效果:

# 管理员 PowerShell;先清空再锁定
Remove-Item "$env:USERPROFILE\.zcode\v2\checkpoints\*" -Recurse -Force
icacls "$env:USERPROFILE\.zcode\v2\checkpoints" /deny "${env:USERNAME}:(OI)(CI)(W)"
# 验证:写入应被拒绝
New-Item "$env:USERPROFILE\.zcode\v2\checkpoints\test" -ErrorAction SilentlyContinue
# 恢复
icacls "$env:USERPROFILE\.zcode\v2\checkpoints" /remove:d "${env:USERNAME}"

更彻底的做法是不在工作机上安装,或至少在工作时段用进程级网络监控确认它的出站连接。

9. 对照组:两个月前的 Grok Build,模式相似、意图不同

这不是孤例。2026 年 7 月,xAI 的 Grok Build 也被曝默认向 Google Cloud 上传完整 git 提交历史和未改动仓库数据——单个会话日志里上传事件贯穿 186 轮对话;有用户在自己家目录上运行 Grok,靠读工具自己的日志才发现 SSH 私钥、密码库、文档、照片都在上传路径里;还有用户账户返回 disable_codebase_upload: true 却仍被传了 8 个私有仓库(Codex 与 Anthropic 各自独立取证)。

但两件事的"签名"完全不同:

维度 Grok Build(2026-07) ZCode(2026-09)
发现方式 工具自己的 unified.json 日志可查,明面上 需拆 app.asar 逆向还原
加密 不加密 信封加密,私钥只在服务端
开关 有 kill switch(尽管失效) 两个开关均被验证不阻断上传
删除后行为 删了重打包,重试 564 次
触发条件 会话上下文同步阶段(伴随使用) 登录即触发,与使用无关
隐私政策 全文、FAQ、changelog 均无提及
定性 疏忽:没人管的同步功能,裸奔在自己的日志里 刻意:加密防的恰恰是代码的主人

区别为什么重要:疏忽可以修(xAI 事后补了 kill switch),而"加密到用户打不开自己代码、开关做成装饰、删除触发重试、政策只字不提"这一整套组合,指向的是设计意图,不是工程失误。

10. 我的三个判断

1)开源权重 ≠ 开源运行时。 GLM 系列权重开放,但 ZCode 这个 harness 是闭源的,而且论坛讨论里大量评论者默认"GLM 开源所以 ZCode 开源"。模型层和运行时层是两个独立的信任面:本地跑着开源模型、外面套一个会打电话回家的闭源壳,这不是本地化。评估"本地部署"方案时,harness 的网络行为必须单独过一遍。

2)"钥匙在哪"是区分备份与收集的试金石。 推理需要代码上下文,这一点所有人都接受——发送任务相关的上下文是使用 AI 编程工具的必要成本。但"任务相关上下文"与"全仓库 + 全历史 + 全局配置、加密到本人不可读"是两回事。下次评估任何工具的"快照/同步/回滚"功能时,只需要问一句:解密私钥在哪? 在用户手里(Git、Time Machine 模式)是功能;只在厂商手里,是收集。

3)工具面审计是必要不充分条件。 这次的管线不在 31 个工具里,不在 131KB 系统提示词里,Agent 自身毫不知情。对企业环境(尤其是有数据不出域要求的),审计清单至少要有:登录态下的出站连接清单、磁盘上的落盘文件与可读性、设置开关的代码级验证、隐私政策与实际行为的比对。行为与文档对不上的那一条,永远按行为算。

另外记一笔:ferstar 的推文 13 小时内 27.6 万浏览,中文圈的预警帖("建议先停用 ZCode……尽量用开源 agent")6.38 万浏览。截至两篇原文发布(2026-09-18),Z.ai 官方账号未回应,最接近官方的回复来自 ZCode 团队关联账号的一句 "hey I am sorry to let you find it"——按字面理解,这更接近"确认机制存在"。后续是否有修复、披露变更或正式声明,建议回原文链接跟进。


参考与核对

核对提醒:本文全部数字、代码与引语来自上述两篇原文与 AIHOT API 返回内容;Z.ai 官方回应状态以 2026-09-18/19 的信源为准,引用任何数字前建议回原文核对。文中 Windows 防御命令为本 Blog 补充的等效思路(原取证文章只覆盖 macOS/Linux),使用前请先在测试目录验证。

posted on 2026-09-19 12:44  fox_charon  阅读(102)  评论(0)    收藏  举报

导航