[GH-ISSUE #5728] [Bug] Apple application projection omits uppercase framework components #3476

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

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-artifact followed by project-apple-application-graph recognizes a nested VerifyKit.FRAMEWORK directory as a framework bundle, retains its Info.plist path and executable, but returns components.frameworks: []. The lowercase .framework control contains identical file bytes and returns both framework files. Both projections report complete coverage without disclosing the missing category membership.

Reproduction

From a source checkout with locked dependencies installed and npm run build:cached completed, run on macOS with Xcode command-line tools and jq:

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" \
  "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" \
  "$work_dir/Upper.app/Contents/MacOS" \
  "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK"
printf 'int main(void) { return 0; }\n' > "$work_dir/main.c"
xcrun clang "$work_dir/main.c" \
  -o "$work_dir/Upper.app/Contents/MacOS/Main"
xcrun clang -dynamiclib "$work_dir/main.c" \
  -o "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK/VerifyKit"
printf '%s\n' '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>Main</string></dict></plist>' \
  > "$work_dir/Upper.app/Contents/Info.plist"
printf '%s\n' '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>VerifyKit</string></dict></plist>' \
  > "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK/Info.plist"
cp -R "$work_dir/Upper.app" "$work_dir/Lower.app"
mv "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.FRAMEWORK" \
  "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.temporary"
mv "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.temporary" \
  "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.framework"
for app in Upper Lower; do
  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 inspect-artifact "$work_dir/$app.app" --json \
    > "$work_dir/$app-inventory.json"
  jq '{inventory_evidence: [.normalized_result.substeps[0].evidence]}' \
    "$work_dir/$app-inventory.json" > "$work_dir/$app-input.json"
  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 project-apple-application-graph \
    "$work_dir/$app-input.json" --json > "$work_dir/$app-projection.json"
  printf '%s\n' "$app"
  jq '.normalized_result | {
    bundles: [.bundles[] | select(.role == "framework") |
      {path, role, info_plist_path}],
    frameworks: [.components.frameworks[].path], coverage
  }' "$work_dir/$app-projection.json"
done

The independent verifier ran this comparison at the baseline below. I reran it once against a fresh build of current main and observed the same upper/lower distinction. The fixtures are inspected without launching them.

Expected behavior

Include both files under the already recognized framework in components.frameworks, preserving their exact path spelling and hashes. The lowercase control should retain its two entries.

docs/apple-application-analysis.md:33-35 promises every component in each inventory category. Existing source behavior supplies the narrower contract here: src/domain/apple/appleBundleAnatomy.ts:11-12,237-244 discovers and assigns the framework role case-insensitively. The expected category membership concerns a bundle REA already accepts and recognizes; the documentation does not explicitly specify framework suffix case handling.

Impact and scope

P2: callers using the framework category receive an incorrect absence result for this bounded input despite complete coverage. The observed source inventory retains both exact file paths, and the bundle row identifies the framework, so callers can recover its descendants from that inventory without modifying the application. General application analysis failure, data loss, security impact, and runtime framework loading were not established. MCP transport and other case variants were not tested.

REA version

rea-agents 6.7.0, baseline source commit 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rechecked with a fresh npm run build:cached on current main d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f, also 6.7.0: still reproduces. Latest published release is rea-agents-6.7.0; these observations use source checkouts.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1; arm64. Xcode command-line tools compile the fixtures. No Hopper/Ghidra provider is involved.

Target and artifact context

Two self-built macOS .app directory fixtures with a real arm64 main Mach-O executable and nested framework dylib, plus readable Info.plist files naming the respective executables. Only the nested framework directory's suffix spelling differs. Inventory Evidence comes from the public inspector and passes directly to the public Apple graph projector.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The reproduction prints this distinguishing subset on current main:

{"input":"Upper","bundles":[{"path":"Contents/Frameworks/VerifyKit.FRAMEWORK","role":"framework","info_plist_path":"Contents/Frameworks/VerifyKit.FRAMEWORK/Info.plist"}],"frameworks":[],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}
{"input":"Lower","bundles":[{"path":"Contents/Frameworks/VerifyKit.framework","role":"framework","info_plist_path":"Contents/Frameworks/VerifyKit.framework/Info.plist"}],"frameworks":["Contents/Frameworks/VerifyKit.framework/Info.plist","Contents/Frameworks/VerifyKit.framework/VerifyKit"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}

The uppercase bundle also has executable_candidates: ["Contents/Frameworks/VerifyKit.FRAMEWORK/VerifyKit"], and that path remains in components.executables. Projection limitations do not mention missing framework membership. Evidence provenance: observed; local verification: reproduced; reproduction completeness: complete.

Typed outcome or recovery expected

Return both uppercase framework descendants in the framework category with their original spelling and authenticated identities. Preserve the lowercase control, bundle role, and executable observations. No change to inventory identity or target bytes is needed.

Analysis or suspected cause

Source-derived reasoning: src/domain/apple/appleApplication.ts:279 uses the case-sensitive /\.framework\//u filter, while bundle discovery and role assignment recognize .FRAMEWORK. This explains the inconsistent category membership for the accepted input. These cited source lines are unchanged between the baseline and rechecked main. No bundled dependency was identified as the cause.

Additional context

Still reproduces after #1486 and its merged fix #1513, commit b4db35f092d7166aebdf4387b48f7652b1d1c3b6, which is an ancestor of the tested baseline. That work corrected anatomy-name matching but left this framework category filter unchanged. No last-known-good version was established.

#4688 concerns unread files with unavailable identities; both files here are readable and authenticated. #3359 concerns file-versus-directory anatomy; this fixture uses real directories. #4310 requests admission of nested bundles as standalone targets; this report projects a framework inside an accepted application.

Prepared with AI assistance and independently verified. Proposed, unexecuted controls for a fix: a .framework2 directory and a standalone regular .framework file should not gain framework-descendant membership. No fix or restoration has been tested.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5728 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior `inspect-artifact` followed by `project-apple-application-graph` recognizes a nested `VerifyKit.FRAMEWORK` directory as a framework bundle, retains its Info.plist path and executable, but returns `components.frameworks: []`. The lowercase `.framework` control contains identical file bytes and returns both framework files. Both projections report complete coverage without disclosing the missing category membership. ### Reproduction From a source checkout with locked dependencies installed and `npm run build:cached` completed, run on macOS with Xcode command-line tools and jq: ```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" \ "$work_dir/Upper.app/Contents/MacOS" \ "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK" printf 'int main(void) { return 0; }\n' > "$work_dir/main.c" xcrun clang "$work_dir/main.c" \ -o "$work_dir/Upper.app/Contents/MacOS/Main" xcrun clang -dynamiclib "$work_dir/main.c" \ -o "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK/VerifyKit" printf '%s\n' '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>Main</string></dict></plist>' \ > "$work_dir/Upper.app/Contents/Info.plist" printf '%s\n' '<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>VerifyKit</string></dict></plist>' \ > "$work_dir/Upper.app/Contents/Frameworks/VerifyKit.FRAMEWORK/Info.plist" cp -R "$work_dir/Upper.app" "$work_dir/Lower.app" mv "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.FRAMEWORK" \ "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.temporary" mv "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.temporary" \ "$work_dir/Lower.app/Contents/Frameworks/VerifyKit.framework" for app in Upper Lower; do 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 inspect-artifact "$work_dir/$app.app" --json \ > "$work_dir/$app-inventory.json" jq '{inventory_evidence: [.normalized_result.substeps[0].evidence]}' \ "$work_dir/$app-inventory.json" > "$work_dir/$app-input.json" 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 project-apple-application-graph \ "$work_dir/$app-input.json" --json > "$work_dir/$app-projection.json" printf '%s\n' "$app" jq '.normalized_result | { bundles: [.bundles[] | select(.role == "framework") | {path, role, info_plist_path}], frameworks: [.components.frameworks[].path], coverage }' "$work_dir/$app-projection.json" done ``` The independent verifier ran this comparison at the baseline below. I reran it once against a fresh build of current main and observed the same upper/lower distinction. The fixtures are inspected without launching them. ### Expected behavior Include both files under the already recognized framework in `components.frameworks`, preserving their exact path spelling and hashes. The lowercase control should retain its two entries. `docs/apple-application-analysis.md:33-35` promises every component in each inventory category. Existing source behavior supplies the narrower contract here: `src/domain/apple/appleBundleAnatomy.ts:11-12,237-244` discovers and assigns the framework role case-insensitively. The expected category membership concerns a bundle REA already accepts and recognizes; the documentation does not explicitly specify framework suffix case handling. ### Impact and scope P2: callers using the framework category receive an incorrect absence result for this bounded input despite complete coverage. The observed source inventory retains both exact file paths, and the bundle row identifies the framework, so callers can recover its descendants from that inventory without modifying the application. General application analysis failure, data loss, security impact, and runtime framework loading were not established. MCP transport and other case variants were not tested. ### REA version `rea-agents` 6.7.0, baseline source commit `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rechecked with a fresh `npm run build:cached` on current main `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`, also 6.7.0: still reproduces. Latest published release is `rea-agents-6.7.0`; these observations use source checkouts. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1; arm64. Xcode command-line tools compile the fixtures. No Hopper/Ghidra provider is involved. ### Target and artifact context Two self-built macOS `.app` directory fixtures with a real arm64 main Mach-O executable and nested framework dylib, plus readable Info.plist files naming the respective executables. Only the nested framework directory's suffix spelling differs. Inventory Evidence comes from the public inspector and passes directly to the public Apple graph projector. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The reproduction prints this distinguishing subset on current main: ```json {"input":"Upper","bundles":[{"path":"Contents/Frameworks/VerifyKit.FRAMEWORK","role":"framework","info_plist_path":"Contents/Frameworks/VerifyKit.FRAMEWORK/Info.plist"}],"frameworks":[],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} {"input":"Lower","bundles":[{"path":"Contents/Frameworks/VerifyKit.framework","role":"framework","info_plist_path":"Contents/Frameworks/VerifyKit.framework/Info.plist"}],"frameworks":["Contents/Frameworks/VerifyKit.framework/Info.plist","Contents/Frameworks/VerifyKit.framework/VerifyKit"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} ``` The uppercase bundle also has `executable_candidates: ["Contents/Frameworks/VerifyKit.FRAMEWORK/VerifyKit"]`, and that path remains in `components.executables`. Projection limitations do not mention missing framework membership. Evidence provenance: observed; local verification: reproduced; reproduction completeness: complete. ### Typed outcome or recovery expected Return both uppercase framework descendants in the framework category with their original spelling and authenticated identities. Preserve the lowercase control, bundle role, and executable observations. No change to inventory identity or target bytes is needed. ### Analysis or suspected cause Source-derived reasoning: `src/domain/apple/appleApplication.ts:279` uses the case-sensitive `/\.framework\//u` filter, while bundle discovery and role assignment recognize `.FRAMEWORK`. This explains the inconsistent category membership for the accepted input. These cited source lines are unchanged between the baseline and rechecked main. No bundled dependency was identified as the cause. ### Additional context Still reproduces after #1486 and its merged fix #1513, commit `b4db35f092d7166aebdf4387b48f7652b1d1c3b6`, which is an ancestor of the tested baseline. That work corrected anatomy-name matching but left this framework category filter unchanged. No last-known-good version was established. #4688 concerns unread files with unavailable identities; both files here are readable and authenticated. #3359 concerns file-versus-directory anatomy; this fixture uses real directories. #4310 requests admission of nested bundles as standalone targets; this report projects a framework inside an accepted application. Prepared with AI assistance and independently verified. Proposed, unexecuted controls for a fix: a `.framework2` directory and a standalone regular `.framework` file should not gain framework-descendant membership. No fix or restoration has been tested.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

classifyComponents now obtains framework roots from the owner’s parsed bundle role and includes components by containment instead of reparsing paths with a case-sensitive suffix regex. Thus the already-recognized VerifyKit.FRAMEWORK enters frameworks with its original file paths and component identities. The targeted parameterized test constructs otherwise equivalent Core.framework/Core.FRAMEWORK inventories and asserts executable and Info.plist exact path spellings in both cases.

Implementation: appleApplication.ts, appleApplication.ts.

The relevant regression checks in appleApplication.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380722030 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. classifyComponents now obtains framework roots from the owner’s parsed bundle role and includes components by containment instead of reparsing paths with a case-sensitive suffix regex. Thus the already-recognized VerifyKit.FRAMEWORK enters frameworks with its original file paths and component identities. The targeted parameterized test constructs otherwise equivalent Core.framework/Core.FRAMEWORK inventories and asserts executable and Info.plist exact path spellings in both cases. Implementation: [appleApplication.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/apple/appleApplication.ts#L268), [appleApplication.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/apple/appleApplication.ts#L293). The relevant regression checks in [appleApplication.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/apple/appleApplication.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3476
No description provided.