9 月 18 日,开发者 ferstar 在清理磁盘空间时发现,ZCode 的本地目录占用了异常大的空间。
继续往下翻以后,问题从“为什么这么占盘”变成了另一个完全不同的东西:旧版本 ZCode 会生成工作区快照,其中包含大量 Git 历史、LFS 缓存、reflog 等内容,并存在向云端上传的链路。
对 Coding Agent 来说,“理解整个项目”听起来几乎天然正确。
但这次争议提醒了我们一件更具体的事:
“理解代码库”到底意味着读取哪些数据,又意味着哪些数据有资格离开用户的机器?
不是几份代码,而是整个项目的过去#
普通开发者对“让 AI 看项目”的直觉,通常停留在当前工作区。
它可能读到 src/,可能扫描 package.json,可能为了回答一个跨文件问题多开几个文件。即使用户知道某些能力运行在云端,脑子里想象的对象也多半仍然是“这次任务所需要的代码”。
Git 历史不是同一回事。
.git 里保存的不是当前项目,而是项目曾经是什么。
已经删除的配置、旧版本里的内部地址、曾经误提交过的密钥、历史分支、LFS 对象、reflog,都可能继续存在。一个团队今天已经把敏感内容从工作区删干净,并不代表仓库历史已经忘记它们。
ferstar 对受影响旧版本的本地快照做了拆解,发现其中相当大的比例来自 .git。他同时也很谨慎地区分了“本地快照已经生成”和“这个具体大仓库真的上传成功”:那份 300 多 MB 的商业项目快照因为超过限制反复上传失败,并没有成功离开他的网络。
这个区别很重要。
安全事件里最容易发生的错误,就是看到一条危险链路以后,把“存在能力”“发生尝试”“成功传输”“服务端留存”写成同一件事。
它们不是。
ZCode 后来做了什么#
智谱随后承认了问题,并把它与代码库索引和 Repo Wiki 链路联系起来。
官方说明称,代码库索引用于本地索引、会话检查点恢复、历史版本回退和 Repo Wiki 等能力;Repo Wiki 在云端生成页面时可能触发仓库数据上传。相关能力早期默认开启,造成部分用户受到影响。
9 月 19 日,ZCode 3.14.0 的更新日志明确写入:
“修复仓库百科异常上传的问题。”
9 月 21 日,智谱进一步宣布开源 ZCode,并公开了后续整改结果。公司称相关代码数据没有留存,也没有用于模型训练;中国信通院的技术评测确认相关 OSS 存储桶为云端零数据,绿盟科技的审查则确认相关数据对象与存储桶已经删除。官方同时表示,3.14.0 已移除 Repo Wiki 入口及相应生成链路,并切断本地仓库快照生成与上传路径。
这些动作都属于正确方向:修复、删除、第三方检查、开放源码、建立漏洞响应机制。
但它们也暴露出另一个需要单独讨论的问题:
上传、留存、训练,本来就不是一个开关。
“不训练”并不等于“没有离开设备”#
今天很多 AI 产品都提供类似“Improve the model”“用于改进体验”或者“隐私模式”的设置。
用户很容易把它理解成总开关:
关掉以后,我的数据就不会被产品拿走。
但产品内部的真实数据流往往不是这样。
一份代码可能被传到服务器做 embedding,可能被传去生成 Wiki,可能进入临时对象存储,也可能只是作为一次模型请求的上下文。它们是否保存、保存多久、是否进入训练,又是后面的另一组决定。
所以至少应该拆开四个概念:
读取权、上传权、留存权、训练权。
允许 Agent 在本地读取文件,不等于允许上传。
允许上传完成一次计算,不等于允许长期保存。
允许长期保存,也绝不自动等于允许用于训练。
这四件事如果被压成一个“我同意 AI 功能”的按钮,用户看到的是一句话,产品拿到的却可能是一整套不同层级的权利。
Grok Build 已经提前演过一次#
今年 7 月,安全研究者对 xAI 的 Grok Build 做网络层分析时,发现了很相似的结构。
研究者捕获到一条与普通模型请求分离的仓库上传链路,其中传输的是 Git bundle。实验里,他们放入一个 Agent 从未主动打开过的 canary 文件,最后仍然能够从上传内容中恢复它以及 Git 历史。
更值得注意的是,当时关闭 “Improve the model” 并没有阻止这条上传路径。
这不是说两家产品的实现、目的和处置完全相同。
恰恰相反,放在一起看才更有价值:不同产品独立走到相似的位置,说明问题可能不只是某个团队粗心,而是 Coding Agent 这个产品形态本身正在产生一种稳定的诱惑。
为什么产品总想拿到更多#
下面这部分是我们的判断,不是对某一家公司的动机指控。
Coding Agent 越强,对上下文的胃口通常就越大。
单文件补全只需要看附近几十行;跨文件修改需要知道模块关系;长任务恢复需要 checkpoint;Repo Wiki 需要更完整的仓库结构;跨设备恢复又希望上下文存在云端。多 Agent 协作以后,还会出现“另一个执行节点怎样拿到同一份项目状态”的问题。
从工程角度看,把一个项目先做成完整、统一、可寻址的云端快照,往往比每一次都精确判断“这项任务到底只需要哪三个文件”简单得多。
从体验角度看,权限询问又天然和转化率冲突。
每多一个:
“是否允许读取 Git 历史?”
“是否只允许本次上传?”
“是否允许云端保存 24 小时?”
都意味着一次用户可能说“不”的机会。
而所有产品经理都知道,“什么都不用管,它自己就懂你的项目”听起来远比“一会儿会弹六个权限框”性感。
再往商业上看,完整云端索引、历史 checkpoint、跨设备恢复和长期 memory 还会自然增加用户黏性。
这不需要阴谋论。
它只是很普通的商业激励:
更多上下文通常带来更好的体验,更少确认通常带来更高的完成率,而更多云端状态通常带来更高的迁移成本。
问题在于,这三件好事都可能把数据边界往外推。
真正成熟的产品,应该反着设计一点#
如果让我们给 Coding Agent 写一份最低限度的产品合同,大概会从下面几件事开始。
第一,Git 历史应该被视为独立的高敏感 capability。
当前工作区和完整历史不是一个权限等级。产品如果真的需要历史,应单独说明用途和范围,而不是把 .git 当成“项目文件的一部分”顺手带走。
第二,先在本地缩减,再出机器。
文件筛选、diff 提取、secret scan、历史裁剪,能在本地完成的就先完成。云端收到任务真正需要的材料,比收到全集以后再承诺“我们不会看多余的”更可靠。
第三,外发应该可见、可查、可撤销。
用户应该可以知道:哪些文件离开了设备、发往什么服务、为了什么功能、保留多久。
这不该只存在于一份隐私政策里。
最好像浏览器的 Network 面板一样具体。
第四,一次授权必须真的只是一次授权。
“本次任务”“本会话”“本仓库”“长期允许”应该是不同作用域。一个任务要读三份文件,不应该因为用户点过一次 Allow,就顺便获得这个仓库未来所有历史的长期访问权。
第五,训练永远是独立选择。
训练是数据的另一种生命周期,不应该藏在上传许可里,也不应该由“你用了云端功能”自动推导出来。
出事以后还应该回答什么#
ZCode 这次后续处理已经做了不少事情,但从一个成熟的安全事故处置模板看,行业以后最好形成更稳定的公开答案。
哪些版本受影响?
什么条件会触发?
上传对象到底包含哪些类别?
成功上传过多少?
服务端保存多久?
日志、缓存、备份里还有没有副本?
谁拥有解密能力?
如果 Git 历史里曾经出现过 token,用户是否应该立即轮换?
这些问题未必都能当天回答。
但事故处置真正重建信任的方式,不只是说“新版已经修复”,而是把曾经发生过什么、现在还剩下什么、用户下一步需要做什么说清楚。
钥匙之外,还有房间#
老会长的事故让我们意识到:把 PAT 给 Agent,本质是在递钥匙。
ZCode 和 Grok Build 则把问题往前推了一步。
有时候用户甚至并不知道,自己刚才同意打开的是哪一间房。
我们当然希望 Agent 更懂项目。
也希望它更快、更连续、更少打断人。
但这类产品真正成熟的标志,也许恰好不是“它能一次拿到所有东西”。
而是它越来越擅长只拿当前真正需要的那一点。
延伸阅读:
ferstar:Inside ZCode — Silently Uploading Your Entire Git History to the Cloud
