[GH-ISSUE #7390] [Bug] Universal signature inspection skips other slice states and its coverage note #4398

Closed
opened 2026-10-31 00:49:29 +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/7390

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

node scripts/rea.mjs inspect-signature "$WORK/fixture.universal" --json exits 0 for a universal Mach-O with a signed arm64 slice and an unsigned x86_64 slice. Its Evidence subject identifies the universal container and selected arm64 architecture. The normalized result correctly contains the selected slice's signing facts, but omits both the promised coverage boundary and the other slice's unsigned state.

Observed with two freshly compiled fixtures: signed: true, format: "Mach-O thin (arm64)", and only the generic snapshot/trust limitation. All three provenance entries select arm64; none probes x86_64. Direct codesign -d -a x86_64 --verbose=4 on the same input exits 1 with code object is not signed at all. The arm64 probe exits 0 and shows an ad hoc linker signature.

Reproduction

From a clean source checkout at the reported commit with its locked dependencies and built runtime already prepared, run this script on an arm64 Mac with Xcode Command Line Tools and Node.js 24.22.1 on PATH. It creates synthetic inputs without executing them and isolates every REA invocation's HOME, XDG directories and TMPDIR. The verifier ran this script against a fresh fixture; it reproduced the omission.

set -eu
# Run from the prepared source checkout at the reported commit, using Node 24.22.1.
WORK="${WORK:-$(mktemp -d)}"
mkdir -p "$WORK/home" "$WORK/config" "$WORK/data" "$WORK/cache" \
  "$WORK/state" "$WORK/runtime" "$WORK/temp"
printf 'int main(void){return 0;}\n' > "$WORK/fixture.c"
xcrun clang -arch arm64 -O1 -o "$WORK/fixture.arm64" "$WORK/fixture.c"
xcrun clang -arch x86_64 -O1 -o "$WORK/fixture.x86_64" "$WORK/fixture.c"
lipo -create "$WORK/fixture.arm64" "$WORK/fixture.x86_64" \
  -output "$WORK/fixture.universal"
lipo -archs "$WORK/fixture.universal"
codesign -d -a arm64 --verbose=4 "$WORK/fixture.universal" \
  > "$WORK/arm64.stdout" 2> "$WORK/arm64.stderr"
if codesign -d -a x86_64 --verbose=4 "$WORK/fixture.universal" \
  > "$WORK/x86_64.stdout" 2> "$WORK/x86_64.stderr"; then
  echo 'Fixture precondition failed: x86_64 is signed.' >&2
  exit 1
fi
cat "$WORK/x86_64.stderr"
run_rea() {
  env -i PATH="$PATH" HOME="$WORK/home" \
    XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data" \
    XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" \
    XDG_RUNTIME_DIR="$WORK/runtime" TMPDIR="$WORK/temp" \
    node scripts/rea.mjs "$@"
}
run_rea --version
run_rea inspect-signature "$WORK/fixture.universal" --json > "$WORK/result.json"
python3 - "$WORK/result.json" <<'PYTHON'
import json, sys
result = json.load(open(sys.argv[1]))
value = result["normalized_result"]
print(json.dumps({
    "subject_format": result["subject"]["format"],
    "subject_architecture": result["subject"]["architecture"],
    "signed": value["signed"],
    "format": value["format"],
    "limitations": value["limitations"],
    "commands": [entry["command"][1:-1] for entry in value["provenance"]]
}, indent=2))
PYTHON

The printed result lists only three arm64 commands, lacks a partial-coverage note and never names the unsigned x86_64 slice. The raw result remains in $WORK/result.json for inspection.

Expected behavior

Keep the selected arm64 signing facts, probe the other universal slice's signing state, and state that the detailed identity, hashes, requirement, entitlements and runtime fields cover only the selected architecture. For this input, include the unsigned x86_64 observation with its command provenance.

Expected source: docs/native-investigation.md:1062 explicitly promises other-slice signing-state probes and a coverage statement for universal inputs. Expected source: src/native/NativeMacOSProvider.ts:814 and src/native/NativeMacOSProvider.ts:904 already construct the partial-coverage and unsigned-slice limitations when slice enumeration succeeds.

Impact and scope

P2: supported universal input loses a consequential signing-state observation and the documented scope warning. A caller relying on the completed inspection misses the unsigned x86_64 slice. The selected arm64 signing facts are correct; this report does not treat signed: true as a claim that every slice is signed, nor demonstrate a security boundary crossing.

A practical workaround was verified: list the slices with lipo -archs, then run codesign -d -a <architecture> --verbose=4 for each. That recovers the signing states without changing the artifact, though outside the combined REA Evidence. The signed arm64 thin-file control also returned matching identity and CDHash through REA. Intel hosts, other macOS releases and the MCP transport were not tested.

REA version

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

Source commit: f8f7ac96de8a66a4ab9a23d87e04779bfcd11045.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; Darwin arm64. Compilation and inspection use Apple's local command-line tools; no Hopper, Ghidra or IDA session is involved.

Target and artifact context

Synthetic universal Mach-O executable combining separately compiled x86_64 and arm64 outputs from int main(void){return 0;}. The arm64 slice is linker-signed; the x86_64 slice is unsigned. No app bundle or external target data is required.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Independently observed summary, with operands represented by variables:

lipo -archs "$WORK/fixture.universal": x86_64 arm64
codesign -d -a arm64 --verbose=4: exit 0, flags=0x20002(adhoc,linker-signed)
codesign -d -a x86_64 --verbose=4: exit 1, code object is not signed at all
REA inspect-signature: exit 0
subject.format: mach-o-universal
subject.architecture: arm64
normalized_result.signed: true
normalized_result.format: Mach-O thin (arm64)
normalized_result.provenance: exactly three codesign commands, each with -a arm64
normalized_result.limitations and Evidence limitations:
  Inspected a digest-verified Mach-O byte snapshot; filesystem-bound signing context and trust validation are not examined.

Failure source: src/native/NativeMacOSProvider.ts:754 requests an architecture-selected display capture. Failure source: src/native/NativeMacOSProvider.ts:813 derives the architecture list from that capture's format; src/native/NativeMacOSProvider.ts:1463 returns an empty list for the observed thin format before considering the target's available architectures. The guards for the coverage note and signing-state probes therefore fail at src/native/NativeMacOSProvider.ts:814 and src/native/NativeMacOSProvider.ts:850.

Typed outcome or recovery expected

For the mixed universal fixture, retain the selected slice's signing fields, add a partial signature coverage limitation and Unsigned Mach-O slices: x86_64., and retain the other slice's probe provenance. Preserve the ordinary single-slice result for a thin arm64 input. These are restoration criteria; no repair was tested.

Analysis or suspected cause

Source-derived explanation: architecture selection changes the real codesign display's Format= field to a thin image. The enumeration helper treats that display format as evidence that the entire admitted target has no other slices. This suppresses both promised universal behaviors despite the Evidence subject retaining its universal identity. The existing all-signed provider regression fabricates a universal display format even for architecture-selected commands, so its simulated output does not exercise the Apple output shape observed here.

Additional context

Related closed reports: #4293 addressed unselected signature facets and missing slice boundaries; #684 and #5697 addressed failures that discarded all facts for mixed signing states. Their fixes or closure revisions are already ancestors of this baseline. This report independently reproduces successful selected-slice inspection that still omits promised universal coverage and unsigned-slice discovery. It is a post-fix recurrence under the current contract; no historical known-good runtime or first-bad revision was established.

Also checked #5538 (completed-capture wording) and #5636 (false unsigned classification after snapshot removal); neither describes this ordinary-input coverage failure. No issue, comment or code change was published during verification.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7390 ## 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 `node scripts/rea.mjs inspect-signature "$WORK/fixture.universal" --json` exits 0 for a universal Mach-O with a signed arm64 slice and an unsigned x86_64 slice. Its Evidence subject identifies the universal container and selected arm64 architecture. The normalized result correctly contains the selected slice's signing facts, but omits both the promised coverage boundary and the other slice's unsigned state. Observed with two freshly compiled fixtures: `signed: true`, `format: "Mach-O thin (arm64)"`, and only the generic snapshot/trust limitation. All three provenance entries select arm64; none probes x86_64. Direct `codesign -d -a x86_64 --verbose=4` on the same input exits 1 with `code object is not signed at all`. The arm64 probe exits 0 and shows an ad hoc linker signature. ## Reproduction From a clean source checkout at the reported commit with its locked dependencies and built runtime already prepared, run this script on an arm64 Mac with Xcode Command Line Tools and Node.js 24.22.1 on PATH. It creates synthetic inputs without executing them and isolates every REA invocation's HOME, XDG directories and TMPDIR. The verifier ran this script against a fresh fixture; it reproduced the omission. ```bash set -eu # Run from the prepared source checkout at the reported commit, using Node 24.22.1. WORK="${WORK:-$(mktemp -d)}" mkdir -p "$WORK/home" "$WORK/config" "$WORK/data" "$WORK/cache" \ "$WORK/state" "$WORK/runtime" "$WORK/temp" printf 'int main(void){return 0;}\n' > "$WORK/fixture.c" xcrun clang -arch arm64 -O1 -o "$WORK/fixture.arm64" "$WORK/fixture.c" xcrun clang -arch x86_64 -O1 -o "$WORK/fixture.x86_64" "$WORK/fixture.c" lipo -create "$WORK/fixture.arm64" "$WORK/fixture.x86_64" \ -output "$WORK/fixture.universal" lipo -archs "$WORK/fixture.universal" codesign -d -a arm64 --verbose=4 "$WORK/fixture.universal" \ > "$WORK/arm64.stdout" 2> "$WORK/arm64.stderr" if codesign -d -a x86_64 --verbose=4 "$WORK/fixture.universal" \ > "$WORK/x86_64.stdout" 2> "$WORK/x86_64.stderr"; then echo 'Fixture precondition failed: x86_64 is signed.' >&2 exit 1 fi cat "$WORK/x86_64.stderr" run_rea() { env -i PATH="$PATH" HOME="$WORK/home" \ XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data" \ XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" \ XDG_RUNTIME_DIR="$WORK/runtime" TMPDIR="$WORK/temp" \ node scripts/rea.mjs "$@" } run_rea --version run_rea inspect-signature "$WORK/fixture.universal" --json > "$WORK/result.json" python3 - "$WORK/result.json" <<'PYTHON' import json, sys result = json.load(open(sys.argv[1])) value = result["normalized_result"] print(json.dumps({ "subject_format": result["subject"]["format"], "subject_architecture": result["subject"]["architecture"], "signed": value["signed"], "format": value["format"], "limitations": value["limitations"], "commands": [entry["command"][1:-1] for entry in value["provenance"]] }, indent=2)) PYTHON ``` The printed result lists only three arm64 commands, lacks a partial-coverage note and never names the unsigned x86_64 slice. The raw result remains in `$WORK/result.json` for inspection. ## Expected behavior Keep the selected arm64 signing facts, probe the other universal slice's signing state, and state that the detailed identity, hashes, requirement, entitlements and runtime fields cover only the selected architecture. For this input, include the unsigned x86_64 observation with its command provenance. Expected source: `docs/native-investigation.md:1062` explicitly promises other-slice signing-state probes and a coverage statement for universal inputs. Expected source: `src/native/NativeMacOSProvider.ts:814` and `src/native/NativeMacOSProvider.ts:904` already construct the partial-coverage and unsigned-slice limitations when slice enumeration succeeds. ## Impact and scope P2: supported universal input loses a consequential signing-state observation and the documented scope warning. A caller relying on the completed inspection misses the unsigned x86_64 slice. The selected arm64 signing facts are correct; this report does not treat `signed: true` as a claim that every slice is signed, nor demonstrate a security boundary crossing. A practical workaround was verified: list the slices with `lipo -archs`, then run `codesign -d -a <architecture> --verbose=4` for each. That recovers the signing states without changing the artifact, though outside the combined REA Evidence. The signed arm64 thin-file control also returned matching identity and CDHash through REA. Intel hosts, other macOS releases and the MCP transport were not tested. ## REA version Exact `node scripts/rea.mjs --version` output: `6.8.0`. Source commit: `f8f7ac96de8a66a4ab9a23d87e04779bfcd11045`. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1; Darwin arm64. Compilation and inspection use Apple's local command-line tools; no Hopper, Ghidra or IDA session is involved. ## Target and artifact context Synthetic universal Mach-O executable combining separately compiled x86_64 and arm64 outputs from `int main(void){return 0;}`. The arm64 slice is linker-signed; the x86_64 slice is unsigned. No app bundle or external target data is required. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Independently observed summary, with operands represented by variables: ```text lipo -archs "$WORK/fixture.universal": x86_64 arm64 codesign -d -a arm64 --verbose=4: exit 0, flags=0x20002(adhoc,linker-signed) codesign -d -a x86_64 --verbose=4: exit 1, code object is not signed at all REA inspect-signature: exit 0 subject.format: mach-o-universal subject.architecture: arm64 normalized_result.signed: true normalized_result.format: Mach-O thin (arm64) normalized_result.provenance: exactly three codesign commands, each with -a arm64 normalized_result.limitations and Evidence limitations: Inspected a digest-verified Mach-O byte snapshot; filesystem-bound signing context and trust validation are not examined. ``` Failure source: `src/native/NativeMacOSProvider.ts:754` requests an architecture-selected display capture. Failure source: `src/native/NativeMacOSProvider.ts:813` derives the architecture list from that capture's format; `src/native/NativeMacOSProvider.ts:1463` returns an empty list for the observed thin format before considering the target's available architectures. The guards for the coverage note and signing-state probes therefore fail at `src/native/NativeMacOSProvider.ts:814` and `src/native/NativeMacOSProvider.ts:850`. ## Typed outcome or recovery expected For the mixed universal fixture, retain the selected slice's signing fields, add a partial signature coverage limitation and `Unsigned Mach-O slices: x86_64.`, and retain the other slice's probe provenance. Preserve the ordinary single-slice result for a thin arm64 input. These are restoration criteria; no repair was tested. ## Analysis or suspected cause Source-derived explanation: architecture selection changes the real codesign display's `Format=` field to a thin image. The enumeration helper treats that display format as evidence that the entire admitted target has no other slices. This suppresses both promised universal behaviors despite the Evidence subject retaining its universal identity. The existing all-signed provider regression fabricates a universal display format even for architecture-selected commands, so its simulated output does not exercise the Apple output shape observed here. ## Additional context Related closed reports: #4293 addressed unselected signature facets and missing slice boundaries; #684 and #5697 addressed failures that discarded all facts for mixed signing states. Their fixes or closure revisions are already ancestors of this baseline. This report independently reproduces successful selected-slice inspection that still omits promised universal coverage and unsigned-slice discovery. It is a post-fix recurrence under the current contract; no historical known-good runtime or first-bad revision was established. Also checked #5538 (completed-capture wording) and #5636 (false unsigned classification after snapshot removal); neither describes this ordinary-input coverage failure. No issue, comment or code change was published during verification.
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#4398
No description provided.