[GH-ISSUE #7404] [Bug] inspect-plist, inspect-macho and inspect-signature return working-directory Evidence for an empty target #4408

Closed
opened 2026-10-31 00:49:34 +00:00 by bsakaguchi · 0 comments
Owner

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

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

From a synthetic application-bundle working directory, passing an empty positional target to inspect-plist, inspect-macho or inspect-signature returned successful Evidence and exit 0. The plist result described that directory's Contents/Info.plist; the other two results selected its Contents/MacOS/Example executable. Each response retained parameters.input_path: "". Explicit . selected the same files. The empty-target control inspect-model-artifact "" --json instead exited 1 with VALIDATION_ERROR naming path.

Reproduction

  1. Use the baseline checkout with its locked dependencies and built runtime available, Node 24.22.1 selected, and the macOS command-line tools available. Run the following command from the checkout root. It creates a fresh empty scratch directory, isolates HOME, writes the fixture inline and removes that directory afterward; no real application is needed.
node --input-type=module <<'JS'
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, relative, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
const cli = resolve('scripts/rea.mjs');
const root = realpathSync(mkdtempSync(join(tmpdir(), 'rea-empty-native-')));
const bundle = join(root, 'Example.app');
const env = { ...process.env, HOME: join(root, 'home'), REA_LOG_LEVEL: 'silent' };
try {
  mkdirSync(join(bundle, 'Contents/MacOS'), { recursive: true });
  mkdirSync(env.HOME);
  writeFileSync(join(bundle, 'Contents/Info.plist'), '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleIdentifier</key><string>test.fixture</string><key>CFBundleExecutable</key><string>Example</string></dict></plist>');
  writeFileSync(join(root, 'main.c'), 'int main(void){return 0;}\n');
  const cc = spawnSync('clang', ['-o', 'Example.app/Contents/MacOS/Example', 'main.c'], { cwd: root, env, encoding: 'utf8' });
  if (cc.status !== 0) throw new Error(cc.stderr);
  for (const command of ['inspect-plist', 'inspect-macho', 'inspect-signature']) {
    for (const input of ['', '.']) {
      const result = spawnSync(process.execPath, [cli, command, input, '--json'], { cwd: bundle, env, encoding: 'utf8' });
      const data = JSON.parse(result.stdout);
      const selected = data.normalized_result?.source_path ?? data.subject?.local_path;
      console.log(JSON.stringify({ command, input, exit: result.status, input_path: data.parameters?.input_path, selected: selected ? relative(root, selected) : null }));
    }
  }
  const result = spawnSync(process.execPath, [cli, 'inspect-model-artifact', '', '--json'], { cwd: bundle, env, encoding: 'utf8' });
  const data = JSON.parse(result.stdout);
  console.log(JSON.stringify({ command: 'inspect-model-artifact', input: '', exit: result.status, code: data.code, issue: data.fieldErrors?.[0]?.code, field: data.fieldErrors?.[0]?.path }));
} finally {
  rmSync(root, { recursive: true, force: true });
}
JS
  1. Observe the exact compact output below. The script invokes the public dispatcher as node scripts/rea.mjs <command> "" --json, using the checkout's resolved entry path while the fixture bundle is cwd. The three empty selections exit 0 and report the same selected files as their explicit-dot controls.
{"command":"inspect-plist","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/Info.plist"}
{"command":"inspect-plist","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/Info.plist"}
{"command":"inspect-macho","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/MacOS/Example"}
{"command":"inspect-macho","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/MacOS/Example"}
{"command":"inspect-signature","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/MacOS/Example"}
{"command":"inspect-signature","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/MacOS/Example"}
{"command":"inspect-model-artifact","input":"","exit":1,"code":"VALIDATION_ERROR","issue":"too_small","field":"path"}

The initial independent replay reproduced the failure twice per native command, with six successful explicit-dot controls. The standalone script above reproduced it once more per native command: three observed failures in three attempts for each command. The model-artifact empty-target rejection was observed in both replays. An additional omitted-target inspect-plist --json control exited 1 with VALIDATION_ERROR and missing: true for path.

Expected behavior

Reject the empty target before resolving or inspecting any local path, and return a nonzero validation error identifying path. Keep explicit . available for intentional directory selection. The “Run a command” section of docs/cli.md, lines 42–45, states: “Local file inspection commands reject empty paths and NUL bytes before resolving relative inputs, auxiliary files or output destinations. An empty string never selects the working directory; use an explicit . when selecting that directory is supported by the command.”

Impact and scope

In the executed fixture, all three commands produced successful analysis Evidence for the working directory when the selected argument named no target. This can make an empty shell variable look like a successful inspection. A caller can refuse an empty variable before invoking REA and pass a nonempty intended path; explicit . was verified for intentional working-directory inspection. The observed scope is these three commands on this macOS bundle fixture. Other commands, other platforms and downstream consumption of the Evidence were not tested. No target execution or modification was involved.

REA version

6.8.0 (source commit a905a71e31)

Runtime and platform

Node 24.22.1; macOS 27.0.1 (build 26A434), arm64.

Target and artifact context

Self-authored XML application bundle with CFBundleExecutable set to Example and a locally compiled arm64 Mach-O containing only an inert main function. The target was not executed. Native inspection used the host's tools; no Hopper, Ghidra or external API was used.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Observed: the exact replay output above projects only the command, original argument, exit code, retained input path and selected file from each complete CLI JSON response. This demonstrates wrong-target success independently of parser diagnosis. The Mach-O subject was arm64; the signature result had signed: true, which is incidental to target selection. Both initial rounds had empty stderr for all six native calls.

Source-only (not executed as a separate source test): src/cli/utilityCommands.ts:193 registers the Mach-O/signature command loop; its path schema at line 200 uses z.string() without the nonempty local-path constraint. The plist declaration at line 219 does the same. Those handlers pass the original argument to shared analysis. src/application/DirectAnalysis.ts:211 retains it as input_path; the plist directory branch at line 347 reaches inspectBundleResourceInput, whose line 754 makes the path absolute before stat. The executable path goes through session.open at line 455, src/application/binary/BinarySessionOpen.ts:90 and src/application/BinaryTargetResolver.ts:144. The resolver makes the input absolute before filesystem access and bundle executable selection.

Typed outcome or recovery expected

A nonzero input-validation result identifying the empty path, with no analysis Evidence for the working directory. The observed model-artifact control provides an existing CLI example: VALIDATION_ERROR with fieldErrors[0].path: "path" and code: "too_small". Repeat the same empty and explicit-dot cases to verify a correction.

Analysis or suspected cause

Source-derived: src/filesystem/callerPath.ts:35 prefixes relative input with cwd; the recursive normalization through line 45 turns "" into the working-directory path. The native registrations admit the empty string before this shared application/session resolution. The existing nonempty, NUL-free schema is defined at src/domain/localPath.ts:20 and used by the model control at src/cli/modelCommands.ts:29. No change to helper semantics is required by this report; the contract is that invalid empty selection must be refused before resolution.

Additional context

Open issue #7280 and open PR #7284 are related but cover decompile-all: its target refusal, export suppression and explicit-dot export control. The PR changes only that command's argument schema and test, so it does not cover the native inspection behavior reproduced here.

Related (closed):
Issue #6757 covered inspect-desktop-application; its fix does not validate these native registrations, which still return working-directory Evidence at the stated baseline.

The npm registry's latest published rea-agents version was 6.8.0 when checked; this report exercised the specified source checkout and its built dispatcher.

Related (open): Issue #7280 reports the same empty-target behavior for decompile-all and issue #7377 for inspect-keyed-archive; each command family has its own argument declarations, so this report covers only the commands named above.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7404 ### 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 From a synthetic application-bundle working directory, passing an empty positional target to `inspect-plist`, `inspect-macho` or `inspect-signature` returned successful Evidence and exit 0. The plist result described that directory's `Contents/Info.plist`; the other two results selected its `Contents/MacOS/Example` executable. Each response retained `parameters.input_path: ""`. Explicit `.` selected the same files. The empty-target control `inspect-model-artifact "" --json` instead exited 1 with `VALIDATION_ERROR` naming `path`. ### Reproduction 1. Use the baseline checkout with its locked dependencies and built runtime available, Node 24.22.1 selected, and the macOS command-line tools available. Run the following command from the checkout root. It creates a fresh empty scratch directory, isolates HOME, writes the fixture inline and removes that directory afterward; no real application is needed. ```sh node --input-type=module <<'JS' import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, relative, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; const cli = resolve('scripts/rea.mjs'); const root = realpathSync(mkdtempSync(join(tmpdir(), 'rea-empty-native-'))); const bundle = join(root, 'Example.app'); const env = { ...process.env, HOME: join(root, 'home'), REA_LOG_LEVEL: 'silent' }; try { mkdirSync(join(bundle, 'Contents/MacOS'), { recursive: true }); mkdirSync(env.HOME); writeFileSync(join(bundle, 'Contents/Info.plist'), '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleIdentifier</key><string>test.fixture</string><key>CFBundleExecutable</key><string>Example</string></dict></plist>'); writeFileSync(join(root, 'main.c'), 'int main(void){return 0;}\n'); const cc = spawnSync('clang', ['-o', 'Example.app/Contents/MacOS/Example', 'main.c'], { cwd: root, env, encoding: 'utf8' }); if (cc.status !== 0) throw new Error(cc.stderr); for (const command of ['inspect-plist', 'inspect-macho', 'inspect-signature']) { for (const input of ['', '.']) { const result = spawnSync(process.execPath, [cli, command, input, '--json'], { cwd: bundle, env, encoding: 'utf8' }); const data = JSON.parse(result.stdout); const selected = data.normalized_result?.source_path ?? data.subject?.local_path; console.log(JSON.stringify({ command, input, exit: result.status, input_path: data.parameters?.input_path, selected: selected ? relative(root, selected) : null })); } } const result = spawnSync(process.execPath, [cli, 'inspect-model-artifact', '', '--json'], { cwd: bundle, env, encoding: 'utf8' }); const data = JSON.parse(result.stdout); console.log(JSON.stringify({ command: 'inspect-model-artifact', input: '', exit: result.status, code: data.code, issue: data.fieldErrors?.[0]?.code, field: data.fieldErrors?.[0]?.path })); } finally { rmSync(root, { recursive: true, force: true }); } JS ``` 2. Observe the exact compact output below. The script invokes the public dispatcher as `node scripts/rea.mjs <command> "" --json`, using the checkout's resolved entry path while the fixture bundle is cwd. The three empty selections exit 0 and report the same selected files as their explicit-dot controls. ```json {"command":"inspect-plist","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/Info.plist"} {"command":"inspect-plist","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/Info.plist"} {"command":"inspect-macho","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/MacOS/Example"} {"command":"inspect-macho","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/MacOS/Example"} {"command":"inspect-signature","input":"","exit":0,"input_path":"","selected":"Example.app/Contents/MacOS/Example"} {"command":"inspect-signature","input":".","exit":0,"input_path":".","selected":"Example.app/Contents/MacOS/Example"} {"command":"inspect-model-artifact","input":"","exit":1,"code":"VALIDATION_ERROR","issue":"too_small","field":"path"} ``` The initial independent replay reproduced the failure twice per native command, with six successful explicit-dot controls. The standalone script above reproduced it once more per native command: three observed failures in three attempts for each command. The model-artifact empty-target rejection was observed in both replays. An additional omitted-target `inspect-plist --json` control exited 1 with `VALIDATION_ERROR` and `missing: true` for `path`. ### Expected behavior Reject the empty target before resolving or inspecting any local path, and return a nonzero validation error identifying `path`. Keep explicit `.` available for intentional directory selection. The “Run a command” section of `docs/cli.md`, lines 42–45, states: “Local file inspection commands reject empty paths and NUL bytes before resolving relative inputs, auxiliary files or output destinations. An empty string never selects the working directory; use an explicit `.` when selecting that directory is supported by the command.” ### Impact and scope In the executed fixture, all three commands produced successful analysis Evidence for the working directory when the selected argument named no target. This can make an empty shell variable look like a successful inspection. A caller can refuse an empty variable before invoking REA and pass a nonempty intended path; explicit `.` was verified for intentional working-directory inspection. The observed scope is these three commands on this macOS bundle fixture. Other commands, other platforms and downstream consumption of the Evidence were not tested. No target execution or modification was involved. ### REA version 6.8.0 (source commit a905a71e31f71457cb21fd056b234f872126974e) ### Runtime and platform Node 24.22.1; macOS 27.0.1 (build 26A434), arm64. ### Target and artifact context Self-authored XML application bundle with CFBundleExecutable set to Example and a locally compiled arm64 Mach-O containing only an inert main function. The target was not executed. Native inspection used the host's tools; no Hopper, Ghidra or external API was used. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Observed: the exact replay output above projects only the command, original argument, exit code, retained input path and selected file from each complete CLI JSON response. This demonstrates wrong-target success independently of parser diagnosis. The Mach-O subject was arm64; the signature result had `signed: true`, which is incidental to target selection. Both initial rounds had empty stderr for all six native calls. Source-only (not executed as a separate source test): `src/cli/utilityCommands.ts:193` registers the Mach-O/signature command loop; its path schema at line 200 uses `z.string()` without the nonempty local-path constraint. The plist declaration at line 219 does the same. Those handlers pass the original argument to shared analysis. `src/application/DirectAnalysis.ts:211` retains it as `input_path`; the plist directory branch at line 347 reaches `inspectBundleResourceInput`, whose line 754 makes the path absolute before stat. The executable path goes through `session.open` at line 455, `src/application/binary/BinarySessionOpen.ts:90` and `src/application/BinaryTargetResolver.ts:144`. The resolver makes the input absolute before filesystem access and bundle executable selection. ### Typed outcome or recovery expected A nonzero input-validation result identifying the empty `path`, with no analysis Evidence for the working directory. The observed model-artifact control provides an existing CLI example: `VALIDATION_ERROR` with `fieldErrors[0].path: "path"` and `code: "too_small"`. Repeat the same empty and explicit-dot cases to verify a correction. ### Analysis or suspected cause Source-derived: `src/filesystem/callerPath.ts:35` prefixes relative input with cwd; the recursive normalization through line 45 turns `""` into the working-directory path. The native registrations admit the empty string before this shared application/session resolution. The existing nonempty, NUL-free schema is defined at `src/domain/localPath.ts:20` and used by the model control at `src/cli/modelCommands.ts:29`. No change to helper semantics is required by this report; the contract is that invalid empty selection must be refused before resolution. ### Additional context [Open issue #7280](https://github.com/morluto/rea/issues/7280) and [open PR #7284](https://github.com/morluto/rea/pull/7284) are related but cover `decompile-all`: its target refusal, export suppression and explicit-dot export control. The PR changes only that command's argument schema and test, so it does not cover the native inspection behavior reproduced here. Related (closed): [Issue #6757](https://github.com/morluto/rea/issues/6757) covered `inspect-desktop-application`; its fix does not validate these native registrations, which still return working-directory Evidence at the stated baseline. The npm registry's latest published rea-agents version was 6.8.0 when checked; this report exercised the specified source checkout and its built dispatcher. Related (open): [Issue #7280](https://github.com/morluto/rea/issues/7280) reports the same empty-target behavior for `decompile-all` and [issue #7377](https://github.com/morluto/rea/issues/7377) for `inspect-keyed-archive`; each command family has its own argument declarations, so this report covers only the commands named above.
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#4408
No description provided.