[PR #1481] [MERGED] fix(ida): canonicalize the headless workspace root before opening #2919

Closed
opened 2026-10-17 21:11:48 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1481
Author: @AS9929
Created: 10/10/2026
Status: ✅ Merged
Merged: 10/10/2026
Merged by: @morluto

Base: main ← Head: fix/ida-canonical-workspace-root


📝 Commits (1)

  • ea80c01 fix(ida): canonicalize the headless workspace root before opening

📊 Changes

2 files changed (+27 additions, -3 deletions)

View changed files

📝 src/ida/IdaWorkspace.ts (+4 -2)
📝 src/ida/IdaWorkspaceOwnership.test.ts (+23 -1)

📄 Description

Summary

Headless IDA analysis fails on every macOS run that uses the default workspace root. IdaWorkspace.allocate places the private input copy under os.tmpdir() (/var/folders/...), but /var is a symlink to /private/var and idalib-mcp reports the canonical path from idb_open / idb_list. sameIdaHostPath compares with normalize() only, so the identity check rejects the session REA itself just opened. No issue is linked; the reproduction is below.

Problem and expected behavior

Smallest trigger (macOS, any binary, default config without workspaceRoot):

REA_ANALYSIS_PROVIDER=ida REA_IDA_MCP_CONFIG=/path/to/headless.json rea decompile /path/to/bin _main --json

REA sends input_path: /var/folders/.../rea-ida-XXXX/bin; upstream answers /private/var/folders/.../rea-ida-XXXX/bin. The CLI/MCP result is:

{ "error": "Analysis failed", "code": "unreadable_output", "category": "execution_failure" }

Each attempt also left an orphaned ida_pro_mcp.idalib_server worker (ppid 1) running, because the release path matches the session by the same path comparison.

Expected: the private input path REA sends equals the path upstream reports, so the open/list identity checks pass and the worker is released with save: false.

Change and scope

IdaWorkspace.allocate now resolves the workspace root with realpath() before creating the private runtime root. The input path is therefore canonical from the start, and the existing strict identity comparison stays unchanged (no loosening of sameIdaHostPath).

Not included: canonicalizing paths reported by upstream, or changes to attached (GUI) mode, which binds by SHA-256 rather than path.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/ida/IdaWorkspace.ts, workspace allocation before idb_open.
  • CLI and MCP/tool-catalog contract: none.
  • Provider, bridge, target-format, or platform compatibility: fixes IDA headless mode on macOS default temp dirs (and any symlinked workspaceRoot); no change on Linux/Windows when the root is already canonical.
  • Evidence, artifact, provenance, or reconstruction contract: none (workspace paths are private and recorded as upstream reports them).
  • Process execution, authorization, cleanup, or containment impact: worker cleanup now succeeds in the affected case; the private workspace stays under the same (resolved) parent with the same mode.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: src/ida/IdaWorkspaceOwnership.test.ts — opens through a symlinked workspaceRoot and asserts idb_open.input_path is under the canonical root.
  • Base reproduction or other evidence: the new test fails on origin/main (1 failed | 2 passed) and passes with the fix. Existing tests did not catch this because createTestTempDirectory already canonicalizes tmpdir().
  • User-visible CLI/MCP output: before — unreadable_output above; after — normal procedure_pseudo_code evidence with provider.id: "ida".
  • Remaining proof gaps: real-provider check done on macOS arm64 only.

Validation performed

  • npx vitest run src/ida — 7 files, 35 tests passed
  • npx vitest run src/ida/IdaWorkspaceOwnership.test.ts with only the source fix reverted — new test fails
  • npm run check — typecheck, lint, format:check, knip, metadata:check and architecture guards passed
  • npm run check:fast — passed
  • npm run verify:test-discovery — all 712 test files discovered once
  • Real provider: macOS 15 (Darwin 24.6) arm64, IDA Pro 9.4.260610, ida-pro-mcp @ c133c38 headless supervisor. Before: unreadable_output plus a leaked worker per attempt. After (equivalent workspaceRoot pointing at a canonical directory, and with this patch): rea decompile and MCP open_binary + list_procedures succeed on a thin arm64 test binary and on universal /usr/bin/true; no workers remain and the private workspace is removed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. A configured workspaceRoot that does not exist now fails at realpath() with ENOENT instead of inside PrivateRuntimeRoot.create.
  • Real Hopper/Ghidra, browser, or OS coverage: IDA on macOS arm64 only; Hopper/Ghidra untouched.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the resolved parent is the same directory the symlink pointed at; workspace mode and ownership checks are unchanged.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • 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.

🤖 Generated with Claude Code


🔄 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/1481 **Author:** [@AS9929](https://github.com/AS9929) **Created:** 10/10/2026 **Status:** ✅ Merged **Merged:** 10/10/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/ida-canonical-workspace-root` --- ### 📝 Commits (1) - [`ea80c01`](https://github.com/morluto/rea/commit/ea80c0178b25f09e49165022bb50fe749a378e31) fix(ida): canonicalize the headless workspace root before opening ### 📊 Changes **2 files changed** (+27 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `src/ida/IdaWorkspace.ts` (+4 -2) 📝 `src/ida/IdaWorkspaceOwnership.test.ts` (+23 -1) </details> ### 📄 Description ## Summary Headless IDA analysis fails on every macOS run that uses the default workspace root. `IdaWorkspace.allocate` places the private input copy under `os.tmpdir()` (`/var/folders/...`), but `/var` is a symlink to `/private/var` and `idalib-mcp` reports the canonical path from `idb_open` / `idb_list`. `sameIdaHostPath` compares with `normalize()` only, so the identity check rejects the session REA itself just opened. No issue is linked; the reproduction is below. ## Problem and expected behavior Smallest trigger (macOS, any binary, default config without `workspaceRoot`): ```bash REA_ANALYSIS_PROVIDER=ida REA_IDA_MCP_CONFIG=/path/to/headless.json rea decompile /path/to/bin _main --json ``` REA sends `input_path: /var/folders/.../rea-ida-XXXX/bin`; upstream answers `/private/var/folders/.../rea-ida-XXXX/bin`. The CLI/MCP result is: ```json { "error": "Analysis failed", "code": "unreadable_output", "category": "execution_failure" } ``` Each attempt also left an orphaned `ida_pro_mcp.idalib_server` worker (ppid 1) running, because the release path matches the session by the same path comparison. Expected: the private input path REA sends equals the path upstream reports, so the open/list identity checks pass and the worker is released with `save: false`. ## Change and scope `IdaWorkspace.allocate` now resolves the workspace root with `realpath()` before creating the private runtime root. The input path is therefore canonical from the start, and the existing strict identity comparison stays unchanged (no loosening of `sameIdaHostPath`). Not included: canonicalizing paths reported by upstream, or changes to attached (GUI) mode, which binds by SHA-256 rather than path. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/ida/IdaWorkspace.ts`, workspace allocation before `idb_open`. - CLI and MCP/tool-catalog contract: none. - Provider, bridge, target-format, or platform compatibility: fixes IDA headless mode on macOS default temp dirs (and any symlinked `workspaceRoot`); no change on Linux/Windows when the root is already canonical. - Evidence, artifact, provenance, or reconstruction contract: none (workspace paths are private and recorded as upstream reports them). - Process execution, authorization, cleanup, or containment impact: worker cleanup now succeeds in the affected case; the private workspace stays under the same (resolved) parent with the same mode. - Generated metadata, package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: `src/ida/IdaWorkspaceOwnership.test.ts` — opens through a symlinked `workspaceRoot` and asserts `idb_open.input_path` is under the canonical root. - Base reproduction or other evidence: the new test fails on `origin/main` (`1 failed | 2 passed`) and passes with the fix. Existing tests did not catch this because `createTestTempDirectory` already canonicalizes `tmpdir()`. - User-visible CLI/MCP output: before — `unreadable_output` above; after — normal `procedure_pseudo_code` evidence with `provider.id: "ida"`. - Remaining proof gaps: real-provider check done on macOS arm64 only. ## Validation performed - `npx vitest run src/ida` — 7 files, 35 tests passed - `npx vitest run src/ida/IdaWorkspaceOwnership.test.ts` with only the source fix reverted — new test fails - `npm run check` — typecheck, lint, format:check, knip, metadata:check and architecture guards passed - `npm run check:fast` — passed - `npm run verify:test-discovery` — all 712 test files discovered once - Real provider: macOS 15 (Darwin 24.6) arm64, IDA Pro 9.4.260610, ida-pro-mcp @ `c133c38` headless supervisor. Before: `unreadable_output` plus a leaked worker per attempt. After (equivalent `workspaceRoot` pointing at a canonical directory, and with this patch): `rea decompile` and MCP `open_binary` + `list_procedures` succeed on a thin arm64 test binary and on universal `/usr/bin/true`; no workers remain and the private workspace is removed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. A configured `workspaceRoot` that does not exist now fails at `realpath()` with `ENOENT` instead of inside `PrivateRuntimeRoot.create`. - Real Hopper/Ghidra, browser, or OS coverage: IDA on macOS arm64 only; Hopper/Ghidra untouched. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the resolved parent is the same directory the symlink pointed at; workspace mode and ownership checks are unchanged. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:11:48 +00:00
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#2919
No description provided.