[PR #1476] [MERGED] fix(apple): decode binary app Info.plist files without plutil #2914

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

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1476
Author: @jayhemnani9910
Created: 10/10/2026
Status: ✅ Merged
Merged: 10/11/2026
Merged by: @morluto

Base: main ← Head: fix/app-bundle-binary-plist


📝 Commits (5)

  • 9cc7594 fix(apple): decode binary app Info.plist files without plutil
  • 4327490 test(apple): use Windows-legal names in the binary plist bundle test
  • f9b075d fix(apple): ignore proto entries in binary app Info.plist files
  • 5112d90 chore: merge main and refresh bundle plist preflight API
  • 9a37645 Merge remote-tracking branch 'origin/main' into codex/pr1476-ci

📊 Changes

2 files changed (+87 additions, -20 deletions)

View changed files

📝 src/application/AppBundleExecutable.ts (+21 -20)
📝 tests/boundary/filesystem/binaryTarget.test.ts (+66 -0)

📄 Description

Summary

On Linux and Windows, REA cannot open an app bundle whose Info.plist is a binary plist. Every command that takes the bundle fails with could not decode app Info.plist <path> (ENOENT), because the executable name is read by running /usr/bin/plutil, which only exists on macOS. iOS apps (extracted from an IPA) ship a binary Info.plist, so this blocks trace-dylib-resolution, inspect-artifact, extract-artifact and decode-interface-builder on those bundles off macOS, even though the dylib guide says that tool "also works off macOS".

Problem and expected behavior

Trigger: a .app (macOS Contents/Info.plist or flat iOS Info.plist layout) whose Info.plist starts with bplist00, opened on a non-macOS host.

Before: resolveLayoutExecutable sends binary plists to readBinaryPlistExecutable, which runs /usr/bin/plutil -extract CFBundleExecutable raw (src/application/AppBundleExecutable.ts:257-260). On Linux the spawn fails with ENOENT and the bundle is rejected. The same bundle with an XML Info.plist opens fine.

Expected: the bundle opens on any host. docs/native-investigation.md:62 already says bundle Info.plist resolution "uses the same decoder as keyed archives and Interface Builder", which is true today only for XML.

Change and scope

No related issue; this is a small, self-contained fix, so it comes straight as a PR.

Binary Info.plist files are now decoded in-process with plist.parseBinary, as InterfaceBuilderAnalysis already does, after the same estimatePropertyListDecodeBytes preflight it runs (bounded here at 16 MiB). The top-level CFBundleExecutable lookup is shared with the XML path. Name safety and program-directory containment checks are unchanged. The injectable decodeBinaryPlist hook stays for tests.

Not included: inspect-plist and other tools that are documented as macOS-only.

Contract and boundary impact

  • Semantic owner and earliest changed stage: app bundle executable resolution (AppBundleExecutable.ts), before any provider runs.
  • CLI and MCP/tool-catalog contract: none.
  • Provider, bridge, target-format, or platform compatibility: app bundles with binary Info.plist now open on Linux and Windows; macOS no longer spawns plutil for this lookup.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Process execution, authorization, cleanup, or containment impact: one less child process; containment checks unchanged.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none (plist is already a dependency).

Evidence and regression coverage

  • Tests added or updated: binary Info.plist app bundle targets in tests/boundary/filesystem/binaryTarget.test.ts builds a macOS-layout and a flat iOS-layout bundle with a binary Info.plist (names Ordinary, App, Tab\t) and resolves each through parseBinaryTarget. Both cases fail on current main on Linux and pass with this change. The existing macOS-only test that converts plists with real plutil -convert binary1 still covers macOS-produced files.
  • Base reproduction or other evidence: executed on Linux x64 with rea-agents 6.2.0 and main. Same arm64 Mach-O bundle, two Info.plist forms:
    XML.app     trace-dylib-resolution: Contents/MacOS/app -> @rpath/libA.dylib resolved, ...
    Binary.app  trace-dylib-resolution: Cannot open artifact: could not decode app Info.plist .../Binary.app/Contents/Info.plist (ENOENT).
    Payload/Demo.app (unzipped IPA): same ENOENT
    
    With this change all three open, and Binary.app reports the same roots, edges, findings and coverage as XML.app. inspect-artifact, extract-artifact and decode-interface-builder also succeed on Binary.app.
  • User-visible CLI/MCP output: the lines above.
  • Remaining proof gaps: not run on macOS or Windows here.

Validation performed

  • npx vitest run tests/boundary/filesystem/binaryTarget.test.ts — 42 passed, 1 skipped (macOS-only); the 2 new cases fail with the fix reverted.
  • npm run check:fast — passed.
  • npx vitest run --project <name> for each project except acceptance, after npm run build:cached — 6146 passed. The 16 failures (firmware tests that need Binwalk 3.1.x, and ASAR tests whose unpacked companion file is missing here) fail the same way on untouched main. acceptance was not run (out of memory on this machine); its app-bundle test is macOS-only.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux only; no Hopper/Ghidra workflow touched.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the bundle's Info.plist is untrusted input; decoding is bounded by the existing binary plist preflight before parseBinary runs.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • 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.

🔄 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/1476 **Author:** [@jayhemnani9910](https://github.com/jayhemnani9910) **Created:** 10/10/2026 **Status:** ✅ Merged **Merged:** 10/11/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/app-bundle-binary-plist` --- ### 📝 Commits (5) - [`9cc7594`](https://github.com/morluto/rea/commit/9cc7594968b85accd7c805a9f5f09ca2744cc02d) fix(apple): decode binary app Info.plist files without plutil - [`4327490`](https://github.com/morluto/rea/commit/4327490de0658f8d42fc0252ee1113c5fe3842c4) test(apple): use Windows-legal names in the binary plist bundle test - [`f9b075d`](https://github.com/morluto/rea/commit/f9b075d985a42214c5909e29947725058e643aa3) fix(apple): ignore __proto__ entries in binary app Info.plist files - [`5112d90`](https://github.com/morluto/rea/commit/5112d90f1054e841d015678730aa5969352b3d9d) chore: merge main and refresh bundle plist preflight API - [`9a37645`](https://github.com/morluto/rea/commit/9a37645b6f6ee4105a6ff333557ce4f6dcdcd8b2) Merge remote-tracking branch 'origin/main' into codex/pr1476-ci ### 📊 Changes **2 files changed** (+87 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `src/application/AppBundleExecutable.ts` (+21 -20) 📝 `tests/boundary/filesystem/binaryTarget.test.ts` (+66 -0) </details> ### 📄 Description ## Summary On Linux and Windows, REA cannot open an app bundle whose `Info.plist` is a binary plist. Every command that takes the bundle fails with `could not decode app Info.plist <path> (ENOENT)`, because the executable name is read by running `/usr/bin/plutil`, which only exists on macOS. iOS apps (extracted from an IPA) ship a binary `Info.plist`, so this blocks `trace-dylib-resolution`, `inspect-artifact`, `extract-artifact` and `decode-interface-builder` on those bundles off macOS, even though the dylib guide says that tool "also works off macOS". ## Problem and expected behavior Trigger: a `.app` (macOS `Contents/Info.plist` or flat iOS `Info.plist` layout) whose `Info.plist` starts with `bplist00`, opened on a non-macOS host. Before: `resolveLayoutExecutable` sends binary plists to `readBinaryPlistExecutable`, which runs `/usr/bin/plutil -extract CFBundleExecutable raw` (`src/application/AppBundleExecutable.ts:257-260`). On Linux the spawn fails with ENOENT and the bundle is rejected. The same bundle with an XML `Info.plist` opens fine. Expected: the bundle opens on any host. `docs/native-investigation.md:62` already says bundle `Info.plist` resolution "uses the same decoder as keyed archives and Interface Builder", which is true today only for XML. ## Change and scope No related issue; this is a small, self-contained fix, so it comes straight as a PR. Binary `Info.plist` files are now decoded in-process with `plist.parseBinary`, as `InterfaceBuilderAnalysis` already does, after the same `estimatePropertyListDecodeBytes` preflight it runs (bounded here at 16 MiB). The top-level `CFBundleExecutable` lookup is shared with the XML path. Name safety and program-directory containment checks are unchanged. The injectable `decodeBinaryPlist` hook stays for tests. Not included: `inspect-plist` and other tools that are documented as macOS-only. ## Contract and boundary impact - Semantic owner and earliest changed stage: app bundle executable resolution (`AppBundleExecutable.ts`), before any provider runs. - CLI and MCP/tool-catalog contract: none. - Provider, bridge, target-format, or platform compatibility: app bundles with binary `Info.plist` now open on Linux and Windows; macOS no longer spawns `plutil` for this lookup. - Evidence, artifact, provenance, or reconstruction contract: none. - Process execution, authorization, cleanup, or containment impact: one less child process; containment checks unchanged. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none (`plist` is already a dependency). ## Evidence and regression coverage - Tests added or updated: `binary Info.plist app bundle targets` in `tests/boundary/filesystem/binaryTarget.test.ts` builds a macOS-layout and a flat iOS-layout bundle with a binary `Info.plist` (names `Ordinary`, ` App `, `Tab\t`) and resolves each through `parseBinaryTarget`. Both cases fail on current main on Linux and pass with this change. The existing macOS-only test that converts plists with real `plutil -convert binary1` still covers macOS-produced files. - Base reproduction or other evidence: executed on Linux x64 with rea-agents 6.2.0 and main. Same arm64 Mach-O bundle, two `Info.plist` forms: ``` XML.app trace-dylib-resolution: Contents/MacOS/app -> @rpath/libA.dylib resolved, ... Binary.app trace-dylib-resolution: Cannot open artifact: could not decode app Info.plist .../Binary.app/Contents/Info.plist (ENOENT). Payload/Demo.app (unzipped IPA): same ENOENT ``` With this change all three open, and `Binary.app` reports the same roots, edges, findings and coverage as `XML.app`. `inspect-artifact`, `extract-artifact` and `decode-interface-builder` also succeed on `Binary.app`. - User-visible CLI/MCP output: the lines above. - Remaining proof gaps: not run on macOS or Windows here. ## Validation performed - `npx vitest run tests/boundary/filesystem/binaryTarget.test.ts` — 42 passed, 1 skipped (macOS-only); the 2 new cases fail with the fix reverted. - `npm run check:fast` — passed. - `npx vitest run --project <name>` for each project except `acceptance`, after `npm run build:cached` — 6146 passed. The 16 failures (firmware tests that need Binwalk 3.1.x, and ASAR tests whose unpacked companion file is missing here) fail the same way on untouched main. `acceptance` was not run (out of memory on this machine); its app-bundle test is macOS-only. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: Linux only; no Hopper/Ghidra workflow touched. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the bundle's `Info.plist` is untrusted input; decoding is bounded by the existing binary plist preflight before `parseBinary` runs. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:11:45 +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#2914
No description provided.