ZCode 静默上传后续与开源代码审查
9 月 18 日凌晨,一位开发者清理磁盘时,在自己的 AI 编程工具数据目录里发现了一个 313MB 的加密文件。旁边的状态文件写着:这是他某个 10GB 商业项目的全量快照,已经向云端重试上传了 564 次,全部失败。当天他在博客公开了完整的逆向分析:智谱旗下编程工具 ZCode 的桌面客户端,会在登录状态下自动把整个工作区打包加密上传,内容不止源代码,还包括完整 Git 历史、LFS 大文件缓存、reflog 和部分全局配置。
我们当天发布了第一篇分析,用两台现成设备做了交叉取证,确认了机制、修正了范围。四天后,智谱宣布 ZCode 开源。开源版本里还有没有这个功能、闭源时期它存在了多久,是事件留给所有用户的两个时间问题。本篇按两步回答:先核查开源源码,再检测能找到的六个历史版本安装包。
事件回顾
先把已经确认的事实摆在一条线上。
爆料方还原的机制是这样的:客户端向 zcode.z.ai 请求 POST /api/v1/snapshot/upload-credential,服务端返回快照编号、RSA 公钥和 OSS 表单签名;本地把工作区打成 tar.gz,用随机对称密钥做 AES-256-CTR 加密,再用服务端下发的 RSA 公钥把对称密钥包裹起来,表单直传阿里云 OSS。打个比方:箱子用随机钥匙锁上,钥匙本身装进一个只有服务端能拆的信封——用户拿到密文,自己打不开自己的箱子。加密私钥只存服务端、界面没有关闭入口、删除本地包后会自动重打包重传,是爆料里争议最大的三点。爆料方对其某个快照清单的统计显示:42,411 个文件里 86.6% 来自 .git 目录,其中 LFS 大文件缓存 196.1MB(56.8%)、Git 对象库 102.2MB(29.6%)、reflog 0.6MB;单个活跃会话最多产生 62 次快照捕获。

我们第一篇的分析用两台设备做了交叉取证。一台 Linux 服务器跑着 ZCode 的无界面形态:31 个任务全部是远程 SSH 会话,检查点目录始终是空的,日志里没有上传痕迹。另一台 macOS 桌面端装着闭源的 3.12.3:十个工作区的状态文件里,记录的路径全部是本地目录,没有一个远程。代码里有一道门卫——远程会话带着身份标记,看到标记直接跳过;本地目录没有标记,才进采集队列。桌面端还有两个更硬的事实:两个隐私开关都处于关闭状态,而 9 月 17 日 13:53 仍有一次快照上传成功,开关不是这条链路的闸门;十个工作区里八个处于"已上传完成"终态,完整加密包已经抵达云端。那篇的结论:机制成立,"全员中招"不成立——远程 SSH/WSL 会话被门卫豁免(这更像采集器只实现了本地目录遍历的架构边界,而非隐私善意),但桌面端本地用户进入过采集管线,且数据确认出境。
官方的回应节奏随后展开:
| 日期 | 事项 |
|---|---|
| 9 月 18 日 | 智谱在 ZCode 官方用户群致歉:归因于"代码库索引"功能,称 Repo Wiki(仓库百科)云端生成页面时触发上传、“生成完成后立即销毁”;承认默认开启且无关闭入口;承诺开源、第三方审查,补偿全体用户一次周额度重置 |
| 9 月 19 日 | 推送 v3.14.0,更新日志写明"修复仓库百科异常上传的问题";当晚太原一家企业用户公开致函,自查称 8 月 28 日至 9 月 14 日有超过 6 个工作区被上传、最大单包 391.94MB,质疑"声称 18 日已修复但 18 日凌晨仍检测到上传",提出十余项书面答复要求并保留法律行动权利 |
| 9 月 20 日 | 争议转向数据出境:客户端请求域名英文版协议的数据控制者为智谱在新加坡注册的全资子公司,中文版隐私政策却写明数据存储于境内、跨境需征得同意 |
| 9 月 21 日 | 官宣开源(GitHub 托管,接受社区审查);公布两家第三方机构首轮核查:涉事 OSS 存储桶"云端零数据"、存储桶已删除、v3.14.0 未发现可触发本地仓库快照或文件外发的功能路径;官方表态代码数据无服务端留存、未用于模型训练;当日股价跌超 4% |

开源把一个技术问题变成了可以公开验证的问题:代码就在仓库里,谁都能查。我们查了。
开源源码的核查
核查对象是 GitHub 上的开源仓库,对应 3.14.0。方法是把爆料涉及的专有命名逐个放进全仓库检索:捕获入口 captureBeforePrompt、类型名 RepoSnapshot、状态字段 lastAcceptedManifestHash、凭证句柄 uploadCredentialHandle、上传动作 attemptUpload、任务结束标记 repo-wiki-update 等十六个强特征串,再用 git log -S 对全部提交历史做增删检索。两路结果一致:零命中。
第一个结论:上传功能的实现代码不在开源仓库中。官方"移除了相关上传链路"的说法,在源码层面成立。
但这个仓库回答不了时间问题。git 历史只有两个 squash 提交——提交被压缩过,功能何时加入、何时移除的开发轨迹在仓库里不存在。源码只能证明"现在没有",证明不了"以前什么时候有"。
疑似遗留的痕迹
检索过程中发现两处值得注意的残留,如实记录如下。
第一处:存储目录管理模块(storageCatalog)里保留着两条清理规则,指向 v2/checkpoints/ 下的 pending 和 tmp 子目录。问题在于,开源代码里已经不存在任何会向这些目录写入数据的逻辑——规则还在,规则服务的写入方消失了。
第二处:ARMS 遥测的白名单路径段里包含 "snapshot" 字样。遥测配置为什么要给一个不存在的功能路径留位置,源码内部同样无法解释。
这两处按实说只能算疑似遗留:它们与爆料描述的机制对得上(闭源客户端确实把待上传队列放在 v2/checkpoints 下),但仅凭源码无法排除其他解释,也无法定案。源码阶段能写下的结论只有一句:上传功能没有源码,有两个来历不明、暂无写入方的残留。
要确定这两个残留以前是不是活的功能部件、功能到底存在了多久,需要闭源时期的发行包。
历史安装包的检测
ZCode 的桌面发行包托管在官方 CDN 上,直链形如 https://cdn-zcode.z.ai/zcode/electron/releases/<版本>/<平台>/<文件名>,源站是阿里云香港区域的 OSS 存储桶。检测的前提是一个有些滑稽的事实:开源前的"清理"只删除了 CDN 上的 macos-arm64 目录,x64、Windows、Linux 的历史包大量存活。3.4.x、3.5.x、3.7.x、3.9.x 与 3.13.x 全系已被删除;其中 3.5.2 下载到 140MB(共 165MB)时连接断流、源入口回落 404,3.4.2 的 Linux 包只拿到 2MB——更早的版本就此永久失去取样可能。
探测过程本身有三个必须写下来的坑。其一,CDN 对 HEAD 请求一律返回 200,不校验对象是否存在,探测必须用真实拉取字节的分段请求;过程中一次偏移量笔误意外返回 416,而 416 恰好只在对象存在时出现,此后被用作存在性信号。其二,边缘节点对已删对象有残影缓存,首次分段请求可能返回 206,一旦断流即回落 404 且无法续传。其三,也是最影响结论的一次:3.11.2 首轮下载因 HTTP/2 断流比标称少了约 0.69MB,解包工具静默解出 0 个文件,首轮一度误判为"不含该功能";字节数复核加断点续传补齐后,复扫翻转为"存在"。另有一次反向教训:扫描 3.14.0 时检索命令少给了路径参数,读到了命令文本自身,产出"每个特征恰好命中 1 次"的完美假象,修正参数后实为全零。自那以后定下规矩:每一个"未命中"结论,都必须先证明样本完整。
最终完整取得的样本是六个(发布时间为公开渠道可考口径,推断项已标注):
| 版本 | 平台 | 大小(字节) | 发布/分发时间 |
|---|---|---|---|
| 3.6.5 | mac-x64 | 188,448,730 | 2026-08 下旬~09 上旬(快照推断) |
| 3.8.1 | mac-arm64 | 180,045,653 | 约 2026-09 上旬(推断) |
| 3.11.2 | mac-arm64 | 235,528,887 | 2026-09-10 前已在售(快照确证) |
| 3.12.1 | mac-arm64 | 235,710,437 | 2026-09-10 至 09-17 间(推断) |
| 3.12.3 | mac-arm64 | 235,801,106 | 2026-09-17 前已分发(设备确证) |
| 3.14.0 | mac-arm64 | 255,178,532 | 2026-09-19 推送(更新日志确证) |
六个样本的哈希值均已存档。扫描利用了应用打包的一个特性:Electron 应用把全部主进程与前端代码打进一个名为 asar 的包文件,代码经压缩混淆后变量名面目全非,但字符串字面量原样保留;编译成 v8 字节码的部分,字符串常量也留在常量池里,可以直接提取。判读分三组:强特征(爆料专有命名,第三方库不可能出现)、技术特征(加密算法与接口路径,需看上下文定性)、对照特征(与上传无关但必须命中的良性串,用来确认扫描环节本身有效)。判定"存在"要求强特征命中在业务代码而非第三方依赖目录;判定"不存在"要求全部扫描面零命中,且样本先通过完整性验证。
检测结果
六个版本的业务代码强特征计数如下:
| 特征串 | 3.6.5 | 3.8.1 | 3.11.2 | 3.12.1 | 3.12.3 | 3.14.0(开源) |
|---|---|---|---|---|---|---|
| captureBeforePrompt | 5 | 5 | 5 | 5 | 5 | 0 |
| lastAcceptedManifestHash | 6 | 6 | 8 | 8 | 8 | 0 |
| lastAcceptedExtraManifestHash | — | — | — | — | 6 | 0 |
| markAcceptedManifest | — | 3 | — | — | 3 | 0 |
| clearAcceptedManifest | — | 2 | — | — | 2 | 0 |
| failPendingUpload | 3 | 3 | 3 | 3 | 3 | 0 |
| requestUploadTarget | — | 2 | — | — | 2 | 0 |
| repo_snapshot | 7 | 8 | 11+1ᵃ | 11+1ᵃ | 11 | 0 |
| RepoSnapshot | 47 | 47 | 60 | 58 | 58 | 0 |
| optimizeAgentExperience | 63+7ᵃ | 70 | 78+7ᵃ | 78+7ᵃ | 78 | 0 |
| repoSnapshotIndexing | — | 40 | — | — | 40 | 0 |
| repo-wiki-update | 1 | 1 | 1 | 1 | 1 | 0 |
/api/v1/snapshot/upload-credential |
✅ | ✅ | ✅ | ✅ | ✅ | 0 |
带 ᵃ 的格子是两个数字:ZCode 安装包里装着图形界面本体和随包内置的命令行工具两套程序(后者代码在 glm/zcode.cjs),扫描两套都做了——加号前是图形界面的命中次数,加号后是命令行工具的命中次数。判读要点有三个。第一,五个闭源版本全部命中全部核心特征,凭证接口路径逐字存在,五点连续、无空隙。第二,计数高度稳定:captureBeforePrompt 在五个版本里恒为 5 次,RepoSnapshot 保持在 47~60 的同一量级——功能形态在 3.6.5 到 3.12.3 之间没有大改,只有增量(lastAcceptedExtraManifestHash 在 3.12.3 才出现,对应"把全局配置跨工作区一并打包"的能力,是后期加上的)。第三,3.14.0 全零:全部扫描面(含字节码、agent bundle、asar 本体)零命中,仅有的命中是第三方依赖里的通用加密库,以及一个用户主动提交问题反馈的附件上传链路——后者与快照上传无关。
以内容最完整的 3.12.3 为例,业务代码里能看到这样一条完整链路(字符串为包内原文):
- 发送指令前的捕获入口:prompt 处理流程内联调用
captureBeforePrompt,入参含工作区路径、身份标识与任务编号; - 采集调度器:带超时与中止参数的后台队列,
maxPendingCaptureIntents控制待捕获上限; - 远程门卫:
t.workspaceIdentity?.trim()||await this.captureScheduler.schedule(t,…)——身份非空(远程会话)即短路,本地目录才调度捕获,与我们第一篇设备取证的门卫行为逐字同构; - 任务结束的第二次捕获:
captureTaskCompleteUpdate,内容标记为"repo-wiki-update"; - 凭证接口:
"/api/v1/snapshot/upload-credential"逐字存在,配套还有一条中文错误串"upload-credential 返回了非法 max_size,已忽略该字段"; - 信封加密:
envelopeInput结构写明contentAlgorithm:"aes-256-ctr"、keyWrapAlgorithm:"rsa-oaep-sha256"、带keyId版本号,算法不符时抛出"unsupported repo snapshot key wrap algorithm"; - 本地状态机:
getRepoSnapshotStatePath指向state.json,存储目录名为"repo-snapshots",打包用tar.gz; - 隐私开关:
optimizeAgentExperienceEnabled默认值为 false——默认关闭的开关,与第一篇"开关关闭期间仍有上传成功"的行为证据吻合,说明开关从来不是上传的闸门。
有两项细节本次静态取证未能验证,如实标注:打包归档内 .git 的内部清单(对象库、LFS、reflog 的具体构成)没有逐项展开核对;prompt 原文是否随包上传未获直接证实,仅确认捕获入参带有 prompt 相关的元数据字段。此两点不影响功能存在性的判定。
还有一个呼应值得单独说。上一节源码核查里那两条"疑似遗留"的清理规则,在 3.12.3 的发行包里同形存在,而且当时对应的是真实运行的上传 pending 队列。闭源版本里它是活的功能部件,开源版本里成了没有写入方的孤儿规则。源码阶段无法定案的痕迹,由发行包补上了前史——它们确系快照上传机制的遗留,只是功能本体已被移除。
版本时间线的还原
综合检测结果与公开渠道的版本时间信息,六个样本的判定如下:
| 版本 | 上传能力判定 | 时间依据 |
|---|---|---|
| 3.6.5 | 有,完整实现 | 发布于 2026-08 下旬至 09 上旬(官网历史快照推断),已知最早实锤 |
| 3.8.1 | 有,完整实现 | 约 2026-09 上旬(据版本序列与官网快照区间推断) |
| 3.11.2 | 有,完整实现 | 2026-09-10 官网在售(历史快照确证) |
| 3.12.1 | 有,完整实现 | 约 2026-09 中旬(介于 3.11.2 与 3.12.3 在售时点之间推断) |
| 3.12.3 | 有,完整实现 | 2026-09-17 前已实际分发(第一篇设备取证所见安装版本),闭源末期 |
| 3.14.0 | 无,零残留 | 2026-09-19 推送,更新日志"修复仓库百科异常上传的问题";9-21 开源,与源码同源一致 |
| ≤3.5.x | 无法考证 | 源站已删,样本永久不可得 |

时间锚点来自官网的历史快照:2 月 12 日在售 0.20.1,4 月 13 日在售 0.27.1,6 月 14 日在售 3.0.1,8 月 6 日在售 3.1.6/3.1.7/3.1.8,9 月 10 日在售 3.10.1/3.10.2/3.11.2。据此框出 3.6.5 的发布窗口在 8 月下旬到 9 月上旬之间——也就是说,这套上传机制至迟在 2026 年 8 月下旬已经随正式版本分发,到 9 月 19 日修复版推送,公开可考的存在时间不少于三周;由于更早版本已从源站删除,真实的引入点只会更早、无法考证。
移除的时间点可以收得很窄:3.12.3 是可取样闭源版本中的最后一个,仍带完整实现;3.14.0 干净无残留;移除发生在两者之间,恰好压在开源发布节点上。这个结果与第三方核查机构"未发现可触发本地仓库快照或文件外发的功能路径"的结论互相印证。还有一个不易注意的细节:3.13.x 全系在源站被删除得最彻底——那是闭源时期的最后一个小版本段。
另有一处外部互证。发函的那家企业用户在函件中称,其工作区自 8 月 28 日起被上传。这个日期落在 3.6.5 的发布窗口之内,与企业独立自查的结果、发行包的检测结果三方对齐。
与官方口径的对照
把官方说明、企业函件和我们的检测摆在一起,有三处值得对照。
触发点的归因。官方把上传归因于 Repo Wiki 云端生成页面时的动作。发行包里能看到至少两条触发路径:一条挂在任务结束,带仓库百科更新标记,对应官方说法;另一条挂在每次发送指令之前,与 Wiki 页面无关。官方说明只覆盖了其中一条。
留存的口径。官方称数据"生成完成后立即销毁"。第三方核查证实的是存储桶当前为零数据——"现在没有"与"从来不留"是两个命题,后者无法从用户侧验证。能确定的只有两端:多个来源的成功上传记录(我们检测的八个工作区终态、企业函件的自查清单)说明数据抵达过云端,9 月 21 日的核查说明存储桶已被清空;中间的时间窗里数据停留了多久,外部永远无法知道。
收集的范围。隐私政策写的是收集"对话中提交的文本、文件和代码"。实际打包的内容按爆料方对其快照清单的统计,86.6% 来自 .git 目录,历史中被删掉的旧密钥、未推送的分支名、内部系统域名都在其中;后期版本还会把用户的全局配置跨工作区一并打包。这超出了政策文字覆盖的范围,企业函件主张的也是这一点。
结论与建议
检测给出的结论可以收敛成几句:爆料所称的工作区快照上传机制,至迟随 2026 年 8 月下旬的 3.6.5 版本分发,此后每个可取得样本的闭源版本(3.8.1、3.11.2、3.12.1、3.12.3)都带有完整实现,触发点、凭证接口、信封加密、状态机与爆料描述逐项吻合;开源首版 3.14.0 的发行包与源码双重零残留,功能移除恰在开源节点;开源源码中仅存两条无写入方的疑似遗留痕迹,经与闭源包比对确认为该机制残留,功能本体已不在。结合第一篇的行为取证:期间桌面端本地用户的工作区数据(含完整 Git 历史)有实际出境记录,远程会话被门卫豁免。
还在使用或使用过 ZCode 的用户,建议按两步处理。第一步升级到 3.14.0 或更高,闭源旧版本不要再运行。第二步排查本机:查看 ~/.zcode/v2/checkpoints/ 目录,若存在 state.json 状态文件与 .enc 加密包,检查其中的 lastAccepted 字段——有值即说明对应工作区的完整加密包曾成功上传,建议按数据泄露事件处理:更换代码仓库访问凭证、轮换历史密钥。
本篇检测的边界也一并说明:全部结论基于静态字符串取证与公开渠道样本,未做运行时抓包,上传频率无法从安装包推断;数据在云端的留存情况属于服务端信息,超出发行包取证范围;更早版本已从源站删除,引入点只能写到"至迟 3.6.5"。样本、哈希与扫描原始输出均已存档,可供复查。
参考
- Ferstar:扒一扒 ZCode 静默上传全量 Git 历史的骚操作(2026-09-18)
- 界面新闻:被指偷传代码企业发函追责,智谱:ZCode将开源并接受第三方审计(2026-09-21)
- 中国经济网:智谱偷传数据风波扩大,有企业要求说明是否跨境传输(2026-09-20)
- PChome:智谱ZCode官宣开源:泄露代码无留存,未用于模型训练(2026-09-21)
- 智谱:ZCode 隐私政策(2026-09)









