[GH-ISSUE #1712] [Bug] Attached IDA analysis reports no UI effects despite legacy GUI actions #573

Closed
opened 2026-10-15 18:26:18 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 11, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1712

Before submitting

  • I searched the open and closed issues for a related report, and checked pull requests.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Provider or capability discovery

Observed behavior

REA advertises may_show_ui: false for attached IDA assembly, pseudocode and function dossiers. Under its PyQt5 active-window condition, its explicitly supported legacy producer navigates the active IDA GUI during those requests: disassembly calls jumpto, and successful decompilation opens or reuses a pseudocode window. Callers therefore receive an inaccurate description of the effects of supported analysis operations.

In both tested REA builds, the production runner selected disassemble_function for assembly, decompile_function for pseudocode, and both for a dossier, while every corresponding descriptor reported mayShowUi: false. BinarySessionStatus exposes that descriptor as effects.may_show_ui.

The exact legacy producer-body check recorded no GUI events with an inactive-window control, and recorded jumpto("0x1000") plus open_pseudocode("0x1000") with the active condition. Both REA routing checks and the producer check exited zero. These stub observations confirm execution of the relevant source branches; they do not claim an observed real IDA window change.

Reproduction

The real-GUI sequence is a conclusive source-traced trigger and was not executed against a live IDA installation. Configure REA's documented attached legacy registration against a GUI database matching the original input's digest, keep an IDA Qt5 top-level widget active, and request assembly, successful pseudocode or a function dossier for an existing function. Compare the session capability's effects.may_show_ui with the producer's GUI actions.

The executed local check separates REA routing from the exact producer branches. Save this script as reproduce-ui-route.mjs outside the repository, then run node reproduce-ui-route.mjs /absolute/path/to/built/rea:

import assert from 'node:assert/strict';
import { resolve, join } from 'node:path';
import { pathToFileURL } from 'node:url';
const root = resolve(process.argv[2] || '.');
const { IdaOperationRunner } = await import(pathToFileURL(join(root, 'dist/ida/IdaOperationRunner.js')));
const { idaCapabilities } = await import(pathToFileURL(join(root, 'dist/ida/IdaProviderCapabilities.js')));

const fn = {address: '0x1000', name: 'main', size: '0x20'};
const calls = [];
const connection = {
  async call(tool, args) {
    calls.push(tool);
    if (tool === 'get_function_by_name') return fn;
    if (tool === 'disassemble_function') return {name:'main', start_ea:'0x1000', lines:[]};
    if (tool === 'decompile_function') return 'int main(void) { return 0; }';
    if (tool === 'get_callers' || tool === 'get_callees') return [];
    throw Error(`Unexpected tool ${tool}`);
  },
};
const results = {};
for (const operation of ['procedure_assembly', 'procedure_pseudo_code', 'analyze_function']) {
  calls.length = 0;
  const runner = new IdaOperationRunner(connection, 'legacy');
  await runner.run(operation, {procedure:'main'});
  const descriptor = idaCapabilities('attached', true).find(item => item.operation === operation);
  assert.equal(descriptor.effects.mayShowUi, false);
  results[operation] = {mayShowUi:descriptor.effects.mayShowUi, upstreamTools:[...calls]};
}
assert(results.procedure_assembly.upstreamTools.includes('disassemble_function'));
assert(results.procedure_pseudo_code.upstreamTools.includes('decompile_function'));
assert(results.analyze_function.upstreamTools.includes('disassemble_function'));
assert(results.analyze_function.upstreamTools.includes('decompile_function'));
console.log(JSON.stringify(results, null, 2));

Save the following as reproduce-ui-producer.py and run python3 reproduce-ui-producer.py /absolute/path/to/ida-pro-mcp. It extracts the exact function bodies from the legacy tag, removes registration decorators and substitutes harmless IDA and PyQt5 stubs, including a synthetic application and top-level widget. The active-window function itself is also extracted unchanged. It exercises both active and inactive branches without a real GUI, binary, network connection or user configuration.

import ast
import json
from pathlib import Path
from types import SimpleNamespace
import subprocess
import sys

# Execute the exact two legacy producer function bodies with harmless IDA stubs.
# No IDA installation, input binary, GUI, network, or real configuration is touched.
source = subprocess.check_output(['git', '-C', sys.argv[1], 'show', '1.4.0:src/ida_pro_mcp/mcp-plugin.py'], text=True)
tree = ast.parse(source)
selected = []
for node in tree.body:
    if isinstance(node, ast.FunctionDef) and node.name in ('is_window_active', 'disassemble_function', 'decompile_function'):
        node.decorator_list = []
        selected.append(node)
module = ast.Module(body=[ast.ImportFrom(module='__future__', names=[ast.alias(name='annotations')], level=0), *selected], type_ignores=[])
ast.fix_missing_locations(module)
events = []
state = {'active': False}
widgets = SimpleNamespace(QApplication=SimpleNamespace(instance=lambda: SimpleNamespace(topLevelWidgets=lambda: [SimpleNamespace(isActiveWindow=lambda: state['active'])])))
sys.modules['PyQt5'] = SimpleNamespace(QtWidgets=widgets)
sys.modules['PyQt5.QtWidgets'] = widgets
fn = SimpleNamespace(start_ea=0x1000)
cfunc = SimpleNamespace(get_pseudocode=lambda: [], entry_ea=0x1000)
namespace = {
    'parse_address': lambda value: int(value, 16),
    'decompile_checked': lambda address: cfunc,
    'idaapi': SimpleNamespace(get_func=lambda address: fn, BADADDR=-1),
    'ida_funcs': SimpleNamespace(get_func_name=lambda address: 'main'),
    'ida_kernwin': SimpleNamespace(jumpto=lambda address: events.append({'kind': 'jumpto', 'address': hex(address)}), simpleline_t=object),
    'ida_hexrays': SimpleNamespace(open_pseudocode=lambda address, flags: events.append({'kind': 'open_pseudocode', 'address': hex(address)}), OPF_REUSE=1),
    'idautils': SimpleNamespace(FuncItems=lambda address: []),
    'ida_typeinf': SimpleNamespace(tinfo_t=lambda: object()),
    'ida_nalt': SimpleNamespace(get_tinfo=lambda *args: False),
    'get_stack_frame_variables_internal': lambda *args: [],
}
exec(compile(module, 'legacy-plugin.py', 'exec'), namespace)
results = {}
for active in (False, True):
    state['active'] = active
    events.clear()
    namespace['disassemble_function']('0x1000')
    namespace['decompile_function']('0x1000')
    results['active' if active else 'inactive'] = list(events)
assert results['inactive'] == []
assert [event['kind'] for event in results['active']] == ['jumpto', 'open_pseudocode']
print(json.dumps(results, indent=2))

Expected behavior

Public capability discovery must truthfully describe the UI effects of the selected supported profile. An attached operation whose producer can navigate a GUI or open pseudocode must not be presented as having no UI effect. The provider guide should describe these reachable side effects consistently. Its exclusion of GUI navigation from adapter coverage concerns available operations and does not describe the legacy analysis side effects. The headless profile and attached operations without these producer actions should remain distinguishable.

Impact and scope

The supported legacy attached profile can move the analyst's current GUI location or open a pseudocode view while session discovery says these operations cannot show UI. This report concerns conditional UI side effects and capability truthfulness. It does not establish artifact mutation, target execution, a security boundary violation, or behavior across all IDA builds.

REA version

REA current main revision 693ed332b58d05192f55b8192d2c0c4bc4f4824a and the latest published rea-agents package 6.4.0 were checked on macOS 27.0 arm64 with Node.js v26.10.0. The source runtime compiled successfully with npm run compile; the published package was extracted without running install scripts. All ten compiled dist/ida/ files match the published npm artifact byte for byte. The supported legacy producer is mrexodia/ida-pro-mcp tag 1.4.0, revision 27722e929052757f4baf074776904effdb23c012. Python 3.12.13 ran the isolated producer-body check. The producer's active-window condition uses PyQt5; an IDA GUI where that condition returns true is required. No live IDA GUI or licensed decompiler was run.

Runtime and platform

The measured local platform and runtimes appear in REA version above. IDA engine version was not measured; no live IDA engine or GUI was used.

Target and artifact context

The executed checks use a synthetic function at 0x1000, harmless provider return values, and harmless IDA/PyQt5 stubs. The unexecuted GUI sequence requires an admitted original input whose digest matches the attached database, a resolvable function, and successful decompilation for the pseudocode action.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: docs/tool-design.md:58 requires truthful UI effect declarations; docs/ida-provider.md explicitly supports the attached legacy profile and states GUI navigation is outside adapter coverage.
  • Failure source: src/ida/IdaProviderCapabilities.ts:59 unconditionally sets mayShowUi: false; src/application/binary/BinarySessionStatus.ts:199 exposes it; src/ida/IdaSessionClient.ts:194-197 selects the legacy runner for attached mode, and src/ida/IdaOperationRunner.ts:181,222,414-416 routes the affected operations to legacy tools whose active-window branches perform GUI actions.
  • Evidence provenance: static
  • Local verification: static-proof
  • Reproduction completeness: complete

Evidence provenance details: static proof of the valid supported call path, with supporting observed production-adapter routing and exact producer-function execution using harmless stubs.

Local verification details: static-proof; runtime reproduction against a live IDA GUI was not run. The required premises are attached legacy configuration, matching target identity, a resolvable function, an active PyQt5 widget, and successful decompilation for the pseudocode branch.

Reproduction completeness details: complete for the source proof and inline isolated checks. Real GUI execution is outside the observed coverage; no real window movement, licensed engine behavior, or successful decompilation in an actual IDA installation was observed.

Pinned source breadcrumbs: REA effect descriptor, legacy active-window test, pseudocode window action, and disassembly navigation.

Restoration check

For the supported active attached profile, compare actual reachable GUI effects with the effects exposed by session discovery and the provider guide. Require them to agree for assembly, pseudocode and dossier. Keep an inactive-window control and a headless-profile control to distinguish actual conditional behavior from an unconditional claim. The developer can restore truthful discovery by changing the reachable behavior or its effect declaration; this report does not require a particular implementation.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The supported active attached profile can move the analyst's GUI location or open a pseudocode view while discovery says the operation cannot show UI. This consequential effect-reporting mismatch is bounded to the relevant legacy calls and active-window condition; no artifact mutation or security impact is claimed.
  • Workaround status: unverified
  • Workaround basis: The isolated inactive-window branch avoids these actions, and headless mode uses different producers, but live usability of keeping the GUI inactive or availability of a headless runtime was not established.
Originally created by @coygeek on GitHub (Oct 11, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1712 ### Before submitting - [x] I searched the open and closed issues for a related report, and checked pull requests. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Provider or capability discovery ### Observed behavior REA advertises `may_show_ui: false` for attached IDA assembly, pseudocode and function dossiers. Under its PyQt5 active-window condition, its explicitly supported legacy producer navigates the active IDA GUI during those requests: disassembly calls `jumpto`, and successful decompilation opens or reuses a pseudocode window. Callers therefore receive an inaccurate description of the effects of supported analysis operations. In both tested REA builds, the production runner selected `disassemble_function` for assembly, `decompile_function` for pseudocode, and both for a dossier, while every corresponding descriptor reported `mayShowUi: false`. `BinarySessionStatus` exposes that descriptor as `effects.may_show_ui`. The exact legacy producer-body check recorded no GUI events with an inactive-window control, and recorded `jumpto("0x1000")` plus `open_pseudocode("0x1000")` with the active condition. Both REA routing checks and the producer check exited zero. These stub observations confirm execution of the relevant source branches; they do not claim an observed real IDA window change. ### Reproduction The real-GUI sequence is a conclusive source-traced trigger and was not executed against a live IDA installation. Configure REA's documented attached legacy registration against a GUI database matching the original input's digest, keep an IDA Qt5 top-level widget active, and request assembly, successful pseudocode or a function dossier for an existing function. Compare the session capability's `effects.may_show_ui` with the producer's GUI actions. The executed local check separates REA routing from the exact producer branches. Save this script as `reproduce-ui-route.mjs` outside the repository, then run `node reproduce-ui-route.mjs /absolute/path/to/built/rea`: ```javascript import assert from 'node:assert/strict'; import { resolve, join } from 'node:path'; import { pathToFileURL } from 'node:url'; const root = resolve(process.argv[2] || '.'); const { IdaOperationRunner } = await import(pathToFileURL(join(root, 'dist/ida/IdaOperationRunner.js'))); const { idaCapabilities } = await import(pathToFileURL(join(root, 'dist/ida/IdaProviderCapabilities.js'))); const fn = {address: '0x1000', name: 'main', size: '0x20'}; const calls = []; const connection = { async call(tool, args) { calls.push(tool); if (tool === 'get_function_by_name') return fn; if (tool === 'disassemble_function') return {name:'main', start_ea:'0x1000', lines:[]}; if (tool === 'decompile_function') return 'int main(void) { return 0; }'; if (tool === 'get_callers' || tool === 'get_callees') return []; throw Error(`Unexpected tool ${tool}`); }, }; const results = {}; for (const operation of ['procedure_assembly', 'procedure_pseudo_code', 'analyze_function']) { calls.length = 0; const runner = new IdaOperationRunner(connection, 'legacy'); await runner.run(operation, {procedure:'main'}); const descriptor = idaCapabilities('attached', true).find(item => item.operation === operation); assert.equal(descriptor.effects.mayShowUi, false); results[operation] = {mayShowUi:descriptor.effects.mayShowUi, upstreamTools:[...calls]}; } assert(results.procedure_assembly.upstreamTools.includes('disassemble_function')); assert(results.procedure_pseudo_code.upstreamTools.includes('decompile_function')); assert(results.analyze_function.upstreamTools.includes('disassemble_function')); assert(results.analyze_function.upstreamTools.includes('decompile_function')); console.log(JSON.stringify(results, null, 2)); ``` Save the following as `reproduce-ui-producer.py` and run `python3 reproduce-ui-producer.py /absolute/path/to/ida-pro-mcp`. It extracts the exact function bodies from the legacy tag, removes registration decorators and substitutes harmless IDA and PyQt5 stubs, including a synthetic application and top-level widget. The active-window function itself is also extracted unchanged. It exercises both active and inactive branches without a real GUI, binary, network connection or user configuration. ```python import ast import json from pathlib import Path from types import SimpleNamespace import subprocess import sys # Execute the exact two legacy producer function bodies with harmless IDA stubs. # No IDA installation, input binary, GUI, network, or real configuration is touched. source = subprocess.check_output(['git', '-C', sys.argv[1], 'show', '1.4.0:src/ida_pro_mcp/mcp-plugin.py'], text=True) tree = ast.parse(source) selected = [] for node in tree.body: if isinstance(node, ast.FunctionDef) and node.name in ('is_window_active', 'disassemble_function', 'decompile_function'): node.decorator_list = [] selected.append(node) module = ast.Module(body=[ast.ImportFrom(module='__future__', names=[ast.alias(name='annotations')], level=0), *selected], type_ignores=[]) ast.fix_missing_locations(module) events = [] state = {'active': False} widgets = SimpleNamespace(QApplication=SimpleNamespace(instance=lambda: SimpleNamespace(topLevelWidgets=lambda: [SimpleNamespace(isActiveWindow=lambda: state['active'])]))) sys.modules['PyQt5'] = SimpleNamespace(QtWidgets=widgets) sys.modules['PyQt5.QtWidgets'] = widgets fn = SimpleNamespace(start_ea=0x1000) cfunc = SimpleNamespace(get_pseudocode=lambda: [], entry_ea=0x1000) namespace = { 'parse_address': lambda value: int(value, 16), 'decompile_checked': lambda address: cfunc, 'idaapi': SimpleNamespace(get_func=lambda address: fn, BADADDR=-1), 'ida_funcs': SimpleNamespace(get_func_name=lambda address: 'main'), 'ida_kernwin': SimpleNamespace(jumpto=lambda address: events.append({'kind': 'jumpto', 'address': hex(address)}), simpleline_t=object), 'ida_hexrays': SimpleNamespace(open_pseudocode=lambda address, flags: events.append({'kind': 'open_pseudocode', 'address': hex(address)}), OPF_REUSE=1), 'idautils': SimpleNamespace(FuncItems=lambda address: []), 'ida_typeinf': SimpleNamespace(tinfo_t=lambda: object()), 'ida_nalt': SimpleNamespace(get_tinfo=lambda *args: False), 'get_stack_frame_variables_internal': lambda *args: [], } exec(compile(module, 'legacy-plugin.py', 'exec'), namespace) results = {} for active in (False, True): state['active'] = active events.clear() namespace['disassemble_function']('0x1000') namespace['decompile_function']('0x1000') results['active' if active else 'inactive'] = list(events) assert results['inactive'] == [] assert [event['kind'] for event in results['active']] == ['jumpto', 'open_pseudocode'] print(json.dumps(results, indent=2)) ``` ### Expected behavior Public capability discovery must truthfully describe the UI effects of the selected supported profile. An attached operation whose producer can navigate a GUI or open pseudocode must not be presented as having no UI effect. The provider guide should describe these reachable side effects consistently. Its exclusion of GUI navigation from adapter coverage concerns available operations and does not describe the legacy analysis side effects. The headless profile and attached operations without these producer actions should remain distinguishable. ### Impact and scope The supported legacy attached profile can move the analyst's current GUI location or open a pseudocode view while session discovery says these operations cannot show UI. This report concerns conditional UI side effects and capability truthfulness. It does not establish artifact mutation, target execution, a security boundary violation, or behavior across all IDA builds. ### REA version REA current `main` revision `693ed332b58d05192f55b8192d2c0c4bc4f4824a` and the latest published `rea-agents` package 6.4.0 were checked on macOS 27.0 arm64 with Node.js v26.10.0. The source runtime compiled successfully with `npm run compile`; the published package was extracted without running install scripts. All ten compiled `dist/ida/` files match the published npm artifact byte for byte. The supported legacy producer is `mrexodia/ida-pro-mcp` tag 1.4.0, revision `27722e929052757f4baf074776904effdb23c012`. Python 3.12.13 ran the isolated producer-body check. The producer's active-window condition uses PyQt5; an IDA GUI where that condition returns true is required. No live IDA GUI or licensed decompiler was run. ### Runtime and platform The measured local platform and runtimes appear in REA version above. IDA engine version was not measured; no live IDA engine or GUI was used. ### Target and artifact context The executed checks use a synthetic function at `0x1000`, harmless provider return values, and harmless IDA/PyQt5 stubs. The unexecuted GUI sequence requires an admitted original input whose digest matches the attached database, a resolvable function, and successful decompilation for the pseudocode action. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: `docs/tool-design.md:58` requires truthful UI effect declarations; `docs/ida-provider.md` explicitly supports the attached legacy profile and states GUI navigation is outside adapter coverage. - Failure source: `src/ida/IdaProviderCapabilities.ts:59` unconditionally sets `mayShowUi: false`; `src/application/binary/BinarySessionStatus.ts:199` exposes it; `src/ida/IdaSessionClient.ts:194-197` selects the legacy runner for attached mode, and `src/ida/IdaOperationRunner.ts:181,222,414-416` routes the affected operations to legacy tools whose active-window branches perform GUI actions. - Evidence provenance: static - Local verification: static-proof - Reproduction completeness: complete Evidence provenance details: static proof of the valid supported call path, with supporting observed production-adapter routing and exact producer-function execution using harmless stubs. Local verification details: static-proof; runtime reproduction against a live IDA GUI was not run. The required premises are attached legacy configuration, matching target identity, a resolvable function, an active PyQt5 widget, and successful decompilation for the pseudocode branch. Reproduction completeness details: complete for the source proof and inline isolated checks. Real GUI execution is outside the observed coverage; no real window movement, licensed engine behavior, or successful decompilation in an actual IDA installation was observed. Pinned source breadcrumbs: [REA effect descriptor](https://github.com/morluto/rea/blob/693ed332b58d05192f55b8192d2c0c4bc4f4824a/src/ida/IdaProviderCapabilities.ts#L59), [legacy active-window test](https://github.com/mrexodia/ida-pro-mcp/blob/27722e929052757f4baf074776904effdb23c012/src/ida_pro_mcp/mcp-plugin.py#L390), [pseudocode window action](https://github.com/mrexodia/ida-pro-mcp/blob/27722e929052757f4baf074776904effdb23c012/src/ida_pro_mcp/mcp-plugin.py#L871), and [disassembly navigation](https://github.com/mrexodia/ida-pro-mcp/blob/27722e929052757f4baf074776904effdb23c012/src/ida_pro_mcp/mcp-plugin.py#L931). ### Restoration check For the supported active attached profile, compare actual reachable GUI effects with the effects exposed by session discovery and the provider guide. Require them to agree for assembly, pseudocode and dossier. Keep an inactive-window control and a headless-profile control to distinguish actual conditional behavior from an unconditional claim. The developer can restore truthful discovery by changing the reachable behavior or its effect declaration; this report does not require a particular implementation. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The supported active attached profile can move the analyst's GUI location or open a pseudocode view while discovery says the operation cannot show UI. This consequential effect-reporting mismatch is bounded to the relevant legacy calls and active-window condition; no artifact mutation or security impact is claimed. - Workaround status: unverified - Workaround basis: The isolated inactive-window branch avoids these actions, and headless mode uses different producers, but live usability of keeping the GUI inactive or availability of a headless runtime was not established.
bsakaguchi 2026-10-15 18:26:18 +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#573
No description provided.