ZCode 事件技术复盘:313MB 加密包里装了什么

wescode · 2026-10-21 · 代码安全 / ZCode / 智谱 / 数据上传 / 技术分析

利益声明:本文作者参与了 wescode 的开发。本文是对公开安全事件的技术分析,所有引用来源已在文末标注。

本文是「代码安全实录」系列第 2 篇。对 ZCode 2026 年 9 月事件做技术层面的拆解,所有分析基于已公开的逆向工程报告。

在系列第 1 篇中,我们按时间线梳理了 2026 年 7-9 月的五起 AI 编程工具安全事件。ZCode 事件是其中影响面最广的一起——它涉及国内开发者最常用的工作环境,引发了企业正式追责,也促使厂商做出开源承诺。

这篇文章不再重复事件经过,而是拆解三个技术问题:313MB 的加密包是怎么打出来的、里面装了什么、为什么用户无法自行验证。


一、打包机制:从工作区到 tar.gz

触发条件

根据 ferstar 的逆向分析,ZCode 的打包上传由以下条件触发:

  1. 用户保持登录状态
  2. 工作区(workspace)被打开
  3. 后台定时任务检测到工作区有变更(或首次打开)

关键点在于:打包逻辑不在用户主动操作的路径上。它不是"你点了某个按钮才触发",而是后台自动运行的。用户在使用编辑器的过程中,可能完全没有注意到后台正在进行文件遍历和打包。

ferstar 在分析中描述了一个场景:他打开一个项目,正常编辑了几分钟,然后通过系统监控工具发现 ~/.zcode/ 目录下出现了一个持续增长的 tar.gz 文件。他没有点击任何"备份"或"同步"按钮——打包是自动开始的。

文件遍历逻辑

ZCode 的打包采用递归目录遍历方式,从工作区根目录开始,将几乎所有文件收集到一个 tar.gz 归档中。

被包含的目录和文件:

包含说明
所有源代码文件.ts、.py、.go、.java 等——这是预期中的
.git/ 目录完整的 Git 对象库、引用、reflog——这是问题的核心
.env 文件包含 API Key、数据库密码等敏感信息
config.yaml 等配置文件包含内部服务器地址、端口等
node_modules/、vendor/ 等依赖目录如果存在的话
.idea/、.vscode/ 等 IDE 配置包含调试配置、运行参数

ferstar 报告中指出,.gitignore 规则没有被用于过滤打包范围。也就是说,即使你在 .gitignore 中排除了 .env,打包逻辑仍然会把它包含进去。

这是一个重要的细节:开发者通常依赖 .gitignore 作为"这些文件不应该被分享"的声明。但 ZCode 的打包逻辑不读 .gitignore——它做的是完整的文件系统遍历。

打包后的处理

tar.gz 归档在本地生成后,存放在 ~/.zcode/ 目录下。文件大小取决于工作区体积——ferstar 测试的项目产生了约 313MB 的归档,魔都水滴测试的更大项目产生了约 700MB 的归档。

归档的具体路径结构(根据逆向分析):

~/.zcode/
├── cache/
│   ├── workspace-{hash}/
│   │   ├── snapshot.tar.gz          # 工作区完整快照
│   │   ├── snapshot.tar.gz.enc      # 加密后的快照
│   │   └── metadata.json            # 上传元数据
│   └── ...
└── config.json                       # ZCode 本地配置

随后,这个归档经过加密处理后被上传到智谱的服务器。加密机制在后文详述。

"打地鼠"现象

魔都水滴的分析中记录了一个更令人不安的现象:手动删除 ~/.zcode 目录会触发无限重传。

流程如下:

  1. 用户发现 ~/.zcode/ 下有异常的大文件
  2. 用户删除 ~/.zcode/ 目录
  3. ZCode 检测到缓存目录消失,重新创建
  4. 重新遍历工作区,重新打包
  5. 重新加密,重新上传

这意味着用户无法通过删除本地缓存来阻止上传——删除只会触发重新打包。要真正停止上传,必须退出 ZCode 进程或断开网络。


二、86.6% 的 .git 历史:这意味着什么

313MB 的归档中,真正的源代码只占约 13%。剩下的 86.6% 来自 .git 目录。很多开发者可能没有意识到,.git 目录里到底存了什么。

Git 对象库的四种对象

Git 的 .git/objects/ 目录是一个内容寻址的对象数据库。每一次 git add 和 git commit,都会往这个数据库里写入新对象。对象分四种:

对象类型存储的内容安全含义
blob文件的完整内容(每个版本)🔴 一个文件改了 50 次,就有 50 份完整内容
tree目录结构的快照🟡 暴露项目结构和文件名
commit提交元数据(作者、时间、消息、父提交)🟡 暴露开发者身份和工作节奏
tag标签引用🟢 一般无敏感信息

核心问题在 blob 对象:Git 存储的是每次变更时文件的完整内容,不是增量差异。

这意味着:

三个版本的 blob 对象全部存在于 .git/objects/ 中。 你在当前代码里看不到那个真实密码,但 Git 历史里有。执行一条 git log -p -S realpassword123 就能找回来。

一个具体的数字

假设一个项目有 3 年的开发历史、1000 次提交、50 个文件平均每个被修改过 20 次。Git 对象库中会有多少个 blob?

50 个文件 × 20 个版本 = 1000 个 blob 对象

每个 blob 是文件的完整内容(Git 在 pack 时会做 delta 压缩,但对象模型是完整内容)。如果平均每个文件 10KB,Git 对象库在打包前的原始大小约为 10MB(pack 后可能压缩到 3-5MB)。

但真正的风险不在体积,在于内容的历史穿透。你删除的密钥、你放弃的方案、你撤销的 commit——在 blob 对象里全部保留。

reflog 暴露了什么

.git/logs/ 目录下的 reflog 记录了所有引用(分支、HEAD)的变更历史,包括:

reflog 条目的格式:

<旧SHA> <新SHA> <作者> <时间戳> <操作>: <描述>

一条典型的 reflog 记录:

a1b2c3d e4f5g6h Zhang San <zhangsan@company-internal.gitlab.com> 1719000000 +0800
    checkout: moving from feature/payment-gateway-v2 to hotfix/fix-alipay-callback

这条记录同时暴露了:

对于企业来说,reflog 可能暴露:

LFS 缓存

如果项目使用了 Git LFS(Large File Storage),.git/lfs/ 目录下会缓存大文件的本地副本。这些通常是二进制资产——模型权重文件、数据集、图片、文档等。

魔都水滴的分析指出,在他测试的项目中,LFS 缓存占了 .git 目录体积的约 11.6%(约 40MB)。

LFS 对象的安全含义取决于内容类型:

LFS 内容类型风险等级说明
设计稿和图片🟡可能包含未公开的产品设计
文档(PDF/Word)🟡-🔴可能包含内部规范、客户资料
模型权重🔴可能是核心知识产权
数据集🔴可能包含用户数据或训练数据
构建产物🟢通常无敏感信息

一个容易被忽略的事实

很多开发者的心理模型是"我的代码就是 src/ 目录下那些文件"。但从 Git 的视角看,你的仓库 = 当前文件 + 所有历史版本 + 所有分支 + 所有操作记录。上传 .git 目录,等同于上传了项目的完整时空快照。

用一个比喻:上传当前源代码,相当于给别人看你书桌上的文件。上传 .git 目录,相当于把你的废纸篓、抽屉里的草稿、日记本上划掉的部分,一起交出去。


三、RSA 加密机制:你打不开你自己的数据

加密流程

ZCode 对 tar.gz 归档的加密采用混合加密方案:

1. 客户端向服务端请求一个 RSA 公钥
2. 客户端生成一个随机 AES-256-CTR 密钥
3. 用 AES-256-CTR 密钥加密 tar.gz 归档
4. 用 RSA 公钥加密 AES-256-CTR 密钥
5. 将加密后的归档 + 加密后的 AES 密钥一起上传

这是一个标准的**信封加密(envelope encryption)**方案。从密码学角度看,算法选择没有问题——AES-256-CTR 用于数据加密,RSA 用于密钥加密,都是成熟的方案。

问题在于密钥分配

问题不在算法,在于谁持有密钥:

密钥位置谁能使用
RSA 公钥服务端下发到客户端客户端用它加密 AES 密钥
RSA 私钥仅在智谱服务端仅智谱能解密
AES-256-CTR 密钥随机生成,用后丢弃被 RSA 公钥加密后,只有持有私钥的智谱能恢复

这意味着一个简单的事实:

你的硬盘上那份加密文件——你打不开。

ZCode 客户端打不开。任何第三方工具打不开。全世界只有智谱服务端的 RSA 私钥能解密。

这不是加密强度的问题,是加密方向的问题。正常的端到端加密(如 Signal、iMessage)是你持有私钥,服务方无法读取。ZCode 的方案是反过来的——服务方持有私钥,你无法读取。

对比:不同加密方案的信任模型

加密方案谁持有解密能力用户能否审计内容用户能否验证删除
端到端加密(Signal 模型)用户✅✅ 可以验证本地副本
服务端加密(ZCode 模型)服务方不能不能
客户端加密 + 用户托管密钥用户 + 服务方可选✅✅
零知识加密(如 Tresorit)仅用户✅✅

为什么这个设计让用户失去控制

在 ZCode 的加密架构下:

  1. 你无法审计上传内容——加密是在打包之后完成的,加密前的明文 tar.gz 被临时存在内存中处理,你无法截获
  2. 你无法验证删除——即使智谱说"已删除",你无法通过任何技术手段验证
  3. 你无法解密自己的数据——即使你从网络层截获了上传的加密包,你也读不出里面的内容
  4. 服务端可以随时更换密钥——每次请求下发的 RSA 公钥可以不同,你无法感知
  5. 无法确认加密是否真的执行——在闭源客户端中,你无法确认 tar.gz 是否真的被加密后才上传,还是在某些路径上存在明文传输

对比一下另一种可能的设计:如果加密用的是用户自己的密钥(比如从用户的 SSH Key 派生,或让用户设置一个加密密码),那么至少用户可以解密自己的备份,也可以审计上传的内容。ZCode 没有选择这种方案。


四、"数据已销毁"的验证困境

智谱的承诺

2026 年 9 月 19 日,智谱发布致歉声明,表示:

为什么"已删除"难以验证

在云端基础设施中,"删除数据"是一个比大多数人想象的更复杂的操作:

对象存储的版本控制。主流云存储(AWS S3、阿里云 OSS、腾讯云 COS)默认或可选开启版本控制。启用后,"删除"操作只是标记一个删除标记(delete marker),原始对象仍然存在,可以被恢复。要真正删除,需要额外执行"永久删除"操作,且需要有版本控制的管理权限。

备份和快照。企业级云服务通常有自动备份策略——每日快照、跨区域复制、灾难恢复副本等。即使原始存储中的数据被真正删除,备份副本可能按照保留策略继续存在数周到数月。

CDN 和缓存。如果数据经过 CDN 或缓存层,边缘节点上可能有副本。这些副本的生命周期由 TTL 策略控制,不受原始存储删除操作的影响。

审计日志。出于合规要求,很多云平台会记录数据访问日志。这些日志本身可能包含数据摘要或元数据——文件名、大小、上传时间、关联用户 ID、访问者 IP 等。

数据库残留。即使文件被删除,元数据(文件名、大小、上传时间、关联用户、处理状态)可能仍存在于业务数据库的记录中。

日志系统。如果上传过程中有日志记录(几乎所有生产系统都有),日志中可能包含文件路径、哈希值、处理过程的中间状态。日志系统通常有独立的保留策略。

这不是说智谱一定没有删除数据。而是说:在当前的架构下,用户没有任何技术手段来独立验证数据是否被彻底删除。 你只能选择相信或不相信厂商的声明。

可以验证的方案存在吗?

存在,但需要架构层面的改变。例如:

目前,智谱承诺的"第三方安全审查"是上述方案中最现实的一个。但截至本文发布时,尚未看到审查结果的公开发布。


五、"体验优化"和"快照索引"开关的问题

用户以为自己关掉了

ZCode 的设置面板里提供了两个看似与数据上传相关的开关:

  1. "体验优化"开关——描述暗示关闭后不会收集使用数据
  2. "快照索引"开关——描述暗示关闭后不会对代码建立云端索引

一个合理的用户预期是:关闭这两个开关后,ZCode 不会将代码数据上传到服务端。

实际发生了什么

根据逆向分析,底层的打包上传逻辑不受这两个 UI 开关控制。关闭开关只影响了部分功能的展示层行为,但后台的文件遍历、tar.gz 打包、加密和上传流程继续运行。

这与 Grok Build 的 "Improve the model" 开关失效是同一类问题,但表现形式不同:

工具开关名称用户预期实际行为
Grok Build"Improve the model"关闭后数据不上传关闭的是"用于训练",上传继续
ZCode"体验优化" / "快照索引"关闭后不打包上传打包上传逻辑不受控制

两者的共同问题是:UI 开关的语义与底层行为之间存在断层。 用户以为自己关闭了数据上传,实际上只是关闭了某个上层功能标记。


六、企业发函追责

承明科技函件

太原承明科技有限公司在事件曝光后,向智谱发出正式函件。根据公开报道,函件提出了五项具体要求:

#要求意义
1提供数据删除证明不是口头承诺"已删除",要有可审计的技术证据
2说明数据流向数据在智谱内部经过了哪些系统、哪些人可以访问
3提供完整操作日志什么时候上传、什么时候访问、什么时候删除的时间线
4说明是否涉及数据出境数据是否存储在境外服务器、是否经过境外节点
5承诺后续整改措施防止类似事件再次发生的具体方案

这份函件为什么重要

它不只是一家公司的维权行动。它为其他受影响的企业提供了一个模板——当你的代码被未经授权上传到第三方服务器时,应该要求什么样的回应。

对于企业安全团队来说,这五项要求可以直接作为供应商安全评估的检查清单:你的 AI 编程工具供应商能不能回答这五个问题?如果不能——这本身就是一个信号。

法律框架

从法律角度看,未经用户明确同意上传用户数据,可能涉及以下法规:

法规相关条款适用性
《个人信息保护法》第 13 条:处理个人信息应取得个人同意🟡 代码本身可能不算个人信息,但 Git 历史中的 author 信息是
《数据安全法》第 27 条:数据处理活动应取得数据所有者的同意🔴 代码是企业数据资产
《网络安全法》第 41 条:收集使用信息应明示并征得同意🔴 适用
《民法典》第 1032 条:隐私权保护🟡 企业代码的隐私属性存在讨论空间

七、代码索引是否可能完全在本地完成

ZCode 上传工作区的目的是"代码库索引"——让 AI 更好地理解你的项目结构和上下文。这是一个合理的功能需求。问题在于实现方式:索引必须在云端完成吗?

答案是不必须。

本地索引的技术可行性

代码索引的核心是对源码进行解析、建立符号关系图、生成搜索索引。这些操作的计算量远低于模型推理——一个 50 万行的项目,在一台普通开发机上用 tree-sitter 解析 + SQLite FTS5 建索引,通常在几十秒到几分钟内完成。

本地索引的技术路线已经存在:

技术用途本地可行性
tree-sitter解析数十种语言的 AST✅ 几十万行项目秒级完成
SQLite FTS5全文搜索索引✅ 无需外部依赖
静态调用图分析函数调用关系✅ AST 级分析在本地完成
ONNX Runtime + 小型 Embedding 模型语义向量✅ 本地 CPU 可运行
SQLite HNSW向量近邻搜索✅ 无需云端向量数据库

代码索引的结果(符号表、调用关系、搜索索引)占用的存储空间远小于源码本身,且不包含代码原文——可以安全地在本地保存和更新。

本地索引的一个具体例子

以一个 50 万行的 Go 项目为例,本地索引的产出物大约是:

索引类型存储大小内容
符号表~5MB函数名、类型名、变量名、文件位置
调用图~3MB函数 A 调用函数 B 的关系
FTS5 全文索引~15MB代码文本的倒排索引
Embedding 向量~30MB代码块的语义向量
合计~53MB

53MB 的本地索引 vs 313MB 的上传包——本地索引不仅更安全,体积还更小,因为它存的是结构化的索引数据而不是原始文件。

这意味着:代码理解能力和数据安全不是非此即彼的选择。 在本地完成解析和索引,只在模型调用时发送必要的上下文片段,是一个技术上可行的方案。


八、这个事件教了我们什么

ZCode 事件不是孤例。它暴露的是整个 AI 编程工具行业在快速发展中功能优先、安全滞后的结构性问题。

对开发者:

  1. 检查你正在使用的 AI 编程工具是否有后台上传行为——用系统监控工具(如 Little Snitch、Wireshark、lsof)观察网络请求
  2. 如果你在 2026 年 9 月之前使用过 ZCode,检查你的仓库中是否有敏感信息(API Key、密码、内部域名)。即使已从当前代码删除,Git 历史中可能仍有。具体命令:
    git log -p -S "password" --all
    git log -p -S "AKIA" --all       # AWS Key
    git log -p -S "sk-" --all        # OpenAI Key
    git log --all --full-history -- "*.env"
    
  3. 重要项目使用 AI 编程工具前,确认工具的数据处理方式

对企业安全团队:

  1. 将 AI 编程工具纳入软件资产管理——明确允许列表和禁止列表
  2. 对已批准使用的工具,要求供应商回答承明科技函件中的五个问题
  3. 考虑要求工具提供网络流量审计接口——让安全团队能看到工具在发送什么
  4. 制定 AI 编程工具安全评估模板,覆盖数据路径、加密方案、存储策略、删除机制

对工具开发者:

  1. "先做功能再补安全"在 AI 编程工具领域是高风险策略——你处理的是用户最敏感的资产
  2. 需要用户代码做索引?在本地做。需要调用模型?只发送必要的上下文片段
  3. 如果必须上传,至少做到:明确告知、用户可控、用户持有密钥、提供删除验证
  4. UI 开关的语义必须与底层行为一致——用户关闭"数据上传",底层就不能继续上传

信息来源汇总

#来源类型URL
1ferstar ZCode 逆向分析技术取证blog.ferstar.org
2魔都水滴 700MB 分析技术取证blog.margrop.net
3InfoQ 深度报道媒体infoq.cn
4虎嗅报道媒体huxiu.com
5新浪财经报道(含智谱致歉声明)媒体/官方finance.sina.com.cn
6Git 内部原理 — Git 对象技术文档git-scm.com
7Git LFS 官方文档技术文档git-lfs.com
8《数据安全法》全文法规npc.gov.cn

本文信息截至 2026 年 10 月 20 日。如各方后续发布更新、审查报告或澄清,请以最新信息为准。