本来只是清理磁盘时顺手看一眼 ~/.zcode 为什么占了 700 多 MB。断断续续排查下来,确认了一件挺离谱的事:
只要你在登录状态,ZCode(智谱官方的 AI 编程桌面端)就会在后台静默把整个工作区——包括完整的 .git 历史、LFS 大文件缓存、reflog 以及全局应用配置——打包加密,直接上传到阿里云 OSS。
更讽刺的是:加密用的 RSA 公钥是服务端动态下发的,私钥只存在云端。 你本地生成的那份几百兆的密文,连你自己和客户端本体都解不开。
这篇文章把排查过程、证据链和最终的一键防御方案完整记录一下。
(注:事件已迎来官方回应与开源进展,9-21 及 9-23 最新源码对账与实测见下方更新;初次阅读可直接顺着下文查看原始排查记录)
2026-09-23 更新 #
今天(9-23)傍晚,ZCode 客户端推送了 v3.14.3,随后公开代码仓库提交了对应 commit(328c1a0 feat: update v3.14.3),开源仓库与发布版本号保持了同步。
不过需要理性指出:版本号对齐并不等同于闭源发布版本与开源仓库完全 1:1 对等,未经逐字节对比不能做绝对判定。这里只呈现针对开源仓库 Diff 以及本机运行状态的客观核验结果:
- 开源仓库 Diff 审查:
- 审查了该 commit 包含的 282 个文件变动,此前捕获的
/api/v1/snapshot/upload-credential与 AES-256-CTR 整仓打包流程依然未见回退; - 检查点实现(
gitCheckpointService.ts)仍维持纯本地 Git CLI 增量调用; - 本次新增代码主要集中在多渠道 IM Bot 接入(微信、飞书、钉钉、Telegram 等远程控制能力)及 Workflow 协议优化。
- 审查了该 commit 包含的 282 个文件变动,此前捕获的
- 本机客户端运行实测:
- 本机应用已更新至 3.14.3,解包静态资源(
app.asar)未发现repoSnapshot逻辑; - 动态监控日志与
~/.zcode/v2/checkpoints/目录,目前未见全仓扫描、打包或新生成.enc文件的行为。
- 本机应用已更新至 3.14.3,解包静态资源(
每次发版能有公开 Diff 对账,总比盲盒迭代让人踏实。不过对闭源桌面软件来说,代码怎么写是一回事,本地怎么跑是另一回事——后文给出的目录只读锁定,依然建议留着当个保底防御。
2026-09-21 更新 #
今天(9-21)上午 9:23,ZCode 官方在 X 上发了通报声明,同时也放出了所谓的开源核心代码仓库(GitHub: zai-org/ZCode)。随着源码公开以及官方引述的第三方评估结论出炉,此前官方通告里的几处说法,终于有了可以拉出来逐一对账的代码依据。
1. 源码审查:只留两条 Commit 的丐版开源(锁 PR、关 Issue) #
翻开刚放出来的 Git 仓库,意料之中,果然是一贯的大厂防守刀法:
- 历史记录被彻底洗净:整个仓库一共就两条提交记录——一个空的初始 Commit,加上一个一次性导入 6,973 个文件、103 万行代码的
feat: open source。开源仓库自身的内部开发历史被彻底抹平,既看不到旧版 sidecar 的代码演进,更看不到剥离repoSnapshot管道的具体 Commit Diff。而且仓库锁了 PR、关了 Issue,纯属单向投喂式的“开源”。 - 开源协议:Apache-2.0。
- 上传链路全量清除:在源码里搜此前捕获的
/api/v1/snapshot/upload-credential、PostObject直传 OSS、AES-256-CTR 加密及公钥下发,全被拔得干干净净,没有留存一行残留代码。 - Repo Wiki 彻底淡化:代码库里除了留着一个外部 Wikipedia 链接,移除了所有关于 Repo Wiki 的实现。现在的代码索引与符号检索,退回为纯本地进程调用内置的
ripgrep 14.1.1与bfs 4.1.1——事实证明压根不需要传整仓,本地检索照样跑得好好的。 - 求生欲拉满的 NOTICE.md:更有意思的是根目录下那份长达 2.7 万字节的
NOTICE.md。法务用密不透风的措辞,把每一个对外请求甚至连已被掏空的 Computer Use 占位符都用免责条款严密武装,唯独对“整仓上传”绝口不提。
2. 检查点(Checkpoint)机制穿帮:源码证实纯属本地 Git #
此前官方说明声称“上传代码库快照是为了用于会话检查点回滚”。源码一放出来,我顺藤摸瓜直接找到了检查点的真实实现(packages/services/src/git/gitCheckpointService.ts 与 gitCheckpointRepo.ts):
- 实现原理:核心全在本地跑 Git CLI,用
git diff --name-status和git diff --numstat基于 Commit OID 仅在当前 Workspace 范围内算增量改动; - 存储位置:元数据以 JSON 格式直接存在本机的
~/.zcode/checkpoints/; - 事实印证:开源源码证实,检查点实现纯粹是基于本地 Git 的增量比对工具,本身完全没有云端依赖,与整仓打包上云毫无关系。此前官方拿“检查点回滚”当理由来解释整仓上传,在源码面前完全站不住脚。
3. 第三方评估与“删桶自证” #
官方通报里摘要引述了中国信通院(CAICT)与绿盟科技(NSFOCUS)的评估结论(声明称完整报告即将发布):
- 通报结论:CAICT 确认
zcode-prodOSS 存储桶数据为零,客户端 3.14.0 移除了快照生成与上传工作流;NSFOCUS 确认该存储桶及内部对象已全部删除,未发现外传本地文件的路径。 - 客观评价:能主动把桶清空并彻底销毁,确实是应有的止损动作。但在技术与时间线逻辑上,哪怕未来出具完整的第三方静态核验报告,也只能证明“存储桶在 9-20 之后确实被删了”,客观上无法倒推 9-18 之前流转进去的数据到底有没有被解密、同步或备份。历史数据的生命周期,始终无法靠事后单次审计来倒推自证。
4. 舆论场中的几点澄清与现实黑色幽默 #
原推阅读量破了 160 万后,流量正逐渐降温,顺带澄清几个有意思的插曲:
- 我并非“社区开发者”,更不是什么“外网黑客”:官方通报里笼统地写着“感谢社区开发者提出问题”(实际上别说 @ 我了,哪怕隔空点名致谢都没有),公关降级修辞罢了。我压根不是这项目的贡献者,只是个自掏腰包买了 Coding Plan 搬砖、发现商业代码被未授权偷运后摆抓包证据的普通一线工程师。至于网传的“神秘黑客”、“逆向大佬”,全属扯淡,排查过程无非是看磁盘、看路由日志和解包。至于少数扣帽子说“给外网递刀子”的声音,更是荒唐——发现自己商业代码资产裸奔时,站出来自保是基本的职业本能。如果指出房间里的大象是“递刀子”,难道保持沉默任由代码被偷运才算支持?真正的技术生态从来不是靠捂盖子捂出来的。
- 那个被传为“凌晨两点发文”的真实故事:各处转发里,几乎所有人都按本文开头的时间,当我是“凌晨两点熬夜发文”。其实这恰好是“尽信 AI 则不如无 AI”的绝佳现实讽刺——我的真实 Git 提交记录清清楚楚写着
Fri Sep 18 10:35:05 2026 +0800,是大白天上午 10 点半提交的。之所以 Front Matter 挂着02:00:00,纯粹是因为写完文章后偷懒让 AI 帮我生成 Hugo 元数据,AI 脑补了这个时间,而我当时急着验证路由阻断没仔细核对。本意是写文章提醒大家切勿盲信 AI 的后台行为,自己却反手被 AI 编的时间戳坑了一道,引得全网脑补了一出“深夜突袭”的大戏。乐呵之余,这大概是近期最接地气的一个提醒:任何时候偷懒全盘信了 AI,它都能冷不丁坑你一把。
2026-09-19 更新 #
原文发出后引发广泛关注,9-18 当天 17:44,官方在用户群发出了说明通告(IT之家等媒体随后于傍晚跟进报道)。下面只做客观对照,后面的原文排查记录不改。
官方说法 #
官方于 9-18 17:44 发出说明,公开报道可参考 IT之家。要点如下:
- 问题出在「代码库索引」,用来做本地索引、会话检查点回滚,以及 Repo Wiki;
- Repo Wiki 在云端生成页面时「可能」触发仓库数据上传;
- Wiki 生成后,相关上传数据会立即销毁、不会保存;
- 上线初期默认开启,部分用户受影响,问题「已经修复」;
- 近期开源 ZCode,并引入第三方审查;全体用户额外一次周额度重置。
官方对「存在上传行为」已无异议。真正存疑的是上传范围、控制开关,以及云端所称的「立即销毁」如何验证。
逆向与实测对照(旧版 3.12.3 vs 新版 3.14.0 vs 官方说法) #
为了避免混淆不同版本下的实际表现,这里将捕获问题时的旧版(3.12.3)、最新升级的 3.14.0 逆向排查结果与官方说法逐一拉平对比:
| 维度 | 官方说法 (9-18 17:44) | 3.12.3 旧版客户端实测与逆向 (问题版本) | 3.14.0 最新版客户端实测 (修复版本) |
|---|---|---|---|
| 传了什么 | 仓库数据(用于 Wiki) | 整仓快照,近 87% 是 .git,含 objects / LFS / reflog |
该上传链路代码已被彻底移除,仅保留本地 checkpoint |
| 触发时机 | Repo Wiki 生成页面时「可能」上传 | sidecar 伴随登录常驻;captureBeforePrompt(每次提问前)与 repo-wiki-update 均无条件触发 |
上传 sidecar 逻辑已拆除,不再触发打包上云 |
| 关不关得掉 | 未说明有关闭上传的开关 | 关掉「优化体验」与「仓库快照索引」后,后台依然打包并尝试直传 OSS | 代码层已物理删除上传管线 |
| 云端处理 | 生成后立即销毁 | 外部无法验证(且与官方声称的“会话检查点回滚”机制在工程逻辑上自相矛盾) | 云端 upload-credential 接口已下线(返回 404) |
| 存量数据 | 称 Wiki 生成后不保存 | 538 文件的小公开仓快照已被服务端正式接收入库,存量私钥与解密权限未作说明 | 存量已上传数据是否物理销毁依然无法从外部证实 |
补一条原文没写清的:313MB 那个商业项目卡在 pending(失败 564 次),没有传成功。另有一个很小的公开仓库工作区,538 个文件、压缩加密后约 15KB,状态是服务端已接收。所以「有没有真传出去」——至少这一次传出去了。
Windows 上别人也复现了同一套目录和状态文件,还有多份无失败记录、看起来已经入库的小仓,见 NodeSeek;另一份本机对照见 silencestar。
9-18 当天存过 官方隐私政策 快照,页面仍标注更新于 2026-06-15,当时通篇没写整仓快照或云同步。
顺便补几句澄清 #
- 313MB 的商业项目到底传上去没:再明确一下,那个 313MB 的商业项目因为体积超标失败了 564 次,一直死死卡在本地
pending,压根没传成功。我家里跑的是 OpenWrt 路由,拉了连接跟踪和流量日志反复对过,这几百兆加密包根本没出局域网。 - 真不是什么「逆向高手」,全靠丐版 MBA 立功:看到有人给我扣「逆向大佬」的帽子,大可不必。整件事的起点非常朴素——完全得托苹果比金子还金贵的存储,我那台丐版 256G 的 MacBook Air 空间天天捉襟见肘,清磁盘时扫到
~/.zcode莫名其妙吞了 700 多 MB 空间,直觉不对劲才顺手 unpack 了app.asar去翻翻到底在搞什么鬼(要是在我另一台 2TB 盘的 Arch Linux 上,我才懒得管区区几百兆的占用)。况且就现在 coding agent 的逆向和代码还原水平,大街上随便拉一个 agent 出来把帖子和源码甩过去都能给你分析得头头是道,人人都是逆向工程师,真谈不上什么独门绝技,纯属工程师的基本排查本能。 - 最打脸的戏剧性:其实我自己本就是 GLM Coding Max 的长期订阅支持者。更讽刺的是,9-17 晚上我还在技术群里热心跟群友安利 ZCode,结果不到半天就当场打脸,自己顺藤摸瓜抓到了这个静默打包全仓的骚操作。
防御还算不算数 #
算。虽然 3.14.0 拔除了代码且网关 404,但客户端随时具备热更新能力。下面「锁目录」那节依然作为绊线保留,Windows 等价做法 NodeSeek 那篇写了 ACL。
还没回答的 #
- 「立即销毁」如何从外部证明?云端存量已上传的加密快照是否已彻底物理清除、谁保留了私钥解密权。
- 官方声称的“检查点回滚”与“立即销毁”前后逻辑冲突,云端到底留存了什么。
- 开源会不会带上被捕获的旧版上传 sidecar,而不只是最新一次 commit。(9-21 源码确认:未带历史,仅 2 条 commit 彻底抹平,见文首 9-21 更新)
(注:开源对账已在文首 9-21 节更新)
起点:一个卡在 pending 里的 313MB 压缩包 #
~/.zcode 是 ZCode 的本地数据根目录。当时扫出来的目录体积分布大概这样:
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
}意思很直白:
- 客户端扫了我本地打开的商业项目,排除了
node_modules等少量目录后,把剩下的 345MB 内容打包加密成了 313MB 的压缩包,标记为baseline(全量快照); - 状态显示它尝试上传失败了 564 次,所以一直卡在本地
pending/目录里等着重试。
整个项目总共 10GB,排除掉依赖后剩下的 345MB 几乎全是核心资产。
它要传到哪:从日志到 asar 逆向 #
日志里没有直接打出具体的上传地址,于是顺手把客户端的 app.asar 扒开看代码。整条上传链路还原出来如下:
sequenceDiagram
participant C as ZCode 客户端
participant S as zcode.z.ai
participant O as 阿里云 OSS
C->>S: POST /api/v1/snapshot/upload-credential
S-->>C: snapshot_id + RSA公钥 + max_size + OSS表单凭证 + callback
C->>C: tar.gz 打包 → AES-256-CTR 加密 → RSA-OAEP 包裹密钥
C->>O: PostObject 表单直传 tar.gz.enc
O->>S: callback 回调确认接收
整个流程分两步:
- 先找协调服务器要凭证:客户端请求
https://zcode.z.ai(代码里的VITE_ZCODE_ENDPOINT_ORIGIN),服务端返回 OSS 表单签名(policy、x-oss-signature)、动态分配的 Object Key、大小限制,以及本次加密要用的公钥; - 表单直传 OSS:客户端在本地打包并流式加密后,根本不经过智谱自己的业务服务器,直接走 HTTP POST 表单把
tar.gz.enc甩给阿里云 OSS。传完之后由 OSS 服务端触发 callback 通知智谱后端登记。
抓包看了下 ZCode 运行时的连接,进程常驻的 HTTPS 连接刚好就是 zcode.z.ai 的解析 IP 外加两个阿里云 OSS 节点。
最讽刺的部分:这把钥匙是服务端的 #
客户端用的加密是标准的信封加密(Envelope Encryption):
keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)- 文件内容用随机生成的对称密钥走 AES-256-CTR 加密;
- 对称密钥用 RSA-OAEP-SHA256 包裹,公钥就是上面从服务端动态拿到的。
关键就在这把 RSA 公钥:它是服务端在下发上传凭证时临时给的,私钥从头到尾只在云端。 我拿本机的所有私钥去解 envelope 里的密钥,毫无悬念全都失败。
这意味着:你硬盘上那份 313MB 的密文,你打不开,ZCode 客户端自己也打不开,全天下只有智谱后端的私钥能解。
如果这玩意真是为了给用户做断点恢复或者跨设备同步,密钥理应绑在本地(像 Git 或者 Time Machine 一样)。一把只有服务端能解开的钥匙,目的只有一个:确保服务端单方面能看。
快照里到底装了啥:近九成是 .git #
密文虽然解不开,但快照生成时留下的 Manifest(文件清单)是明明白白写在本地的。拉出这份包含 42,411 个文件的清单统计了一下:
| 内容 | 体积 | 占比 | 包含的信息 |
|---|---|---|---|
.git/lfs/ |
196.1 MB | 56.8% | LFS 缓存,项目历史下拉过的所有大文件和二进制资产 |
.git/objects/ |
102.2 MB | 29.6% | 完整的 Git 历史对象库(Commit、Tree、Blob) |
.git/logs/ |
0.6 MB | 0.2% | reflog 轨迹,本地所有分支操作和未推送记录 |
| 其余源码与文档 | ~46.2 MB | 13.4% | src/、各类配置文件和业务代码 |
.git 一个目录就占了整整 86.6%。
也就是说,只要这个包传上去了,云端拿到的绝不只是你当前工作区的代码,而是这个仓库自打创建以来的全部历史底裤:
- 哪怕早就被覆盖删掉的敏感配置和历史 key;
- 本地还没推远端的分支名(直接暴露业务未公开的研发动向);
.git/config里配的内部自建 GitLab 域名和仓库路径。
另外代码里还带了一个 repo_snapshot_extra_manifest,会把你的 ZCode 全局配置文件(比如 settings.behavior.json)算完哈希,跨工作区打包,随每次快照一起传上云端。
开关的真相:你关掉的开关根本管不着它 #
很多人第一反应是去设置里找开关关掉。我把设置项和代码逻辑逐个对了一遍:
| 开关 | 你以为它管 | 实际管 |
|---|---|---|
优化体验 (optimizeAgentExperienceEnabled) |
数据采集 / 遥测上传 | 只管要不要拿你的数据去训练模型。关了照样抓快照上传 |
仓库快照索引 (repoSnapshotIndexingEnabled) |
快照功能本身 | 只管服务端拿了快照后要不要建索引。关了本地打包上传一点不落 |
看客户端组装代码更直白:负责快照捕获和上传的 sidecar 是在启动时无条件实例化的。代码里根本没有任何针对用户配置的 if 判断,唯一的要求就是 tokenProvider 能拿到登录后的 JWT。
一句话:只要你登录了账号,这个上传机制就是常开的,而且 UI 里没有任何开关能把它关掉。
抓取触发点有两个:一个是 captureBeforePrompt(每次发 Prompt 前),另一个是任务结束时标记 repo-wiki-update。翻看日志,单个活跃会话里最多能产生 62 次快照捕获。
隐私政策怎么说的 #
翻了下 ZCode 的隐私政策,里面明确写了会收集“对话中提交的文本、文件和代码”——这属于 AI 助手调模型推理的常规操作,各家都一样。
但通篇只字未提会把整个工作区连同完整 Git 历史静默打包上传。官方文档、FAQ 和更新日志里对此也完全没有任何说明。
能对得上的只有一句万能套话:“优化计划默认关闭,不主动加入不会将输入用于训练”。
防御:删是打地鼠,直接锁目录 #
刚发现这个 pending 包时我顺手把它删了,结果半小时内它又重新抓了一次——新的 313MB 压缩包,失败计数从 564 蹦到 565。上传器发现本地文件没了,直接重新打包一个。纯靠手动删就是打地鼠。
最直接有效的办法是在文件系统层加不变锁,从内核层面禁止写入这个目录:
macOS #
# 清空并锁定 checkpoints 目录
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# 验证:应该输出 Operation not permitted
touch ~/.zcode/v2/checkpoints/testLinux #
# 清空并锁定 checkpoints 目录
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints
# 验证:应该输出 Operation not permitted
touch ~/.zcode/v2/checkpoints/test影响与恢复 #
- 阻断效果:快照逻辑在尝试写目录时直接被系统内核拦截,没有产物,后续向 OSS 的直传自然无从谈起;
- 功能影响:ZCode 的“检查点回滚 / 时间线”功能不可用(本来也是拿全量代码上云换的)。日常的代码补全、对话、工具调用完全正常,日志里被吞掉的 IO 报错不影响使用;
- 想恢复:执行
chflags nouchg ~/.zcode/v2/checkpoints(macOS)或sudo chattr -i ~/.zcode/v2/checkpoints(Linux)即可。
写在最后 #
用 AI 工具,模型推理必然要吃代码上下文,这个在用的时候大家心里都有数。但这事越线的地方很明确:
一是数据范围。推理给的是当前任务相关的上下文,而快照是把整个仓库连同几年的 Git 提交历史全部打包端走。
二是架构姿态。如果真是给用户做断点恢复或跨端同步,解密密钥理应在用户本地。一把只有服务端能解的钥匙、零披露的隐私政策、关不掉的默认行为、删了还自动重传的执拗——这摆明了不是备份,更像采集。
工具没有原罪,但红线应该由使用者自己划。既然软件里关不掉,那就用操作系统的锁把它关进笼子里。