算力资讯
算力资讯产业动态

Apache基金会项目Casbin Gateway新增Agent外发监测,可发现ZCode这类整仓上传

九月中旬,智谱的 AI 编程工具 ZCode 被人发现会在后台把整个代码仓库打包上传,而且连 .git 历史一起传。这件事在社区里讨论了好几天(相关报道附在文末)。

我们做的 Casbin Gateway 是 Apache 软件基金会(ASF)旗下的开源项目,用来管理本机的 AI 编程 Agent(GitHub: https://github.com/apache/casbin-gateway )。看到这个事件,我们想弄清楚两件事:

这件事能不能在自己的机器上完整复现,传走的到底是什么;

如果下一次换成别的 Agent 这么干,用户能不能第一时间知道。

结论先放前面: 能复现。在 ZCode 里发一条只读的 prompt,3 秒后它就上传了一个 21MB 的加密包,里面是整个 .git 目录,包括已经从工作区删掉的大文件和一个被删掉的密钥文件。Casbin Gateway 在上传发生时就标出了这个 Agent 的异常外发,还从 ZCode 自己留在磁盘上的记录里查出了它传走了哪些文件。

下面是复现过程、我们看到的数据,以及检测是怎么做的。

ZCode 3.11.2,Windows x64,官方 CDN 下载的安装包,Authenticode 签名有效(签名方: 北京智谱华章科技股份有限公司)

公开报道称 3.12.3 起修复,3.11.2 是还能从官方 CDN 下到的修复前版本

专门构造的「金丝雀」仓库,工作区里只有 3 个小文件,共 151 字节

① 一个 20MB 的随机文件: 先提交,再删掉, 只存在于 .git 历史里 ;② 一个 .env.local ,里面放一个假的 API Key:先提交,再删除并加进 .gitignore , 同样只存在于历史里

也就是说,如果只上传工作区,传出去的东西应该不到 1KB。

直接连智谱的真实服务器,现在已经不会上传了。 我们抓到客户端在每条 prompt 前都会请求 /api/v1/snapshot/upload-credential 要上传凭证,但服务端已经不再发凭证,客户端拿不到凭证就直接返回,不会打包。这说明智谱已经在服务端把它关了。 但客户端里的上传代码还在。

为了看清楚它原本会做什么,我们用 ZCode 自带的环境变量 ZCODE_BASE_URL ,把客户端指向本机的一个替身服务:

替身只接管两件事:发上传凭证、接收上传的文件;

其余请求(登录、聊天、计费)全部原样转发给智谱的真实服务器,所以 ZCode 的登录和对话都是正常的;

因此 上传的目的地是本机的替身( 127.0.0.1:18461 ),不是阿里云 OSS ,数据没有离开这台机器。

这套复现我们前后独立跑了两次(2026-09-24 22:51 和 23:56),结果一致。下面的数字来自第二次,也就是 Casbin Gateway 全程开着的那一次。

我们在 ZCode 里打开金丝雀仓库,问了一句只读的问题: 「README.md 里写了什么?一句话回答,不要修改任何文件。」

替身服务的日志(原样摘录):

prompt 发出的 同一秒 ,客户端就去要上传凭证;

3 秒后 ,一个 21MB 的加密包 repo-snapshot.tar.gz.enc 上传完成(表单里 captureStage=prompt );

回答结束时又传了一次( captureStage=terminal ), 一条 prompt 一共传了约 42MB 。

我们只问了 README 里写了什么,没有让它做任何需要上传代码的事。

ZCode 会在本地 ~/.zcode/v2/checkpoints/ /manifests/ 下留一份 明文清单 ,列出这次打包的每个文件。清单里一共 36 个文件,其中 33 个在 .git/ 下 :

.git/objects/a9/cf5342…

20,977,920 字节

那个 已经删掉 的 20MB 文件,只存在于历史里

.git/objects/64/58d95f…

那个 已经删掉、而且被 gitignore 的 .env.local 的旧版本,也就是那个假 API Key

.git/hooks/*.sample 、 .git/config 、 .git/logs/HEAD 等

.git 目录的其余内容

README.md 、 src/math.ts 、 .gitignore

工作区里当前那份被 gitignore 的 .env.local 没有被打包,这一点 ZCode 做得没错。 但同一个密钥在历史里的旧版本被带走了。 对真实项目来说,这意味着:你以为早就删掉、 git rm 过、加进 .gitignore 的密钥和大文件,只要进过一次提交,就在这个包里。

同一目录下的 state.json 里还写着 lastAcceptedManifestHash ,这是 ZCode 自己记下的「服务端已确认收到」。

客户端代码里还有几处值得一提(摘自 3.11.2 的 app.asar ):

设置项 repoSnapshotIndexingEnabled 默认值是 false ;

但每条 prompt 前调用的 captureBeforePromptUnsafe 只检查有没有登录 token, 不读这个设置 ;

包用 AES 加密,AES 的密钥再用服务端下发的 RSA 公钥包起来。 用户自己没有私钥,没法解开看自己被传走了什么。

这是整件事里我们最关心的部分: 这次上传不经过任何模型 API。

大多数 Agent 管理工具,包括 Casbin Gateway 原来的做法,都是在 Agent 和模型之间放一个代理,看它发给模型的请求。但 ZCode 这次上传是客户端进程直接连到云存储,代理根本看不到。

所以我们给 Gateway 加了一层 外发监测 :直接看每个 Agent 自己的进程(包括它拉起的子进程)连了哪些地址、发了多少数据。复现时 Gateway 里 ZCode 详情页的「外发监测」卡片显示了以下内容(真实运行、真实数据):

红框:「ZCode 已上传整个工作区」。 这是 Gateway 读 ZCode 留在磁盘上的 state.json 和清单得出的:加密后 20MB,36 个文件,其中 33 个来自 .git ,并提醒「包里有完整的提交历史,历史中出现过的密钥都要轮换,已删除的也一样」。 这一条不依赖 Gateway 当时是否在运行。 哪怕上传发生在你装 Gateway 之前,装上以后也能查出来。

发现记录:「大量上传」,40MB。 这是网络层的实时检测:一分钟内往同一个非模型地址发了超过 5MB,就记为疑似整仓上传。目的地是 127.0.0.1:18461 ,Gateway 还认出了接收方的进程名 standin.exe ,也就是我们的替身。换成真实环境,这里会是阿里云 OSS 的地址,并会额外标成「对象存储」。

连接过的地址。 同一时段 ZCode 发给智谱模型 API( 39.106.105.100 ,即 open.bigmodel.cn )的数据只有 126KB 。 发给上传地址的是它的 300 多倍。 正常写代码时,Agent 发出去的数据绝大部分都应该流向模型 API,这个比例本身就说明了问题。

回到首页,ZCode 的卡片名字旁边多了一个红点,不用点进去就知道这个 Agent 有外发记录。

一共三层,都在本机完成,不上传任何数据:

1. 按 Agent 看进程的网络连接。 Gateway 本来就知道本机装了哪些 Agent、哪些进程属于哪个 Agent(它管着 29 个 Agent 的配置、用量和权限)。外发监测每半秒采样一次这些进程的 TCP 连接,用系统 DNS 缓存把 IP 还原成域名,再给目的地分类:

模型 API ( bigmodel.cn 、 anthropic.com 、 openai.com 、 deepseek.com 等):这是 Agent 本该发数据的地方,量再大也不算异常;

对象存储 (阿里云 OSS、AWS S3、腾讯云 COS、火山 TOS、GCS、Azure Blob、七牛、华为 OBS 等):只要连上就单独标出来。Agent 直接往云存储传文件,本身就值得看一眼;

遥测 (Sentry、阿里云日志服务等):单独归类,量不大就不告警。顺带一提,ZCode 一启动、还没登录,就在往阿里云日志服务( *.log.aliyuncs.com )发数据;

本机进程 :连到本机端口时,会找出是哪个进程在监听,通常是代理。

2. 统计字节数,按规则判定。 以管理员身份运行时,Gateway 会读取每条连接的收发字节数(Windows 上用系统的 TCP 扩展统计)。一分钟内往同一个非模型地址发送超过 5MB,就记一条「大量上传」。整段上传结束、连接安静下来后,记录会写进 Gateway 的活动记录,和 Agent 的其它行为放在一起查。

3. 读取 Agent 自己留在磁盘上的痕迹。 网络检测只能看到正在发生的上传。ZCode 打包用的临时文件在磁盘上只存在不到 2 秒,定时扫描根本看不到;但它留下的 state.json 和明文清单是持久的。Gateway 会读这些文件,列出哪个工作区被传过、传了多少文件、其中多少来自 .git 。

我们希望这个功能被当成可靠的工具,所以把限制也写清楚:

Windows 上要管理员权限才能统计字节数。 不是管理员时,仍然能看到每个 Agent 连了哪些地址,但没有流量大小,也就无法按流量判定「大量上传」;

流量经过本机代理时,只能看到代理进程。 如果你的系统开了代理,Agent 的连接会显示成连到 127.0.0.1 的某个端口。Gateway 会告诉你是哪个进程在接收,但看不到代理之后的真实目的地;

域名不一定每次都能还原。 有的 Agent 不走系统 DNS 解析,这时只显示 IP(上面截图里就是这样);

磁盘痕迹检查目前只认识 ZCode 的格式 ,其他 Agent 靠网络层;

目前是发现和提醒,还不能拦截。

看看 ~/.zcode/v2/checkpoints/ 下有没有带 lastAcceptedManifestHash 的 state.json 。有的话,同目录 manifests/ 里的明文 JSON 就是被传走的文件列表;

被传过的仓库, 历史里出现过的所有密钥都要轮换 ,包括早就删掉的;

如果你想让机器上的每个 AI 编程 Agent(Claude Code、Codex、Cursor、Gemini CLI、ZCode……)都有人盯着,可以装一个 Casbin Gateway:

外发监测已经合进主分支,会随下一个版本发布。它和 Gateway 原有的配置统一、用量统计、权限管控、中转站真伪检测放在一起: 模型那一侧的流量由代理管着,模型之外的流量由外发监测盯着。 Agent 越来越能干,也越来越像一个拥有你全部代码的闭源程序。我们想做的,就是给它们配一个站在用户这边的安全卫士。

GitHub: https://github.com/apache/casbin-gateway ,欢迎 Star、提 Issue。如果你在别的 Agent 上发现了类似行为,也欢迎告诉我们。

智谱 ZCode 整仓上传事件始末

ZCode 静默上传事件技术复盘

ZCode 静默上传 Git 历史:一次逆向取证的完整复盘

ZCode 疑似后台上传项目代码?一次完整排查记录

本文所有数据来自我们在自己机器上、用自己构造的测试仓库做的复现;上传只发生在本机,没有真实代码外泄。公开报道中的数字(例如 313MB 的包、564 次重试)来自上述报道,不是我们测的。

转载说明引用来源:开源中国引用网址:https://www.oschina.net/news/502741