ZCode 编码客户端:无论模型是否本地,整份仓库都会静默出境
你平时把编码智能体的信任边界画在哪里?多数人的答案是一句话:模型跑在本地、工具授权我点过同意,数据就出不了门。2026 年 9 月 18 日,独立安全研究者 ferstar 的取证把这句话拆成了两半。智谱 Z.ai 的 AI 编码桌面应用 ZCode 会在你登录后静默打包整个工作区,连同完整 Git 历史一起加密直传阿里云 OSS,而这一切不受任何 UI 开关控制,跟模型跑不跑在本地也毫无关系。
这篇专题沿着一条主线走:编码智能体的数据出境风险,不由「模型是否本地部署」决定,而由宿主运行时是否把「读取 → 打包 → 出站」纳入同一条可验证的授权链决定。我们一步步看这条链在哪里断掉、为什么断掉、边界又该画在哪几件事上。
你点过的「同意」,到底覆盖了哪条通道?
使用 Claude Code、Cursor 或 Codex 这类编码智能体时,你做过两件很自然的事:允许 agent 读写工作区,允许它调用工具。在直觉里,这两步授权就是数据安全的全部;如果模型还跑在自己机器上,那更是「双保险」,东西没离开过我的电脑,何来外传一说。
ZCode 把这个直觉拆成了两半。Tokenstead 的可视化分析里有一张对照图:上半是抓取的 ZCode 系统提示词,大小 131KB,来自公开 prompt 集合 OrcaPromptVault;里面有一段「Workspace rewind applied」的 checkpoint(检查点)模板,在提示词里出现了 5 次,这是 agent 自己看得见的回滚功能;下半是 agent 的 31 个工具面,Agent、Task、Bash、Edit、Grep、Read、Write、WebFetch,列出来的每一个都是工具循环内的常规成员,而最下面是结论性的一行红条:snapshot / upload / telemetry / OSS tools: NONE,没有任何一个抓取或上传工具。Tokenstead
图注:上半是 ZCode 系统提示词里出现 5 次的 checkpoint 回滚模板,这是 agent 在工具循环内「看得见」的功能;下半是 31 个工具面(图中 12 个 chip 为代表样本),快照、上传、遥测、OSS 类外传工具一个都没有。Tokenstead
也就是说,你在授权面板里点过的每一个「同意」,覆盖到的是这条由 31 个工具组成的循环。循环里根本没有外传工具,所以「我授权了 agent,agent 不会上传」这句话在工具循环内部成立。
但取证的另一侧证明上传确实发生了。ferstar 在 ~/.zcode 数据根目录发现一个 313MB 的加密包和一份状态元数据,元数据里记录着 564 次失败上传重试。他还从客户端 asar 反编译出 capture/upload 组件:它在启动时无条件实例化,没有任何基于用户偏好的判断分支,唯一门槛是 tokenProvider 能返回有效的 JWT(登录令牌)。这条通道的触发条件只有一个:你登录了。你点没点同意、关没关开关,都不在它的考虑范围内。ferstar
把 UI 里的两个开关单独拎出来对照,这一点更清楚。设置里有「Optimize Experience」和「Repo Snapshot Indexing」两项,用户会本能地以为它们管理数据收集。ferstar 的代码交叉对照显示:前者只控制数据是否被授权用于模型训练,后者只控制服务端是否索引已上传的快照,本地打包和上传照常进行。这就是「UI 关闭开关 ≠ 关闭抓取」,是这篇专题要拆掉的第一个直觉等式。
那条与工具循环并行的数据通道,就是宿主 sidecar。sidecar 直译「边车」,这里指宿主进程里一个独立于 agent 工具的伴随组件:它不经过 agent 的工具调用,直接以宿主身份抓取、加密、上传。它甚至不是 ZCode 的独家设计。把模型、工具、上下文装配成可用 agent 的那套宿主程序,业界称为 harness;UIUC 团队对 12 个开源 harness 的审计发现,编码 agent 的上下文组装普遍是带角色标签的黑箱拼接:低权限内容可以被装进高权限消息角色(M-CPE),短生命周期的会话内容可以被写进持久作用域(X-CPE)。副作用工具面里干净,宿主侧的黑箱组装却在悄悄扩张,这正是 sidecar 现象的学术对应物。ContextPriv

图注:Codex CLI 一次运行时实际送进 LLM 的上下文片段:多段带角色标签的消息(system、developer、user、assistant、tool)拼接而成,而非一个扁平字符串,这就是「看不见的上下文组装」的物理形态。ContextPriv
现在问题被磨利了:工具循环里没有任何外传工具,可 313MB 密文真实存在。那它到底是谁打包的、走的哪条路、路上经过了什么?我们把取证链一条条走完。
从登录令牌到阿里云 OSS:整份 Git 历史是怎么出去的?
ferstar 从客户端 asar 逆向出了完整的四步管道。第一步,客户端向 zcode.z.ai 的 upload-credential 接口发起凭证协商,服务端返回 snapshot_id、本次加密用的 RSA 公钥、大小上限和阿里云 OSS 的表单凭证。第二步,客户端在本地把工作区打包成 tar.gz,用临时对称密钥做 AES-256-CTR 加密,再用 RSA-OAEP-SHA256 把对称密钥包裹起来。第三步,客户端绕过 ZCode 自己的应用服务器,直接以 HTTP form POST 把密文送到阿里云 OSS。第四步,OSS 回调服务端,完成 snapshot 注册。
图注:ZCode 客户端从协调服务端取得 snapshot_id、RSA 公钥与 OSS 表单凭证,本地打包加密后直传阿里云 OSS(绕过应用服务器),再由回调完成注册;底部红框点出「密钥不对称」:私钥从未下发到本机,313MB 密文连 ZCode 客户端自己都解不开。Tokenstead
这张图最该记住的是底部那个红框。加密的私钥只存在于智谱云端:服务端每次凭证协商时下发公钥,私钥从不落地,ferstar 用本机所有私钥尝试解密全部失败。这意味着加密的目的不是「防止传输中被偷听」,那是 TLS 的活;它的目的是「确保只有厂商能读」。一个只有服务端能解的密文,用户自己都打不开,更谈不上删除、审计或恢复。论密钥归属,这跟 Time Machine 这类本地可解密的备份设计正好相反。ferstar
管道里装的到底是什么?快照清单给出了 42,411 个文件的构成:
| 内容 | 大小 | 占比 |
|---|---|---|
.git/lfs/(LFS 缓存:下载过的二进制资产与媒体) | 196.1 MB | 56.8% |
.git/objects/(完整 commit 历史对象) | 102.2 MB | 29.6% |
.git/logs/(reflogs:本地分支历史与未推送操作痕迹) | 0.6 MB | 0.2% |
| 源码 & 文档 | 46.2 MB | 13.4% |
.git 合计占 86.6%,是源码本体的六倍多。这组数字能支撑的结论在构成层面:.git 在这一归档中占 86.6%,上传物显然不只是当前工作副本的源码与文档,还包括完整 commit 历史、reflogs 与 LFS 缓存。后续 commit 里已删除的历史 API key、未推送的本地分支名(等于暴露未发布的功能计划)、.git/config 里配置的 GitLab 主机名和仓库路径,全部随包出境。已删除的秘密和未推送的分支,是你唯一「以为已经消失」的数据。
这条 sidecar 通道在宿主结构里挂在哪个位置?2026-07 的 Harness Engineering 论文对 11 个生产 harness 做了源码解剖,提出七个子系统规范:Agent loop、LLM integration、Tools & actions、Memory & context、Safety & permissions、Orchestration、Extensibility,外加一层 Interface Layer。以这个坐标系来看,注意它只覆盖到 2026-07;sidecar 走的是 Orchestration 或 Extensibility 这两个边界子系统,不在 Tools & actions 里。HarnessAnatomy

图注:编码 agent harness 围绕 Agent Loop 的七个子系统,外加人类与程序驱动的 Interface Layer;抓取上传这类通道通常经 Orchestration 或 Extensibility 边界子系统接入,而不是 Tools & actions。论文本身不用 sidecar 一词,这是我们借取证事实给出的定位表述。HarnessAnatomy
当天,智谱在官方飞书群作出回应,三件事:致歉,承认是 Repo Wiki 功能在生成页面时把仓库数据上传到云端,页面生成后立即销毁、不留存;承认该功能上线初期默认开启、影响部分用户、目前已修复;承诺近期开源 ZCode 代码库、邀请第三方评估并持续公开审查进展、给全体用户发放一次周额度重置。Z.ai 回应
陈述到这里就停。把官方说法和取证事实并排看,五个缺口自己会说话:影响范围没有数字,多少用户、多少仓库被传过无人回答;数据在 OSS 上实际停留多久没有说明;「立即销毁」没有独立审计;修复版本号对不上,最新版 v3.12.3 的 release notes 列的是模型管理与连接修复,没有仓库快照相关变更;开源时间表只有一句「soon」。官方版本说上传与 Wiki 页面生成强绑定,取证版本说 capture 在客户端拿到登录令牌时就启动、每次提示词前(captureBeforePrompt)和任务完成打 repo-wiki-update 标签时都会触发、一个活跃会话记录到 62 次 capture、关闭 Repo Snapshot Indexing 后仍有 accepted snapshot 记录。两套说法在触发时机上不一致,谁对谁错不是本文能够裁断的,但「已修复」至少还缺一份可核验的答案。
这就引出一个更根本的问题:为什么系统里会存在一条没人点头的管道?这不像 ZCode 一家产品的怪癖,下一章我们把镜头拉远,看看安全学界五十年前就给它起好的名字。
这条没人点头的通道,1975 年就有名字
1975 年,Saltzer 和 Schroeder 在《The Protection of Information in Computer Systems》里给保护系统立了八条设计原则,其中第三条「完全仲裁」(complete mediation)的逐字定义是:Every access to every object must be checked for authority,每一次对每一个对象的访问都必须经过授权检查。把 ZCode 案例逐项对照:按 ferstar 反编译 asar 的结论,capture 组件启动时无条件实例化,打包这一步没有触发任何用户授权检查;上传到 OSS 这一步也没有。读取发生在工具循环里,你授权过;打包和出站发生在 sidecar 里,没人授权过。同一条数据链路被切成两段,一段有检查、一段没有,这正是「完全仲裁」缺位的标准形态。Saltzer & Schroeder
为什么会出现这种断裂?因为 agent 运行的环境默认就是「环境权限」(ambient authority),大白话是权限跟着进程走。你启动 ZCode,它继承了你登录用户的全部工作区权限;它再派生出任何子进程,都自动拿到同一份权限,不需要任何显式授权。最隐蔽的后果是:程序无法分辨「我此刻正在用谁的授权做哪件事」。Adam Langley 在 2004 年的报告里给了这句总论:Ambient authority amplifies the impact of any exploited component by failing to contain it,环境权限无法约束被攻破组件的影响范围,只会把它放大。AmbientAuthority
权限随进程走,还是每次访问都被检查
| 维度 | 环境权限(现状) | 完全仲裁(应然) |
|---|---|---|
| 权限来源 | 进程继承:子进程自动拿到父进程的 UID 级权限 | 每一次访问单独进行授权检查 |
| 能否区分「为谁授权」 | 不能,程序无法把权限物化为可见值 | 能,授权与具体访问绑定 |
| ZCode 里的表现 | sidecar 继承登录用户权限,直接打包上传 | 打包、出站两步没有任何授权检查中介 |
这个问题在 1988 年被 Norm Hardy 形式化,名字叫 confused deputy(被搞糊涂的代理人)。原型故事是个编译器:编译器装在 SYSX 目录,被授予在目录里写统计文件的权限;用户调用它时,可以传一个「调试输出文件名」作为参数。有个用户传了账务文件 (SYSX)BILL,编译器就用自己的写权限,把调试信息覆盖到了账务文件上。Hardy 的诊断是:编译器同时服务两个主人、携带两边的权限,却没有办法表达「这一次写操作的授权来自谁」。把故事平移过来:编译器就是宿主进程,调试输出文件名就是被 sidecar 顺手拿走的工作区路径,账务文件就是整份仓库。宿主进程同样无法分辨,这次打包上传用的是「执行任务的授权」还是「登录用户的环境权限」。AmbientAuthority
到了 2026 年,这套原则落在 agent framework 上的翻译是:capability gating 不等于 per-call authorization。capability gating 回答「哪些工具存在」,per-call authorization 回答「这一次带着这些参数值的调用允不允许」。David Mellafe Zuvic 对 LangChain/LangGraph、LlamaIndex、Stripe Agent Toolkit 三个框架的 pinned 源码审计,结论是三者默认都只有 capability gating,没有任何一个在副作用执行前对具体参数值做确定性检查。schema 校验能挡住畸形参数,但挡不住一个格式正确、却是攻击者账户的 account=acct_ATTACKER_999。这跟 ZCode 打包出站两步无人把关,是同一个缺口的两种尺寸。CapGateAuth

图注:ScopeGate 的 per-call 重授权:不可信的 LLM 解析器发出 tool_call,确定性的 PDP/PEP 菱形逐次判定,蓝色 Allow 放行到副作用工具,红色 Deny 留下审计记录;每次调用的参数值都被判一次,而不是只在设计时决定工具清单。CapGateAuth
原则讲完,用一个「关不掉」的事实收尾。ferstar 试过手动删除 pending/ 里的密文,半小时内客户端重新打包,重试计数从 564 涨到 565。UI 开关、删文件、改配置都拦不住这条通道,唯一可靠的修复是给 ~/.zcode/v2/checkpoints 加内核级不可变标志:macOS 用 chflags uchg,Linux 用 chattr +i,代价是 checkpoint 回滚功能不可用。环境权限之下,你点过的同意只是局部控制,它管不到进程继承下来的那一整片权限。ferstar
那么问题来了:如果「只给工具面、不审每次调用」是 2026 年框架的默认,那 ZCode 和 Grok Build 先后踩进同一个坑,是巧合还是架构使然?下一章看一份独立的线上复现。
一家厂商的怪癖,还是一类架构的通病?
2026 年 7 月,独立研究者 cereblab 对 xAI 的 Grok Build CLI(0.2.93 版)做了一次 wire-level 复现。实验设计很干净:构造一个 12GB 的仓库,里面全是模型永远不会去读的随机文件,每个文件带唯一标记,这类探针文件叫 canary 文件,只要出现在流量里就能被确凿认出;用 mitmproxy 抓全部出站流量,mitmproxy 是中间人代理,能解密并记录 HTTPS 流量;提示词明确要求模型「只回复 OK,不要读也不要打开任何文件」。
同一场会话里出现了两条通道的字节对比:
| 通道 | 端点 | 字节 | 请求数 |
|---|---|---|---|
| A:模型轮次 | /v1/responses | 192 KB(196,705 B) | 5 |
| B:代码库上传 | /v1/storage | 5.10 GiB(5,476,228,005 B) | 82 |
比例约 27,800 倍。模型通道只走了 192KB,一个从没被读过的仓库不可能通过模型传出 5GiB;字节只能是从另一条整库快照通道出去的,与模型读了什么毫无关系。烟枪证据更直白:抓包抓到的一个上传包里有个 git bundle,git clone 之后,模型被明确告知不要读的 canary 文件 never_read_canary.txt 原样躺在里面,标记字串一字不差;第二个无关仓库复现了同样结果。还有一层:把 UI 里「Improve the model」开关关掉重跑,整个仓库照样上传,服务端 /v1/settings 依然返回 upload_enabled: true。所谓 opt-out,管的只是留存,不是传输。cereblab
cereblab 的复现和 ferstar 的取证并排,Tokenstead 画了一张对照图:
图注:同一形态、两家厂商、相隔两个月:上方是 Grok Build 的 unified.json 日志里明文记录的上传事件(turn 3/108/189),下方是 ZCode 的「打包 42,411 个文件(.git 占 86.6%)→ AES-256-CTR 加密 → 直传阿里云 OSS」管道。图内数字为图中文字所述,一手数字以 ferstar 与 cereblab 的原始取证为准。Tokenstead
注意区分两种可观察的形态,但不要越界下动机结论。两者可观察的差异在于:Grok 以明文上传,自己的 unified.json 日志留下完整痕迹,事后靠服务端 kill switch 收场;ZCode 在客户端加密到只有服务端能解,删除密文会触发重传,隐私政策对这条通道只字不提。两者在「读什么与传什么脱节」这一架构事实上完全一致;至于这是「无意过度同步」还是「有意收集」,需要动机证据,现有公开证据到不了那一层,本文不替 xAI 或智谱任何一方作判断。
孤例可以归咎于失误,但同一年里两家头部厂商先后踩进同一个坑位,就值得看看行业地基了。Red Hat 团队对 3,171 个公开 GitHub 仓库的 harness 配置做了静态审计,16.0% 的配置携带至少一个安全缺陷:未锁定版本的 MCP server 占 9.8%,看似受限实则预授权任意执行的 grant 占 3.1%,skill 前置批准 shell 的占 3.8%。HarnessScanner

图注:论文 Figure 1:左 panel 是 2,660 个 setups 与 511 个 collections 上 confirmed defect 与 confirmed finding 的占比(含 95% Wilson 区间),右 panel 是 6 条 gating rule 在 setups 上的 per-rule prevalence,按 family 配色。HarnessScanner
harness 组件像依赖层一样从 marketplace 安装、以开发者权限运行、跨会话持久存在,却既没有 lockfile,也没有安装期检查和「组件能做什么」的规范词汇。这就是 ZCode 和 Grok Build 生长的土壤:两个独立案例显示,整库快照出境是一种会重复出现的宿主级风险形态,而不是哪一家厂商的怪癖。需要分清的是,16.0% 的配置缺陷率提供的只是行业治理普遍薄弱的背景,它衡量的是配置文件自身的质量,不能用来估计快照通道在整体行业里的普遍程度。既然这种形态会重复出现,边界就不能指望某家厂商良心发现。那边界到底该画在哪几层、由谁把关?最后一章给出四件可勾选的事。
边界该画在哪四件事上:从「信厂商」到「可核验」
HZDR CASUS 团队 2026 年 8 月提交的 Terok 论文,把「agent 是不可信单元、宿主与容器是信任锚」的模型做成了完整控制栈:5 个 scope(LOCAL、CONTAINER、GIT FORGE、LLM PROVIDER、REST OF THE WORLD)加 4 项 safeguard,实现分布在 7 个组件里。与授权链直接相关的三层是:LOCAL,你的本机,agent 不能完全控制;GIT FORGE,远程仓库,改动能到谁手里;LLM PROVIDER,模型厂商,双向隔离。Terok

图注:Terok 的 7 个组件树状分层:terok 编排、terok-executor 运行 agent、terok-sandbox 容器隔离、terok-shield 出站防火墙、terok-clearance 授权弹窗,全部汇入 system foundation(rootless Podman、nftables、systemd、D-Bus)。图中不显示容器内 agent、git forge、LLM provider 等外部实体。Terok
4 项 safeguard 各自回答授权链上的一环。容器环境管「挂载什么」:每个任务跑在独立容器里,LOCAL 的数据不整盘暴露给 agent。出站防火墙(egress firewall)管「能出到哪里」:Terok-Shield 基于 nftables 维护每任务的出站白名单,默认拒绝,交互式 Allow/Deny 弹窗,全程审计日志。git 网关管「改动能到谁手里」:每个任务一个独立仓库作为上游的延迟代理,agent 照常走 git 工作流,但 commit 只落到网关,由你决定是否 forward 到上游。凭据保险库管「秘密进不进容器」:API key、SSH key 放在容器外,使用时经代理转出容器注入真实 secret,底层用 systemd TPM2 密封。需要注明的是,Terok 目前是 GitHub 自标的 alpha 项目,README 明说「不适合生产部署」;这里引用的是它的设计骨架,不是「已生产可用」的背书。
容器先收窄可读取的挂载面;数据能否离开该边界还要由出口白名单决定。挂载与出站都设防之后,沙箱之内还有一个问题:授权为什么不残留?Portico 给出了工具调用层的答案:把能力做成任务级、带闭包(closure)。生命周期是五段式:latent(潜伏)→ request(请求)→ grant(授予,此时才可见)→ use(使用)→ closure(闭包)。grant 铸造一个 epoch 绑定的 handle,closure 把这个 handle 从下一步的接口里移除,执行端拒绝过期的 stale replay。Portico

图注:两行六阶段对比:上排 Portico 在 use 之后执行 closure,late reread 被拒(红色);下排对照组不做闭包、权限残留,late reread 被允许(黄色)。图中画的是接口层 handle 消失加执行端 stale replay 拒绝两件事,不是一句「运行时拦截」能概括的。Portico
在受控 fixtures 下,stale-write 审计里 Portico 接受并执行 0/6 次过期写入,不做闭包的对照组是 6/6。样本很小,但方向明确:授权跟着子目标生灭,而不是跟着进程永生。
把五章收成一张可勾选的清单。第一,最小挂载:只挂当前任务需要的目录,别把整个 HOME 交给 agent。第二,任务级能力:每个临时授权绑定一个子目标,子目标结束授权就闭包,而不是一次同意永续有效。第三,出口白名单:默认拒绝,只有名单内的目的地能出站,且全程审计。第四,可核验出站清单:任何一键便利功能,云端索引、回滚快照、改善模型,要么先过这条授权链,要么把数据代价明示给你、由你选择。ZCode 的 Repo Snapshot Indexing、Grok 的 Improve-the-model,问题都不在于功能本身,而在于开关默认值握在厂商手里,数据代价由用户事后买单。信任修复的第一步,是把这些默认值交回用户能核验的链路上。
回到开篇那句话。模型在本地,仓库照样出境;工具授权了你,sidecar 照样整库打包。编码智能体的信任边界,从来不在模型部署位置,而在宿主运行时有没有把「读取 → 打包 → 出站」放进同一条可验证的授权链。这条链上任何一环脱节,你点过的所有「同意」,都只是一层局部控制。
参考文献
本专题引用的核心资料如下,建议深入学习时阅读原文:
- Inside ZCode: Silently Uploading Your Entire Git History to the Cloud(ferstar,2026-09-18)— https://blog.ferstar.org/en/posts/zcode-silent-workspace-snapshot-upload/
- ZCode uploads your git history; Z.ai holds the only key(Tokenstead,2026-09-18)— https://tokenstead.ai/guides/zcode-silent-git-history-upload
- Z.ai ZCode 官方回应与 Wiki 文档、zai-org/feedback #70 / #199 — https://zcode.z.ai/cn/docs/repo-wiki
- What xAI Grok Build CLI actually sends to xAI - a wire-level analysis(cereblab,2026-07)— https://gist.github.com/cereblab/dc9a40bc26120f4540e4e09b75ffb547
- The Protection of Information in Computer Systems(Saltzer & Schroeder, 1975)— https://web.mit.edu/Saltzer/www/publications/protection/
- Ambient authority(Wikipedia)+ Capability Myths Demolished + A Practical UNIX Capability System — https://en.wikipedia.org/wiki/Ambient_authority
- What's in Your Agent's Context? Context Privilege Escalation Attacks against AI Agent Harness — arXiv:2609.01222
- Scanning the Harness: An Empirical Study of Supply-Chain Defects in AI Coding-Agent Configurations — arXiv:2609.07360
- Capability Gates Are Not Authorization: Confused-Deputy Failures in LLM Agent Frameworks — arXiv:2606.28679
- Lingering Authority: Revocable Resource-and-Effect Capabilities for Coding Agents(Portico)— arXiv:2606.22504
- Concepts for Securing Agentic AI Coding and the Terok Environment — arXiv:2608.22930
- Harness Engineering: Anatomy, Architecture, and Evolution of Coding Agents — arXiv:2609.00006