ZCode 静默上传 Git 历史:一次逆向取证的完整复盘
写作日期:2026-09-19 事件性质:AI 编程工具隐私 / 供应链信任 信源:ferstar 原始取证博客(2026-09-18)、Tokenstead 深度梳理、AIHOT 聚合(精选池最高分 86) 一句话:Z.ai(智谱)官方 AI 编程桌面应用 ZCode,只要处于登录状态,就会把你的整个工作区——连同完整
.git历史——加密打包上传到阿里云 OSS,而解密私钥只存在于 Z.ai 的云端。
0. TL;DR
- 触机:登录即触发,与是否使用无关。单次活跃会话日志里最多记录了 62 次捕获事件。
- 范围:不是"发送给模型的上下文",而是整个仓库快照——一次实测 42,411 个文件、313MB,其中
.git目录占 86.6%。 - 最关键的设计:信封加密(AES-256-CTR + RSA-OAEP-SHA256),但 RSA 公钥由服务端随凭证下发、私钥从不下发。用户连自己磁盘上那份 313MB 密文都打不开,ZCode 客户端自己也打不开。
- 设置开关是摆设:两个看起来能关掉它的开关,经代码比对均只控制"训练授权"和"服务端索引",本地打包上传照常运行。
- 删除无用:删掉待传归档,半小时内重新打包,重试计数器从 564 涨到 565。
- 隐私政策只字未提。截至原文发布(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"——按字面理解,这更接近"确认机制存在"。后续是否有修复、披露变更或正式声明,建议回原文链接跟进。
参考与核对
- ferstar 原始取证:Inside ZCode: Silently Uploading Your Entire Git History to the Cloud — https://blog.ferstar.org/en/posts/zcode-silent-workspace-snapshot-upload/
- Tokenstead 深度梳理(含 Grok Build 对照与传播数据):https://tokenstead.ai/guides/zcode-silent-git-history-upload
- AIHOT 聚合条目(精选池 86 分,2026-09-18 收录):https://aihot.news/items/cmu6y9sjz0jbyrowkh7tus28l
- V2EX 社区讨论:https://www.v2ex.com/t/1242973
- ferstar / FeiZ 的 X 帖子、ZCode 官方 changelog 见 Tokenstead 文末来源列表
核对提醒:本文全部数字、代码与引语来自上述两篇原文与 AIHOT API 返回内容;Z.ai 官方回应状态以 2026-09-18/19 的信源为准,引用任何数字前建议回原文核对。文中 Windows 防御命令为本 Blog 补充的等效思路(原取证文章只覆盖 macOS/Linux),使用前请先在测试目录验证。
posted on 2026-09-19 12:44 fox_charon 阅读(102) 评论(0) 收藏 举报
浙公网安备 33010602011771号