[PR #5788] [MERGED] fix(native): admit thin arm64_32 Mach-O targets for static inspection #7061

Closed
opened 2026-11-01 21:45:04 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5788
Author: @Lew1sWong
Created: 10/21/2026
Status: ✅ Merged
Merged: 10/22/2026
Merged by: @morluto

Base: main ← Head: fix/thin-arm64-32-macho-admission


📝 Commits (2)

  • a73ae99 fix(native): admit thin arm64_32 Mach-O targets for static inspection
  • b011081 ci(apple): run the thin arm64_32 CLI regression on macOS

📊 Changes

15 files changed (+213 additions, -20 deletions)

View changed files

📝 .github/workflows/ci.yml (+1 -0)
📝 scripts/ci/scopes.mjs (+1 -0)
📝 src/domain/binaryTarget.test.ts (+1 -0)
📝 src/domain/binaryTarget.ts (+19 -1)
📝 src/domain/binaryTargetTypes.ts (+11 -1)
📝 src/domain/elfFixedHeader.ts (+8 -5)
📝 src/ghidra/GhidraProvider.behavior.test.ts (+19 -1)
📝 src/hopper/HopperAnalysisProfile.ts (+19 -3)
📝 src/hopper/HopperProvider.test.ts (+19 -0)
📝 src/hopper/HopperProvider.ts (+3 -8)
📝 src/ida/IdaProvider.test.ts (+19 -0)
📝 src/ida/IdaProvider.ts (+7 -0)
📝 src/native/MachoArchitectureSelection.ts (+4 -0)
➕ tests/boundary/cli/nativeMachoArm64_32Cli.test.ts (+77 -0)
📝 tests/fixtures/nativeCommands.ts (+5 -1)

📄 Description

Summary

Fixes #5713. A thin arm64_32 Mach-O is now admitted as a target, so list-architectures, inspect-macho and inspect-signature inspect it with Apple's native tools instead of refusing it at target admission.

Problem and expected behavior

parseThinMachO mapped Mach-O CPU types only to x86, x86_64, arm and arm64. CPU_TYPE_ARM64_32 (0x0200000c) fell through to err("unsupported Mach-O architecture"), so all three commands failed with target_unavailable / unsupported_target before any tool ran. The FAT control already showed the inconsistency: the same slice inside a universal file was inventoried by lipo.

Expected, per the list_architectures contract and the issue: the thin object returns ordinary thin-file Evidence. Thin rows keep the existing convention of null CPU code, offset, size and alignment.

Change and scope

  • Domain. arm64_32 joins BINARY_ARCHITECTURES as a Mach-O-only architecture. This follows the Hexagon precedent from #4819: it is excluded from HeaderArchitecture and gets its own thin mach-o variants in ExecutableMetadata and BinaryTarget. As a result, the ELF, PE, raw-binary and universal paths cannot carry it, and the ELF fixed-header type now says so explicitly.

  • Native slice policy. MACHO_ARCHITECTURE_POLICIES gains arm64_32. Without it, the newly admitted target reached the 32-bit dispatch-metadata reader and failed as execution_failure: "Mach-O CPU type does not match requested architecture arm64_32". With it, that reader raises its existing unsupported-32-bit error, like other 32-bit Mach-O images.

  • Deep-analysis providers. Ghidra, Hopper and IDA have no verified arm64_32 lane, so they decline the target with architecture_unsupported:

    • Ghidra, through its existing supported-architecture set;
    • Hopper, through one isHopperDeclinedArchitecture predicate that now replaces the two duplicated checks;
    • IDA, through an explicit refusal.

    Kuna already declines it through its verified-architecture set.

Not included:

  • FAT files. Universal availableArchitectures still lists only the four provider-neutral families, so arm64_32 slices in FAT files remain visible only through lipo inventory, as before.

  • Duplicated CPU-type maps. These existed before this PR. This PR updates the two that arm64_32 targets reach: machArchitecture admission and the native slice policy. It leaves two unchanged:

    • machoTargetSelectionMatchesArchitecture, which only deep-analysis profiles reach;
    • Hopper's slice CPU map.

    Consolidating them is a separate refactor.

Contribution scope: ordinary fix within existing boundaries; maintainer scope decision not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: target admission, thin Mach-O header parsing in src/domain/binaryTarget.ts.

  • Contribution scope fit and linked maintainer decision: not applicable (bug fix).

  • CLI and MCP/tool-catalog contract: additive. Schemas that enumerate BINARY_ARCHITECTURES gain arm64_32, for example Evidence subject.architecture. The value appears only for targets that were previously rejected.

  • Provider, bridge, target-format, or platform compatibility: native macOS tools inspect thin arm64_32 targets. Ghidra, Hopper, IDA and Kuna decline them.

  • Evidence, artifact, provenance, or reconstruction contract: thin arm64_32 Evidence carries format: "mach-o" and architecture: "arm64_32" with native-tool provenance.

  • Target selection, authority, upstream behavior, and cleanup details: thin targets stay unfiltered, so no -arch is passed. FAT selection is unchanged.

  • Generated metadata, package, or installation impact: none committed. npm run docs:check passes.

  • Read existing files or captured data.

Evidence and regression coverage

  • Tests added or updated:

    • tests/boundary/cli/nativeMachoArm64_32Cli.test.ts (macOS only): compiles a header-free object with xcrun clang -arch arm64_32 -c and checks the public CLI. It is listed in the macOS apple-artifacts job and in SCOPES.apple, so CI runs it.
      • All three commands exit 0 with subject.architecture: "arm64_32".
      • list-architectures lists exactly arm64_32.
      • inspect-native-dispatch-metadata returns the typed refusal capability_unavailable / target_unsupported, retryable: false.
    • Parser row in binaryTarget.test.ts, plus refusal tests for Ghidra, Hopper and IDA.
    • tests/fixtures/nativeCommands.ts: the arm64 fixture helper's return type is narrowed to what it builds.
  • Base reproduction:

    • The issue's nine-command probe on 7d8e4e433 (macOS arm64, Apple clang): all three thin arm64_32 commands exit 1 with unsupported Mach-O architecture; the arm64 and FAT controls exit 0. After the change, all nine exit 0.
    • The new CLI test fails on the unmodified source with that same target_unavailable error.
    • With only the slice policy removed, it fails on the execution_failure CPU-mismatch error described above.
  • User-visible CLI output (list-architectures arm64_32.o --json, abridged):

    "subject": { "format": "mach-o", "architecture": "arm64_32" },
    "normalized_result": { "architectures": { "items": [
      { "name": "arm64_32", "cpu_type": null, "cpu_subtype": null,
        "file_offset": null, "size": null, "alignment": null } ],
      "total": 1, "exhaustive": true, "limitations": [] } }
    
  • Remaining proof gaps:

    • No linked watchOS executable was tested; only a compiler-produced object was used.
    • Provider refusals are verified with unit tests only; no real Ghidra, Hopper or IDA run.
    • MCP was not exercised directly. From the source, it uses the same target admission as the CLI.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

Node 24.18.0, macOS 27.0 arm64, Apple CommandLineTools clang, lipo, otool and codesign. Head b0110810a on 7501911f1.

  • npm run check:fast — passed (typecheck, lint 0 errors)
  • npm run knip — passed
  • npm run docs:check — passed
  • npm run test:focused -- on every test under src/domain, src/hopper, src/ida, src/ghidra, src/native and tests/boundary/providers/native, src/application/binaryDiagnostics, both native Mach-O CLI tests, and the four CI planning tests — 454 files, 9072 tests passed, 2 skipped
  • scripts/ci/plan.mjs, simulated as a pull_request from 7501911f1 to this head — apple=true
  • Full npm run check:pr — not run

Compatibility, safety, and release

  • Breaking changes or migration steps: none. The new enum value appears only for targets that were rejected before.
  • Real Hopper/Ghidra, browser, or OS coverage: real macOS Apple tools only; no deep-analysis provider installed.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: static file inspection only. No target execution.

Author readiness

Human owner: @Lew1sWong

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5788 **Author:** [@Lew1sWong](https://github.com/Lew1sWong) **Created:** 10/21/2026 **Status:** ✅ Merged **Merged:** 10/22/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/thin-arm64-32-macho-admission` --- ### 📝 Commits (2) - [`a73ae99`](https://github.com/morluto/rea/commit/a73ae99dee289aadc64ddbe1215e439868068db5) fix(native): admit thin arm64_32 Mach-O targets for static inspection - [`b011081`](https://github.com/morluto/rea/commit/b0110810ae4f775a31fc3d873d8c593819379162) ci(apple): run the thin arm64_32 CLI regression on macOS ### 📊 Changes **15 files changed** (+213 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflows/ci.yml` (+1 -0) 📝 `scripts/ci/scopes.mjs` (+1 -0) 📝 `src/domain/binaryTarget.test.ts` (+1 -0) 📝 `src/domain/binaryTarget.ts` (+19 -1) 📝 `src/domain/binaryTargetTypes.ts` (+11 -1) 📝 `src/domain/elfFixedHeader.ts` (+8 -5) 📝 `src/ghidra/GhidraProvider.behavior.test.ts` (+19 -1) 📝 `src/hopper/HopperAnalysisProfile.ts` (+19 -3) 📝 `src/hopper/HopperProvider.test.ts` (+19 -0) 📝 `src/hopper/HopperProvider.ts` (+3 -8) 📝 `src/ida/IdaProvider.test.ts` (+19 -0) 📝 `src/ida/IdaProvider.ts` (+7 -0) 📝 `src/native/MachoArchitectureSelection.ts` (+4 -0) ➕ `tests/boundary/cli/nativeMachoArm64_32Cli.test.ts` (+77 -0) 📝 `tests/fixtures/nativeCommands.ts` (+5 -1) </details> ### 📄 Description ## Summary Fixes #5713. A thin `arm64_32` Mach-O is now admitted as a target, so `list-architectures`, `inspect-macho` and `inspect-signature` inspect it with Apple's native tools instead of refusing it at target admission. ## Problem and expected behavior `parseThinMachO` mapped Mach-O CPU types only to `x86`, `x86_64`, `arm` and `arm64`. `CPU_TYPE_ARM64_32` (`0x0200000c`) fell through to `err("unsupported Mach-O architecture")`, so all three commands failed with `target_unavailable` / `unsupported_target` before any tool ran. The FAT control already showed the inconsistency: the same slice inside a universal file was inventoried by `lipo`. Expected, per the `list_architectures` contract and the issue: the thin object returns ordinary thin-file Evidence. Thin rows keep the existing convention of null CPU code, offset, size and alignment. ## Change and scope - **Domain.** `arm64_32` joins `BINARY_ARCHITECTURES` as a Mach-O-only architecture. This follows the Hexagon precedent from #4819: it is excluded from `HeaderArchitecture` and gets its own thin `mach-o` variants in `ExecutableMetadata` and `BinaryTarget`. As a result, the ELF, PE, raw-binary and universal paths cannot carry it, and the ELF fixed-header type now says so explicitly. - **Native slice policy.** `MACHO_ARCHITECTURE_POLICIES` gains `arm64_32`. Without it, the newly admitted target reached the 32-bit dispatch-metadata reader and failed as `execution_failure`: "Mach-O CPU type does not match requested architecture arm64_32". With it, that reader raises its existing unsupported-32-bit error, like other 32-bit Mach-O images. - **Deep-analysis providers.** Ghidra, Hopper and IDA have no verified `arm64_32` lane, so they decline the target with `architecture_unsupported`: - Ghidra, through its existing supported-architecture set; - Hopper, through one `isHopperDeclinedArchitecture` predicate that now replaces the two duplicated checks; - IDA, through an explicit refusal. Kuna already declines it through its verified-architecture set. Not included: - **FAT files.** Universal `availableArchitectures` still lists only the four provider-neutral families, so `arm64_32` slices in FAT files remain visible only through `lipo` inventory, as before. - **Duplicated CPU-type maps.** These existed before this PR. This PR updates the two that `arm64_32` targets reach: `machArchitecture` admission and the native slice policy. It leaves two unchanged: - `machoTargetSelectionMatchesArchitecture`, which only deep-analysis profiles reach; - Hopper's slice CPU map. Consolidating them is a separate refactor. Contribution scope: ordinary fix within existing boundaries; maintainer scope decision not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: target admission, thin Mach-O header parsing in `src/domain/binaryTarget.ts`. - Contribution scope fit and linked maintainer decision: not applicable (bug fix). - CLI and MCP/tool-catalog contract: additive. Schemas that enumerate `BINARY_ARCHITECTURES` gain `arm64_32`, for example Evidence `subject.architecture`. The value appears only for targets that were previously rejected. - Provider, bridge, target-format, or platform compatibility: native macOS tools inspect thin `arm64_32` targets. Ghidra, Hopper, IDA and Kuna decline them. - Evidence, artifact, provenance, or reconstruction contract: thin `arm64_32` Evidence carries `format: "mach-o"` and `architecture: "arm64_32"` with native-tool provenance. - Target selection, authority, upstream behavior, and cleanup details: thin targets stay unfiltered, so no `-arch` is passed. FAT selection is unchanged. - Generated metadata, package, or installation impact: none committed. `npm run docs:check` passes. - [x] Read existing files or captured data. ## Evidence and regression coverage - Tests added or updated: - `tests/boundary/cli/nativeMachoArm64_32Cli.test.ts` (macOS only): compiles a header-free object with `xcrun clang -arch arm64_32 -c` and checks the public CLI. It is listed in the macOS `apple-artifacts` job and in `SCOPES.apple`, so CI runs it. - All three commands exit 0 with `subject.architecture: "arm64_32"`. - `list-architectures` lists exactly `arm64_32`. - `inspect-native-dispatch-metadata` returns the typed refusal `capability_unavailable` / `target_unsupported`, `retryable: false`. - Parser row in `binaryTarget.test.ts`, plus refusal tests for Ghidra, Hopper and IDA. - `tests/fixtures/nativeCommands.ts`: the arm64 fixture helper's return type is narrowed to what it builds. - Base reproduction: - The issue's nine-command probe on `7d8e4e433` (macOS arm64, Apple clang): all three thin `arm64_32` commands exit 1 with `unsupported Mach-O architecture`; the `arm64` and FAT controls exit 0. After the change, all nine exit 0. - The new CLI test fails on the unmodified source with that same `target_unavailable` error. - With only the slice policy removed, it fails on the `execution_failure` CPU-mismatch error described above. - User-visible CLI output (`list-architectures arm64_32.o --json`, abridged): ```json "subject": { "format": "mach-o", "architecture": "arm64_32" }, "normalized_result": { "architectures": { "items": [ { "name": "arm64_32", "cpu_type": null, "cpu_subtype": null, "file_offset": null, "size": null, "alignment": null } ], "total": 1, "exhaustive": true, "limitations": [] } } ``` - Remaining proof gaps: - No linked watchOS executable was tested; only a compiler-produced object was used. - Provider refusals are verified with unit tests only; no real Ghidra, Hopper or IDA run. - MCP was not exercised directly. From the source, it uses the same target admission as the CLI. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed Node 24.18.0, macOS 27.0 arm64, Apple CommandLineTools clang, lipo, otool and codesign. Head `b0110810a` on `7501911f1`. - `npm run check:fast` — passed (typecheck, lint 0 errors) - `npm run knip` — passed - `npm run docs:check` — passed - `npm run test:focused --` on every test under `src/domain`, `src/hopper`, `src/ida`, `src/ghidra`, `src/native` and `tests/boundary/providers/native`, `src/application/binaryDiagnostics`, both native Mach-O CLI tests, and the four CI planning tests — 454 files, 9072 tests passed, 2 skipped - `scripts/ci/plan.mjs`, simulated as a `pull_request` from `7501911f1` to this head — `apple=true` - Full `npm run check:pr` — not run ## Compatibility, safety, and release - Breaking changes or migration steps: none. The new enum value appears only for targets that were rejected before. - Real Hopper/Ghidra, browser, or OS coverage: real macOS Apple tools only; no deep-analysis provider installed. - Package or release metadata impact: none. - Security, privacy, process, or containment review: static file inspection only. No target execution. ## Author readiness Human owner: @Lew1sWong - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:45:04 +00:00
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#7061
No description provided.