[GH-ISSUE #7575] [Bug] Valid backslash-named app executables fail bundle admission on macOS #4513

Closed
opened 2026-10-31 00:50:25 +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/7575

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

For a macOS application whose declared executable is literally named App\Name, node scripts/rea.mjs inspect-macho "$WORK/Back.app" --format json exits 1 with target_unavailable and reason app has an unsafe CFBundleExecutable. The file exists inside the bundle, and Apple's Foundation bundle API resolves it as that bundle's executable.

list-architectures and inspect-signature reject the same bundle. Selecting the inner executable directly succeeds, as does selecting a control bundle whose executable is named App Name. The successful executable inspections identify identical Mach-O bytes.

Reproduction

  1. From the built baseline checkout on macOS, use the pinned Node.js 24.22.1 and run this fixture and command sequence. HOME and XDG state are isolated for these invocations; the last command removes the fixtures.
WORK="$(mktemp -d)"
export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config" XDG_CACHE_HOME="$WORK/cache"
export XDG_DATA_HOME="$WORK/data" XDG_STATE_HOME="$WORK/state"
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME"
python3 - "$WORK" <<'PY'
from pathlib import Path
import shutil, sys
root = Path(sys.argv[1])
for label, name in [("Back", "App\\Name"), ("Space", "App Name")]:
    app = root / (label + ".app")
    programs = app / "Contents" / "MacOS"
    programs.mkdir(parents=True)
    (app / "Contents" / "Info.plist").write_text(
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<plist version="1.0"><dict><key>CFBundleExecutable</key>'
        '<string>' + name + '</string></dict></plist>'
    )
    executable = programs / name
    shutil.copyfile("/usr/bin/true", executable)
    executable.chmod(0o755)
PY
node scripts/rea.mjs inspect-macho "$WORK/Back.app" --format json
node scripts/rea.mjs list-architectures "$WORK/Back.app" --format json
node scripts/rea.mjs inspect-signature "$WORK/Back.app" --format json
node scripts/rea.mjs inspect-plist "$WORK/Back.app" --format json
node scripts/rea.mjs inspect-macho "$WORK/Back.app/Contents/MacOS/App\Name" --format json
node scripts/rea.mjs inspect-macho "$WORK/Space.app" --format json
rm -rf "$WORK"
  1. The first three REA commands return target_unavailable. The next three return successful plist, direct-executable, and control-bundle Evidence. The bundle inspect-macho refusal reproduced on three independently created fixtures, including this sequence; list-architectures and inspect-signature each reproduced twice.

Expected behavior

The advertised “Mach-O or app path” route should resolve this app's declared local executable and return the same inspection facts available when that executable is selected directly. The repository requires preserving caller-selected paths. The fixture uses the supported Contents/Info.plist and Contents/MacOS layout, and native Foundation recognizes its literal executable name.

No backslash-specific app-name exclusion was found in the checked documentation or related tests. The ordinary containment requirements still apply: resolving a literal backslash inside a POSIX filename must preserve the bundle boundary.

Impact and scope

P2: a bounded class of valid macOS bundles cannot be inspected through its app path. The caller receives no native inspection result for that bundle. Passing the exact inner executable path is a demonstrated workaround, with the original bytes unchanged.

The observations cover XML Info.plist files, three native inspection CLI commands, and the resource-only plist control on macOS. Binary Info.plists, other host platforms, executable symlinks and MCP were not tested. No target program was executed.

REA version

Exact node scripts/rea.mjs --version output:

6.8.0

Baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, build 26A434; arm64. The native Foundation, plutil and lipo probes ran on this host; Hopper, Ghidra and IDA were not used.

Target and artifact context

Minimal app directories with XML Info.plists and unmodified copies of the universal /usr/bin/true executable. The backslash and space controls contain executable bytes with SHA-256 511602b8cf083bdff74d43242b71c27484bd63121c07f3b59575d35676e1f6cb.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

The three bundle-path failures return these fields; path fields are omitted from this excerpt:

{
  "error": "Analysis failed",
  "code": "target_unavailable",
  "category": "unavailable",
  "message": "Cannot open artifact: app has an unsafe CFBundleExecutable. Check that the path exists, is readable, and points to a supported file.",
  "details": {"reason": "app has an unsafe CFBundleExecutable"}
}

The direct inspect-macho result has subject.name: "App\\Name" and subject.format: "mach-o-universal". The bundle inspect-plist result retains CFBundleExecutable: "App\\Name". Native plutil -extract CFBundleExecutable raw -n prints the literal declaration, and lipo -archs accepts the file. A separate fixture probed through NSBundle.bundleWithPath, objectForInfoDictionaryKey("CFBundleExecutable"), and executablePath returned the declaration and the existing executable under Contents/MacOS; REA then refused that same bundle.

Baseline source evidence:

  • src/cli/utilityCommands.ts:195-208 registers the three affected commands with “Mach-O or app path” as the argument description; AGENTS.md, Product and Authority, requires preserving caller-selected paths.
  • src/application/BinaryTargetResolver.ts:820-836 sends an app directory to executable resolution.
  • src/application/AppBundleExecutable.ts:292-295 rejects the decoded declaration before resolving the program file; isSafeExecutableName at lines 360-365 rejects both slash and backslash on every host.
  • src/application/AppBundleExecutable.ts:306-320 separately checks canonical bundle, programs-directory and executable containment.
  • tests/boundary/filesystem/binaryTarget.test.ts:600-607 tests rejection of ../escape; it does not establish a literal-backslash exclusion. docs/apple-application-analysis.md documents bundle anatomy and inventory projection without that exclusion. Its Windows dyld-component rule concerns a different operation.

Typed outcome or recovery expected

Return native inspection Evidence for the declared executable through the bundle path, preserving its literal name and canonical containment checks.

Analysis or suspected cause

Source-derived: the host-independent name predicate treats a literal POSIX backslash as disallowed alongside slash, even though the native bundle API and direct inspection accept this file. The original motivation for that predicate was not established. The failure occurs before native tool invocation.

Additional context

Closed #765 and merged #815 preserve executable whitespace during plist decoding and explicitly leave name checks unchanged; they do not claim to fix this refusal. Closed #760 addresses lipo parsing of newline filenames. Open #7556 concerns an otool operand failure for parentheses after admission. #3321/#5595 address workflow routing, and #7362/#7365 concern classification of a resolved executable's format. Searches across issue states, issue comments and PR states found no report with this bundle-declaration refusal and the same closure signal.

Prepared from independent source, CLI and native-API verification. No known-good REA revision was established, and no regression claim is made.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Three native inspection CLI routes reject a valid macOS bundle solely for its literal backslash executable name, while identical-byte controls succeed.
  • Workaround status: available
  • Workaround basis: Selecting the existing inner executable directly returned successful Mach-O Evidence with its original name and digest; no renaming or source change was needed.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7575 ### 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 For a macOS application whose declared executable is literally named `App\Name`, `node scripts/rea.mjs inspect-macho "$WORK/Back.app" --format json` exits 1 with `target_unavailable` and reason `app has an unsafe CFBundleExecutable`. The file exists inside the bundle, and Apple's Foundation bundle API resolves it as that bundle's executable. `list-architectures` and `inspect-signature` reject the same bundle. Selecting the inner executable directly succeeds, as does selecting a control bundle whose executable is named `App Name`. The successful executable inspections identify identical Mach-O bytes. ### Reproduction 1. From the built baseline checkout on macOS, use the pinned Node.js 24.22.1 and run this fixture and command sequence. HOME and XDG state are isolated for these invocations; the last command removes the fixtures. ```sh WORK="$(mktemp -d)" export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config" XDG_CACHE_HOME="$WORK/cache" export XDG_DATA_HOME="$WORK/data" XDG_STATE_HOME="$WORK/state" mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" python3 - "$WORK" <<'PY' from pathlib import Path import shutil, sys root = Path(sys.argv[1]) for label, name in [("Back", "App\\Name"), ("Space", "App Name")]: app = root / (label + ".app") programs = app / "Contents" / "MacOS" programs.mkdir(parents=True) (app / "Contents" / "Info.plist").write_text( '<?xml version="1.0" encoding="UTF-8"?>' '<plist version="1.0"><dict><key>CFBundleExecutable</key>' '<string>' + name + '</string></dict></plist>' ) executable = programs / name shutil.copyfile("/usr/bin/true", executable) executable.chmod(0o755) PY node scripts/rea.mjs inspect-macho "$WORK/Back.app" --format json node scripts/rea.mjs list-architectures "$WORK/Back.app" --format json node scripts/rea.mjs inspect-signature "$WORK/Back.app" --format json node scripts/rea.mjs inspect-plist "$WORK/Back.app" --format json node scripts/rea.mjs inspect-macho "$WORK/Back.app/Contents/MacOS/App\Name" --format json node scripts/rea.mjs inspect-macho "$WORK/Space.app" --format json rm -rf "$WORK" ``` 2. The first three REA commands return `target_unavailable`. The next three return successful plist, direct-executable, and control-bundle Evidence. The bundle `inspect-macho` refusal reproduced on three independently created fixtures, including this sequence; `list-architectures` and `inspect-signature` each reproduced twice. ### Expected behavior The advertised “Mach-O or app path” route should resolve this app's declared local executable and return the same inspection facts available when that executable is selected directly. The repository requires preserving caller-selected paths. The fixture uses the supported `Contents/Info.plist` and `Contents/MacOS` layout, and native Foundation recognizes its literal executable name. No backslash-specific app-name exclusion was found in the checked documentation or related tests. The ordinary containment requirements still apply: resolving a literal backslash inside a POSIX filename must preserve the bundle boundary. ### Impact and scope P2: a bounded class of valid macOS bundles cannot be inspected through its app path. The caller receives no native inspection result for that bundle. Passing the exact inner executable path is a demonstrated workaround, with the original bytes unchanged. The observations cover XML Info.plist files, three native inspection CLI commands, and the resource-only plist control on macOS. Binary Info.plists, other host platforms, executable symlinks and MCP were not tested. No target program was executed. ### REA version Exact `node scripts/rea.mjs --version` output: ```text 6.8.0 ``` Baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js `v24.22.1`; macOS `27.0.1`, build `26A434`; arm64. The native Foundation, plutil and lipo probes ran on this host; Hopper, Ghidra and IDA were not used. ### Target and artifact context Minimal app directories with XML Info.plists and unmodified copies of the universal `/usr/bin/true` executable. The backslash and space controls contain executable bytes with SHA-256 `511602b8cf083bdff74d43242b71c27484bd63121c07f3b59575d35676e1f6cb`. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence The three bundle-path failures return these fields; path fields are omitted from this excerpt: ```json { "error": "Analysis failed", "code": "target_unavailable", "category": "unavailable", "message": "Cannot open artifact: app has an unsafe CFBundleExecutable. Check that the path exists, is readable, and points to a supported file.", "details": {"reason": "app has an unsafe CFBundleExecutable"} } ``` The direct `inspect-macho` result has `subject.name: "App\\Name"` and `subject.format: "mach-o-universal"`. The bundle `inspect-plist` result retains `CFBundleExecutable: "App\\Name"`. Native `plutil -extract CFBundleExecutable raw -n` prints the literal declaration, and `lipo -archs` accepts the file. A separate fixture probed through `NSBundle.bundleWithPath`, `objectForInfoDictionaryKey("CFBundleExecutable")`, and `executablePath` returned the declaration and the existing executable under `Contents/MacOS`; REA then refused that same bundle. Baseline source evidence: - `src/cli/utilityCommands.ts:195-208` registers the three affected commands with “Mach-O or app path” as the argument description; `AGENTS.md`, Product and Authority, requires preserving caller-selected paths. - `src/application/BinaryTargetResolver.ts:820-836` sends an app directory to executable resolution. - `src/application/AppBundleExecutable.ts:292-295` rejects the decoded declaration before resolving the program file; `isSafeExecutableName` at lines 360-365 rejects both slash and backslash on every host. - `src/application/AppBundleExecutable.ts:306-320` separately checks canonical bundle, programs-directory and executable containment. - `tests/boundary/filesystem/binaryTarget.test.ts:600-607` tests rejection of `../escape`; it does not establish a literal-backslash exclusion. `docs/apple-application-analysis.md` documents bundle anatomy and inventory projection without that exclusion. Its Windows dyld-component rule concerns a different operation. ### Typed outcome or recovery expected Return native inspection Evidence for the declared executable through the bundle path, preserving its literal name and canonical containment checks. ### Analysis or suspected cause Source-derived: the host-independent name predicate treats a literal POSIX backslash as disallowed alongside slash, even though the native bundle API and direct inspection accept this file. The original motivation for that predicate was not established. The failure occurs before native tool invocation. ### Additional context Closed #765 and merged #815 preserve executable whitespace during plist decoding and explicitly leave name checks unchanged; they do not claim to fix this refusal. Closed #760 addresses lipo parsing of newline filenames. Open #7556 concerns an otool operand failure for parentheses after admission. #3321/#5595 address workflow routing, and #7362/#7365 concern classification of a resolved executable's format. Searches across issue states, issue comments and PR states found no report with this bundle-declaration refusal and the same closure signal. Prepared from independent source, CLI and native-API verification. No known-good REA revision was established, and no regression claim is made. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Three native inspection CLI routes reject a valid macOS bundle solely for its literal backslash executable name, while identical-byte controls succeed. - Workaround status: available - Workaround basis: Selecting the existing inner executable directly returned successful Mach-O Evidence with its original name and digest; no renaming or source change was needed.
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#4513
No description provided.