[GH-ISSUE #7424] [Bug] Desktop inspection succeeds for a path whose intermediate component is a regular file #4430

Closed
opened 2026-10-31 00:49:44 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

On macOS, node scripts/rea.mjs inspect-desktop-application "$WORK/regular/../Demo.app" --json exits 0 and inventories Demo.app, although filesystem lookup of the selected spelling fails with ENOTDIR. The result retains the invalid root.path and reports the collapsed directory as root.canonical_path. Its file list and snapshot digest equal a direct inspection of the valid directory. The sibling inspect-artifact command rejects the same invalid spelling with target_unavailable.

Reproduction

From a fresh checkout at the commit below, select Node 24.22.1 and run npm ci && npm run build:cached. Dependencies were already installed in the verification checkout; its current-main build passed. Run the following from the checkout root on macOS, allowing the expected failing commands to finish without stopping the shell. One complete fixture replay was observed; $WORK replaces the disposable fixture location.

WORK="$(mktemp -d)"
mkdir -p "$WORK/Demo.app" "$WORK/home" "$WORK/cfg" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/tmp"
cat > "$WORK/Demo.app/Info.plist" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>CFBundleIdentifier</key><string>com.example.demo</string></dict></plist>
PLIST
printf 'marker\n' > "$WORK/Demo.app/marker.txt"
printf 'regular file\n' > "$WORK/regular"
export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/cfg" XDG_DATA_HOME="$WORK/data" XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/tmp"
stat "$WORK/regular/../Demo.app"
node scripts/rea.mjs inspect-desktop-application "$WORK/regular/../Demo.app" --json > "$WORK/invalid-desktop.json"
printf 'invalid desktop exit: %s\n' "$?"
node scripts/rea.mjs inspect-desktop-application "$WORK/Demo.app" --json > "$WORK/valid-desktop.json"
printf 'valid desktop exit: %s\n' "$?"
node scripts/rea.mjs inspect-artifact "$WORK/regular/../Demo.app" --json > "$WORK/invalid-artifact.json"
printf 'invalid artifact exit: %s\n' "$?"

Inspect the three JSON files. Both desktop results list Info.plist and marker.txt, have equal normalized_result.root.snapshot_sha256 values, and report coverage.status: "complete-within-scan". Node's fs/promises.stat on the invalid spelling was also observed to throw ENOTDIR, while realpath returned the canonical Demo.app directory.

Expected behavior

Reject the unresolvable selection before directory inspection and return a typed unavailable-target result. src/cli/desktopApplicationCommands.ts:25 defines the target as a local installation directory. src/filesystem/callerPath.ts:18-25 promises to leave POSIX parent traversal to filesystem resolution; src/artifacts/DesktopApplicationReader.ts:178-181 identifies the root as the caller's selection and preserves host target-unavailability semantics. src/artifacts/ArtifactSelectedSource.ts:5-14 explicitly handles Darwin's realpath versus stat disagreement by checking the original spelling first. Valid selected-root symlinks remain supported by docs/desktop-applications.md:117.

Impact and scope

P2: this bounded input produces successful Evidence for a directory the selected spelling cannot resolve, which can mislead scripts relying on successful target admission. Passing the intended valid directory succeeds and is a practical workaround; callers can also validate the original spelling with stat before invocation. Only the public CLI on this macOS fixture was exercised. No target execution, modification or security boundary crossing was involved. Complete scan coverage does not mean every file's contents were read; this fixture reported zero probed files and zero bytes read.

REA version

Exact node scripts/rea.mjs --version output: 6.8.0. Built source commit: f2277454b8c5356abbe311ad6dbb1dd3a3e334a5.

Runtime and platform

Node v24.22.1; macOS 27.0.1; uname -s -m returned Darwin arm64. No native analysis provider was required.

Target and artifact context

Synthetic local Demo.app directory containing a small XML plist and ordinary text marker; a sibling regular file creates the invalid intermediate path component. No real application or device was used.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Observed command statuses and selected JSON fields, with fixture paths generalized:

stat selected spelling: Not a directory; exit 1
Node stat selected spelling: ENOTDIR
invalid desktop exit: 0
valid desktop exit: 0
invalid artifact exit: 1; code target_unavailable; category unavailable
invalid desktop files equal valid desktop files: true
invalid desktop snapshot equals valid desktop snapshot: true
invalid desktop coverage.status: complete-within-scan

The desktop results' shared snapshot was 104a6a6e1f8df309b8bb82b3bea9121f0211a2a226554ba486187b2a7c2d3743; that metadata-dependent value is evidence from this replay, not an expected constant for fresh fixtures. All three CLI calls had empty stderr.

Typed outcome or recovery expected

A nonzero target_unavailable refusal for the original invalid spelling, without directory Evidence. Proposed restoration check: repeat the invalid-path failure and preserve the successful direct-directory control; no production fix was made or tested here.

Analysis or suspected cause

Source-derived: src/artifacts/DirectoryArtifactReader.ts:186-203 calls realpath on the selected spelling, then checks the returned canonical directory with lstat. Darwin accepts this canonicalization despite stat rejecting the original selection. src/artifacts/DesktopApplicationReader.ts:109-125 passes the selected path directly to that reader. The shared service's returned-root equality check retains the original text but does not establish that filesystem lookup accepts it.

Additional context

Closed issue #6757 concerns empty desktop CLI paths selecting cwd; this report uses a nonempty absolute path that passes string validation. Closed issue #5513 and merged PR #5577 classify rejected roots; this case incorrectly succeeds before that classification. No exact open duplicate was found. MCP execution and other operating systems were not tested.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7424 ## 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 Evidence, artifacts, or reconstruction ## Observed behavior On macOS, `node scripts/rea.mjs inspect-desktop-application "$WORK/regular/../Demo.app" --json` exits 0 and inventories `Demo.app`, although filesystem lookup of the selected spelling fails with `ENOTDIR`. The result retains the invalid `root.path` and reports the collapsed directory as `root.canonical_path`. Its file list and snapshot digest equal a direct inspection of the valid directory. The sibling `inspect-artifact` command rejects the same invalid spelling with `target_unavailable`. ## Reproduction From a fresh checkout at the commit below, select Node 24.22.1 and run `npm ci && npm run build:cached`. Dependencies were already installed in the verification checkout; its current-main build passed. Run the following from the checkout root on macOS, allowing the expected failing commands to finish without stopping the shell. One complete fixture replay was observed; `$WORK` replaces the disposable fixture location. ```sh WORK="$(mktemp -d)" mkdir -p "$WORK/Demo.app" "$WORK/home" "$WORK/cfg" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/tmp" cat > "$WORK/Demo.app/Info.plist" <<'PLIST' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict><key>CFBundleIdentifier</key><string>com.example.demo</string></dict></plist> PLIST printf 'marker\n' > "$WORK/Demo.app/marker.txt" printf 'regular file\n' > "$WORK/regular" export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/cfg" XDG_DATA_HOME="$WORK/data" XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/tmp" stat "$WORK/regular/../Demo.app" node scripts/rea.mjs inspect-desktop-application "$WORK/regular/../Demo.app" --json > "$WORK/invalid-desktop.json" printf 'invalid desktop exit: %s\n' "$?" node scripts/rea.mjs inspect-desktop-application "$WORK/Demo.app" --json > "$WORK/valid-desktop.json" printf 'valid desktop exit: %s\n' "$?" node scripts/rea.mjs inspect-artifact "$WORK/regular/../Demo.app" --json > "$WORK/invalid-artifact.json" printf 'invalid artifact exit: %s\n' "$?" ``` Inspect the three JSON files. Both desktop results list `Info.plist` and `marker.txt`, have equal `normalized_result.root.snapshot_sha256` values, and report `coverage.status: "complete-within-scan"`. Node's `fs/promises.stat` on the invalid spelling was also observed to throw `ENOTDIR`, while `realpath` returned the canonical `Demo.app` directory. ## Expected behavior Reject the unresolvable selection before directory inspection and return a typed unavailable-target result. `src/cli/desktopApplicationCommands.ts:25` defines the target as a local installation directory. `src/filesystem/callerPath.ts:18-25` promises to leave POSIX parent traversal to filesystem resolution; `src/artifacts/DesktopApplicationReader.ts:178-181` identifies the root as the caller's selection and preserves host target-unavailability semantics. `src/artifacts/ArtifactSelectedSource.ts:5-14` explicitly handles Darwin's `realpath` versus `stat` disagreement by checking the original spelling first. Valid selected-root symlinks remain supported by `docs/desktop-applications.md:117`. ## Impact and scope P2: this bounded input produces successful Evidence for a directory the selected spelling cannot resolve, which can mislead scripts relying on successful target admission. Passing the intended valid directory succeeds and is a practical workaround; callers can also validate the original spelling with `stat` before invocation. Only the public CLI on this macOS fixture was exercised. No target execution, modification or security boundary crossing was involved. Complete scan coverage does not mean every file's contents were read; this fixture reported zero probed files and zero bytes read. ## REA version Exact `node scripts/rea.mjs --version` output: `6.8.0`. Built source commit: `f2277454b8c5356abbe311ad6dbb1dd3a3e334a5`. ## Runtime and platform Node `v24.22.1`; macOS `27.0.1`; `uname -s -m` returned `Darwin arm64`. No native analysis provider was required. ## Target and artifact context Synthetic local `Demo.app` directory containing a small XML plist and ordinary text marker; a sibling regular file creates the invalid intermediate path component. No real application or device was used. ## Interface CLI ## Earliest failure stage Setup or provider admission ## Relevant output or evidence Observed command statuses and selected JSON fields, with fixture paths generalized: ```text stat selected spelling: Not a directory; exit 1 Node stat selected spelling: ENOTDIR invalid desktop exit: 0 valid desktop exit: 0 invalid artifact exit: 1; code target_unavailable; category unavailable invalid desktop files equal valid desktop files: true invalid desktop snapshot equals valid desktop snapshot: true invalid desktop coverage.status: complete-within-scan ``` The desktop results' shared snapshot was `104a6a6e1f8df309b8bb82b3bea9121f0211a2a226554ba486187b2a7c2d3743`; that metadata-dependent value is evidence from this replay, not an expected constant for fresh fixtures. All three CLI calls had empty stderr. ## Typed outcome or recovery expected A nonzero `target_unavailable` refusal for the original invalid spelling, without directory Evidence. Proposed restoration check: repeat the invalid-path failure and preserve the successful direct-directory control; no production fix was made or tested here. ## Analysis or suspected cause Source-derived: `src/artifacts/DirectoryArtifactReader.ts:186-203` calls `realpath` on the selected spelling, then checks the returned canonical directory with `lstat`. Darwin accepts this canonicalization despite `stat` rejecting the original selection. `src/artifacts/DesktopApplicationReader.ts:109-125` passes the selected path directly to that reader. The shared service's returned-root equality check retains the original text but does not establish that filesystem lookup accepts it. ## Additional context [Closed issue #6757](https://github.com/morluto/rea/issues/6757) concerns empty desktop CLI paths selecting cwd; this report uses a nonempty absolute path that passes string validation. [Closed issue #5513](https://github.com/morluto/rea/issues/5513) and [merged PR #5577](https://github.com/morluto/rea/pull/5577) classify rejected roots; this case incorrectly succeeds before that classification. No exact open duplicate was found. MCP execution and other operating systems were not tested.
Author
Owner

@amarthya-14 commented on GitHub (Oct 29, 2026):

Source-only finding (not a runtime replay): this looks fixed on main by 5d30f82 (fix(artifacts): validate directory source spelling), which landed the same day as this report. It changes DirectoryArtifactReader#resolveRoot from realpath(this.#rootPath) to resolveArtifactSourcePath(this.#rootPath), which runs stat on the selected spelling before realpath. That is the Darwin realpath/stat disagreement described above, so regular/../Demo.app should now fail with ENOTDIR before any inventory. I haven't re-run the macOS reproduction against a build of main, so it may be worth a quick replay before closing.

<!-- gh-comment-id:6502997411 --> @amarthya-14 commented on GitHub (Oct 29, 2026): Source-only finding (not a runtime replay): this looks fixed on `main` by 5d30f82 (`fix(artifacts): validate directory source spelling`), which landed the same day as this report. It changes `DirectoryArtifactReader#resolveRoot` from `realpath(this.#rootPath)` to `resolveArtifactSourcePath(this.#rootPath)`, which runs `stat` on the selected spelling before `realpath`. That is the Darwin `realpath`/`stat` disagreement described above, so `regular/../Demo.app` should now fail with `ENOTDIR` before any inventory. I haven't re-run the macOS reproduction against a build of `main`, so it may be worth a quick replay before closing.
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#4430
No description provided.