[GH-ISSUE #7280] [Bug] decompile-all accepts an empty target and exports the working directory #4323

Open
opened 2026-10-31 00:48:54 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

CLI

Observed behavior

The registered decompile-all source CLI accepts an empty target, selects the executable inside an application-bundle working directory, returns successful Evidence and writes pseudocode. In the offline reproduction it returned exit 0, preserved caller_input_path: "", selected Example.app/Contents/MacOS/Example and wrote functions/0x1000.c. This was executed through the production CLI/session/resolver/export workflow with the repository's fake Kuna provider, not a live decompiler or built package dispatcher.

Reproduction

  1. Use the source revision below with its locked dependencies installed. Run the self-contained shell probe under Relevant output or evidence from the checkout root.
  2. The probe isolates HOME, creates an application bundle with CFBundleExecutable=Example, places a minimal ELF64 little-endian x86_64 fixture at Contents/MacOS/Example, and configures the existing offline Kuna fixture with one main function at 0x1000.
  3. With that bundle as cwd, it invokes createCli(environment).serve(...) with ["decompile-all", "", "--provider", "kuna", "--output", outputDirectory, "--json"], where the output directory is fresh and absolute.
  4. Compare the empty target with explicit . and omitted-target invocations. The empty and explicit-dot commands both exit 0, select the bundle executable and write one file; omission exits 1 with VALIDATION_ERROR identifying the missing path.

Two source-test samples completed these three command invocations. The first harness asserted the wrong Evidence field after the calls completed; the corrected focused test passed. The standalone shell replay also exited 0 and reproduced the same outcomes using those production calls without adding a test file. The probe removes its fixtures on completion.

Expected behavior

An empty target must fail validation before relative input or output resolution, without selecting or exporting the working directory. docs/cli.md:42-45 explicitly promises that local file inspection commands reject empty paths before resolution and that an empty string never selects the working directory. Explicit . should remain available when directory selection is supported.

Impact and scope

P2: a script with an empty target variable can produce successful Evidence and output files for an unintended working-directory executable. The reproduced scope is a readable synthetic app bundle and an offline provider. A safe caller workaround is to reject empty input before invoking REA; explicit . was verified to preserve intentional selection of the bundle working directory. No security boundary was crossed. Real-provider reconstruction quality, other operating systems and the packaged dispatcher were not tested.

REA version

615fbbbdd7 (package.json version 6.8.0)

Runtime and platform

Node.js v26.12.1; macOS arm64; locked Vitest 5.0.3 for the focused test. The offline Kuna fixture advertises version 1.833. No real Kuna, Hopper, Ghidra, IDA or device was used.

Target and artifact context

Synthetic application bundle containing a minimal ELF64 x86_64 parser fixture. The offline provider supplies one pseudocode function. The target is never executed. Output is confined to fresh disposable directories.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The source-CLI probe produced these compact outcomes; selected paths are relative to the disposable fixture root:

{"label":"empty","exit":0,"operation":"decompile_all","caller_input_path":"","selected":"Example.app/Contents/MacOS/Example","written":1}
{"label":"dot","exit":0,"operation":"decompile_all","caller_input_path":".","selected":"Example.app/Contents/MacOS/Example","written":1}
{"label":"omitted","exit":1,"code":"VALIDATION_ERROR"}
{"outputFiles":["0x1000.c"]}

Runnable offline source-CLI probe, from the checkout root:

node --import tsx --input-type=module <<'JS'
import { mkdir, mkdtemp, readdir, realpath, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, relative } from "node:path";
const checkout = process.cwd();
const oldHome = process.env.HOME;
const root = await realpath(await mkdtemp(join(tmpdir(), "rea-empty-target-")));
process.env.HOME = join(root, "home");
await mkdir(process.env.HOME);
try {
  const { createCli } = await import("./src/cli.ts");
  const { elf } = await import("./src/domain/binaryTarget.fixture.ts");
  const { writeFakeKuna } = await import("./tests/boundary/providers/kuna/fakeKuna.ts");
  const bundle = join(root, "Example.app");
  await mkdir(join(bundle, "Contents", "MacOS"), { recursive: true });
  await writeFile(join(bundle, "Contents", "Info.plist"), '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>Example</string></dict></plist>');
  await writeFile(join(bundle, "Contents", "MacOS", "Example"), elf(2, 1, 62));
  const kuna = await writeFakeKuna(root, {
    functions: [{ name: "main", addr: "0x1000" }], inventoryImports: false,
  });
  const environment = {
    HOME: process.env.HOME, REA_LOG_LEVEL: "silent",
    REA_KUNA_COMMAND: kuna, REA_KUNA_CACHE_DIR: join(root, "cache"),
  };
  process.chdir(bundle);
  for (const [label, target] of [["empty", [""]], ["dot", ["."]], ["omitted", []]]) {
    let text = "";
    let exit = 0;
    await createCli(environment).serve([
      "decompile-all", ...target, "--provider", "kuna",
      "--output", join(root, `out-${label}`), "--json",
    ], {
      stdout: chunk => { text += chunk; },
      exit: code => { exit = code; }, env: {},
    });
    const result = JSON.parse(text);
    console.log(JSON.stringify({
      label, exit, operation: result.operation,
      caller_input_path: result.caller_input_path,
      selected: result.subject ? relative(root, result.subject.local_path) : undefined,
      written: result.normalized_result?.counts.written, code: result.code,
    }));
  }
  console.log(JSON.stringify({ outputFiles: await readdir(join(root, "out-empty", "functions")) }));
} finally {
  process.chdir(checkout);
  if (oldHome === undefined) delete process.env.HOME;
  else process.env.HOME = oldHome;
  await rm(root, { recursive: true, force: true });
}
JS

Source-derived corroboration: src/cli/coreBinaryCommands.ts:59 registers the command; src/cli/decompileDirectoryCommand.ts:25-27,83-90 accepts an unconstrained target string and forwards it to direct analysis. src/application/DirectAnalysis.ts:441-445 opens the same path, and src/application/binary/BinarySessionOpen.ts:88-105 forwards it to parseBinaryTarget. src/application/BinaryTargetResolver.ts:138-159 resolves the bundle after src/filesystem/callerPath.ts:27-39 makes the empty input absolute as cwd. The actual full CLI invocation confirms that its earlier argument guards do not reject this input.

Typed outcome or recovery expected

A nonzero input error identifying path, before provider work or export creation. Verify restoration with the same empty-target case, preserve the successful explicit-dot control, and retain omitted-target validation. A provider-availability error after resolution would not restore the documented contract.

Analysis or suspected cause

Source-derived: the target argument has string-type validation without a nonempty constraint, allowing the original empty selection to reach relative-path resolution. The observed CLI response and exported file establish the user-visible failure independently of that diagnosis.

Additional context

Closed issue 6757 reports related historical behavior in inspect-desktop-application. It is a different command and was closed; the present decompile-all failure remains in the tested source. Searches across open and closed reports found no open duplicate for this command and empty-target behavior.

Originally created by @coygeek on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7280 ### 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 CLI ### Observed behavior The registered `decompile-all` source CLI accepts an empty target, selects the executable inside an application-bundle working directory, returns successful Evidence and writes pseudocode. In the offline reproduction it returned exit 0, preserved `caller_input_path: ""`, selected `Example.app/Contents/MacOS/Example` and wrote `functions/0x1000.c`. This was executed through the production CLI/session/resolver/export workflow with the repository's fake Kuna provider, not a live decompiler or built package dispatcher. ### Reproduction 1. Use the source revision below with its locked dependencies installed. Run the self-contained shell probe under Relevant output or evidence from the checkout root. 2. The probe isolates HOME, creates an application bundle with `CFBundleExecutable=Example`, places a minimal ELF64 little-endian x86_64 fixture at `Contents/MacOS/Example`, and configures the existing offline Kuna fixture with one `main` function at `0x1000`. 3. With that bundle as cwd, it invokes `createCli(environment).serve(...)` with `["decompile-all", "", "--provider", "kuna", "--output", outputDirectory, "--json"]`, where the output directory is fresh and absolute. 4. Compare the empty target with explicit `.` and omitted-target invocations. The empty and explicit-dot commands both exit 0, select the bundle executable and write one file; omission exits 1 with `VALIDATION_ERROR` identifying the missing `path`. Two source-test samples completed these three command invocations. The first harness asserted the wrong Evidence field after the calls completed; the corrected focused test passed. The standalone shell replay also exited 0 and reproduced the same outcomes using those production calls without adding a test file. The probe removes its fixtures on completion. ### Expected behavior An empty target must fail validation before relative input or output resolution, without selecting or exporting the working directory. `docs/cli.md:42-45` explicitly promises that local file inspection commands reject empty paths before resolution and that an empty string never selects the working directory. Explicit `.` should remain available when directory selection is supported. ### Impact and scope P2: a script with an empty target variable can produce successful Evidence and output files for an unintended working-directory executable. The reproduced scope is a readable synthetic app bundle and an offline provider. A safe caller workaround is to reject empty input before invoking REA; explicit `.` was verified to preserve intentional selection of the bundle working directory. No security boundary was crossed. Real-provider reconstruction quality, other operating systems and the packaged dispatcher were not tested. ### REA version 615fbbbdd750de2df5d92b9827d0c7c4422c1d03 (package.json version 6.8.0) ### Runtime and platform Node.js v26.12.1; macOS arm64; locked Vitest 5.0.3 for the focused test. The offline Kuna fixture advertises version 1.833. No real Kuna, Hopper, Ghidra, IDA or device was used. ### Target and artifact context Synthetic application bundle containing a minimal ELF64 x86_64 parser fixture. The offline provider supplies one pseudocode function. The target is never executed. Output is confined to fresh disposable directories. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The source-CLI probe produced these compact outcomes; selected paths are relative to the disposable fixture root: ```json {"label":"empty","exit":0,"operation":"decompile_all","caller_input_path":"","selected":"Example.app/Contents/MacOS/Example","written":1} {"label":"dot","exit":0,"operation":"decompile_all","caller_input_path":".","selected":"Example.app/Contents/MacOS/Example","written":1} {"label":"omitted","exit":1,"code":"VALIDATION_ERROR"} {"outputFiles":["0x1000.c"]} ``` Runnable offline source-CLI probe, from the checkout root: ```bash node --import tsx --input-type=module <<'JS' import { mkdir, mkdtemp, readdir, realpath, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, relative } from "node:path"; const checkout = process.cwd(); const oldHome = process.env.HOME; const root = await realpath(await mkdtemp(join(tmpdir(), "rea-empty-target-"))); process.env.HOME = join(root, "home"); await mkdir(process.env.HOME); try { const { createCli } = await import("./src/cli.ts"); const { elf } = await import("./src/domain/binaryTarget.fixture.ts"); const { writeFakeKuna } = await import("./tests/boundary/providers/kuna/fakeKuna.ts"); const bundle = join(root, "Example.app"); await mkdir(join(bundle, "Contents", "MacOS"), { recursive: true }); await writeFile(join(bundle, "Contents", "Info.plist"), '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>Example</string></dict></plist>'); await writeFile(join(bundle, "Contents", "MacOS", "Example"), elf(2, 1, 62)); const kuna = await writeFakeKuna(root, { functions: [{ name: "main", addr: "0x1000" }], inventoryImports: false, }); const environment = { HOME: process.env.HOME, REA_LOG_LEVEL: "silent", REA_KUNA_COMMAND: kuna, REA_KUNA_CACHE_DIR: join(root, "cache"), }; process.chdir(bundle); for (const [label, target] of [["empty", [""]], ["dot", ["."]], ["omitted", []]]) { let text = ""; let exit = 0; await createCli(environment).serve([ "decompile-all", ...target, "--provider", "kuna", "--output", join(root, `out-${label}`), "--json", ], { stdout: chunk => { text += chunk; }, exit: code => { exit = code; }, env: {}, }); const result = JSON.parse(text); console.log(JSON.stringify({ label, exit, operation: result.operation, caller_input_path: result.caller_input_path, selected: result.subject ? relative(root, result.subject.local_path) : undefined, written: result.normalized_result?.counts.written, code: result.code, })); } console.log(JSON.stringify({ outputFiles: await readdir(join(root, "out-empty", "functions")) })); } finally { process.chdir(checkout); if (oldHome === undefined) delete process.env.HOME; else process.env.HOME = oldHome; await rm(root, { recursive: true, force: true }); } JS ``` Source-derived corroboration: `src/cli/coreBinaryCommands.ts:59` registers the command; `src/cli/decompileDirectoryCommand.ts:25-27,83-90` accepts an unconstrained target string and forwards it to direct analysis. `src/application/DirectAnalysis.ts:441-445` opens the same path, and `src/application/binary/BinarySessionOpen.ts:88-105` forwards it to `parseBinaryTarget`. `src/application/BinaryTargetResolver.ts:138-159` resolves the bundle after `src/filesystem/callerPath.ts:27-39` makes the empty input absolute as cwd. The actual full CLI invocation confirms that its earlier argument guards do not reject this input. ### Typed outcome or recovery expected A nonzero input error identifying `path`, before provider work or export creation. Verify restoration with the same empty-target case, preserve the successful explicit-dot control, and retain omitted-target validation. A provider-availability error after resolution would not restore the documented contract. ### Analysis or suspected cause Source-derived: the target argument has string-type validation without a nonempty constraint, allowing the original empty selection to reach relative-path resolution. The observed CLI response and exported file establish the user-visible failure independently of that diagnosis. ### Additional context [Closed issue 6757](https://github.com/morluto/rea/issues/6757) reports related historical behavior in `inspect-desktop-application`. It is a different command and was closed; the present `decompile-all` failure remains in the tested source. Searches across open and closed reports found no open duplicate for this command and empty-target behavior.
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#4323
No description provided.