夜雨聆风学习资料网

ARTICLE · 1056166

ZCode 开源后,我拆了 4 个官方安装包:上传代码删干净了吗

ZCode 开源后,我拆了 4 个官方安装包:上传代码删干净了吗

5600 颗星、1600 次 fork、两天——这是智谱把 ZCode 开源之后,GitHub 上给出的数字。而这款工具一周前被开发者抓包:登录状态下,它会把你本地整个代码仓库打包、加密、传到云端。

9 月 21 日,智谱在开源公告里说得很具体:v3.14.0 客户端已移除 Repo Wiki 功能,已切断本地仓库快照的生成与上传链路,并公布了信通院与绿盟的审计结论。

声明可以写得很干净,安装包不会。我把四个版本的 ZCode 从官方 CDN 下下来,解开内部的 app.asar,逐符号比了一遍。

一、三天之内发生了什么

时间
事件
9 月 18 日
开发者抓包:登录后 ZCode 静默打包本地工作区(含完整 .git 历史)加密上传至阿里云 OSS;官方当日致歉,称问题源于「代码库索引(Repo Wiki)」功能上线初期默认开启,已修复,并承诺开源代码库、引入第三方审查、给全体用户重置周额度
9 月 19 日
修复版 3.14.0 客户端发布
9 月 20 日
zai-org/ZCode
 仓库建立
9 月 21 日
正式开源(Apache-2.0);公布信通院、绿盟审计结论;创始人唐杰实名投诉小红书网友「偷代码」传闻;社区发现开源版与官网版「不完全一致」

从曝光到开源,三天。速度值得肯定,但三天的信任重建只等于一份声明加一个仓库——能不能自证,要看你愿不愿意把安装包拆开看。

二、我把四个版本的安装包拆开比了一遍

官方说 v3.14.0 切断,我就把这条当假设去验。四个版本全部来自官方 CDN(cdn-zcode.z.ai),Linux arm64 的 AppImage,每个约 200 MB,解开 squashfs 后取出 resources/app.asar,再抽取桌面主进程的打包产物 out/host/index.js 逐符号计数。

符号(主机进程包内出现次数)
3.12.3(9/17 构建)
3.14.0(9/19)
3.14.1(9/20)
3.14.2(9/21)
RepoSnapshot
(仓库快照子系统)
56
0
0
0
uploadCredential
(上传凭证)
18
0
0
0
publicKeySpkiPem
(服务端公钥)
2
0
0
0
keyWrapAlgorithm
(信封加密算法)
2
0
0
0
encryptedSizeBytes
(密文大小)
10
0
0
0
host/index.js
 体积
2,588,069 B
1,496,603 B
1,497,796 B
1,497,867 B

结论与官方口径完全一致:事发前最后一版 3.12.3 里有一整套带 RepoSnapshot 前缀的代码,含 53 个具名函数;从 3.14.0 起,这些名字在客户端里一个都不剩,主进程包一次性瘦了约 42%(1.09 MB)。

同一套代码在命令行运行时里也被删了:3.12.3 的 zcode.cjs 有 3 处 RepoSnapshot,3.14.2 是 0。

服务端一侧也能对上一个便宜的证据。用 curl 直接打当时的上传凭证接口,今天两种方法都是 404;而同一台服务器上的 /api/v1/client/configs,GET 是正常的 200。这不是「全站 404」,是那条路由没了:

curl -s -o /dev/null -w '%{http_code}\n' https://zcode.z.ai/api/v1/snapshot/upload-credential   "color:#6a9955"># 404curl -s -o /dev/null -w '%{http_code}\n' https://zcode.z.ai/api/v1/client/configs              "color:#6a9955"># 200

三、那段被删掉的代码,到底做了什么

更值得写清楚的是它原来的样子。废掉的 3.12.3 安装包本身还在 CDN 上,谁都能下——想自己复核的话,unsquashfs 解开 AppImage,用 asar 解析器取 out/host/index.js,搜 publicKeySpkiPem 就能看到这段代码。

流程和早先开发者还原的一致,但代码里还有几处此前没被提到的东西:

密钥由服务端掌控。客户端用随机 32 字节密钥 + 16 字节 nonce 走 AES-256-CTR 加密打包好的 tar.gz,再用服务端在凭证里下发的 RSA 公钥(SPKI)以 RSA-OAEP-SHA256 封装这把密钥,文件落盘为 repo-snapshot.tar.gz.enc。私钥从不出现在客户端,本地密文你自己解不开。

上传走 OSS 表单直传。凭证接口是 GET /api/v1/snapshot/upload-credential,返回 OSS 的 hostpathpolicyx-oss-signaturex-oss-credentialx-oss-security-tokenmax_size,以及一个回执用的 callback。客户端组 multipart 表单,把密文以字段名 repo-snapshot.tar.gz.enc POST 上去,并在回执字段里带上 sessionIdqueryIdrequestIdfailureCountcaptureStagehistoryRoundCount 等归因信息。

.git 被显式豁免了所有过滤规则。文件收集器里确实有一长串排除表:node_modules 等依赖目录、缓存、构建产物、符号链接、二进制文件、超过 1 MiB 的大文件,以及名字像密钥的文件(.env.npmrcid_rsa*.pem*.key*.p12*.pfx,以及文件名里含 token 或 secret 的)。但只要路径里出现 .git,函数直接返回「包含」,大小、二进制、密钥名三道检查一道都不跑。所以历史上被删掉的配置文件、早年的测试密钥、内部域名,全都随 .git 目录一起进了压缩包——过滤器拦得住当前目录里的 .env,拦不住提交历史里的 .env

除了代码,还顺走了你的全局 Agent 配置。快照里有一个 extra 包,采集函数名字就叫 collectRepoSnapshotGlobalConfigs,内容包括:家目录下的全局 AGENTS.md(超过 20 MiB 才截断,截断标记写的是 ...[repo-snapshot-global-configs truncated])、用户级 MCP 服务配置(含启动命令、环境变量、请求头)、用户级技能与命令清单、hooks(含要执行的命令与参数)、memory 内容、子代理定义、插件清单,以及 17 项行为设置的当前值。

那个开关,从来没有管过它。客户端里有个设置项叫 repoSnapshotIndexingEnabled,界面上对应「仓库快照索引」。它在整个主进程包里只出现两次:一次是用户改动它时打个「用户已配置」的标记,一次是作为设置值被塞进上面那个 extra 包。没有一处代码读它来决定要不要采集、要不要上传。早先开发者说「没找到开关控制上传的逻辑」,我这次是从代码层面对上了。

它在你按回车之前就开始干活。捕获函数有两个入口:发送 prompt 之前(captureBeforePrompt,标记 captureStage: "prompt",prompt 正文一起进归因字段),以及任务完成之后。此外本地还有配额管理:单份密文上限默认 2 GiB,最多同时存在 3 份、保留 2 份,磁盘预算 6 GiB——这解释了为什么有人的 ~/.zcode 会悄悄涨到几百兆。

四、开源的那份,和你装的那份

开源当天,社区就发现 GitHub 上的代码和官网下载版不完全一样,「公关式开源」的质疑随之而来。这一条需要拆开说,因为两件事被混在了一起。

第一件,争议代码。我把开源仓库全库搜了一遍:RepoSnapshotrepoSnapshotsnapshot/upload-credential,全部零命中。开源版里唯一带 upload-credential 的地方是反馈附件的上传,那件事写在 NOTICE.md 的对外请求清单里,是用户主动提交工单才触发的。我又抽查了几个只可能来自同一份源码的字符串(例如主进程里那句「任务终态未读裁决」),开源仓库与线上 3.14.x 能对上——修复后的代码,两边是同源的。所以就「你审查的是哪一份 ZCode」这个问题,答案比社区猜测的更清楚:争议中的那段,两份里都没有。

第二件,商业功能。仓库自己的 NOTICE.md 第 70 行写得很直白:「受第三方版权、许可及再分发条件等约束,不承诺提供官方产品的全部功能及活动政策」。开源版少掉的是额度活动一类的商业化能力,官网仍在单独分发完整客户端。这属于「开源不等于免费送全部权益」的常规操作,但它确实意味着仓库不能替代对线上产品的审计——静态代码能证明「没有这段」,证明不了「线上跑的那份是什么」。

顺便说三个开源仓库的硬数据:6,973 个文件、14 个包、84.3 万行 TypeScript;NOTICE.md 27.7 KB,第三方声明 THIRD-PARTY-NOTICES.md 1.98 MB;2 个 commit、0 个 tag、0 个 release。星标 5,596、fork 1,597——fork 比例 28%,远高于正常仓库的 10% 到 15%,一部分是真想改,一部分可能只是想在自己账号下留个副本。

还有一个细节:仓库的 Issues 是关着的(GitHub API 返回 has_issues: false),PR 也是 0 条。官方在公告里说「把代码交给社区监督,欢迎开发者持续检查和反馈」——但反馈入口目前不在这个仓库里。

五、还没被验证的三件事

一、服务端的行为只能靠审计背书。信通院与绿盟的结论都指向同一个点:zcode-prod 的阿里云 OSS 存储桶「云端零数据」、全部对象与桶本身已删除。这对用户是好消息,但两份报告本身没有公开原文,外界看到的是官方转述的结论。国内机构做审计、三天出结论,效率和可信度都摆在那,唯一缺的是可复查的过程。

二、旧安装包还在。事发前的 3.12.3(9 月 17 日构建,199,710,914 字节)今天仍然能从官方 CDN 直接下载。这一点对想自己复核的人是便利,对没开自动更新的用户是风险——修复只在客户端,装了老版本的人不会因为服务端下线而变安全。

三、本地历史删不干净。无论客户端怎么改,已经被打包上传过的内容、以及 .git 里那些「以前删掉的密钥」,都不因为这次整改而变得不存在。涉及生产凭据的仓库,该轮换的密钥还是要轮换。

我的判断

这件事到目前为止,是一次兑现得比较扎实的信任修复:客户端删干净了,而且删得可以被任何人从安装包层面验证;服务端数据删除了,有第三方机构背书;开源把代码摆上了台面。

但信任重建有三条腿,现在只稳了一条。代码可自证,数据删除靠背书,长期制度还只是一句承诺——「常态化安全漏洞机制」怎么运转、反馈入口在哪、下次出事多久公布,都还没有可检验的样本。开源第一天就把 Issues 关掉,恰恰是这种落差最直观的注脚。

给正在用或者准备用的人三句话:

1确认客户端版本不低于 3.14.0,这是官方口径里切断上传链路的分界,我的比对结果与之一致。
2想自己复核,检查 ~/.zcode/v2/checkpoints 目录里有没有几百兆的密文文件;顺手看一眼 ~/.zcode 的总占用。
3在用过老版本的机器上,把 .git 历史里出现过的密钥当作已泄露处理——文件名过滤挡不住提交历史。

相关阅读:

Plugin4Shell 实测:分支名伪装成 SHA,四大 AI 编程助手的插件校验全线失守[1]
ChatGPT 跨站追踪实证:一枚 Cookie 挂满一年,936 个广告像素把你在别处买的东西送回 OpenAI[2]
智谱「牛来」跑在 10 万国产卡上:成本 1/40 Claude Opus,用量超 DeepSeek 两倍[3]

关注 AI 商业快讯,每天一篇 AI 热点深度解读。

参考链接

[1] Plugin4Shell 实测:分支名伪装成 SHA,四大 AI 编程助手的插件校验全线失守:https://www.geeyo.com/plugin4shell-%e5%ae%9e%e6%b5%8b%ef%bc%9a%e5%88%86%e6%94%af%e5%90%8d%e4%bc%aa%e8%a3%85%e6%88%90-sha%ef%bc%8c%e5%9b%9b%e5%a4%a7-ai-%e7%bc%96%e7%a8%8b%e5%8a%a9%e6%89%8b%e7%9a%84%e6%8f%92%e4%bb%b6/

[2] ChatGPT 跨站追踪实证:一枚 Cookie 挂满一年,936 个广告像素把你在别处买的东西送回 OpenAI:https://www.geeyo.com/chatgpt-%e8%b7%a8%e7%ab%99%e8%bf%bd%e8%b8%aa%e5%ae%9e%e8%af%81%ef%bc%9a%e4%b8%80%e6%9e%9a-cookie-%e6%8c%82%e6%bb%a1%e4%b8%80%e5%b9%b4%ef%bc%8c936-%e4%b8%aa%e5%b9%bf%e5%91%8a%e5%83%8f%e7%b4%a0%e6%8a%8a/

[3] 智谱「牛来」跑在 10 万国产卡上:成本 1/40 Claude Opus,用量超 DeepSeek 两倍:https://www.geeyo.com/%e6%99%ba%e8%b0%b1%e3%80%8c%e7%89%9b%e6%9d%a5%e3%80%8d%e8%b7%91%e5%9c%a810%e4%b8%87%e5%9b%bd%e4%ba%a7%e5%8d%a1%e4%b8%8a%ef%bc%9a%e6%88%90%e6%9c%ac1-40-claude-opus%ef%bc%8c%e7%94%a8%e9%87%8f%e8%b6%85de/

相关学习资料