[PR #4813] feat(ida): support the official Hex-Rays IDA MCP headlessly #6421

Open
opened 2026-10-31 01:14:20 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4813
Author: @NaiJii
Created: 10/17/2026
Status: 🔄 Open

Base: main ← Head: feat/ida-hexrays-mcp-profile


📝 Commits (10+)

  • 284dc94 feat(ida): support the official Hex-Rays IDA MCP headlessly
  • c8df281 feat(ida): name the Hex-Rays registration for its catalog
  • fd23f10 docs(ida): state Hex-Rays prerequisites and upstream session traces
  • e5b9285 fix(ida): skip inline data when scanning Hex-Rays calls
  • 455886a fix(ida): retain Hex-Rays health reads in evidence
  • e3fb3e7 test(ida): list the Hex-Rays bridge in package fixtures
  • 2e95683 feat(ida): open existing IDA databases through the Hex-Rays profile
  • 4f2cd4c Merge remote-tracking branch 'origin/main' into feat/ida-hexrays-mcp-profile
  • 0c38b4e fix(ida): verify the private workspace of a database target by idb_path
  • c20fb64 test(ida): fix Hex-Rays health stand-ins and Windows path and runtime teardown

📊 Changes

35 files changed (+1948 additions, -135 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/SKILL.md (+4 -1)
➕ bridge/ida/hexrays_queries.py (+325 -0)
➕ bridge/ida/test_hexrays_queries.py (+248 -0)
📝 docs/ida-provider.md (+41 -7)
📝 package.json (+2 -1)
➕ scripts/lib/ida-private-input.d.mts (+8 -0)
➕ scripts/lib/ida-private-input.mjs (+13 -0)
📝 scripts/verify-packed-bridge.mjs (+17 -14)
📝 scripts/verify-real-ida.mjs (+60 -13)
📝 src/application/BinaryTargetResolver.ts (+25 -1)
📝 src/application/binary/AnalysisProviderRegistry.test.ts (+1 -0)
➕ src/domain/analysisDatabase.ts (+10 -0)
📝 src/domain/binaryTargetTypes.ts (+3 -0)
📝 src/hopper/HopperNameInventory.test.ts (+1 -0)
📝 src/hopper/HopperProvider.ts (+8 -0)
📝 src/hopper/HopperProviderDiscovery.test.ts (+1 -0)
📝 src/ida/IdaConfiguration.ts (+9 -1)
➕ src/ida/IdaHexRaysProtocol.test.ts (+383 -0)
➕ src/ida/IdaHexRaysProtocol.ts (+126 -0)
📝 src/ida/IdaMcpConnection.ts (+5 -2)

...and 15 more files

📄 Description

Summary

Adds a hexrays protocol to the IDA provider so REA can use the official Hex-Rays IDA MCP (HexRaysSA/ida-mcp) as a headless upstream. Today REA rejects that server at startup with a missing-tools error, because it exposes database reads only through execute_python.

Relates to https://github.com/morluto/rea/issues/1416.

Problem and expected behavior

A registration such as

{ "command": "uvx", "args": ["--exclude-newer=1s", "ida-mcp", "stdio", "--agent=rea"], "mode": "headless", "protocol": "hexrays" }

now supports the same analyst operations as the database-supervisor headless profile: inventory and search, lookup, containment, pseudocode, assembly, instruction windows, callees, xrefs and dossiers. It uses the same private-workspace ownership, identity checks and cleanup guarantees.

Change and scope

  • bridge/ida/hexrays_queries.py (new, packaged): a fixed, read-only query module. REA sends it with one trailing _rea_query(name, arguments_json) line, so caller input reaches it only as JSON data. Its replies use the database-supervisor shapes REA already validates.
  • IdaHexRaysProtocol.ts (new): the catalog, the open/list/close schemas, and runHexRaysQuery, which records execute_python, the query, its arguments and the bridge SHA-256 rather than the module body.
  • IdaOperationRunner: an optional invoke hook. For hexrays, the runner keeps its modern parsing and records the actual upstream observation.
  • IdaSessionClient: the open, inventory, health and close steps branch per protocol. Hex-Rays inventory is normalized into the existing session vocabulary, so the binding, blocking-session and retained-workspace logic is shared. The supervisor open moves unchanged into #openSupervisor.
  • IdaConfiguration: protocol: "hexrays" requires mode: "headless" and a stdio registration, because Hex-Rays leases belong to the server process.
  • Startup profile mismatches report advertised_tools. When a registration without protocol reaches a Hex-Rays server, the message and recovery name the hexrays registration.
  • IdaMcpConnection: isError replies throw IdaToolRejection, a subclass of AnalysisProtocolError. Lifecycle code can then tell a completed rejection apart from an unreadable or missing reply.
  • scripts/verify-real-ida.mjs: accepts Hex-Rays observations, keyed by instance_id through execute_python, and checks release by path, since leases don't survive a new server process. For every profile it now also asserts that no file appears beside the original target.
  • scripts/verify-packed-bridge.mjs: requires and parses the packed Hex-Rays bridge.
  • Docs: a new docs/ida-provider.md section, plus a table row and coverage note.

Not included: attached use or databases already open in the IDA GUI (#1416 use case 4), HTTP registrations, direct callers, and any forwarding of caller Python. REA never calls save_database.

Contract and boundary impact

  • Semantic owner and earliest changed stage: IDA provider registration (IdaConfiguration) and upstream lifecycle (IdaSessionClient).
  • Contribution scope fit and linked maintainer decision: pending in #1416. The profile runs REA-authored Python in the user's idalib worker.
  • CLI and MCP/tool-catalog contract: unchanged. Operations and output contracts are the existing IDA ones.
  • Provider, bridge, target-format, or platform compatibility: new optional protocol. Existing registrations behave as before.
  • Evidence, artifact, provenance, or reconstruction contract: observations name the real upstream tool (execute_python) and the bridge digest. compatibility_profile is hexrays-ida-mcp.
  • Target selection, authority, upstream behavior, and cleanup details: the private copy only. GUI backends are refused. Release requires list_databases to show neither the lease nor the private path. The Hex-Rays MCP packs the private .i64 on release, inside the workspace REA removes.
  • Generated metadata, package, or installation impact: package.json#files adds bridge/ida/hexrays_queries.py. Nothing is installed.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process. (The registered stdio MCP server, which starts an idalib worker.)
  • Inject instrumentation or run scripts/code. (A fixed REA query module inside IDA, never inside the target.)
  • Change process memory, registers, files, or configuration. (Files only: the private copy and its .i64 inside REA's workspace. Upstream Hex-Rays MCP session traces under ~/.idapro/mcp/sessions/. No process memory.)
  • Contact network services. (Upstream only: uvx package resolution and IDA licensing. REA makes no network calls.)
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated:

    • src/ida/IdaHexRaysProtocol.test.ts (11 tests) with a stateful RecordingHexRaysIdaMcp fixture:
      • the registration boundary
      • a full lease, read and release run with observation provenance
      • ignoring peer databases
      • a profile mismatch naming execute_python, and a protocol-less registration pointed at Hex-Rays naming the hexrays registration
      • GUI refusal, with no close_database call and the workspace retained
      • a lost open reply, both with a late lease (adopted and released) and with none (workspace retained)
      • cleanup after an explicit open rejection
      • a rejected close, both released and retained, and retried on the next close
      • a Python round trip of the generated _rea_query line with adversarial lookup names
    • bridge/ida/test_hexrays_queries.py (8 tests, added to npm run test:python): runs the bridge as execute_python would, against IDA module stand-ins. It covers paging and cursors, the snapshot bound, failure rows, callees keeping interior, non-function and pointer-slot targets, call-graph entries only, and xrefs excluding ordinary flow and duplicates.
  • Base reproduction: on the base revision, a Hex-Rays registration fails at startup with missing_tools covering the whole database-supervisor catalog.

  • Remaining proof gaps:

    • Unverified: macOS and Windows hosts, PE and Mach-O targets, and large binaries. The bridge holds one enumeration snapshot, such as the full string list, while it is paged.
    • Every read resends the module (about 11 KB). Local reads took about 20–30 ms each; no before/after performance comparison was made.
    • Health reads during auto-analysis drain on cancellation rather than abort.
  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity, source provenance, and failed attempts remain preserved.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:local -- src/ida/*.test.ts tests/composition/idaProvider.test.ts: 20 files, 338 tests passed.
  • npm run test:focused -- tests/boundary/cli/idaStartupAttributionCli.test.ts: passed.
  • Production CLI with a protocol-less registration against the real Hex-Rays server: the error reported the six advertised tools and recommended "protocol": "hexrays".
  • npm run test:python: passed, including 8 new bridge tests.
  • npm run check:fast: typecheck and lint passed.
  • npm run verify:test-discovery: all test files discovered exactly once.
  • npm run docs:check: passed.
  • npx oxfmt --check .: passed.
  • npm run verify:ida -- --target <x86-64 ELF fixture> --procedure rea_fixture_add --expected-xref <call site>: passed (Linux x64, Hex-Rays IDA MCP 20261008.0.1).
  • The same with --expected-string, on the fixture plus one string literal: passed with no unverified items.
  • The same on a statically linked AArch64 ELF build of the fixture (--expected-xref taken from the decoded BL instruction): passed. main's callees came from the call-graph query.
  • All three runs repeated after the review fixes: passed, including the new no-sibling-files check.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. protocol gains a value; existing registrations are unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable. IDA coverage is listed above.
  • Package or release metadata impact: one bridge file added to files.
  • Security, privacy, process, or containment review: the bridge is fixed and read-only, and its digest is recorded. Caller input is JSON data, never source. Stdio only.

Author readiness

Human owner: @NaiJii

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/4813 **Author:** [@NaiJii](https://github.com/NaiJii) **Created:** 10/17/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/ida-hexrays-mcp-profile` --- ### 📝 Commits (10+) - [`284dc94`](https://github.com/morluto/rea/commit/284dc949a2825f66111157903446e6ec1d6a0811) feat(ida): support the official Hex-Rays IDA MCP headlessly - [`c8df281`](https://github.com/morluto/rea/commit/c8df2817b9e1452231ce7f972ac1a02c6d9d124c) feat(ida): name the Hex-Rays registration for its catalog - [`fd23f10`](https://github.com/morluto/rea/commit/fd23f10107504d7ffb7e403e396d2786263e2678) docs(ida): state Hex-Rays prerequisites and upstream session traces - [`e5b9285`](https://github.com/morluto/rea/commit/e5b9285ef7977e05e4a555d2a8c0b09eef8e279b) fix(ida): skip inline data when scanning Hex-Rays calls - [`455886a`](https://github.com/morluto/rea/commit/455886af5b0ccf8f19944665d21bb5ffa2794be3) fix(ida): retain Hex-Rays health reads in evidence - [`e3fb3e7`](https://github.com/morluto/rea/commit/e3fb3e736ea74a932667d40be863d56e802ac217) test(ida): list the Hex-Rays bridge in package fixtures - [`2e95683`](https://github.com/morluto/rea/commit/2e956833f00b1d68b927bade7924243b1e7864c5) feat(ida): open existing IDA databases through the Hex-Rays profile - [`4f2cd4c`](https://github.com/morluto/rea/commit/4f2cd4c5331ed5f646aa5f79c0f79da13e1cca83) Merge remote-tracking branch 'origin/main' into feat/ida-hexrays-mcp-profile - [`0c38b4e`](https://github.com/morluto/rea/commit/0c38b4ee990fda4f1167374e07d7b0ae2c8d4be9) fix(ida): verify the private workspace of a database target by idb_path - [`c20fb64`](https://github.com/morluto/rea/commit/c20fb64577884179c0fc2ab4a73f5dcda97acd1c) test(ida): fix Hex-Rays health stand-ins and Windows path and runtime teardown ### 📊 Changes **35 files changed** (+1948 additions, -135 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/SKILL.md` (+4 -1) ➕ `bridge/ida/hexrays_queries.py` (+325 -0) ➕ `bridge/ida/test_hexrays_queries.py` (+248 -0) 📝 `docs/ida-provider.md` (+41 -7) 📝 `package.json` (+2 -1) ➕ `scripts/lib/ida-private-input.d.mts` (+8 -0) ➕ `scripts/lib/ida-private-input.mjs` (+13 -0) 📝 `scripts/verify-packed-bridge.mjs` (+17 -14) 📝 `scripts/verify-real-ida.mjs` (+60 -13) 📝 `src/application/BinaryTargetResolver.ts` (+25 -1) 📝 `src/application/binary/AnalysisProviderRegistry.test.ts` (+1 -0) ➕ `src/domain/analysisDatabase.ts` (+10 -0) 📝 `src/domain/binaryTargetTypes.ts` (+3 -0) 📝 `src/hopper/HopperNameInventory.test.ts` (+1 -0) 📝 `src/hopper/HopperProvider.ts` (+8 -0) 📝 `src/hopper/HopperProviderDiscovery.test.ts` (+1 -0) 📝 `src/ida/IdaConfiguration.ts` (+9 -1) ➕ `src/ida/IdaHexRaysProtocol.test.ts` (+383 -0) ➕ `src/ida/IdaHexRaysProtocol.ts` (+126 -0) 📝 `src/ida/IdaMcpConnection.ts` (+5 -2) _...and 15 more files_ </details> ### 📄 Description <!-- Title: feat(ida): support the official Hex-Rays IDA MCP headlessly --> ## Summary Adds a `hexrays` protocol to the IDA provider so REA can use the official Hex-Rays IDA MCP (`HexRaysSA/ida-mcp`) as a headless upstream. Today REA rejects that server at startup with a missing-tools error, because it exposes database reads only through `execute_python`. Relates to [ https://github.com/morluto/rea/issues/1416](https://github.com/morluto/rea/issues/1416). ## Problem and expected behavior A registration such as ```json { "command": "uvx", "args": ["--exclude-newer=1s", "ida-mcp", "stdio", "--agent=rea"], "mode": "headless", "protocol": "hexrays" } ``` now supports the same analyst operations as the database-supervisor headless profile: inventory and search, lookup, containment, pseudocode, assembly, instruction windows, callees, xrefs and dossiers. It uses the same private-workspace ownership, identity checks and cleanup guarantees. ## Change and scope - `bridge/ida/hexrays_queries.py` (new, packaged): a fixed, read-only query module. REA sends it with one trailing `_rea_query(name, arguments_json)` line, so caller input reaches it only as JSON data. Its replies use the database-supervisor shapes REA already validates. - `IdaHexRaysProtocol.ts` (new): the catalog, the open/list/close schemas, and `runHexRaysQuery`, which records `execute_python`, the query, its arguments and the bridge SHA-256 rather than the module body. - `IdaOperationRunner`: an optional `invoke` hook. For `hexrays`, the runner keeps its modern parsing and records the actual upstream observation. - `IdaSessionClient`: the open, inventory, health and close steps branch per protocol. Hex-Rays inventory is normalized into the existing session vocabulary, so the binding, blocking-session and retained-workspace logic is shared. The supervisor open moves unchanged into `#openSupervisor`. - `IdaConfiguration`: `protocol: "hexrays"` requires `mode: "headless"` and a stdio registration, because Hex-Rays leases belong to the server process. - Startup profile mismatches report `advertised_tools`. When a registration without `protocol` reaches a Hex-Rays server, the message and recovery name the `hexrays` registration. - `IdaMcpConnection`: `isError` replies throw `IdaToolRejection`, a subclass of `AnalysisProtocolError`. Lifecycle code can then tell a completed rejection apart from an unreadable or missing reply. - `scripts/verify-real-ida.mjs`: accepts Hex-Rays observations, keyed by `instance_id` through `execute_python`, and checks release by path, since leases don't survive a new server process. For every profile it now also asserts that no file appears beside the original target. - `scripts/verify-packed-bridge.mjs`: requires and parses the packed Hex-Rays bridge. - Docs: a new `docs/ida-provider.md` section, plus a table row and coverage note. Not included: attached use or databases already open in the IDA GUI (#1416 use case 4), HTTP registrations, direct callers, and any forwarding of caller Python. REA never calls `save_database`. ## Contract and boundary impact - Semantic owner and earliest changed stage: IDA provider registration (`IdaConfiguration`) and upstream lifecycle (`IdaSessionClient`). - Contribution scope fit and linked maintainer decision: pending in #1416. The profile runs REA-authored Python in the user's idalib worker. - CLI and MCP/tool-catalog contract: unchanged. Operations and output contracts are the existing IDA ones. - Provider, bridge, target-format, or platform compatibility: new optional protocol. Existing registrations behave as before. - Evidence, artifact, provenance, or reconstruction contract: observations name the real upstream tool (`execute_python`) and the bridge digest. `compatibility_profile` is `hexrays-ida-mcp`. - Target selection, authority, upstream behavior, and cleanup details: the private copy only. GUI backends are refused. Release requires `list_databases` to show neither the lease nor the private path. The Hex-Rays MCP packs the private `.i64` on release, inside the workspace REA removes. - Generated metadata, package, or installation impact: `package.json#files` adds `bridge/ida/hexrays_queries.py`. Nothing is installed. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. (The registered stdio MCP server, which starts an idalib worker.) - [x] Inject instrumentation or run scripts/code. (A fixed REA query module inside IDA, never inside the target.) - [x] Change process memory, registers, files, or configuration. (Files only: the private copy and its `.i64` inside REA's workspace. Upstream Hex-Rays MCP session traces under `~/.idapro/mcp/sessions/`. No process memory.) - [x] Contact network services. (Upstream only: `uvx` package resolution and IDA licensing. REA makes no network calls.) - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: - `src/ida/IdaHexRaysProtocol.test.ts` (11 tests) with a stateful `RecordingHexRaysIdaMcp` fixture: - the registration boundary - a full lease, read and release run with observation provenance - ignoring peer databases - a profile mismatch naming `execute_python`, and a protocol-less registration pointed at Hex-Rays naming the `hexrays` registration - GUI refusal, with no `close_database` call and the workspace retained - a lost open reply, both with a late lease (adopted and released) and with none (workspace retained) - cleanup after an explicit open rejection - a rejected close, both released and retained, and retried on the next close - a Python round trip of the generated `_rea_query` line with adversarial lookup names - `bridge/ida/test_hexrays_queries.py` (8 tests, added to `npm run test:python`): runs the bridge as `execute_python` would, against IDA module stand-ins. It covers paging and cursors, the snapshot bound, failure rows, callees keeping interior, non-function and pointer-slot targets, call-graph entries only, and xrefs excluding ordinary flow and duplicates. - Base reproduction: on the base revision, a Hex-Rays registration fails at startup with `missing_tools` covering the whole database-supervisor catalog. - Remaining proof gaps: - Unverified: macOS and Windows hosts, PE and Mach-O targets, and large binaries. The bridge holds one enumeration snapshot, such as the full string list, while it is paged. - Every read resends the module (about 11 KB). Local reads took about 20–30 ms each; no before/after performance comparison was made. - Health reads during auto-analysis drain on cancellation rather than abort. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:local -- src/ida/*.test.ts tests/composition/idaProvider.test.ts`: 20 files, 338 tests passed. - `npm run test:focused -- tests/boundary/cli/idaStartupAttributionCli.test.ts`: passed. - Production CLI with a protocol-less registration against the real Hex-Rays server: the error reported the six advertised tools and recommended `"protocol": "hexrays"`. - `npm run test:python`: passed, including 8 new bridge tests. - `npm run check:fast`: typecheck and lint passed. - `npm run verify:test-discovery`: all test files discovered exactly once. - `npm run docs:check`: passed. - `npx oxfmt --check .`: passed. - `npm run verify:ida -- --target <x86-64 ELF fixture> --procedure rea_fixture_add --expected-xref <call site>`: passed (Linux x64, Hex-Rays IDA MCP 20261008.0.1). - The same with `--expected-string`, on the fixture plus one string literal: passed with no unverified items. - The same on a statically linked AArch64 ELF build of the fixture (`--expected-xref` taken from the decoded `BL` instruction): passed. `main`'s callees came from the call-graph query. - All three runs repeated after the review fixes: passed, including the new no-sibling-files check. ## Compatibility, safety, and release - Breaking changes or migration steps: none. `protocol` gains a value; existing registrations are unchanged. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. IDA coverage is listed above. - Package or release metadata impact: one bridge file added to `files`. - Security, privacy, process, or containment review: the bridge is fixed and read-only, and its digest is recorded. Caller input is JSON data, never source. Stdio only. ## Author readiness Human owner: @NaiJii - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6421
No description provided.