optimuslabs-io/grokpatrol

GitHub: optimuslabs-io/grokpatrol

一个离线只读的 Go 取证扫描器,用于检测 Grok Build CLI 是否将本地 git 仓库静默上传至 xAI,并识别需要轮换的泄露密钥。

Stars: 12 | Forks: 0

# grokpatrol 检测您的机器上的 **Grok Build CLI** 是否收集了您的 git 仓库并将其排队准备上传至 xAI —— 并告诉您**哪些密钥随之泄露**。 Grok Build CLI 被发现在静默状态下将整个 git 仓库上传到 Google Cloud Storage。该上传由一个在**工具调用权限系统之外**运行的后台收集器执行,因此即使在模型被拒绝访问文件的会话中,它也会触发。它上传的内容包括: - git HEAD 下的每一个被追踪文件, - 从 HEAD 可达的每一个 git object, - **已从 checkout 中删除,但在 git 历史记录中仍然可达的文件** —— 这正是 密钥往往藏身的地方。 确认受影响的版本:`0.2.93`。据报告,至少在 `0.2.99` 及之前的版本中仍然存在此问题。 该工具版本为 `0.1.0`,可检测 `0.1.212` 到目前已观测到的最新版本。 大多数已公开的指标都是基于网络的 —— 您必须在它发生时对网络流量进行监控。`grokpatrol` 回答了您事后仍可提出的问题:**此磁盘上留下了什么证据,哪些仓库被获取了,以及我需要轮换哪些凭据?** ## 安装 ### 1. 一键安装 ``` curl -fsSL https://raw.githubusercontent.com/optimuslabs-io/grokpatrol/main/install.sh | sh ``` 仅限 macOS 和 Linux;目前未针对 Windows 进行构建或提供支持。 ### 2. 通过 Go module proxy 从源码构建 ``` go install github.com/optimuslabs-io/grokpatrol/cmd/grokpatrol@latest ``` ### 3. 下载并验证 从 [发布页面](https://github.com/optimuslabs-io/grokpatrol/releases) 获取适用于您平台的二进制文件以及 `SHA256SUMS`,然后执行: ``` shasum -a 256 -c --ignore-missing SHA256SUMS # sha256sum on Linux # 证明二进制文件由该 repo 的 release workflow 构建 (sigstore): gh attestation verify grokpatrol_v0.1.0_darwin_arm64 -R optimuslabs-io/grokpatrol chmod +x grokpatrol_v0.1.0_darwin_arm64 && mv grokpatrol_v0.1.0_darwin_arm64 /usr/local/bin/grokpatrol ``` 校验和证明了下载文件的完整性。而证明(attestation)证明了更强有力的内容:该二进制文件由本仓库的发布工作流从源码构建而成,并记录在透明日志中,即使本仓库遭到入侵也无法篡改该日志。 ## 使用 ``` grokpatrol # scan this machine (summarized output) grokpatrol --verbose # scan this machine (full archive & secret list) grokpatrol --json # machine-readable, for fleet collection (all details) ``` 默认报告是一份透明的**摘要**:它列出了总数(归档数量、密钥数量),告诉您哪些最重要(已从您的 checkout 中删除的密钥),并引导您使用 `--verbose` 和 `--json` 获取完整的清单。这份摘要并非一种掩饰,而是一种声明,并附带了指向所有未展示内容的指针。 `--verbose` 会列出每一个 `gs://` 目标地址、每一个密钥文件(按名称和 blob id 列出)以及所有的证据行。 `--json` 提供完整的取证记录,适用于集群收集或自动化工具。 ### 观察其运行状态 报告本身会输出到 stdout,因此在您观察时,`grokpatrol --json | jq` 依然有效。`--quiet` 会将其静音。 ``` grokpatrol 0.1.0 scanning /Users/you → deepscan walking the filesystem for grok homes, upload queues, staged archives, and executables carrying the bucket name ✓ deepscan 1 executable carrying the bucket name, 1 upload queue, 2 staged archives (28ms) → logs reading Grok's logs (incl. rotated and gzipped) for repo_state.upload.start / .enqueued events → queue listing the upload_queue: staged codebase archives, and manifests naming the destination bucket → config checking config.toml for BOTH upload mitigations: harness.disable_codebase_upload and telemetry.trace_upload → version inferring the Grok version from install manifests, package metadata and binary strings ✓ logs 2 repositories with 3 archives QUEUED FOR UPLOAD, 1 repository collected, upload unconfirmed ✓ queue 2 codebase archives staged (371.2 KB), 1 manifest naming the bucket ✓ config NEITHER mitigation set: uploads are not blocked ✓ version 0.1.212, 0.2.39, 0.2.51, 0.2.56 -- REPORTED AFFECTED → secrets git rev-list --objects HEAD minus the working tree, per implicated repository ✓ secrets 3 secret files, 2 DELETED FROM THE CHECKOUT but still in history VERDICT: EXPOSED Queued 2 repos · 3 archives → gs://grok-code-session-traces/ Exfiltrated unconfirmed (enqueue logged; completion is not) Repos 2 repos touched · 3 credential paths ACTION Rotate credentials from full git history of touched repos. Mitigate uploads: set harness.disable_codebase_upload = true and telemetry.trace_upload = false in ~/.grok/config.toml (both required; see MITIGATIONS). CREDENTIAL PATHS (filenames and object ids only -- contents were never read by this tool) PATH PATHS DELETED ~/work/api 3 2 3 credential paths found. --verbose lists them by name, class and blob id; --json has the full record. Rotate the 2 you cannot see in your own checkout first. ``` 一个什么都没发现的检测器会大声声明这一点,而不是什么都不打印:一条无声的输出与崩溃的检测器毫无分别,而产生零发现的崩溃看起来与干净的机器完全一样。 用于脚本编写的退出码: | 代码 | 含义 | |---|---| | `0` | 扫描已运行并打印了报告 —— 无论发现了什么。请阅读报告中的 `VERDICT`,或 `--json` 中的 `"verdict"` 来查看结果。 | | `1` | 工具本身发生故障(参数错误、内部错误)。**绝不用于表示发现了结果。** | 退出码仅回答“grokpatrol 是否运行了”这个问题 —— 它无法告诉您主机状态是 CLEAN(干净)、INDETERMINATE(不确定)、EXPOSED(暴露)还是 COMPROMISED(已入侵)。请查看报告(在脚本中可使用 `--json | jq -r .verdict`)来获取这些信息。 ## 它检查的内容 | 指标 | 位置 | |---|---| | `repo_state.upload.start` / `.enqueued` 事件 | `~/.grok/logs/unified*.jsonl`(包括已轮转 + 已 gzip 压缩的文件) | | 等待上传的暂存归档 | `~/.grok/upload_queue/` | | 指明目标 bucket 的 manifest | 暂存的 `metadata.json` → `gs://grok-code-session-traces/` | | 嵌入在二进制文件中的 bucket 名称 | 磁盘上的任何可执行文件 | | 两个缺失的缓解措施 | `~/.grok/config.toml` → 见下文 | | 受影响版本 | 安装 manifest、包元数据、二进制字符串 | | **上传对象集合中的密钥** | `git rev-list --objects HEAD` 减去工作区 | ### 密钥 密钥部分是最重要的。被窃取的集合是“所有从 HEAD 可达的 git object”,这正是 `git rev-list --objects HEAD` 所枚举的内容。 从中减去当前的 checkout,就会得到那些**已从工作区中消失但仍在历史记录中存留**的文件 —— 比如被删除的 `.env`、已轮换掉的 `.pem`。 **在默认模式下**,报告会显示密钥的数量,并特别标明其中有多少已从您的 checkout 中删除(那些您无法通过查看当前目录来找到的密钥)—— 因为这些是优先处理的:它们已经离开了您的磁盘,但在 git 历史中依然存活,并且被优先发送出去。 **使用 `--verbose` 时**,每个密钥都会附带其**完整路径和 git object id** 进行报告,`rev-list` 会将路径和它们打印在同一行。这是本报告中您唯一可以自行验证的声明: ``` git -C ~/work/payments-api cat-file -p d6da7879bc89 # the .env you deleted, still in history ``` grokpatrol 绝不会运行该命令。`cat-file` 不在其 git 允许列表中,因此它交给您一个它在结构上根本无法读取的文件的指针 —— 这也正是它为什么能放心地把指针交给您的原因。 ## 保证 以下保证是由机制强制执行的: - **绝不联网。永不。** 由链接器证明:`make verify-deps` 断言 `net`、`net/http` 和 `crypto/tls` 未出现在 `go list -deps` 中,且 `go.sum` 为空。一个没有链接网络包的二进制文件无法向外界发送数据。这里没有远程检查、没有遥测、没有更新探测。 - **零依赖。** 仅使用标准库。一个专门用来搜寻未经审核代码的工具,本身不应该携带任何此类代码。 - **只读。** 每一个文件的打开操作都通过一个带有 `O_RDONLY` 标志的函数执行。这里没有 `--out` 参数、没有缓存、没有状态目录。有一项测试会在扫描前后对 `.git` 进行快照,并要求达到字节级别的完全一致。 - **绝不执行 `grok` 二进制文件** —— 连 `grok --version` 都不执行。它携带着一个在权限系统之外运行的收集器;为了问一个问题而启动它,本身就可能开启一个会话。版本号是被动推断的。 - **绝不读取密钥的*值*;但始终读取密钥的*位置*。** 报告会打印出每一个暴露的凭据文件的完整路径和 git object id,因为一份您无法定位的轮换清单是毫无用处的。它绝不读取其内容:`git cat-file` 被排除在允许列表之外,因此不存在任何能够读取 blob 的代码路径。`model.Evidence` 没有任何能够容纳文件内容的字段,这使得这一点成为结构上的硬性限制,而非一种口头承诺 —— 行*号*是证据,而一行的*文本*永远不是。`~/.grok/auth.json` 仅检查其是否存在,从不打开。 - **每一项肯定的发现都会引用您可以亲自去查看的内容。** 被排队的归档会报告其 `gs://` 目标地址,以及 Grok 将其排队时写入的日志文件和行号;暂存的归档会附带其 SHA-256;暴露的密钥会附带其 blob id。一份需要您盲目相信的判定结果并不是取证结果。 - **您的归档绝不会被解压。** 上传队列中的 `*codebase.tar.gz` 是您自己的源代码。它仅记录名称、大小和 SHA-256。未导入 `archive/tar`。 - **降级的扫描绝不会报告 CLEAN。** 如果 macOS TCC 拦截了对 `~/Documents` 的访问,判定结果将是 INDETERMINATE,并且报告会说明它无法查看哪些目录。 ## 两件值得了解的事情 **没有证据并不代表证据不存在。** 被清空的上传队列意味着归档已经*发出*了,而不是说它们从未存在过。轮转掉的日志不会留下任何痕迹。报告中的“BLIND SPOTS(盲区)”部分在每次运行时都会打印出来(包括结果干净的运行),正是因为这个原因。 **一个提及该 bucket 的文件并不代表它是一个安装。** grokpatrol 会区分一个*包含*收集器的可执行文件和一个仅仅*提及*了这些指标的文本文件 —— 比如您自己的笔记、IoC 列表、另一个检测工具,或者是本仓库的测试夹具。可执行文件和已打包的 bundle(≥512 KB,因为 Grok 可能以 Bun/Node bundle 的形式发布,没有可执行文件的 magic 标识)会被报告为一次安装;小的文本文件会为了完整性而被列出,但不会影响判定结果。 ## 常见问题解答 (FAQ) **Grok Build CLI 是否将我的 git 仓库上传到了 xAI?** grokpatrol 会根据磁盘上留下的证据为*您的*机器回答这个问题:它会报告哪些仓库被收集并排队准备上传到 `gs://grok-code-session-traces/`,以及是否确认了上传行为。它无法代表未扫描的机器发言。运行它并阅读 `VERDICT` 即可。 **哪些 Grok Build CLI 版本受到影响?** `0.2.93` 是确认受影响的构建版本 —— 已被公开复现出收集整个仓库并上传的行为。据报道,至少在 `0.2.99` 及之前的版本中,收集器依然存在。grokpatrol 可以检测 `0.1.212` 到目前已观测到的最新版本,并将每个构建标记为 CONFIRMED AFFECTED(确认受影响)、REPORTED AFFECTED(报告受影响)或两者皆非。 **我如何事后检查 Grok 是否上传了我的代码?** 大多数已发布的指标都是基于网络的 —— 您必须在它发生时对网络流量进行监控。grokpatrol 提供了事后的检查:它会读取 Grok 自己的日志(包括已轮转和 gzip 压缩的)、`~/.grok/upload_queue/`、暂存的归档 manifest 以及版本号,然后点名相关的仓库和密钥。不需要实时抓包。 **上传队列是空的,而且我的日志已经轮转了 —— 这意味着我安全吗?** 不。被清空的队列意味着归档已经*发出*了,而不是从未存在过,轮转掉的日志也不会留下痕迹。这就是为什么降级或盲测扫描的结果是 INDETERMINATE,而永远不会是 CLEAN 的原因,也是为什么报告在每次运行时都会打印出它无法看到的内容。 **我该如何阻止 Grok 上传我的仓库?** 在 `~/.grok/config.toml` 中设置**两项**缓解措施:`harness.disable_codebase_upload = true` 和 `telemetry.trace_upload = false`。仅设置其中任何一项都是不够的 —— 如果一台主机只设置了一项,grokpatrol 会将其报告为 EXPOSED(未缓解)。 **哪些密钥暴露了,我应该先轮换哪些?** 被窃取的集合是每一个从 HEAD 可达的 git object,因此一个您从 checkout 中*删除*的密钥依然存在于历史记录中,并随之发送出去了。grokpatrol 会优先标记这些:请在轮换其余凭据之前,优先轮换您在自己的工作树中已经无法看到的凭据。 **grokpatrol 会读取或传输我的密钥值吗?** 绝对不会。它报告的是密钥的*位置*(路径和 git object id),以便您知道需要轮换什么,并且它在结构上就无法读取它们的*内容*:`git cat-file` 不在其允许列表中,并且证据模型没有可以容纳文件内容的字段。它完全不会发起任何网络调用 —— 这由链接器(`make verify-deps`)证明,该命令断言 `net`/`net/http`/`crypto/tls` 未被链接进来。 **在可能已遭到入侵的主机上运行它安全吗?** 这正是它的设计目标。grokpatrol 是一个单一静态二进制文件,仅使用标准库,完全离线且只读(每个文件打开方式都是 `O_RDONLY`;测试证明 `.git` 在扫描后达到字节级一致)。它绝不执行 `grok` 二进制文件。发布的二进制文件带有 sigstore 来源证明,因此您可以在运行它之前证明它是由本仓库的工作流构建的 —— 详见 [AGENTS.md](AGENTS.md)。 **我可以在集群或 CI 中运行 grokpatrol 吗?** 可以。`grokpatrol --json` 在 stdout 上输出完整的取证记录以供收集;只要扫描运行了(无论发现了什么),退出码就是 `0`,而 `1` 仅表示工具故障,因此请从报告中读取判定结果,退出状态:`grokpatrol --json | jq -r .verdict`。 ## 开发 单独运行 `make` 会列出所有 target。 | Target | 作用 | |---|---| | `make build` | 为当前机器构建 `./dist/grokpatrol` | | `make run` | 构建,然后扫描当前机器(使用 `ARGS="--json"` 传递参数) | | `make demo` | 构建一个合成的受入侵主机并扫描它 —— 预期结果为 COMPROMISED | | `make check` | CI 运行的内容:依赖 + fmt + vet + 竞争测试 + 交叉编译冒烟测试 | | `make verify-deps` | 证明二进制文件仅使用标准库,无网络连接且无 cgo | | `make test` / `fuzz` / `bench` | 竞争测试、日志解析器 fuzz 测试、扫描器基准测试 | | `make fmt` / `vet` | 原地运行 gofmt,go vet | | `make release` | 全部四个平台,精简构建,无 CGO,附带 `SHA256SUMS` | | `make clean` / `distclean` | 移除构建输出;同时清除缓存和演示夹具 | `make demo` 是最值得运行的目标。由于没有真实的 Grok 安装可供测试,受入侵的情况是人为构造的:植入每一个主机端指标,包括一个包含已提交随后被删除的密钥的仓库。它应该会打印出 `VERDICT: COMPROMISED`,并将 `.env.production` 和 `certs/prod.pem` 标记为*已从 checkout 删除,仍在历史记录中*。 测试夹具是动态生成的而不是直接提交到仓库中的:携带真实 IoC 字符串的文件会触发企业 EDR,这对于任何克隆本仓库的人来说都是一个大麻烦。
标签:BurpSuite集成, EVTX分析, 日志审计