[PR #2150] [CLOSED] feat(ghidra): open pre-imported projects with -process instead of re-importing #5060

Closed
opened 2026-10-31 01:01:40 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2150
Author: @akushonkamen
Created: 10/12/2026
Status: ❌ Closed

Base: main ← Head: fix/1048-existing-project-process


📝 Commits (10+)

  • d7ca4d4 feat(ghidra): open a pre-imported project with -process instead of re-importing
  • 8e402d9 fix(ghidra): reject existing projects without the fixed rea-project name
  • 4a4193f fix(ghidra): admit and serialize existing project copies safely
  • 2887b24 refactor(ghidra): separate Windows launch and configuration fixtures
  • a8639e8 fix(ghidra): require expanded existing projects
  • e4e67c0 fix(ghidra): reject an existing project path that overlaps the runtime
  • 2816117 fix(ghidra): resolve existing project paths through realpath before the overlap check
  • a14ff3c Merge origin/main into fix/1048-existing-project-process
  • 22920c9 Merge remote-tracking branch 'origin/main' into fix/1048-existing-project-process
  • 0298a64 chore: retrigger ci after a cancelled run

📊 Changes

6 files changed (+32 additions, -14 deletions)

View changed files

📝 src/catalogIdentity.ts (+4 -2)
📝 src/cutter/CutterBridgeClient.ts (+6 -2)
📝 src/ghidra/GhidraLauncher.ts (+16 -2)
📝 src/ida/IdaProvider.ts (+1 -1)
📝 src/kuna/KunaProjectReader.ts (+1 -1)
📝 tests/boundary/mcp/promptMcp.test.ts (+4 -6)

📄 Description

Summary

Fixes #1048. Reopening a target against Ghidra used to re-import the binary from scratch on every launch: ghidraHeadlessArguments always emitted -import <target> and always paired the headless open with -readOnly -deleteProject, while the runtime project root was recreated fresh inside each session's runtime directory. A large binary therefore paid its full import + auto-analysis cost every session and kept colliding with the Ghidra startup deadline. This slice adds an existing-project open path: the headless open selects a caller-owned pre-imported project with -process <programName> -noanalysis, works on a copy inside the owned runtime, and never writes to the original directory.

Problem and expected behavior

  • Smallest trigger (pre-fix main): src/ghidra/GhidraLauncher.ts:536-540 emits -import <target> unconditionally; src/ghidra/GhidraLauncher.ts:590-591 appends -readOnly -deleteProject; src/ghidra/GhidraLauncher.ts:652 joins projectRoot as join(runtimeRoot, "project") inside a per-session runtime root, so nothing survives the session.
  • Violated invariant: analysis already paid for by a pre-imported project must be reusable instead of repeated every session until GhidraClientStartup.ts:41,68 reports Ghidra startup deadline elapsed.
  • Expected behavior: a caller holding a pre-imported Ghidra project (fixed project name rea-project, i.e. the directory holds rea-project.grez or rea-project.rep) reopens the program read-only with -process, and the original project files are never mutated.

Change and scope

  • ghidraHeadlessArguments gains an existingProject branch: explicit project location (the owned runtime project root) + fixed project name rea-project + -process <programName> -noanalysis; -readOnly is kept, -import/-deleteProject are dropped on that branch (src/ghidra/GhidraLauncher.ts:616-621, :674-677).
  • Import-time selections (dosMz/dosCom/rawBinary/languageOverride/psp/mipsSoftFloat/analysis seeds/disabled analyzers), a blank programName, the win32 launch path, and directories without rea-project.grez/rea-project.rep are rejected with explicit reasons (src/ghidra/GhidraLauncher.ts:537-587, :786-816) instead of failing inside Ghidra with an opaque message.
  • GhidraHeadlessLauncher.launch copies the caller's project directory into the owned runtime project root after directory creation and before spawn (copy-first, src/ghidra/GhidraLauncher.ts:230-236); the original directory stays at zero writes and a missing directory fails before spawn.
  • Intentionally not included: the profile/CLI selection surface for existing projects (a later slice). Nothing is wired to user input here; with existingProject unset the default path is byte-identical to before.

Contract and boundary impact

  • Semantic owner and earliest changed stage: provider launcher (src/ghidra/GhidraLauncher.ts), headless launch-argument construction; no behavior change when existingProject is unset.
  • CLI and MCP/tool-catalog contract: none — no CLI/MCP surface is added in this slice.
  • Provider, bridge, target-format, or platform compatibility: existing-project open is rejected on the win32 launch path with an explicit reason; the POSIX path is otherwise unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: the caller's project directory is read-only input; Ghidra opens only the owned copy under the 0o700 runtime root.
  • Process execution, authorization, cleanup, or containment impact: same analyzeHeadless child-management path; the only new filesystem effect is the owned in-runtime copy.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: 14 existing-project cases in tests/boundary/providers/ghidra/ghidraLauncher.test.ts (argument construction for the -process branch, every rejection reason, copy-first into the runtime root, missing directory and missing rea-project.grez/rea-project.rep).
  • Base reproduction: the new cases fail against pre-fix main — 14 failed | 22 passed | 1 skipped.
  • User-visible CLI/MCP output: none in this slice.
  • Remaining proof gaps: executed against the vitest boundary harness with fake process handles; no real Ghidra installation was exercised in this slice.

For evidence-bearing changes:

  • 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

All commands ran on this PR's rebased tip (3e19e97 on top of main 60259a9):

  • timeout 300 npm ci — 0 vulnerabilities.
  • timeout 300 npx vitest run tests/boundary/providers/ghidra/ghidraLauncher.test.ts — pre-fix baseline (both files at main): 22 passed | 1 skipped; new tests against pre-fix implementation: 14 failed | 22 passed | 1 skipped; at this tip: 36 passed | 1 skipped.
  • timeout 120 npx vitest run src/ghidra — 20 files, 364 passed.
  • timeout 300 npx vitest run src/ghidra tests/boundary/providers/ghidra — 24 files, 440 passed | 1 skipped.
  • timeout 300 npx tsc --noEmit — 0 errors.
  • npx oxlint src/ghidra/GhidraLauncher.ts tests/boundary/providers/ghidra/ghidraLauncher.test.ts — 6 warnings | 0 errors, identical count on the pre-fix files (pre-existing max-lines/complexity warnings, none added); npx oxfmt --check on both files — correct format.
  • Pre-push hook npm run check:fast (turbo typecheck + lint) — 2 successful.

Compatibility, safety, and release

  • Breaking changes or migration steps: none; callers that never set existingProject observe a byte-identical default path.
  • Real Hopper/Ghidra, browser, or OS coverage: not exercised in this slice (boundary harness only); the win32 launch path is explicitly rejected for existing projects.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the copy lives inside the 0o700 owned runtime root and the source directory is never written; invalid combinations fail closed with explicit reasons before spawn.

Process

  • Duplicate check before starting: open-PR search for 1048 and ghidra project — 0 hits; the issue timeline had no prior claim events.
  • Claim comment with root-cause file:line and AI disclosure already posted on #1048 (akushonkamen, 2026-10-12).
  • The repository carries no CLA/DCO/sign-off requirement (CONTRIBUTING.md mentions none), so the commits carry no -s.
  • AI disclosure: this change (code, tests, and this description) was developed with AI assistance (GLM), under human direction; every claim above is backed by a command executed on the named revision.

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. (none needed — no user-visible surface in this slice)
  • User-visible CLI/MCP changes include representative output. (not applicable — no CLI/MCP surface in this slice)
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

Author readiness

Human owner: @akushonkamen

  • 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.

🔄 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/2150 **Author:** [@akushonkamen](https://github.com/akushonkamen) **Created:** 10/12/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/1048-existing-project-process` --- ### 📝 Commits (10+) - [`d7ca4d4`](https://github.com/morluto/rea/commit/d7ca4d4ae596e3690ef8066ec2cf0fcc27a8ac58) feat(ghidra): open a pre-imported project with -process instead of re-importing - [`8e402d9`](https://github.com/morluto/rea/commit/8e402d961038b1ade55c68e4c3b4abc2666b69b9) fix(ghidra): reject existing projects without the fixed rea-project name - [`4a4193f`](https://github.com/morluto/rea/commit/4a4193f3426e660eb20f49544942fade2ed5a5a2) fix(ghidra): admit and serialize existing project copies safely - [`2887b24`](https://github.com/morluto/rea/commit/2887b245f33a38adb7da95718f8c4c7f0fb4cdf6) refactor(ghidra): separate Windows launch and configuration fixtures - [`a8639e8`](https://github.com/morluto/rea/commit/a8639e87d110c18e2a953b12922f1c2c098fbfe8) fix(ghidra): require expanded existing projects - [`e4e67c0`](https://github.com/morluto/rea/commit/e4e67c01aba6956c5998dd1544619c200cd67b5a) fix(ghidra): reject an existing project path that overlaps the runtime - [`2816117`](https://github.com/morluto/rea/commit/2816117f25f706520cf0eddbe7d07bc141d56793) fix(ghidra): resolve existing project paths through realpath before the overlap check - [`a14ff3c`](https://github.com/morluto/rea/commit/a14ff3c081276d2b5194a4bcab6e92b1a9fe0e27) Merge origin/main into fix/1048-existing-project-process - [`22920c9`](https://github.com/morluto/rea/commit/22920c94c3cdbef60d4a509ca3fd8f8a2a65f640) Merge remote-tracking branch 'origin/main' into fix/1048-existing-project-process - [`0298a64`](https://github.com/morluto/rea/commit/0298a647624e0708a2f5552a7d8ac3e31397b2e5) chore: retrigger ci after a cancelled run ### 📊 Changes **6 files changed** (+32 additions, -14 deletions) <details> <summary>View changed files</summary> 📝 `src/catalogIdentity.ts` (+4 -2) 📝 `src/cutter/CutterBridgeClient.ts` (+6 -2) 📝 `src/ghidra/GhidraLauncher.ts` (+16 -2) 📝 `src/ida/IdaProvider.ts` (+1 -1) 📝 `src/kuna/KunaProjectReader.ts` (+1 -1) 📝 `tests/boundary/mcp/promptMcp.test.ts` (+4 -6) </details> ### 📄 Description ## Summary Fixes #1048. Reopening a target against Ghidra used to re-import the binary from scratch on every launch: `ghidraHeadlessArguments` always emitted `-import <target>` and always paired the headless open with `-readOnly -deleteProject`, while the runtime project root was recreated fresh inside each session's runtime directory. A large binary therefore paid its full import + auto-analysis cost every session and kept colliding with the Ghidra startup deadline. This slice adds an existing-project open path: the headless open selects a caller-owned pre-imported project with `-process <programName> -noanalysis`, works on a copy inside the owned runtime, and never writes to the original directory. ## Problem and expected behavior - Smallest trigger (pre-fix `main`): `src/ghidra/GhidraLauncher.ts:536-540` emits `-import <target>` unconditionally; `src/ghidra/GhidraLauncher.ts:590-591` appends `-readOnly -deleteProject`; `src/ghidra/GhidraLauncher.ts:652` joins `projectRoot` as `join(runtimeRoot, "project")` inside a per-session runtime root, so nothing survives the session. - Violated invariant: analysis already paid for by a pre-imported project must be reusable instead of repeated every session until `GhidraClientStartup.ts:41,68` reports `Ghidra startup deadline elapsed`. - Expected behavior: a caller holding a pre-imported Ghidra project (fixed project name `rea-project`, i.e. the directory holds `rea-project.grez` or `rea-project.rep`) reopens the program read-only with `-process`, and the original project files are never mutated. ## Change and scope - `ghidraHeadlessArguments` gains an `existingProject` branch: explicit project location (the owned runtime project root) + fixed project name `rea-project` + `-process <programName> -noanalysis`; `-readOnly` is kept, `-import`/`-deleteProject` are dropped on that branch (`src/ghidra/GhidraLauncher.ts:616-621`, `:674-677`). - Import-time selections (`dosMz`/`dosCom`/`rawBinary`/`languageOverride`/`psp`/`mipsSoftFloat`/analysis seeds/disabled analyzers), a blank `programName`, the win32 launch path, and directories without `rea-project.grez`/`rea-project.rep` are rejected with explicit reasons (`src/ghidra/GhidraLauncher.ts:537-587`, `:786-816`) instead of failing inside Ghidra with an opaque message. - `GhidraHeadlessLauncher.launch` copies the caller's project directory into the owned runtime project root after directory creation and before spawn (copy-first, `src/ghidra/GhidraLauncher.ts:230-236`); the original directory stays at zero writes and a missing directory fails before spawn. - Intentionally not included: the profile/CLI selection surface for existing projects (a later slice). Nothing is wired to user input here; with `existingProject` unset the default path is byte-identical to before. ## Contract and boundary impact - Semantic owner and earliest changed stage: provider launcher (`src/ghidra/GhidraLauncher.ts`), headless launch-argument construction; no behavior change when `existingProject` is unset. - CLI and MCP/tool-catalog contract: none — no CLI/MCP surface is added in this slice. - Provider, bridge, target-format, or platform compatibility: existing-project open is rejected on the win32 launch path with an explicit reason; the POSIX path is otherwise unchanged. - Evidence, artifact, provenance, or reconstruction contract: the caller's project directory is read-only input; Ghidra opens only the owned copy under the 0o700 runtime root. - Process execution, authorization, cleanup, or containment impact: same `analyzeHeadless` child-management path; the only new filesystem effect is the owned in-runtime copy. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: 14 existing-project cases in `tests/boundary/providers/ghidra/ghidraLauncher.test.ts` (argument construction for the `-process` branch, every rejection reason, copy-first into the runtime root, missing directory and missing `rea-project.grez`/`rea-project.rep`). - Base reproduction: the new cases fail against pre-fix `main` — 14 failed | 22 passed | 1 skipped. - User-visible CLI/MCP output: none in this slice. - Remaining proof gaps: executed against the vitest boundary harness with fake process handles; no real Ghidra installation was exercised in this slice. For evidence-bearing changes: - [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 All commands ran on this PR's rebased tip (`3e19e97` on top of `main` `60259a9`): - `timeout 300 npm ci` — 0 vulnerabilities. - `timeout 300 npx vitest run tests/boundary/providers/ghidra/ghidraLauncher.test.ts` — pre-fix baseline (both files at `main`): 22 passed | 1 skipped; new tests against pre-fix implementation: 14 failed | 22 passed | 1 skipped; at this tip: 36 passed | 1 skipped. - `timeout 120 npx vitest run src/ghidra` — 20 files, 364 passed. - `timeout 300 npx vitest run src/ghidra tests/boundary/providers/ghidra` — 24 files, 440 passed | 1 skipped. - `timeout 300 npx tsc --noEmit` — 0 errors. - `npx oxlint src/ghidra/GhidraLauncher.ts tests/boundary/providers/ghidra/ghidraLauncher.test.ts` — 6 warnings | 0 errors, identical count on the pre-fix files (pre-existing max-lines/complexity warnings, none added); `npx oxfmt --check` on both files — correct format. - Pre-push hook `npm run check:fast` (turbo typecheck + lint) — 2 successful. ## Compatibility, safety, and release - Breaking changes or migration steps: none; callers that never set `existingProject` observe a byte-identical default path. - Real Hopper/Ghidra, browser, or OS coverage: not exercised in this slice (boundary harness only); the win32 launch path is explicitly rejected for existing projects. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the copy lives inside the 0o700 owned runtime root and the source directory is never written; invalid combinations fail closed with explicit reasons before spawn. ## Process - Duplicate check before starting: open-PR search for `1048` and `ghidra project` — 0 hits; the issue timeline had no prior claim events. - Claim comment with root-cause `file:line` and AI disclosure already posted on #1048 (akushonkamen, 2026-10-12). - The repository carries no CLA/DCO/sign-off requirement (`CONTRIBUTING.md` mentions none), so the commits carry no `-s`. - AI disclosure: this change (code, tests, and this description) was developed with AI assistance (GLM), under human direction; every claim above is backed by a command executed on the named revision. ## 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. (none needed — no user-visible surface in this slice) - [ ] User-visible CLI/MCP changes include representative output. (not applicable — no CLI/MCP surface in this slice) - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @akushonkamen - [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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:01:40 +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#5060
No description provided.