[GH-ISSUE #5697] [Bug] inspect-signature still fails on mixed ad hoc signed universal Mach-O after #701 #3448

Closed
opened 2026-10-31 00:40:36 +00:00 by bsakaguchi · 1 comment
Owner

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

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

inspect-signature exits 1 with execution_failure and no normalized_result for a universal Mach-O with an ad hoc signed arm64 slice and an unsigned x86_64 slice. Still reproduces after #684 / #701: the merged fix is an ancestor of the tested baseline and current main. This is a post-fix regression report; no earlier known-good runtime was established.

Direct aggregate codesign display succeeds, the aggregate requirement query reports an unsigned slice, and direct per-slice inspection establishes the two signing states. Extracting and inspecting each thin slice through REA succeeds. The failure therefore discards available signing observations for the tested mixed input.

Reproduction

From a source checkout with locked dependencies installed and npm run build:cached completed, use the pinned Node version on an arm64 Mac with Apple's command-line tools:

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \
  "$work_dir/cache" "$work_dir/state" "$work_dir/tmp"
cat > "$work_dir/main.c" <<'C'
int main(void) { return 7; }
C
clang -arch arm64 -o "$work_dir/arm" "$work_dir/main.c"
clang -arch x86_64 -o "$work_dir/x86" "$work_dir/main.c"
codesign --force --sign - "$work_dir/arm"
lipo -create "$work_dir/arm" "$work_dir/x86" -output "$work_dir/mixed"

run_rea() {
  env -i PATH="$PATH" HOME="$work_dir/home" \
    XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \
    XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \
    TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \
    node scripts/rea.mjs "$@"
}
run_rea inspect-signature "$work_dir/mixed" --json
printf 'rea exit %s\n' "$?"

# Direct observations and the verified workaround:
codesign -d --verbose=4 "$work_dir/mixed"
codesign -d -r- "$work_dir/mixed"
codesign -d --entitlements :- "$work_dir/mixed"
codesign -d -a arm64 --verbose=4 "$work_dir/mixed"
codesign -d -a x86_64 --verbose=4 "$work_dir/mixed"
lipo "$work_dir/mixed" -thin arm64 -output "$work_dir/thin-arm"
lipo "$work_dir/mixed" -thin x86_64 -output "$work_dir/thin-x86"
run_rea inspect-signature "$work_dir/thin-arm" --json
run_rea inspect-signature "$work_dir/thin-x86" --json

An independent verifier ran this fixture and isolated CLI workflow twice at the baseline. Submission freshly rebuilt current main, compiled another fixture, and reproduced the mixed-file failure twice and both thin-file successes. No fixture was executed.

Expected behavior

Return a typed signature result with signed: true, Unsigned Mach-O slices: x86_64., and provenance for both slice probes. Preserve available facts and report the unavailable aggregate requirement as a mixed-signing limitation. Handle entitlements according to their own capture result; that query succeeds in this fixture.

The existing mixed-signature regression test explicitly expects that signed result, unsigned-slice limitation, unavailable-requirement limitation and both probes. The tool contract advertises signing facts and exact command provenance.

Impact and scope

P2: a deterministic, bounded supported-input failure returns no signature facts. Extracting each architecture with lipo and inspecting the thin files is a verified workaround: REA returns signed: true for arm64 and signed: false for x86_64, but requires separate artifacts and loses the combined result/provenance. Fully signed and fully unsigned universal controls also succeed at the baseline. No data loss or security-boundary crossing was demonstrated. Other OS versions, Intel hosts, arm64e variants and MCP transport were not tested.

REA version

rea-agents 6.7.0, source baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked after a fresh npm run build:cached at origin/main commit 949966712e68d8857a50a165ecddf04c2d7e6d3b (also 6.7.0): still fails. Latest published release checked: rea-agents-6.7.0. These reproductions used source builds.

Runtime and platform

Node.js v24.18.0 (repository pin); macOS 27.0.1 (26A434), arm64; Apple clang 21.0.0 (clang-2100.3.34.2). Hopper/Ghidra are not involved.

Target and artifact context

Self-built universal x86_64 + arm64 Mach-O executable from the C source above. Only the arm64 slice is explicitly ad hoc signed. Fixtures are inspected without target execution.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Both tested revisions exit 1 without normalized_result. Relevant JSON subset:

{
  "code": "execution_failure",
  "category": "execution_failure",
  "retryable": false,
  "details": {
    "provider_id": "native-macos",
    "operation": "inspect_signature",
    "diagnostics": {
      "tool": "codesign",
      "reason": "nonzero-exit",
      "exit_code": 1,
      "signal": null,
      "stdout_bytes": 0
    }
  }
}

The captured aggregate requirement has empty stdout and this two-line stderr shape, with private paths replaced by placeholders. The canonical and operand paths refer to the same snapshot but have different path spellings:

Executable=$CANONICAL_SNAPSHOT
$SNAPSHOT: code object is not signed at all

Direct codesign -d -r- on the original fixture produces the same shape and exits 1. Display, aggregate entitlements and the arm64 probe exit 0; the x86_64 probe exits 1 with only its unsigned diagnostic. Both thin-file REA inspections exit 0. Evidence provenance: observed; local verification: independently reproduced.

Typed outcome or recovery expected

The mixed fixture should exit 0 with the expected typed result and explicit partial coverage. Preserve thin-file and fully signed/unsigned universal results. Unrelated nonzero codesign diagnostics must remain failures; accepting arbitrary failed captures would not restore this contract. These are proposed restoration checks, not a tested repair.

Analysis or suspected cause

Source-derived explanation: the aggregate requirement failure check runs before mixed-slice handling. isNonzeroUnsignedObservation accepts only the exact one-line unsigned diagnostic, so the additional Executable= line causes rejection. The existing test's signatureRunner rewrites unsigned responses to one line and does not retain this real output shape. No bundled dependency was identified as the cause.

Additional context

Prepared with AI assistance and independently verified against real Apple tools. Closed #684 and merged #701 establish the intended mixed-signature behavior; #701's merge is already in the baseline. Open #4293 and PRs #3583/#4359 concern slice facts in otherwise successful inspections. PR #3583 lists the ad hoc mixed case as an unchanged control; this report concerns rejection before any result exists. #5538 / #5548 concern limitation wording after successful captures. Closed #5636 concerns false unsigned observations from echoed paths, whereas this fixture has a real unsigned slice and fails to return its facts. No earlier known-good version or historical regression boundary was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5697 ### 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 `inspect-signature` exits 1 with `execution_failure` and no `normalized_result` for a universal Mach-O with an ad hoc signed arm64 slice and an unsigned x86_64 slice. Still reproduces after #684 / #701: the merged fix is an ancestor of the tested baseline and current main. This is a post-fix regression report; no earlier known-good runtime was established. Direct aggregate codesign display succeeds, the aggregate requirement query reports an unsigned slice, and direct per-slice inspection establishes the two signing states. Extracting and inspecting each thin slice through REA succeeds. The failure therefore discards available signing observations for the tested mixed input. ### Reproduction From a source checkout with locked dependencies installed and `npm run build:cached` completed, use the pinned Node version on an arm64 Mac with Apple's command-line tools: ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \ "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" cat > "$work_dir/main.c" <<'C' int main(void) { return 7; } C clang -arch arm64 -o "$work_dir/arm" "$work_dir/main.c" clang -arch x86_64 -o "$work_dir/x86" "$work_dir/main.c" codesign --force --sign - "$work_dir/arm" lipo -create "$work_dir/arm" "$work_dir/x86" -output "$work_dir/mixed" run_rea() { env -i PATH="$PATH" HOME="$work_dir/home" \ XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" \ XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" \ TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent \ node scripts/rea.mjs "$@" } run_rea inspect-signature "$work_dir/mixed" --json printf 'rea exit %s\n' "$?" # Direct observations and the verified workaround: codesign -d --verbose=4 "$work_dir/mixed" codesign -d -r- "$work_dir/mixed" codesign -d --entitlements :- "$work_dir/mixed" codesign -d -a arm64 --verbose=4 "$work_dir/mixed" codesign -d -a x86_64 --verbose=4 "$work_dir/mixed" lipo "$work_dir/mixed" -thin arm64 -output "$work_dir/thin-arm" lipo "$work_dir/mixed" -thin x86_64 -output "$work_dir/thin-x86" run_rea inspect-signature "$work_dir/thin-arm" --json run_rea inspect-signature "$work_dir/thin-x86" --json ``` An independent verifier ran this fixture and isolated CLI workflow twice at the baseline. Submission freshly rebuilt current main, compiled another fixture, and reproduced the mixed-file failure twice and both thin-file successes. No fixture was executed. ### Expected behavior Return a typed signature result with `signed: true`, `Unsigned Mach-O slices: x86_64.`, and provenance for both slice probes. Preserve available facts and report the unavailable aggregate requirement as a mixed-signing limitation. Handle entitlements according to their own capture result; that query succeeds in this fixture. The existing [mixed-signature regression test](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/tests/boundary/providers/native/nativeInventory.golden.test.ts#L88) explicitly expects that signed result, unsigned-slice limitation, unavailable-requirement limitation and both probes. The [tool contract](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/contracts/native/nativeToolContracts.ts#L107) advertises signing facts and exact command provenance. ### Impact and scope P2: a deterministic, bounded supported-input failure returns no signature facts. Extracting each architecture with lipo and inspecting the thin files is a verified workaround: REA returns `signed: true` for arm64 and `signed: false` for x86_64, but requires separate artifacts and loses the combined result/provenance. Fully signed and fully unsigned universal controls also succeed at the baseline. No data loss or security-boundary crossing was demonstrated. Other OS versions, Intel hosts, arm64e variants and MCP transport were not tested. ### REA version `rea-agents` 6.7.0, source baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked after a fresh `npm run build:cached` at `origin/main` commit `949966712e68d8857a50a165ecddf04c2d7e6d3b` (also 6.7.0): still fails. Latest published release checked: `rea-agents-6.7.0`. These reproductions used source builds. ### Runtime and platform Node.js v24.18.0 (repository pin); macOS 27.0.1 (26A434), arm64; Apple clang 21.0.0 (clang-2100.3.34.2). Hopper/Ghidra are not involved. ### Target and artifact context Self-built universal x86_64 + arm64 Mach-O executable from the C source above. Only the arm64 slice is explicitly ad hoc signed. Fixtures are inspected without target execution. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Both tested revisions exit 1 without `normalized_result`. Relevant JSON subset: ```json { "code": "execution_failure", "category": "execution_failure", "retryable": false, "details": { "provider_id": "native-macos", "operation": "inspect_signature", "diagnostics": { "tool": "codesign", "reason": "nonzero-exit", "exit_code": 1, "signal": null, "stdout_bytes": 0 } } } ``` The captured aggregate requirement has empty stdout and this two-line stderr shape, with private paths replaced by placeholders. The canonical and operand paths refer to the same snapshot but have different path spellings: ```text Executable=$CANONICAL_SNAPSHOT $SNAPSHOT: code object is not signed at all ``` Direct `codesign -d -r-` on the original fixture produces the same shape and exits 1. Display, aggregate entitlements and the arm64 probe exit 0; the x86_64 probe exits 1 with only its unsigned diagnostic. Both thin-file REA inspections exit 0. Evidence provenance: observed; local verification: independently reproduced. ### Typed outcome or recovery expected The mixed fixture should exit 0 with the expected typed result and explicit partial coverage. Preserve thin-file and fully signed/unsigned universal results. Unrelated nonzero codesign diagnostics must remain failures; accepting arbitrary failed captures would not restore this contract. These are proposed restoration checks, not a tested repair. ### Analysis or suspected cause Source-derived explanation: [the aggregate requirement failure check](https://github.com/morluto/rea/blob/949966712e68d8857a50a165ecddf04c2d7e6d3b/src/native/NativeMacOSProvider.ts#L690) runs before mixed-slice handling. [isNonzeroUnsignedObservation](https://github.com/morluto/rea/blob/949966712e68d8857a50a165ecddf04c2d7e6d3b/src/native/NativeMacOSProvider.ts#L1311) accepts only the exact one-line unsigned diagnostic, so the additional `Executable=` line causes rejection. The existing test's [signatureRunner](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/tests/boundary/providers/native/nativeInventory.golden.test.ts#L423) rewrites unsigned responses to one line and does not retain this real output shape. No bundled dependency was identified as the cause. ### Additional context Prepared with AI assistance and independently verified against real Apple tools. Closed #684 and merged #701 establish the intended mixed-signature behavior; #701's merge is already in the baseline. Open #4293 and PRs #3583/#4359 concern slice facts in otherwise successful inspections. PR #3583 lists the ad hoc mixed case as an unchanged control; this report concerns rejection before any result exists. #5538 / #5548 concern limitation wording after successful captures. Closed #5636 concerns false unsigned observations from echoed paths, whereas this fixture has a real unsigned slice and fails to return its facts. No earlier known-good version or historical regression boundary was established.
Author
Owner

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

Cross-referencing with canonical issue #4701 (Signature inspection reports ad hoc Mach-O as unsigned after #701) and sibling signature reports #5092, #5548, #5636.

Consolidated under Cluster AY (Code Signature Inspection Metadata, Temporary Filesystem State & Limitation Tracking) in tracker #4330.

<!-- gh-comment-id:6304476914 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #4701 (`Signature inspection reports ad hoc Mach-O as unsigned after #701`) and sibling signature reports #5092, #5548, #5636. Consolidated under **Cluster AY (Code Signature Inspection Metadata, Temporary Filesystem State & Limitation Tracking)** in tracker #4330.
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#3448
No description provided.