Grok Build 抓包实录:12GB 仓库走了两条通道
利益声明:本文作者参与了 wescode 的开发。本文是对公开安全事件的技术分析,所有引用来源已在文末标注。
本文是「代码安全实录」系列第 3 篇。基于安全研究者 cereblab 公开的 wire-level 抓包分析,拆解 Grok Build 的双通道数据传输架构。
2026 年 7 月 12 日,cereblab 在 GitHub Gist 公布了一份对 xAI Grok Build CLI(版本 0.2.93)的网络层分析。这不是源代码审计(Grok Build 当时闭源),而是一次纯网络层的黑盒取证——通过代理拦截所有出站请求,记录每个 endpoint 的请求内容和数据量。
这份分析揭示了一个出乎意料的架构:Grok Build 的出站流量走了两条完全独立的通道,两条通道传输的数据量相差五个数量级。
一、抓包实验设计
为什么值得详细讲实验设计
大多数安全事件报告只给结论——"XX 工具上传了用户数据"。但结论的可信度完全取决于取证方法的严谨程度。cereblab 的实验设计值得仔细看,因为它展示了如何对一个闭源工具做黑盒安全审计。
测试环境搭建
cereblab 的实验遵循了取证分析的标准方法论:
隔离环境。使用一个专用的测试仓库,与日常工作环境隔离。这个仓库不包含任何真实的敏感数据——所有的"敏感信息"都是故意放置的 canary(金丝雀文件)。隔离的目的有两个:一是避免真实数据在实验过程中泄露;二是让实验结果更容易解读——上传包中出现的任何数据都可以追溯到已知来源。
Canary 文件设计。在仓库中放置了假密钥文件(canary):
| Canary 文件 | 内容 | 作用 |
|---|---|---|
.env | AWS_ACCESS_KEY_ID=AKIA_FAKE_...、DATABASE_URL=postgres://fake:fake@... | 验证环境文件是否被读取 |
config/secrets.yaml | 假的 API Token 和服务器地址 | 验证配置文件是否被打包 |
~/.claude/config.json | 假的 Claude API Key | 验证 Grok Build 是否扫描 Claude 配置 |
这些假密钥有两个作用:一是验证哪些文件被读取并上传;二是如果未来在任何数据泄露事件中发现这些假密钥,可以追溯来源——这是数字取证中常用的"蜜标"技术。
中间人代理。使用 mitmproxy 作为 HTTPS 代理,拦截 Grok Build 的所有出站 HTTPS 请求。配置方式:
- 在系统中安装 mitmproxy 的 CA 证书,使其能解密 HTTPS 流量
- 设置环境变量
HTTP_PROXY和HTTPS_PROXY指向 mitmproxy - 启动 mitmproxy 并记录所有请求——URL、header、body、响应、时间戳
所有请求的完整记录被保存为 mitmproxy 的 flow 文件,可以事后重放和分析。
Git bundle 验证。对于上传的 tar.gz 包,研究者从网络层截获后解压,使用 git bundle 命令恢复,验证其中包含的文件和历史记录。这一步确认了上传包中确实包含完整的 Git 仓库——不只是文件快照。
测试仓库规格
| 属性 | 值 |
|---|---|
| 仓库大小 | 约 12GB(含 Git 历史和 LFS 对象) |
| 文件数量 | 数千个 |
| Git 历史 | 多年的提交记录 |
| Canary 文件 | .env(含假 AWS Key)、config/secrets.yaml(含假数据库密码)、~/.claude/config.json(含假 Claude Key) |
| Git LFS | 包含大型二进制文件 |
二、双通道架构
抓包结果显示,Grok Build 的出站流量走了两条完全独立的 HTTP endpoint。两条通道的功能、目标和数据量截然不同。
通道一:模型调用通道
| 属性 | 值 |
|---|---|
| Endpoint | POST /v1/responses |
| 目标服务器 | xAI 的模型推理 API |
| 传输的数据 | 用户指定的文件内容 + 对话上下文 |
| 数据量 | 约 192KB |
| 加密 | TLS(标准 HTTPS) |
| 是否正常 | ✅ 正常——AI 编程工具调用模型必须发送代码上下文 |
这条通道做的是所有 AI 编程工具都会做的事:把用户正在编辑的文件和对话历史发送给模型,获取 AI 响应。192KB 的数据量与一次正常的模型调用一致——大约是几个源文件的内容加上对话历史。
cereblab 确认,通道一传输的文件列表与 Grok Build UI 中显示的"已读取文件"列表一致——没有多余的文件。如果 Grok Build 只有这条通道,它的行为就是完全正常的。
通道二:存储通道
| 属性 | 值 |
|---|---|
| Endpoint | POST /v1/storage |
| 目标服务器 | Google Cloud Storage |
| 存储桶名 | grok-code-session-traces |
| 传输的数据 | 整个 Git 仓库的 tar.gz 归档 |
| 数据量 | 5.1GiB(上传未完成时即已超过此数字) |
| 加密 | TLS + 存储端加密(GCS 默认) |
| 是否正常 | 异常——远超模型调用所需 |
5.1GiB 与 192KB 的对比需要直观化:
存储通道数据量 / 模型通道数据量 ≈ 5,100,000KB / 192KB ≈ 26,562 倍
模型只需要 192KB 就能回答你的编程问题。存储通道传走了 26,562 倍于此的数据。
为什么存在两条通道
从技术架构角度分析,两条通道的存在暗示了不同的后端系统和不同的产品目的:
- 通道一(
/v1/responses):标准的模型推理 API,用于实时对话。低延迟,小数据量。 - 通道二(
/v1/storage):数据收集系统,用于记录完整的编程会话。高吞吐,大数据量。
两条通道使用不同的认证 token,走不同的后端服务,有不同的超时设置。它们不是同一个系统的两个 endpoint,而是两个独立的系统。
三、存储桶 grok-code-session-traces 的分析
GCS 路径结构
上传目标是 Google Cloud Storage 上一个名为 grok-code-session-traces 的存储桶。根据抓包记录,上传路径遵循以下结构:
gs://grok-code-session-traces/{session_id}/before_codebase.tar.gz
gs://grok-code-session-traces/{session_id}/after_codebase.tar.gz
gs://grok-code-session-traces/{session_id}/metadata.json
每次会话(session)会上传三个文件:
| 文件 | 内容 | 大小范围 | 说明 |
|---|---|---|---|
before_codebase.tar.gz | 会话开始时仓库的完整快照 | 数百 MB 到数 GB | 包含所有文件 + .git 历史 |
after_codebase.tar.gz | 会话结束时仓库的完整快照 | 数百 MB 到数 GB | 用于记录 AI 做了什么修改 |
metadata.json | 会话元数据 | 数 KB | 模型版本、会话时长、使用的功能、用户标识 |
为什么是 before/after 两份
从产品设计角度可以理解这个逻辑——xAI 想记录"AI 编程前"和"AI 编程后"的代码差异,用于评估模型效果、改进产品。这在 ML 产品开发中是常见的做法——记录 input/output pair 来衡量模型质量。
但这个设计的问题在于实现方式的选择:
- before/after 差异只需要一个 diff。
git diff的输出通常只有几 KB 到几 MB,不需要两份完整仓库快照。即使需要完整的文件内容做对比,也只需要被修改的那几个文件。 - 完整仓库包含了大量与当前编程任务无关的文件。你在改一个 API handler,不需要把整个项目的测试文件、文档、CI 配置全部上传。
.git历史与当前会话完全无关。你的提交历史不会影响模型的编程能力——模型不需要知道你三年前改了什么才能帮你今天写代码。- 两份快照意味着同一个仓库被上传了两次。对于 12GB 的仓库来说,这就是 10.2GiB 的上传量——远超合理需求。
metadata.json 的内容
metadata.json 的结构(基于抓包记录的推测)包含:
{
"session_id": "...",
"user_id": "...",
"model_version": "grok-build-0.2.93",
"started_at": "2026-07-12T...",
"ended_at": "2026-07-12T...",
"files_read": ["src/api/handler.go", "src/models/user.go"],
"tools_used": ["edit", "search", "terminal"],
"trace_upload_enabled": true,
"improve_model_enabled": false
}
注意最后两行:trace_upload_enabled: true(上传开启)和 improve_model_enabled: false(用户已关闭"改进模型")。两个开关可以同时存在不同状态——这就是下一节要讨论的核心问题。
被上传内容的恢复验证
cereblab 将上传的 tar.gz 包从 mitmproxy 记录中提取、解压后,确认了以下内容:
| 内容 | 是否存在于上传包中 | 说明 |
|---|---|---|
| 模型读取的文件 | ✅ 是 | 预期中的 |
| 模型未读取的文件 | ✅ 是 | 连模型没有使用的文件也被上传了 |
| 完整 Git 提交历史 | ✅ 是 | 所有 commit、所有 blob 对象 |
.env 中的假密钥(canary) | ✅ 是 | 假密钥在上传包中被完整恢复 |
.gitignore 列出的文件 | ✅ 是 | .gitignore 不影响打包范围 |
~/.claude/ 目录内容 | ✅ 是 | Grok Build 扫描了 Claude 配置 |
node_modules/ | 部分 | 取决于仓库中是否存在 |
特别值得注意的是 ~/.claude/ 目录。Grok Build 声称兼容 Claude Code 的配置格式,会扫描用户机器上的 ~/.claude/ 目录。扫描时,它把目录中的配置文件(可能包含 Claude API Key)也收集到了上传包中。一个工具扫描另一个竞品工具的配置目录,并将其中的凭证上传到自己的服务器——这个行为的安全含义不需要多解释。
四、"关闭训练"开关为什么不管用
两个独立的开关
Grok Build 的设置中有一个 "Improve the model" 开关,用户可以选择关闭它来表示"不希望我的数据被用于模型训练"。
cereblab 关闭了这个开关后重新测试,发现:
数据仍然被上传。 网络层的流量没有变化——POST /v1/storage 请求依然发出,数据量依然是 GiB 级别。
原因在于 Grok Build 的架构中存在两个独立的控制开关:
| 开关 | 控制的行为 | 用户可见吗 | 关闭后的效果 |
|---|---|---|---|
| "Improve the model" | 数据是否被用于模型训练 | ✅ 用户可以在设置中关闭 | 数据仍上传,但承诺不用于训练 |
trace_upload_enabled | 数据是否被上传到存储桶 | 用户不可见,由服务端配置控制 | 不上传 |
关闭 "Improve the model" 只是告诉 xAI "请不要用我的数据训练模型"。但数据的上传和存储由另一个完全独立的开关 trace_upload_enabled 控制——这个开关在服务端,用户看不到也关不了。
为什么"不训练"≠"不上传"
这两个开关的独立性反映了一个常见的产品设计模式:数据收集和数据使用是分离的。
在这个模式下:
数据生命周期:产生 → 收集 → 传输 → 存储 → 使用(训练/分析/...) → 删除
↑ ↑
trace_upload_enabled improve_model_enabled
控制这一步 控制这一步
用户能控制的只是"使用"这一步。数据的"收集"和"传输"发生在上游,由另一个开关控制——而这个开关不对用户开放。
一个类比
这相当于:你告诉银行"请不要把我的消费记录卖给广告商",银行答应了。但银行仍然在记录你的每一笔消费,只是承诺不卖给广告商。记录本身没有停止。
对于安全意识强的用户来说,这几层的区别至关重要:
- "不训练"不等于"不上传"
- "不上传"不等于"不收集"
- "不收集"不等于"不读取"
- "不存储"不等于"已删除"
每一层都是独立的——关闭下游不等于关闭上游。
五、服务端远程关闭 vs 客户端修复
事件时间线中最诡异的一刻
2026 年 7 月 13 日——cereblab 公布分析后的第二天——同一个 0.2.93 版本的 Grok Build 客户端停止了上传行为。
cereblab 验证了客户端二进制的 SHA-256 哈希值:完全没变。
不是 xAI 推送了新版本。不是自动更新机制拉取了补丁。是同一个二进制文件,运行出了不同的行为。
技术实现
Grok Build 启动时会向 xAI 服务器请求一份运行时配置(这在很多 SaaS 工具中是标准做法)。这份配置中包含了 disable_codebase_upload 这样的 flag。
Grok Build 启动
↓
GET /v1/config → 返回 { "disable_codebase_upload": false, "trace_upload_enabled": true, ... }
↓
根据配置决定是否执行上传
7 月 13 日,xAI 在服务端将 disable_codebase_upload 设为 true,于是所有客户端——不需要任何更新——立即停止了上传。
从事件响应速度的角度看,这很高效:不需要等用户更新客户端,一秒钟之内全球生效。
但从安全角度看,这暴露了一个根本问题:
服务端开关的双面性
好处:
- 快速响应安全事件——一秒钟全球生效
- 不需要用户配合(更新客户端的渗透率通常很低)
- 可以灰度控制——先关闭部分用户的上传
坏处:
- 上传功能在客户端代码中完整保留,只是被服务端开关临时关闭
- xAI 可以在任何时候重新打开上传功能,不需要推送任何更新
- 用户不会收到任何通知——客户端二进制不变,行为变了
- 如果 xAI 的服务端配置出现 bug 或被攻击者修改,上传可能意外重新启用
- 用户无法通过"锁定版本"来保护自己——老版本和新版本一样受服务端开关控制
- 信任模型是单向的——你必须持续信任服务端,而不只是信任你下载的那个版本
与 OTA 更新的区别
有人可能会说:这和手机的 OTA 更新有什么区别?App 也可以推送新版本来改变行为。
区别在于可见性和可审计性:
| OTA 更新 | 服务端配置切换 | |
|---|---|---|
| 二进制是否变化 | 是——可以比较哈希值 | 否——哈希完全不变 |
| 用户是否知道 | 是——有更新通知 | 否——完全静默 |
| 能否拒绝 | 通常可以关闭自动更新 | 无法拒绝 |
| 能否回退 | 可以降级到旧版本 | 无法控制,版本相同但行为不同 |
| 安全审计 | 新版本可以被审计 | 配置切换不可审计 |
六、开源后发现的问题
Grok Build 开源的背景
2026 年 7 月 16 日,在持续的社区压力下,xAI 宣布 Grok Build 开源。
开源后,社区开发者审查了源代码,确认了 cereblab 抓包分析的所有发现:
- 上传代码确实存在,确实会打包整个仓库
trace_upload_enabled确实是一个独立于 "Improve the model" 的服务端开关~/.claude/目录扫描确实存在
TechTimes 的报道标题精确概括了这一发现:"Grok Build Open-Sourced After Covert Upload — Code to Exfiltrate Repos Stays"。
上传代码仍存在于源码中
开源后最受关注的发现是:上传功能的代码没有被移除。它仍然完整地存在于源码中,只是被服务端配置的 flag 控制。
这意味着:
- 如果你 fork 了 Grok Build 的代码并自己编译,你需要手动移除上传逻辑
- 如果你使用 xAI 提供的编译版本,上传功能仍然是"装载着的武器"——随时可以被服务端重新激活
- 开源本身没有解决信任问题——开源 + 保留上传代码 + 服务端开关 = 你能看到枪,但扳机在别人手里
社区的补救措施
开源后,社区迅速产生了几个 fork 版本,这些 fork 的共同改动是:
- 删除
POST /v1/storage相关的所有代码 - 删除服务端配置拉取逻辑中与上传相关的 flag
- 添加网络请求白名单——只允许向
/v1/responses(模型调用)发送请求
七、Elon Musk 的回应与未回答的问题
回应了什么
Grok Build 事件曝光后,Elon Musk 在 X(前 Twitter)上回应称数据会被 "completely and utterly deleted"。
这个表述简洁有力,但缺乏技术细节。
没有回答什么
截至本文发布时(2026 年 10 月),xAI 没有发布正式的安全公告或事件报告。以下问题仍未得到回答:
| 问题 | 状态 | 为什么重要 |
|---|---|---|
| 默认上传整库的产品决策是谁做的? | 未回答 | 这是 bug 还是 feature? |
| 产品文档中是否有说明? | 未回答 | 如果没说明,涉及知情同意问题 |
grok-code-session-traces 中的数据保留了多长时间? | 未回答 | "会删除"和"已删除"是两件事 |
| 全球有多少用户受影响? | 未回答 | 影响范围决定事件级别 |
| "completely and utterly deleted" 包括备份和日志吗? | 未回答 | GCS 有版本控制和自动备份 |
| 是否有 xAI 员工访问了上传的代码数据? | 未回答 | 访问控制是数据安全的关键 |
| 上传的数据在被曝光之前是否已用于模型训练? | 未回答 | 如果用了,"事后删除"不够——模型已经学到了 |
八、每个用过 Grok Build 的人应该做什么
如果你在 2026 年 7 月 13 日之前使用过 Grok Build(版本 0.2.93 或更早),以下步骤建议立即执行:
第一步:识别受影响的仓库
回忆或检查你在使用 Grok Build 期间打开过哪些项目。Grok Build 上传的是整个工作区,不只是你当时在编辑的文件。
第二步:扫描 Git 历史中的敏感信息
对每个可能受影响的仓库,扫描 Git 历史中是否曾包含敏感信息:
# 搜索可能的 API Key 模式
git log -p -S "AKIA" # AWS Access Key
git log -p -S "sk-" # OpenAI API Key
git log -p -S "ghp_" # GitHub Personal Access Token
git log -p -S "password" # 通用密码字段
# 搜索 .env 文件的历史
git log --all --full-history -- "*.env"
git log --all --full-history -- "*secret*"
git log --all --full-history -- "*credential*"
# 搜索内部域名
git log -p -S "internal.company.com"
git log -p -S "gitlab.company.com"
第三步:轮换所有可能泄露的密钥
如果在 Git 历史中发现了敏感信息,立即轮换(更换新的密钥/密码)。不要假设"应该没人看到"——安全的基本原则是假设泄露已发生。
| 密钥类型 | 轮换方式 | 紧急程度 |
|---|---|---|
| AWS Access Key | AWS Console → IAM → Security credentials → Create access key → 停用旧 key | 🔴 立即 |
| OpenAI / Anthropic API Key | 对应平台控制台 → API Keys → 生成新 key → 删除旧 key | 🔴 立即 |
| GitHub Token | Settings → Developer settings → Personal access tokens → Regenerate | 🔴 立即 |
| 数据库密码 | 通过数据库管理工具修改密码,更新所有连接配置 | 🔴 立即 |
| SSH Private Key | ssh-keygen -t ed25519 生成新密钥对,更新所有授权密钥 | 🟡 尽快 |
| 内部服务器地址 | 无法"轮换",但应告知安全团队评估暴露风险 | 🟡 告知 |
第四步:检查 ~/.claude/ 目录
如果你同时使用 Claude Code,检查 ~/.claude/ 目录中是否包含 API Key 或其他凭证。如果有,轮换 Claude API Key。
第五步:审查 Git remote URL
git remote -v
如果 remote URL 包含内部 GitLab/GitHub 服务器地址,这些地址也已被上传。评估内部服务器域名泄露的风险——域名泄露本身可能不直接导致入侵,但它给攻击者提供了目标信息。
第六步:今后的防护
对于任何新的 AI 编程工具:
- 首次使用前用网络监控工具观察其出站请求。macOS 上 Little Snitch 或 Wireshark,Linux 上 tcpdump 或 mitmproxy
- 使用
.gitignore+ 额外的文件排除机制——但不要把.gitignore当作安全边界(ZCode 和 Grok Build 都不读.gitignore) - 永远不要在 Git 仓库中存储明文密钥——使用环境变量、密钥管理服务(如 AWS Secrets Manager)或本地
.env文件(加入.gitignore且确认从未被 commit)
九、来源汇总表
| # | 来源 | 类型 | URL |
|---|---|---|---|
| 1 | cereblab 的 wire-level 分析(含哈希校验和抓包日志) | 技术取证 | GitHub Gist |
| 2 | Grok Build Open-Sourced After Covert Upload | 媒体 | TechTimes |
| 3 | OurCoders 中文分析 | 社区 | ourcoders.com |
| 4 | Google Cloud Storage 对象版本控制文档 | 技术文档 | cloud.google.com |
| 5 | mitmproxy 官方文档 | 技术文档 | mitmproxy.org |
| 6 | Git bundle 文档 | 技术文档 | git-scm.com |
本文信息截至 2026 年 10 月 20 日。xAI 已于 7 月 16 日开源 Grok Build,如后续版本有安全改进,请以最新源码为准。