[GH-ISSUE #5462] [Bug] Native inspection admits universal static archives as Mach-O images #3320

Closed
opened 2026-10-31 00:39:08 +00:00 by bsakaguchi · 2 comments
Owner

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

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 universal static archive containing x86_64 and arm64 ar slices, inspect-macho returns execution_failure with rea doctor remediation. The same input succeeds in inspect-signature as an unsigned mach-o-universal snapshot and in list-architectures as an exhaustive Mach-O slice inventory without limitations. The corresponding thin archive is rejected as an unsupported format.

An independent verifier ran each command once through the built CLI. This report was prepared with AI assistance from that independently reproduced result. No target execution or deep-analysis provider was involved.

Reproduction

From the baseline source checkout with dependencies and the built CLI available, on macOS with Apple clang and the native command-line tools, the verifier compiled and archived this minimal source for both architectures, then combined the archives:

work="$PWD/work-universal-archive"
mkdir -p "$work"/{home,config,data,cache,state,runtime,out}
printf '{}\n' > "$work/config/ida.json"
printf 'int main(void) { return 0; }\n' > "$work/hello.c"
cc -arch arm64 -c "$work/hello.c" -o "$work/hello-arm64.o"
cc -arch x86_64 -c "$work/hello.c" -o "$work/hello-x86_64.o"
ar rcs "$work/libarm64.a" "$work/hello-arm64.o"
ar rcs "$work/libx86_64.a" "$work/hello-x86_64.o"
lipo -create -output "$work/libfat.a" "$work/libarm64.a" "$work/libx86_64.a"
file -b "$work/libfat.a"
lipo -archs "$work/libfat.a"
rea_probe() {
  env HOME="$work/home" XDG_CONFIG_HOME="$work/config" \
    XDG_DATA_HOME="$work/data" XDG_CACHE_HOME="$work/cache" \
    XDG_STATE_HOME="$work/state" TMPDIR="$work/runtime" \
    REA_IDA_MCP_CONFIG="$work/config/ida.json" REA_LOG_LEVEL=silent \
    node scripts/rea.mjs "$@"
}
rea_probe inspect-macho "$work/libfat.a" --json > "$work/out/fat-inspect.json"
rea_probe inspect-macho "$work/libarm64.a" --json > "$work/out/thin-inspect.json"
rea_probe inspect-signature "$work/libfat.a" --json > "$work/out/fat-signature.json"
rea_probe list-architectures "$work/libfat.a" --json > "$work/out/fat-architectures.json"

Paths above use a neutral disposable directory name. The actual run isolated HOME, all four XDG roots, TMPDIR and the IDA configuration. Preparation commands exited 0. The four REA calls exited 1, 1, 0 and 0 respectively. Inspect the retained JSON even when a call exits 1.

file identified the universal container as containing two current ar archive slices; lipo -archs printed x86_64 arm64. As controls, the verifier linked genuine arm64 and x86_64 Mach-O executables from the same source and combined them with lipo. inspect-macho succeeded for that universal executable and for the original thin arm64 object member.

Expected behavior

Refuse unsupported archive input with a format or non-Mach-O applicability result, consistent with the thin-archive refusal. Do not describe archive slices as an inspected Mach-O snapshot or derive image-signature conclusions from that assumption. Genuine universal Mach-O images should remain inspectable.

Source contract: src/contracts/native/nativeToolContracts.ts:95, :107 and :119 advertise Mach-O image, code-signature and slice inspection. docs/native-investigation.md:311 distinguishes a FAT container from its selected Mach-O image. These contracts do not advertise archive analysis.

Impact and scope

P2, bounded format admission and misleading results or recovery guidance for the tested unsupported input class. Each relevant command was run once. Architecture names and byte ranges are valid FAT-container facts; the incorrect claim is their presentation under the Mach-O-only contract without identifying the archive slice kind.

Selecting the original thin Mach-O object member worked for image metadata. Thin archives remain unsupported; this workaround does not provide full archive analysis. No data loss, code execution, signature trust bypass or security boundary crossing was observed. Other archive layouts and MCP were not replayed.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Apple clang 21.0.0; native ar, lipo, vtool and codesign. Hopper/Ghidra were not used.

Target and artifact context

A synthetic universal static archive with x86_64 and arm64 ar slices, each containing a Mach-O object compiled from int main(void) { return 0; }. Controls were a thin archive, its thin arm64 Mach-O object member, and a genuine universal Mach-O executable.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Selected fields from the verifier's retained CLI output, with path-bearing details omitted:

inspect-macho libfat.a: exit 1
  code: execution_failure
  diagnostics: tool=vtool, reason=nonzero-exit, exit_code=1
  stderr: file #0 for cputype (16777223, 3) is not mach-o
  remediation: Run `rea doctor`; repeating the unchanged request is not expected to succeed.

inspect-macho libarm64.a: exit 1
  code: target_unavailable
  reason: unsupported binary format
  constraint: file_format_rejected

inspect-signature libfat.a: exit 0
  subject.format: mach-o-universal
  normalized_result.signed: false
  normalized_result.format: null
  normalized_result.cdhashes: []
  limitations:
    Artifact is not signed.
    Inspected a digest-verified Mach-O byte snapshot; filesystem-bound signing context and trust validation are not examined.

list-architectures libfat.a: exit 0
  x86_64: file_offset=48, size=808
  arm64: file_offset=856, size=720
  architectures.exhaustive: true
  limitations: []

The genuine universal-image control returned populated image metadata with arm64 selected at container offset 16384. The thin arm64 object control also succeeded.

The submitter fetched upstream main at 052956af3416fa01d93837e54fdec3ad2739f6d1. No commits since the reproduced baseline changed the cited admission, inspection or contract files. This is a source comparison, not a runtime replay on that newer revision.

Typed outcome or recovery expected

Return an unsupported-format or non-Mach-O applicability result for the universal archive rather than a native-tool execution failure with doctor remediation. Signature inspection should not claim an evaluated Mach-O signature layer, and architecture inspection should not present archive ranges as successfully inspected Mach-O slices. Preserve the thin-archive refusal and genuine universal-image and thin-object successes. These are proposed restoration checks, not verification of a fix.

Analysis or suspected cause

Source-derived reasoning at the baseline: src/domain/apple/machoContainer.ts:125 accepts a known CPU declaration with valid ranges without checking slice magic. src/domain/binaryTarget.ts:141 and :167 admit and label the container mach-o-universal. src/native/NativeMacOSProvider.ts:511 and :586 then trust that format for architecture and signature inspection. src/native/NativeMachoInspection.ts:127 propagates vtool's failure. This explanation is separate from the observed CLI outcomes above.

Additional context

Related but different: #5404 rejects a genuine universal Mach-O without a host-compatible slice. This fixture contains both host-compatible and x86_64 slices, but each slice is an ar archive. The JVM-class identity reports #2317 and #3176 and PRs #2327 and #3305 concern class-file lookalikes rather than valid universal archive containers. No last-known-good release was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5462 ### 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 universal static archive containing x86_64 and arm64 ar slices, `inspect-macho` returns `execution_failure` with `rea doctor` remediation. The same input succeeds in `inspect-signature` as an unsigned `mach-o-universal` snapshot and in `list-architectures` as an exhaustive Mach-O slice inventory without limitations. The corresponding thin archive is rejected as an unsupported format. An independent verifier ran each command once through the built CLI. This report was prepared with AI assistance from that independently reproduced result. No target execution or deep-analysis provider was involved. ### Reproduction From the baseline source checkout with dependencies and the built CLI available, on macOS with Apple clang and the native command-line tools, the verifier compiled and archived this minimal source for both architectures, then combined the archives: ```sh work="$PWD/work-universal-archive" mkdir -p "$work"/{home,config,data,cache,state,runtime,out} printf '{}\n' > "$work/config/ida.json" printf 'int main(void) { return 0; }\n' > "$work/hello.c" cc -arch arm64 -c "$work/hello.c" -o "$work/hello-arm64.o" cc -arch x86_64 -c "$work/hello.c" -o "$work/hello-x86_64.o" ar rcs "$work/libarm64.a" "$work/hello-arm64.o" ar rcs "$work/libx86_64.a" "$work/hello-x86_64.o" lipo -create -output "$work/libfat.a" "$work/libarm64.a" "$work/libx86_64.a" file -b "$work/libfat.a" lipo -archs "$work/libfat.a" rea_probe() { env HOME="$work/home" XDG_CONFIG_HOME="$work/config" \ XDG_DATA_HOME="$work/data" XDG_CACHE_HOME="$work/cache" \ XDG_STATE_HOME="$work/state" TMPDIR="$work/runtime" \ REA_IDA_MCP_CONFIG="$work/config/ida.json" REA_LOG_LEVEL=silent \ node scripts/rea.mjs "$@" } rea_probe inspect-macho "$work/libfat.a" --json > "$work/out/fat-inspect.json" rea_probe inspect-macho "$work/libarm64.a" --json > "$work/out/thin-inspect.json" rea_probe inspect-signature "$work/libfat.a" --json > "$work/out/fat-signature.json" rea_probe list-architectures "$work/libfat.a" --json > "$work/out/fat-architectures.json" ``` Paths above use a neutral disposable directory name. The actual run isolated HOME, all four XDG roots, TMPDIR and the IDA configuration. Preparation commands exited 0. The four REA calls exited 1, 1, 0 and 0 respectively. Inspect the retained JSON even when a call exits 1. `file` identified the universal container as containing two current ar archive slices; `lipo -archs` printed `x86_64 arm64`. As controls, the verifier linked genuine arm64 and x86_64 Mach-O executables from the same source and combined them with `lipo`. `inspect-macho` succeeded for that universal executable and for the original thin arm64 object member. ### Expected behavior Refuse unsupported archive input with a format or non-Mach-O applicability result, consistent with the thin-archive refusal. Do not describe archive slices as an inspected Mach-O snapshot or derive image-signature conclusions from that assumption. Genuine universal Mach-O images should remain inspectable. Source contract: `src/contracts/native/nativeToolContracts.ts:95`, `:107` and `:119` advertise Mach-O image, code-signature and slice inspection. `docs/native-investigation.md:311` distinguishes a FAT container from its selected Mach-O image. These contracts do not advertise archive analysis. ### Impact and scope P2, bounded format admission and misleading results or recovery guidance for the tested unsupported input class. Each relevant command was run once. Architecture names and byte ranges are valid FAT-container facts; the incorrect claim is their presentation under the Mach-O-only contract without identifying the archive slice kind. Selecting the original thin Mach-O object member worked for image metadata. Thin archives remain unsupported; this workaround does not provide full archive analysis. No data loss, code execution, signature trust bypass or security boundary crossing was observed. Other archive layouts and MCP were not replayed. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Apple clang 21.0.0; native ar, lipo, vtool and codesign. Hopper/Ghidra were not used. ### Target and artifact context A synthetic universal static archive with x86_64 and arm64 ar slices, each containing a Mach-O object compiled from `int main(void) { return 0; }`. Controls were a thin archive, its thin arm64 Mach-O object member, and a genuine universal Mach-O executable. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Selected fields from the verifier's retained CLI output, with path-bearing details omitted: ```text inspect-macho libfat.a: exit 1 code: execution_failure diagnostics: tool=vtool, reason=nonzero-exit, exit_code=1 stderr: file #0 for cputype (16777223, 3) is not mach-o remediation: Run `rea doctor`; repeating the unchanged request is not expected to succeed. inspect-macho libarm64.a: exit 1 code: target_unavailable reason: unsupported binary format constraint: file_format_rejected inspect-signature libfat.a: exit 0 subject.format: mach-o-universal normalized_result.signed: false normalized_result.format: null normalized_result.cdhashes: [] limitations: Artifact is not signed. Inspected a digest-verified Mach-O byte snapshot; filesystem-bound signing context and trust validation are not examined. list-architectures libfat.a: exit 0 x86_64: file_offset=48, size=808 arm64: file_offset=856, size=720 architectures.exhaustive: true limitations: [] ``` The genuine universal-image control returned populated image metadata with arm64 selected at container offset 16384. The thin arm64 object control also succeeded. The submitter fetched upstream main at `052956af3416fa01d93837e54fdec3ad2739f6d1`. No commits since the reproduced baseline changed the cited admission, inspection or contract files. This is a source comparison, not a runtime replay on that newer revision. ### Typed outcome or recovery expected Return an unsupported-format or non-Mach-O applicability result for the universal archive rather than a native-tool execution failure with doctor remediation. Signature inspection should not claim an evaluated Mach-O signature layer, and architecture inspection should not present archive ranges as successfully inspected Mach-O slices. Preserve the thin-archive refusal and genuine universal-image and thin-object successes. These are proposed restoration checks, not verification of a fix. ### Analysis or suspected cause Source-derived reasoning at the baseline: `src/domain/apple/machoContainer.ts:125` accepts a known CPU declaration with valid ranges without checking slice magic. `src/domain/binaryTarget.ts:141` and `:167` admit and label the container `mach-o-universal`. `src/native/NativeMacOSProvider.ts:511` and `:586` then trust that format for architecture and signature inspection. `src/native/NativeMachoInspection.ts:127` propagates vtool's failure. This explanation is separate from the observed CLI outcomes above. ### Additional context Related but different: #5404 rejects a genuine universal Mach-O without a host-compatible slice. This fixture contains both host-compatible and x86_64 slices, but each slice is an ar archive. The JVM-class identity reports #2317 and #3176 and PRs #2327 and #3305 concern class-file lookalikes rather than valid universal archive containers. No last-known-good release was established.
Author
Owner

@CassieFox23 commented on GitHub (Oct 20, 2026):

TL;DR: This looks fixed on main. A universal static archive is now refused with a clear format reason. Closing is the maintainers' call.

I checked main 1629266e3 (Linux, Node 24.18.0). The fix is 497037e6e.

I built a universal file with two ar archive slices, one x86_64 and one arm64. inspect-macho, list-architectures and inspect-signature all exit 1. All three say "FAT table does not identify a Mach-O image" with file_format_rejected. The rea doctor advice is gone. A universal file of two thin Mach-O executables passes this gate. Then it stops, because the Mach-O provider needs macOS.

Not run

  • inspect-macho on macOS, so no real universal executable was inspected end to end.
  • Archives made with a real compiler and lipo.

Thanks, coygeek, for the clear report.

Commands
rea inspect-macho fat-archive.a --json
rea list-architectures fat-archive.a --json
rea inspect-signature fat-archive.a --json

All exit 1: {"error":"Analysis failed","code":"target_unavailable","category":"unsupported_target","message":"Cannot open artifact: FAT table does not identify a Mach-O image.","details":{"reason":"FAT table does not identify a Mach-O image","constraint":"file_format_rejected"}}

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.11
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Head: 1629266e3

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6299402367 --> @CassieFox23 commented on GitHub (Oct 20, 2026): **TL;DR:** This looks fixed on main. A universal static archive is now refused with a clear format reason. Closing is the maintainers' call. I checked main 1629266e3 (Linux, Node 24.18.0). The fix is 497037e6e. I built a universal file with two `ar` archive slices, one x86_64 and one arm64. `inspect-macho`, `list-architectures` and `inspect-signature` all exit 1. All three say "FAT table does not identify a Mach-O image" with `file_format_rejected`. The `rea doctor` advice is gone. A universal file of two thin Mach-O executables passes this gate. Then it stops, because the Mach-O provider needs macOS. ### Not run - `inspect-macho` on macOS, so no real universal executable was inspected end to end. - Archives made with a real compiler and `lipo`. Thanks, coygeek, for the clear report. <details> <summary>Commands</summary> ```sh rea inspect-macho fat-archive.a --json rea list-architectures fat-archive.a --json rea inspect-signature fat-archive.a --json ``` All exit 1: `{"error":"Analysis failed","code":"target_unavailable","category":"unsupported_target","message":"Cannot open artifact: FAT table does not identify a Mach-O image.","details":{"reason":"FAT table does not identify a Mach-O image","constraint":"file_format_rejected"}}` </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.11 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Head: 1629266e3 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

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

Closing as fixed on main by github.com/morluto/rea@497037e6e3, retained at 67a6477f01.

FAT target admission now validates embedded slice format and rejects archive payloads before publishing a Mach-O target. Regressions include archive-only and mixed Mach-O/archive FAT inputs plus genuine universal Mach-O controls. No new lipo or macOS provider qualification was run.

This closure follows current-main producer and regression source inspection and the existing issue evidence. Tests and runtime reproductions were not rerun in this audit; no additional platform/provider coverage is implied.

<!-- gh-comment-id:6324280455 --> @morluto commented on GitHub (Oct 21, 2026): Closing as fixed on main by https://github.com/morluto/rea/commit/497037e6e3975cbb37cfd89ea0ad300faa2a4769, retained at 67a6477f011ae358fc333e5a6e26c86d932654ed. FAT target admission now validates embedded slice format and rejects archive payloads before publishing a Mach-O target. Regressions include archive-only and mixed Mach-O/archive FAT inputs plus genuine universal Mach-O controls. No new lipo or macOS provider qualification was run. This closure follows current-main producer and regression source inspection and the existing issue evidence. Tests and runtime reproductions were not rerun in this audit; no additional platform/provider coverage is implied.
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#3320
No description provided.