实测 REA:102 万个 AST 节点挖出 Halo CLI 的 8 个 API 端点,但 17149 个「不知道」才是重点

封面占位

前面两篇都在讲模型怎么接(OpenCode 免费模型中枢、Step 5 实测),这次换个方向 —— 把 agent 接到逆向工具上。

目标是 REA: Reverse Engineer Anything,一个把 agent 接到 Ghidra / IDA / Hopper / jadx 这些工具链的 MCP。我拿本机一个真实 CLI 直接跑了一遍,先说结论:

它最有价值的不是给了多少答案,而是把「我不知道」量化到了小数点。一次分析吐出 2409 条 limitation、17149 个 unknown,并且每个 unknown 都标了原因。

先说装机路上的三个坑

坑 现象 处理
PATH rea: command not found —— npm prefix 是 /root/.hermes/node,不在 PATH symlink 到 /root/.local/bin/rea
skill 比 npm 新 SKILL.md 写着 inspect_go_binary,但 v6.4.0 没这个命令 skill 来自 main 分支,skill 自己就警告过这情况
--dry-run 失效 rea mcp add --dry-run 直接执行了注册,不是预览 事后逐文件核查

第三个坑值得展开:它一口气改了 3 个文件 —— ~/.claude.json、~/.codex/config.toml、~/.config/opencode/opencode.jsonc。我原本只想看计划。好在核查下来是纯增量:Codex 里原有的 ruflo、model_provider 全在,Claude 那 30 来个顶层键没丢,我自己的 Hermes config.yaml sha256 与备份完全一致,一个字节没动。

还有个更要紧的:rea setup --dry-run 返回 clients: 空、plannedActions: 空、doctor.healthy: false —— 它的自动检测根本不认 Hermes。所以对这台机器,rea setup 是个空操作,只有 rea mcp add 写死的那几个 agent(Claude Code、Codex、OpenCode、Amp)被接上了。Hermes 这边我用 CLI 直接驱动,没改配置。

分析目标:本机的 Halo CLI

选它有两个理由:一是它不是为演示准备的样本,是真实装在机器上的工具;二是我手上有 Halo 站点,分析结果能立刻对照。

rea analyze-javascript-application 
  /root/.hermes/node/lib/node_modules/@halo-dev/cli 
  --format json

耗时约 7 分钟,产出 65,059,553 字节的单个 Evidence JSON。中间进度条刷了 1520 行 —— 每解析一个源文件打一行。

硬数据

指标 数值
相关文件 1520
解析成功的 JS 文件 1294
读入文本字节 10,856,559(约 10.8 MB)
访问的 AST 节点 1,020,490
findings 4079
parse failures 17
invalid UTF-8 文件 0

两张图:

  • JavaScriptApplicationGraph:6132 节点 / 6572 边。边类型 contains 4542、exposes 1561、imports 457、calls 9、loads 2、maps_to 1。
  • JavaScriptSemanticRelationGraph:11022 节点 / 8188 关系 / 17149 个 unknown / 3241 个 evidence context / 257 个函数指纹。

它挖到了什么:8 个 API 端点

这是本次最有实用价值的产出 —— 不用跑程序、不用抓包,静态就列出了 Halo CLI 会调的接口,连 HTTP 方法和调用机制都在:

端点 机制
/apis/api.store.halo.run/v1alpha1/applications call:appStoreClient.get
/apis/api.console.halo.run/v1alpha1/themes/install call:clients.axios.post
/apis/console.api.license.pro.halo.run/v1alpha1/activations call:clients.axios.get
/apis/content.halo.run/v1alpha1/categories axios.get / axios.post
/apis/content.halo.run/v1alpha1/tags axios.get / axios.post
/actuator/info call:clients.axios.get

两个端点值得单独说:/actuator/info 是 Spring Boot Actuator 的健康探针,说明服务端带了这个;另一条是 license 激活接口,对应 Halo 的 Pro 版授权。

另外还捞到一个本地 native addon:@napi-rs/keyring-linux-arm64-gnu,2.7 MB 的 .node 文件,是整个包里最大的文件。它的存在说明 Halo CLI 把凭据写进系统钥匙串(Linux 下是 Secret Service),而不是明文塞进配置文件 —— 这是静态看 package.json 很难一眼注意到的。

REA 逆向 @halo-dev/cli:双图产出、17,149 个 unknown 成因分布与预算耗尽根因
REA 逆向 @halo-dev/cli:应用图 6132 节点 / 6572 边(含 8 个 API 端点),语义图 11022 节点 / 8188 关系;17,149 个 unknown 中 14,460 条(84%)源于 resource-limit,根因是 javascript_analysis_semantic_result_budget_bytes = 0(coverage=partial、truncated=true)。

但 65MB 里的另一半是「我不知道」

把它的自我评价摊开看,信息量比上面那些发现还大:

项 数值
顶层 limitations 2409 条(其中 2403 条是 Resource constraint)
semantic graph unknowns 17149 个
coverage status partial, truncated: true
语义结果预算 javascript_analysis_semantic_result_budget_bytes: 0

unknowns 按原因分布:

原因 数量 占比
resource-limit 14460 84%
unknown-value 955 5.6%
ambiguous-target 942 5.5%
dynamic-call 731 4.3%
dynamic-property 61 0.4%

84% 的「不知道」不是因为代码难分析,是因为预算耗尽。语义结果预算被设成 0 字节,父结果预算 839 MB、worker 堆 1.02 GB、单文件超时 300 秒 —— 撞到上限的那些关系,REA 不是丢掉,而是原样标成 resource-limit 记在账上。

它的 limitation 文案也写得相当克制,几条原文:

  • 「Graph coverage is incomplete; unavailable facts remain explicit and must not be read as absence.」
  • 「Function fingerprints are static candidates; equal digests can remain ambiguous and do not prove behavioral equivalence.」
  • 「The semantic graph contains static syntax observations and conservative relationship candidates; it does not claim runtime execution.」

这些不是免责套话 —— 每一条都对应一个具体的、编号可查的 unknown。

两处我得指出来的毛病

  1. electron-renderer 是误报。图里有两个节点标成 electron 渲染进程:node_modules/sprintf-js/demo/angular.html 和它引用的 https://ajax.googleapis.com/.../angular.min.js。这俩是第三方依赖自带的 demo 页面,Halo CLI 是纯命令行工具,跟 Electron 没有任何关系 —— REA 顺着 html-script-entrypoint 机制把 HTML 里的 <script> 认成了 renderer 入口。
  2. summary 和 graph 自相矛盾。同一份 Evidence 里,summary 是一整片 0:browser_windows: 0、ipc.operations: 0、preload_entrypoints: 0、context_bridge_apis: 0…… 全部为零(这个判断是对的,它确实不是 Electron 应用);但 graph 里却有 electron-renderer 节点。两层结论没对齐,如果 agent 只读 graph 层就会得出错误结论。

还有一个更底层的:语义预算 = 0 这件事,只写在 coverage.limits 里,没进 summary。不翻到那层就看不到。

什么情况下该用它

场景 判断
拿到一个没有源码的 JS/Electron 包,想知道它调了哪些接口、依赖什么 ✅ 合适,本次就是这个用法
要审计一个 npm 包有没有可疑外联 ✅ endpoint 节点直接列出来
要确认「这个包里没有某功能」 ⚠️ 不行 —— coverage 是 partial,缺失不等于不存在
Native 二进制 / 固件反编译 ❌ 要 Ghidra / IDA / Hopper,我这台一个都没装
Go 二进制 ❌ v6.4.0 还没有这个命令

结论

  1. 能力是实的:102 万 AST 节点、6132 图节点、11022 语义节点,7 分钟出 65MB 带证据链的结果,8 个 API 端点和钥匙串 native addon 都是直接捞出来的。
  2. 诚实度是它最大的差异化:2409 条 limitation、17149 个带原因的 unknown、partial 覆盖率明写在结果里 —— 传统反编译工具不会告诉你这些。
  3. 但覆盖率就是覆盖率:语义预算 0 导致 84% 的 unknown 源于资源限制,而且 electron-renderer 有实打实的误报。用它下「有」的结论很稳,下「没有」的结论不行。
  4. 它现在对 Hermes 是空操作,只有 OpenCode / Claude Code / Codex / Amp 被自动接入;想要 Hermes 直接调,得手动改配置。

下回要是装上 Ghidra,就能把本机那 4 个 Go 二进制(komari agent、podman、cloudflared、gh)也过一遍 —— 那才是这个工具真正对准的靶心。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注