GrokCLI风险分析:一个prompt如何把敏感文件送上云端?

admin 2026-07-17 04:31:06 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 慢雾安全团队分析发现GrokCLI0.2.98版本存在严重数据泄露风险。每次对话开始时,CLI会通过gitbundle将整个代码库(包括.gitignore排除的.env、.envrc、config.secret等敏感文件)以明文形式上传至xAI云端存储。上传是独立于模型调用的前置步骤,且脱敏系统仅作用于trace日志,不覆盖gitbundle路径。服务端可通过远程配置强制开启上传,用户本地关闭无效。建议用户谨慎使用或等待官方修复。 综合评分: 88 文章分类: 漏洞分析,数据泄露,安全工具,渗透测试,红队


cover_image

Grok CLI 风险分析:一个 prompt 如何把敏感文件送上云端?

原创

慢雾安全团队 慢雾安全团队

慢雾科技

2026年7月16日 19:01 中国香港

在小说阅读器读本章

去阅读

**## 事件背景

**##

事情的起因是山哥(23pds,慢雾 CISO)从社区看到一个说法:Grok CLI 可能有风险。具体是什么风险、风险多大,社区里众说纷纭。我(Thinking,慢雾业务安全负责人)手头刚好有新版 0.2.98 的 macOS aarch64 二进制(SHA-256 d5952131…),于是决定自己动手分析一遍——与其猜测,不如把二进制拆开、把流量抓下来、把上传内容恢复出来看个究竟。

分析下来,结论比社区传言更具体:Grok CLI 在每次 turn 开始时,会通过 git bundle 把整个代码库——包括 .gitignore 排除的 .env、.envrc、config.secret——逐字无脱敏地上传到 xAI 的云端存储。上传发生在模型收到推理请求之前,与「Improve the model」开关完全独立,且服务端可通过远程设置强制开启。我用 IDA Pro 9.3 做静态反编译、Frida 做运行时字节验证、mitmproxy 12.2.3 做流量捕获,三条路径交叉验证,并和社区已分析的 0.2.93 做了版本对比——上传机制两版本完全一致。

由于 Grok CLI 没有开源代码,在没有源码的情况下通过反编译和动态调试可以获得一些信息,但是很难保证这些信息的 100% 准确,在这次分析中用了静态分析和动态调试,以及流量分析进行了交叉验证,尽可能让结论不会偏离太多,以下为本文复现的情况。

(注:本文分析完成时,Grok CLI 尚未开源。2026 年 7 月 16 日,Grok CLI 已正式开源,后续我们将结合公开源码进一步验证并补充本文分析结果。)## 复现环境

**##

我搭了一个最小化的测试项目,专门用来验证上传行为:**

/tmp/grok-test-project/├── .env            ← 6 个假 API 密钥├── .envrc          ← 2 个密钥├── config.secret   ← RSA 私钥├── .gitignore      ← 排除 .env, .envrc, *.secret├── src/main.py     ← print("hello")└── README.md       ← # Test Project

**.env 里放了 6 个假的 API 密钥(OpenAI、Anthropic、数据库、AWS、Stripe、JWT),.envrc 放了 2 个,config.secret 放了一段假的 RSA 私钥。.gitignore 明确排除了 .env、.envrc 和 *.secret——这是关键,因为如果 Grok CLI 遵守 .gitignore,这三个文件就不该被上传。

Grok 的配置(~/.grok/config.toml)我用默认条件下观察完整上传行为:**

[features]telemetry = false -> 默认配置
[telemetry]trace_upload = false -> 默认配置
[harness]disable_codebase_upload = false -> 默认配置, 2026.07.13 之后服务端返回的配置变成了 true,这个开关是服务端是可以控制的!

mitmproxy 这边,Grok CLI 用的是 rustls 0.23.37(静态编译 webpki-roots),不走 macOS 系统钥匙串,也没有证书固定(搜 cert.*pin|pinned 全无结果)。于是只要把 SSL_CERT_FILE 指向 mitmproxy 的 CA 证书,所有 HTTPS 流量都能解密:

export GROK_DEPLOYMENT_KEY="fake-deployment-key-for-testing"export GROK_RESPECT_GITIGNORE=0export GROK_SANDBOX=noneexport HTTPS_PROXY="http://127.0.0.1:8080"export SSL_CERT_FILE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"

**发送的 prompt 很简单:Hello World!。

prompt 发出去后,5.2 秒内 mitmproxy 捕获了 45 个 HTTP 请求。其中 14 个是 POST /v1/storage——上传请求。

最值得关注的是时间线。上传不是模型调用的副产品,而是先于模型调用的前置步骤:

18:31:29.882  POST /v1/storage  config.json (4,086B)           ← 上传开始18:31:29.883  POST /v1/storage  config_files.json (1,485B)18:31:29.895  POST /v1/storage  plugins.json (42B)18:31:29.896  POST /v1/storage  tool_definitions.json (45,440B)18:31:29.898  POST /v1/storage  metadata.json (913B)18:31:29.901  POST /v1/storage  before_session_state.tar.gz (4,689B)18:31:29.905  POST api.x.ai/v1/responses  (1,252B)            ← 模型调用 (晚 4ms)18:31:30.179  ← 模型返回 403 (无 credits)18:31:30.217  ← 上传返回 401 (伪造 key)18:31:30.458  POST /v1/storage  git bundle (1,120B)           ← 代码库上传18:31:33.815  POST /v1/traces  (18,272B)                      ← OpenTelemetry trace

**6 个初始上传在 19 毫秒内完成(29.882 → 29.901),然后模型调用才开始(29.905)。上传比模型调用早 4 毫秒。换句话说,用户的代码被发往服务端之后,模型才收到推理请求——上传是一个独立的前置步骤,不是模型调用的副作用。

更奇怪的是,即使模型返回 403(账户无 credits),上传请求照样发;即使上传返回 401(我用的是伪造的 deployment key),上传队列仍然持续尝试,直到断路器介入:****

Upload queue circuit breaker tripped, pausing dispatch

## 复现要点

****##

从流量里恢复出敏

感文件,定位 bundle:在捕获到的 14 个上传请求中找 Content-Type: application/gzip 且 x-storage-path 含 repo_changes_dedup/v2/bundles/*.bundle 的请求。

提取与克隆:

cp captured/requests/req_036_*.raw.bin /tmp/captured.bundlegit bundle verify /tmp/captured.bundlegit clone /tmp/captured.bundle grok_bundle_clone

**git bundle verify 输出 The bundle contains 1 ref: refs/heads/main 即为有效 bundle,可直接克隆恢复完整仓库。

这是整个分析里我最想让大家亲眼看到的一步。

我在 14 个上传请求里发现 Content-Type 是 application/gzip,x-storage-path 指向 turn_0/repo_changes_dedup/v2/bundles/sha256_84aa…bundle。这不是一个文件摘要,而是一个标准的 git bundle——可以通过 git clone 直接恢复出完整的仓库历史。

把它从流量里提取出来,用 git 原生命令验证并克隆:

$ git bundle verify /tmp/captured.bundleThe bundle contains 1 ref:    refs/heads/main/tmp/captured.bundle is okay
$ git clone /tmp/captured.bundle grok_bundle_clone

**克隆成功,恢复出 6 个文件:.env、.envrc、config.secret、.gitignore、README.md、src/main.py。其中三个是 .gitignore 明确排除的。

.env 的内容,无脱敏:

OPENAI_API_KEY=sk-test-key-1234567890abcdefANTHROPIC_API_KEY=sk-ant-test-key-abcdef123456DATABASE_URL=postgresql://user:password@localhost:5432/dbAWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYSTRIPE_SECRET_KEY=sk_live_test_stripe_secret_key_12345JWT_SECRET=my-super-secret-jwt-key-1234567890

****6 个 API 密钥、2 个环境变量密钥、一段 RSA 私钥,全部原样躺在 git bundle 里。脱敏过滤器(如果它生效的话)本该把这些替换成 [REDACTED_SECRET]——但它没有。

脱敏过滤器为什么没拦住?这是一个典型的「一致性缝隙」——声明层(有脱敏系统)和实现层(git bundle 不经过脱敏)不一致。安全机制存在,但没有覆盖全部数据出口。

同一个 sk-test-key-1234567890abcdef,在 trace 日志里是 [REDACTED_SECRET],在 git bundle 里是明文。两条路径,两种待遇。脱敏系统给了一个「我已经处理过敏感数据」的承诺,但 git bundle 路径绕过了这个承诺——缝隙就在这里。

Grok CLI 确实内置了一套脱敏系统。从 IDA 里能提取出完整的 12 种正则(基于 Rust regex::RegexSet):Bearer 令牌、GitHub 令牌、GitLab/Slack 令牌、Stripe/xAI 密钥、AWS 密钥、Google API 密钥、PEM 私钥、JWT、用户主目录路径……覆盖面不算窄,匹配后替换为 [REDACTED_SECRET]。

但这套脱敏只作用于 trace 日志和 JSON 元数据里的键值对。源文件路径是 xai-grok-shell/src/upload/trace.rs。而 git bundle 上传走的是另一条路径——xai-data-collector/src/queue.rs,一个独立的上传队列。

于是出现了一个割裂:同一个 .env 文件里的 sk-test-key-1234567890abcdef,在 trace 日志里会被脱敏成 [REDACTED_SECRET],但在 git bundle 里会原样上传。两条路径,两种待遇。一次 turn 里上传的 12 类内容中,git bundle 是唯一携带原始文件内容的那一类,恰好也是唯一不经过脱敏的那一类。

发现了 8 个独立开关

trace_upload 的 8 个独立启用来源(IDA find_regex 地址 0x1060da2b0):****

telemetry_mode -- 全局 telemetry 模式in_requirement_pin -- 编译期需求标记in_requirement_src -- 源码级需求标记in_env_trace_upload -- GROK_TELEMETRY_TRACE_UPLOADin_env_telemetry_enabled -- GROK_TELEMETRY_ENABLEDin_cfg_telemetry_trace_upload -- 配置 telemetry.trace_uploadin_cfg_features_telemetry -- 配置 features.telemetryin_remote_trace_upload_enabled -- 远程设置覆盖

**配置优先级 env > config > remote,但 in_remote_trace_upload_enabled + has_remote_settings 本身就是独立启用来源——服务端可远程强制开启。

race_upload 的启停不由单一开关控制。IDA 里有一段连续字符串,明确列出了 8 个独立启用来源。

最后一个来源 in_remote_trace_upload_enabled 配合 has_remote_settings,这似乎意味着 xAI 后端可以通过远程配置强制开启上传——即使用户在本地关掉了所有环境变量和配置文件。配置优先级是 env > config > remote,但远程设置本身就是独立的启用来源。

更要紧的是,trace_upload 和界面上那个「Improve the model」开关(coding_data_retention_opt_out)完全独立。关掉「Improve the model」不会禁用 trace_upload。二进制里有一条日志说得很直白:

Telemetry disabled but trace uploads enabled: session artifacts will be uploaded, analytics events will not

**respect_gitignore 默认 false

.gitignore 排除的文件为什么还是被上传了?因为 respect_gitignore 的默认值是 false。

IDA 反汇编 0.2.98 的配置解析函数,地址 0x10372EB5C:

loc_10372EB5C:    MOV  W27, #0              ; respect_gitignore 默认值 = 0 (false)0x10372EBAC:    AND  W8, W27, #1          ; 提取布尔位0x10372EBB0:    STRB W8, [X19, #0xC8]     ; 写入结构体偏移 0xC80x10372EBC4:    ADRL X0, aGrokRespectGit  ; 加载 "GROK_RESPECT_GITIGNORE"

**

MOV W27, #0 把默认值设为 0(false)。为了排除 IDA 分析误差,我用 Frida spawn 进程后直接读运行时内存:**

var base = Process.enumerateModules()    .find(m => m.name === "grok").base;// base = 0x100f0c000, ASLR slide = 0xf0c000// IDA 地址 0x10372EBB0 + slide = 0x10463abb0var bytes = Memory.readByteArray(ptr("0x10463abb0"), 4);// 返回: 68 22 03 39 (little-endian)

**读到的字节是 68 22 03 39,little-endian 解码为 0x39032268,即 ARM64 指令 STRB W8, [X19, #0xC8]——和 IDA 反汇编完全一致。二进制里的帮助文本也写明了:respect_gitignore = false # default: false。

默认 false 意味着 Grok CLI 在创建 git bundle 时不会遵守 .gitignore。这就是 .env、.envrc、config.secret 出现在上传内容里的直接原因。

(我在 0.2.93 上也做了同样的验证)

**## 版本对比

社区白帽子 cereblab 之前在 Gist 上分析了 0.2.93,列了 10 项声明。我逐项在 0.2.98 上做了验证。

通过 IDA find_regex 搜 0.2.98、strings | grep 搜 0.2.93,以下方面两版本完全一致:8 个 trace_upload 来源、12 种脱敏正则、8 个存储端点、断路器的状态、GCS 存储桶 grok-code-session-traces、GROK_TELEMETRY_GCS_BUCKET 环境变量、无证书绑定。

(顺带一提,mitmproxy 还抓到 0.2.98 运行时会自动检查 x.ai/cli/stable 获取最新版本号,发现 0.2.101 后自动分 7 片下载新版本二进制。自动更新本身不意外,但它意味着即使你今天分析透了手里这个版本,明天它可能就换了,自动更新可能会引入新版本的功能,但是也会有新的风险。)

**## 缓解措施

由于 in_remote_trace_upload_enabled 的存在,服务端可远程下发配置覆盖本地设置,单一防护层不足以完全禁用上传。建议至少三层并用:

  • 环境变量层:GROK_TELEMETRY_ENABLED=0 + GROK_TELEMETRY_TRACE_UPLOAD=0 + GROK_RESPECT_GITIGNORE=1

  • 配置文件层:~/.grok/config.toml 中 disable_codebase_upload=true + telemetry=false

  • 网络层:防火墙阻断 cli-chat-proxy.grok.com 与 storage.googleapis.com 出站

  • 另外:敏感文件移出项目目录;chmod 600 ~/.grok/auth.json。

如果要继续用 Grok CLI 同时减少数据上传,建议三层并用——因为远程设置可覆盖本地配置,单一防护层不够。具体配置见左侧 note。## 写在最后

这次分析让我比较在意的,这不是简单的漏洞,而是「一致性缝隙」——脱敏系统存在但不覆盖 git bundle、8 个开关里有一个是远程可控、「Improve the model」关了但 trace_upload 照传。每一处单独看都有解释(脱敏是给 trace 用的、远程设置是为了运营、「Improve the model」管的是另一回事),但拼在一起,用户的 .env等敏感数据就这么被上传到了云端。

CLI 编码工具的隐私边界,本质上是信任边界。你把整个仓库交给一个会在 turn 开始时就打包上传的二进制,等于把信任交给了它的每一个开关、每一条上传路径、每一次远程配置下发。而信任该持续验证,不该一次性授予。大家也多注意安全。

本文基于 IDA Pro 9.3 反编译、Frida 动态插桩、mitmproxy 12.2.3 流量捕获的交叉验证。分析的二进制版本为 grok-0.2.98 和 grok-0.2.93(macOS aarch64)。

参考资源

cereblab 的 0.2.93 分析:https://gist.github.com/cereblab/dc9a40bc26120f4540e4e09b75ffb547**

往期回顾

TG 账号失守、钱包被调包,macOS 木马如何突破防线?

威胁情报 | Injective SDK 投毒,加密钱包私钥失窃

Google Sites 社群申请钓鱼与 macOS 窃密木马分析

链上 AI 情报官 TrackAgent 正式接入 SlowMist 丢币公益评估

慢雾招募令 | AML 产品 GTM 市场负责人

慢雾导航

慢雾科技官网

https://www.slowmist.com/

慢雾区官网

https://slowmist.io/

慢雾 GitHub

https://github.com/slowmist

Telegram

https://t.me/slowmistteam

Twitter

https://twitter.com/@slowmist_team

Medium

https://medium.com/@slowmist

知识星球

https://t.zsxq.com/Q3zNvvF


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:慢雾科技 慢雾安全团队 慢雾安全团队《Grok CLI 风险分析:一个 prompt 如何把敏感文件送上云端?》

评论:0   参与:  0