[GH-ISSUE #4631] [Bug] Kuna procedure search attributes installation failures to health #2411

Closed
opened 2026-10-17 21:04:17 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

rea search <target.elf> main --kind procedures --provider kuna --json returns a failure with details.operation: "health" when installation acquisition fails after a Kuna analysis profile has been selected. The requested operation is search_procedures. A second version probe refusing to run, changing executable bytes, or removing the executable each produces this mismatch before functions runs.

Reproduction

From the source checkout listed under REA version, use its pinned Node/npm toolchain, run npm ci and npm run build:cached, then run this from the repository root. These setup commands and the fixture were executed. It creates only disposable inputs, gives each CLI child a fresh HOME, TMPDIR and cache with a whitelist environment, and removes its fixture directories on completion. No real Kuna installation is needed.

node --input-type=module <<'NODE'
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';
const root = await mkdtemp(join(tmpdir(), 'kuna-operation-'));
try {
  const target = join(root, 'target.elf');
  const bytes = Buffer.alloc(64);
  bytes.set([127, 69, 76, 70, 2, 1, 1]);
  bytes.writeUInt16LE(62, 18);
  bytes.writeUInt32LE(1, 20);
  bytes.writeUInt16LE(64, 52);
  await writeFile(target, bytes);
  for (let repeat = 1; repeat <= 2; repeat++) {
    for (const mode of ['working', 'version-failure', 'changed', 'removed', 'missing-before']) {
      const home = await mkdtemp(join(root, 'home-'));
      const command = join(home, 'kuna');
      const fixture = `#!${process.execPath}
const fs = require('node:fs');
const mode = ${JSON.stringify(mode)};
const args = process.argv.slice(2);
fs.appendFileSync(__filename + '.calls', JSON.stringify(args) + '\\n');
if (args[0] === '--version') {
  let n = 0; try { n = Number(fs.readFileSync(__filename + '.count', 'utf8')); } catch {}
  fs.writeFileSync(__filename + '.count', String(++n));
  if (n === 2 && mode === 'version-failure') { process.stderr.write('fixture version refusal\\n'); process.exit(7); }
  if (n === 2 && mode === 'changed') fs.appendFileSync(__filename, '\\n// changed fixture\\n');
  if (n === 2 && mode === 'removed') fs.unlinkSync(__filename);
  console.log('kuna 1.833');
} else if (args[0] === 'functions') {
  console.log(JSON.stringify({ binary: args[1], count: 1, total: 1, error: null, functions: [{ name: 'main', address: 4096, address_hex: '0x1000', aliases: [], object_location: null, size: 16 }] }));
} else process.exit(9);
`;
      if (mode !== 'missing-before') await writeFile(command, fixture, { mode: 0o700 });
      const child = spawn(process.execPath, ['scripts/rea.mjs', 'search', target, 'main', '--kind', 'procedures', '--provider', 'kuna', '--json'], {
        env: { PATH: dirname(process.execPath) + ':/usr/bin:/bin:/usr/sbin:/sbin', HOME: home, TMPDIR: home, REA_KUNA_COMMAND: command, REA_KUNA_CACHE_DIR: join(home, 'cache'), REA_LOG_LEVEL: 'silent' }
      });
      let stdout = '', stderr = '';
      child.stdout.on('data', x => stdout += x);
      child.stderr.on('data', x => stderr += x);
      const exit = await new Promise((resolve, reject) => { child.on('error', reject); child.on('close', resolve); });
      const outcome = JSON.parse(stdout);
      let calls = [];
      try { calls = (await readFile(command + '.calls', 'utf8')).trim().split('\n').filter(Boolean).map(JSON.parse).map(a => a[0]); }
      catch (e) { if (e.code !== 'ENOENT') throw e; }
      const operation = outcome.details?.operation ?? outcome.operation;
      assert.equal(stderr, '');
      if (mode === 'working') {
        assert.equal(exit, 0);
        assert.equal(operation, 'search_procedures');
        assert.deepEqual(outcome.normalized_result, [{ address: '0x1000', value: 'main' }]);
        assert.deepEqual(calls, ['--version', '--version', 'functions']);
      } else {
        assert.equal(exit, 1);
        assert.equal(operation, mode === 'missing-before' ? 'open_binary' : 'health');
        assert.equal(outcome.retryable, mode === 'missing-before');
        assert.deepEqual(calls, mode === 'missing-before' ? [] : ['--version', '--version']);
        assert.equal(outcome.code, mode === 'changed' ? 'execution_failure' : mode === 'missing-before' ? 'provider_unavailable' : 'capability_unavailable');
        if (mode === 'version-failure') assert.match(outcome.details.reason, /fixture version refusal/);
        if (mode === 'removed') assert.match(outcome.details.reason, /Cannot fingerprint.*ENOENT/);
        if (mode === 'changed') assert.notEqual(outcome.details.diagnostics.committed_sha256, outcome.details.diagnostics.observed_sha256);
      }
      console.log(JSON.stringify({ repeat, mode, exit, code: outcome.code ?? null, operation, calls }));
    }
  }
} finally { await rm(root, { recursive: true, force: true }); }
NODE

Each of the five cases completed twice with all assertions passing. The first version probe selects the profile; the second performs lazy session installation acquisition. The script currently asserts the observed incorrect health identity so the evidence can be reproduced; change those three expectations to search_procedures when checking restoration.

Expected behavior

Failures acquiring the selected runtime should identify the operation requiring it, search_procedures, while retaining the failure code, reason, retryability and executable-digest diagnostics. The selected-runtime contract in docs/mcp-contracts.md:518-522 says these failures are reported at the operation needing the runtime. The Kuna guide documents explicit provider selection and procedure search support.

Impact and scope

Consumers correlating typed failures with requested operations receive an internal operation name for a public procedure search. All three simulated acquisition failures reproduced twice in the source build. The command still fails closed and retains useful diagnostics; none invokes functions. The successful control returns the expected procedure. Real Kuna occurrence frequency, downstream client effects and MCP adapter behavior were not measured.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A selected-runtime failure carries an incorrect machine-readable operation identity, violating the documented caller-operation contract; the failure category and diagnostics remain available.
  • Workaround status: unverified
  • Workaround basis: A functioning executable avoids the trigger, but no workaround preserving the correct failure identity for an unusable selected runtime was verified.

REA version

Source main commit 0a51b246c6 (package version 6.5.0); separately installed published npm package rea-agents@6.5.0, which does not expose Kuna and therefore does not reproduce this attribution defect.

Runtime and platform

Node.js v24.18.0; npm 11.16.0; Darwin arm64. The fake executable prints kuna 1.833, a fixture version string rather than a measured real-engine version.

Target and artifact context

A synthetic 64-byte x86_64 ELF header passes target admission. The disposable executable returns one main procedure at 0x1000 in the successful control. The target is never executed, and no real analysis database is used.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

  • Expected source: docs/mcp-contracts.md:518-522 (selected-runtime failures identify the operation needing it); docs/kuna-provider.md (explicit selection and procedure search); src/cli/coreBinaryCommands.ts:489 (public procedure search dispatch).
  • Failure source: Executed public CLI fixture above; src/kuna/KunaSessionClient.ts:360-361 returns the installation failure unchanged, while lines 1322 and 1326 construct failures with health.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Both repetitions in the source build returned these fields. operation is details.operation for failures and Evidence.operation for success.

mode             exit  code                    operation           executable calls
working          0     none                    search_procedures   --version,--version,functions
version-failure  1     capability_unavailable  health              --version,--version
changed          1     execution_failure       health              --version,--version
removed          1     capability_unavailable  health              --version,--version
missing-before   1     provider_unavailable    open_binary         none

All stderr streams were empty. The three acquisition failures have retryable: false. Version refusal retains fixture version refusal; removal retains the fingerprint ENOENT reason; changed bytes retain distinct committed_sha256 and observed_sha256 values. The successful normalized result is exactly [{"address":"0x1000","value":"main"}].

The published package was tested with the same fake/missing executable matrix, twice per case, using its installed scripts/rea.mjs entry point. Every request instead returned exit 1, code: "capability_unavailable", details.operation: "open_binary", selection_reason: "unknown_provider", and candidate IDs ghidra, hopper, ida, with no executable calls. The release tag also contains no src/kuna directory. This report concerns the source build's supported Kuna path, not a failure of the release to support an advertised provider.

Typed outcome or recovery expected

For the second-probe refusal, changed-executable and removed-executable cases, preserve failure status, codes, retryability and diagnostics but identify search_procedures. Each must still stop before functions. Preserve the working control's result and operation, and the initially missing executable's open_binary admission rejection.

Analysis or suspected cause

Source-derived reasoning: #ensureInstallation creates errors for internal health, and #execute returns the failed Result unchanged before the operation execution block. CompositeProvider creates child clients lazily, so the profile can be committed before the search acquires its Kuna session. The second version probe exercises this boundary deterministically. Cleanup-error branches were not fault-injected and are outside this report.

Additional context

Related #4597 concerns JEB metadata failures, #4607 concerns Cutter completion status, and #4614 concerns IDA startup attribution. Their provider-specific paths are separate from the Kuna installation helper exercised here. #4374 concerns empty diagnostic wording on a Kuna subprocess failure; its linked change affects message wording rather than this operation field. Open and closed issue/PR searches found no matching Kuna installation-attribution report. No last-known-good source revision has been established. The fixture establishes REA's response to a simulated changing installation; it does not establish real-engine incidence.

Originally created by @coygeek on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4631 ### Before submitting - [X] I searched the open and closed issues for a related report. - [X] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior `rea search <target.elf> main --kind procedures --provider kuna --json` returns a failure with `details.operation: "health"` when installation acquisition fails after a Kuna analysis profile has been selected. The requested operation is `search_procedures`. A second version probe refusing to run, changing executable bytes, or removing the executable each produces this mismatch before `functions` runs. ### Reproduction From the source checkout listed under REA version, use its pinned Node/npm toolchain, run `npm ci` and `npm run build:cached`, then run this from the repository root. These setup commands and the fixture were executed. It creates only disposable inputs, gives each CLI child a fresh HOME, TMPDIR and cache with a whitelist environment, and removes its fixture directories on completion. No real Kuna installation is needed. ```sh node --input-type=module <<'NODE' import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; const root = await mkdtemp(join(tmpdir(), 'kuna-operation-')); try { const target = join(root, 'target.elf'); const bytes = Buffer.alloc(64); bytes.set([127, 69, 76, 70, 2, 1, 1]); bytes.writeUInt16LE(62, 18); bytes.writeUInt32LE(1, 20); bytes.writeUInt16LE(64, 52); await writeFile(target, bytes); for (let repeat = 1; repeat <= 2; repeat++) { for (const mode of ['working', 'version-failure', 'changed', 'removed', 'missing-before']) { const home = await mkdtemp(join(root, 'home-')); const command = join(home, 'kuna'); const fixture = `#!${process.execPath} const fs = require('node:fs'); const mode = ${JSON.stringify(mode)}; const args = process.argv.slice(2); fs.appendFileSync(__filename + '.calls', JSON.stringify(args) + '\\n'); if (args[0] === '--version') { let n = 0; try { n = Number(fs.readFileSync(__filename + '.count', 'utf8')); } catch {} fs.writeFileSync(__filename + '.count', String(++n)); if (n === 2 && mode === 'version-failure') { process.stderr.write('fixture version refusal\\n'); process.exit(7); } if (n === 2 && mode === 'changed') fs.appendFileSync(__filename, '\\n// changed fixture\\n'); if (n === 2 && mode === 'removed') fs.unlinkSync(__filename); console.log('kuna 1.833'); } else if (args[0] === 'functions') { console.log(JSON.stringify({ binary: args[1], count: 1, total: 1, error: null, functions: [{ name: 'main', address: 4096, address_hex: '0x1000', aliases: [], object_location: null, size: 16 }] })); } else process.exit(9); `; if (mode !== 'missing-before') await writeFile(command, fixture, { mode: 0o700 }); const child = spawn(process.execPath, ['scripts/rea.mjs', 'search', target, 'main', '--kind', 'procedures', '--provider', 'kuna', '--json'], { env: { PATH: dirname(process.execPath) + ':/usr/bin:/bin:/usr/sbin:/sbin', HOME: home, TMPDIR: home, REA_KUNA_COMMAND: command, REA_KUNA_CACHE_DIR: join(home, 'cache'), REA_LOG_LEVEL: 'silent' } }); let stdout = '', stderr = ''; child.stdout.on('data', x => stdout += x); child.stderr.on('data', x => stderr += x); const exit = await new Promise((resolve, reject) => { child.on('error', reject); child.on('close', resolve); }); const outcome = JSON.parse(stdout); let calls = []; try { calls = (await readFile(command + '.calls', 'utf8')).trim().split('\n').filter(Boolean).map(JSON.parse).map(a => a[0]); } catch (e) { if (e.code !== 'ENOENT') throw e; } const operation = outcome.details?.operation ?? outcome.operation; assert.equal(stderr, ''); if (mode === 'working') { assert.equal(exit, 0); assert.equal(operation, 'search_procedures'); assert.deepEqual(outcome.normalized_result, [{ address: '0x1000', value: 'main' }]); assert.deepEqual(calls, ['--version', '--version', 'functions']); } else { assert.equal(exit, 1); assert.equal(operation, mode === 'missing-before' ? 'open_binary' : 'health'); assert.equal(outcome.retryable, mode === 'missing-before'); assert.deepEqual(calls, mode === 'missing-before' ? [] : ['--version', '--version']); assert.equal(outcome.code, mode === 'changed' ? 'execution_failure' : mode === 'missing-before' ? 'provider_unavailable' : 'capability_unavailable'); if (mode === 'version-failure') assert.match(outcome.details.reason, /fixture version refusal/); if (mode === 'removed') assert.match(outcome.details.reason, /Cannot fingerprint.*ENOENT/); if (mode === 'changed') assert.notEqual(outcome.details.diagnostics.committed_sha256, outcome.details.diagnostics.observed_sha256); } console.log(JSON.stringify({ repeat, mode, exit, code: outcome.code ?? null, operation, calls })); } } } finally { await rm(root, { recursive: true, force: true }); } NODE ``` Each of the five cases completed twice with all assertions passing. The first version probe selects the profile; the second performs lazy session installation acquisition. The script currently asserts the observed incorrect `health` identity so the evidence can be reproduced; change those three expectations to `search_procedures` when checking restoration. ### Expected behavior Failures acquiring the selected runtime should identify the operation requiring it, `search_procedures`, while retaining the failure code, reason, retryability and executable-digest diagnostics. The selected-runtime contract in `docs/mcp-contracts.md:518-522` says these failures are reported at the operation needing the runtime. The Kuna guide documents explicit provider selection and procedure search support. ### Impact and scope Consumers correlating typed failures with requested operations receive an internal operation name for a public procedure search. All three simulated acquisition failures reproduced twice in the source build. The command still fails closed and retains useful diagnostics; none invokes `functions`. The successful control returns the expected procedure. Real Kuna occurrence frequency, downstream client effects and MCP adapter behavior were not measured. - Impact level: P2 - Assessment status: supported - Impact basis: A selected-runtime failure carries an incorrect machine-readable operation identity, violating the documented caller-operation contract; the failure category and diagnostics remain available. - Workaround status: unverified - Workaround basis: A functioning executable avoids the trigger, but no workaround preserving the correct failure identity for an unusable selected runtime was verified. ### REA version Source main commit 0a51b246c632a36b24c121282aeb51fda1ddb461 (package version 6.5.0); separately installed published npm package `rea-agents@6.5.0`, which does not expose Kuna and therefore does not reproduce this attribution defect. ### Runtime and platform Node.js v24.18.0; npm 11.16.0; Darwin arm64. The fake executable prints `kuna 1.833`, a fixture version string rather than a measured real-engine version. ### Target and artifact context A synthetic 64-byte x86_64 ELF header passes target admission. The disposable executable returns one `main` procedure at `0x1000` in the successful control. The target is never executed, and no real analysis database is used. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence - Expected source: `docs/mcp-contracts.md:518-522` (selected-runtime failures identify the operation needing it); `docs/kuna-provider.md` (explicit selection and procedure search); `src/cli/coreBinaryCommands.ts:489` (public procedure search dispatch). - Failure source: Executed public CLI fixture above; `src/kuna/KunaSessionClient.ts:360-361` returns the installation failure unchanged, while lines 1322 and 1326 construct failures with `health`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Both repetitions in the source build returned these fields. `operation` is `details.operation` for failures and `Evidence.operation` for success. ```text mode exit code operation executable calls working 0 none search_procedures --version,--version,functions version-failure 1 capability_unavailable health --version,--version changed 1 execution_failure health --version,--version removed 1 capability_unavailable health --version,--version missing-before 1 provider_unavailable open_binary none ``` All stderr streams were empty. The three acquisition failures have `retryable: false`. Version refusal retains `fixture version refusal`; removal retains the fingerprint `ENOENT` reason; changed bytes retain distinct `committed_sha256` and `observed_sha256` values. The successful normalized result is exactly `[{"address":"0x1000","value":"main"}]`. The published package was tested with the same fake/missing executable matrix, twice per case, using its installed `scripts/rea.mjs` entry point. Every request instead returned exit 1, `code: "capability_unavailable"`, `details.operation: "open_binary"`, `selection_reason: "unknown_provider"`, and candidate IDs `ghidra`, `hopper`, `ida`, with no executable calls. The release tag also contains no `src/kuna` directory. This report concerns the source build's supported Kuna path, not a failure of the release to support an advertised provider. ### Typed outcome or recovery expected For the second-probe refusal, changed-executable and removed-executable cases, preserve failure status, codes, retryability and diagnostics but identify `search_procedures`. Each must still stop before `functions`. Preserve the working control's result and operation, and the initially missing executable's `open_binary` admission rejection. ### Analysis or suspected cause Source-derived reasoning: `#ensureInstallation` creates errors for internal `health`, and `#execute` returns the failed Result unchanged before the operation execution block. `CompositeProvider` creates child clients lazily, so the profile can be committed before the search acquires its Kuna session. The second version probe exercises this boundary deterministically. Cleanup-error branches were not fault-injected and are outside this report. ### Additional context Related [#4597](https://github.com/morluto/rea/issues/4597) concerns JEB metadata failures, [#4607](https://github.com/morluto/rea/issues/4607) concerns Cutter completion status, and [#4614](https://github.com/morluto/rea/issues/4614) concerns IDA startup attribution. Their provider-specific paths are separate from the Kuna installation helper exercised here. [#4374](https://github.com/morluto/rea/issues/4374) concerns empty diagnostic wording on a Kuna subprocess failure; its linked change affects message wording rather than this operation field. Open and closed issue/PR searches found no matching Kuna installation-attribution report. No last-known-good source revision has been established. The fixture establishes REA's response to a simulated changing installation; it does not establish real-engine incidence.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Published in 5717ad24aa on main.

Installation failures remain immutable producer-owned facts. At the requesting boundary, the existing authored-failure normalizer attributes them to each caller while preserving class/reason, digest diagnostics, retryability, captured facts and live cleanup dependencies. A direct health request retains the original health outcome; no later abort signal is sampled to reclassify a failure.

Validation executed on Node24.18/Darwin arm64:

  • The report's ten-case public CLI fixture on baseline 7086768a2 and candidate (five cases, twice each). The six second-probe acquisition failures now identify search_procedures. Exit codes, failure codes, refusal/ENOENT/digest assertions, retryability and command traces remain unchanged; functions never runs on those failures. Working search and initially missing executable controls retain their original behavior.
  • Nineteen boundary checks passed, including concurrent search/list_strings/health callers sharing one failed acquisition without changing the producer, and fingerprint/shebang failed-close retry propagating to both original and reattributed errors. Four overlapping final cleanup controls also passed.
  • check:fast, docs:check/five README checks and normal commit/pre-push hooks passed.

Scope is simulated Kuna executables with production file/process ownership and public CLI, not real Kuna analysis or Windows behavior. MCP-specific failure delivery and performance/token/peak-family-memory costs remain unmeasured. Newly found #4689 concerns the later inventory boundary, and #4693 covers stale cleanup state in its older query-remapping helper; neither is claimed repaired here. The broader audit remains active.

<!-- gh-comment-id:6230375586 --> @morluto commented on GitHub (Oct 17, 2026): Published in 5717ad24aa6471aa4207c370e26274866fb8aee5 on main. Installation failures remain immutable producer-owned facts. At the requesting boundary, the existing authored-failure normalizer attributes them to each caller while preserving class/reason, digest diagnostics, retryability, captured facts and live cleanup dependencies. A direct health request retains the original health outcome; no later abort signal is sampled to reclassify a failure. Validation executed on Node24.18/Darwin arm64: - The report's ten-case public CLI fixture on baseline 7086768a2 and candidate (five cases, twice each). The six second-probe acquisition failures now identify search_procedures. Exit codes, failure codes, refusal/ENOENT/digest assertions, retryability and command traces remain unchanged; functions never runs on those failures. Working search and initially missing executable controls retain their original behavior. - Nineteen boundary checks passed, including concurrent search/list_strings/health callers sharing one failed acquisition without changing the producer, and fingerprint/shebang failed-close retry propagating to both original and reattributed errors. Four overlapping final cleanup controls also passed. - check:fast, docs:check/five README checks and normal commit/pre-push hooks passed. Scope is simulated Kuna executables with production file/process ownership and public CLI, not real Kuna analysis or Windows behavior. MCP-specific failure delivery and performance/token/peak-family-memory costs remain unmeasured. Newly found #4689 concerns the later inventory boundary, and #4693 covers stale cleanup state in its older query-remapping helper; neither is claimed repaired here. The broader audit remains active.
Author
Owner

@coygeek commented on GitHub (Oct 18, 2026):

Closing as addressed on main by 5717ad24a. Shared Kuna installation failures now identify the caller operation, including search_procedures, while preserving the original health producer context and failure details. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.

<!-- gh-comment-id:6244113761 --> @coygeek commented on GitHub (Oct 18, 2026): Closing as addressed on `main` by [5717ad24a](https://github.com/morluto/rea/commit/5717ad24aa6471aa4207c370e26274866fb8aee5). Shared Kuna installation failures now identify the caller operation, including search_procedures, while preserving the original health producer context and failure details. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.
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#2411
No description provided.