[GH-ISSUE #5727] [Bug] Unread archive members lose known bundle plist and signing paths #3472

Closed
opened 2026-10-31 00:40:52 +00:00 by bsakaguchi · 0 comments
Owner

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

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 inventories an encrypted IPA's Payload/Mobile.app/Info.plist, and project-apple-application-graph retains that exact path in unavailable_components. However, the projection returns info_plist_path: null and platforms: []. The readable archive returns the known plist path and platforms: ["ios"].

An encrypted macOS ZIP also loses inventoried app/framework plist paths and the app's CodeResources signing path. Its single-version framework gains a false several-versions-or-missing-plist limitation. Bundle roots, layouts, the macOS platform, unavailable entries, and partial coverage survive.

Reproduction

From a built source checkout with locked dependencies installed, run the following. It uses system zip, unzip, and jq, creates every fixture locally, and isolates REA configuration. No executable, Xcode, provider, target execution, or decryption is required.

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/Payload/Mobile.app"
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' \
  '<plist version="1.0"><dict><key>CFBundleIdentifier</key><string>example.mobile</string></dict></plist>' \
  > "$work_dir/Payload/Mobile.app/Info.plist"
(cd "$work_dir" && zip -q -r -P fixture-only encrypted.ipa Payload)
(cd "$work_dir" && zip -q -r readable.ipa Payload)
unzip -l "$work_dir/encrypted.ipa"
rm "$work_dir/Payload/Mobile.app/Info.plist"
printf 'fixture resource\n' > "$work_dir/Payload/Mobile.app/resource.txt"
(cd "$work_dir" && zip -q -r missing.ipa Payload)
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 "$@"
}
project_case() {
  case_name="$1"
  archive="$2"
  run_rea inspect-artifact "$archive" --json > "$work_dir/$case_name-inventory.json"
  jq '{inventory_evidence: [.normalized_result.substeps[0].evidence]}' \
    "$work_dir/$case_name-inventory.json" > "$work_dir/$case_name-input.json"
  run_rea project-apple-application-graph "$work_dir/$case_name-input.json" --json \
    > "$work_dir/$case_name-projection.json"
  jq --arg case "$case_name" '.normalized_result | {case:$case, platforms,
    bundles: [.bundles[] | {path, info_plist_path, signing_paths}],
    unavailable: [.unavailable_components[].path], coverage,
    framework_limitations: [.limitations[] | select(contains("versioned framework"))]}' \
    "$work_dir/$case_name-projection.json"
}
for case_name in encrypted readable missing; do
  project_case "$case_name" "$work_dir/$case_name.ipa"
done

For the related macOS variant, continue in the same shell:

mkdir -p "$work_dir/Desk.app/Contents/Frameworks/Kit.framework/Versions/A/Resources" \
  "$work_dir/Desk.app/Contents/_CodeSignature"
printf '<plist version="1.0"><dict/></plist>\n' > "$work_dir/Desk.app/Contents/Info.plist"
cp "$work_dir/Desk.app/Contents/Info.plist" \
  "$work_dir/Desk.app/Contents/Frameworks/Kit.framework/Versions/A/Resources/Info.plist"
cp "$work_dir/Desk.app/Contents/Info.plist" \
  "$work_dir/Desk.app/Contents/_CodeSignature/CodeResources"
(cd "$work_dir" && zip -q -r -P fixture-only desk-encrypted.zip Desk.app)
(cd "$work_dir" && zip -q -r desk-readable.zip Desk.app)
project_case desk-encrypted "$work_dir/desk-encrypted.zip"
project_case desk-readable "$work_dir/desk-readable.zip"

The independent verifier ran these five fixture cases at the baseline. The submission recheck ran them again on current main; all ten inspection/projection calls exited 0 in each run. The snippets consolidate the executed commands into a shared helper.

Expected behavior

Both IPA variants should retain Payload/Mobile.app/Info.plist and the documented shallow-app ios platform. Both macOS variants should retain their inventoried app/framework plist and CodeResources paths. Unread contents should retain unavailable identity and partial coverage. The missing-plist control should keep a null plist path and no platform.

The Apple guide supports encrypted members as unavailable components. Its platform and anatomy contract derives these facts from paths. Its framework selection rule selects the plist for one real version and states that projection does not read plists.

Impact and scope

P2: supported unavailable-member inventories lose known metadata/signing paths and the shallow platform derived from them. One framework limitation becomes inaccurate. The original inventory and unavailable_components retain the exact paths, so callers can recover them manually and reconstruct bundle associations. Real-world prevalence, other unread-content causes, and MCP execution were not tested. No data mutation or security boundary crossing was observed.

REA version

rea-agents 6.7.0; verified baseline 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Rebuilt and reproduced on fetched origin/main at d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1; arm64; system Info-ZIP 3.0. No external analysis provider involved.

Target and artifact context

Self-built ZIP-format IPAs and macOS ZIPs containing inert plist/resource data. CodeResources contains placeholder plist bytes; this establishes signing-path classification only, with no valid-signature claim. Projection receives the actual inventory Evidence produced by the public inspector.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The encrypted IPA returned:

{
  "platforms": [],
  "bundles": [{"path": "Payload/Mobile.app", "info_plist_path": null, "signing_paths": []}],
  "unavailable": ["Payload/Mobile.app/Info.plist"],
  "coverage": {"status": "partial", "inventory_complete": true}
}

The readable IPA returned platforms: ["ios"] and info_plist_path: "Payload/Mobile.app/Info.plist". The missing-plist control returned no platform and a null plist path. unzip -l independently lists the encrypted archive's plist.

The encrypted macOS case retained all three metadata/signing paths in unavailable_components, but returned null app/framework plist paths and empty signing arrays. It also returned:

A versioned framework has several version directories or no Versions/*/Resources/Info.plist; the Info.plist that Versions/Current selects is unknown because symlink targets are not inventoried.

The readable macOS control returned both plist paths and Desk.app/Contents/_CodeSignature/CodeResources, without that limitation. Evidence provenance is observed; local verification is reproduced through the built public CLI on both revisions.

Typed outcome or recovery expected

Retain known positive file-path facts in bundle anatomy while preserving unavailable content identity, partial coverage, and unknown executable classification. Keep the missing-plist control and genuine multiple-framework-version ambiguity unchanged. No decryption or plist-semantic interpretation is needed to retain paths.

Analysis or suspected cause

Source-derived reasoning: src/domain/mobileApplicationGraphProjection.ts:106-130 retains null-identity occurrences without a readable component. src/domain/apple/appleBundleAnatomy.ts:182-200 excludes those files from its name index. Named-file selection at lines 300-307 and shallow platform derivation at lines 364-375 consume that index. src/domain/apple/appleApplication.ts:401-406 derives the framework limitation from the resulting null plist path. These source ranges are unchanged between the baseline and rechecked main.

Additional context

Still reproduces after #4688, fixed in c192c5881 before this baseline. That fix correctly retains unavailable entries and partial coverage. Its reproduction leaves Info.plist readable and encrypts a dylib; the uncovered contract here is bundle anatomy for unread metadata/signing files. Closed PR #4692 covers that earlier omission and coverage fix.

#3359, #4779, and closed PR #3505 concern file/directory identity; #1486 concerns case policy. These fixtures use canonical names and real directories, with no misleading Contents leaf. Open #4302 requests plist semantics, which this report does not require. Prepared with AI assistance and independent verification. No fix, successful restoration, or earlier known-good version has been established.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5727 ### 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` inventories an encrypted IPA's `Payload/Mobile.app/Info.plist`, and `project-apple-application-graph` retains that exact path in `unavailable_components`. However, the projection returns `info_plist_path: null` and `platforms: []`. The readable archive returns the known plist path and `platforms: ["ios"]`. An encrypted macOS ZIP also loses inventoried app/framework plist paths and the app's CodeResources signing path. Its single-version framework gains a false several-versions-or-missing-plist limitation. Bundle roots, layouts, the macOS platform, unavailable entries, and partial coverage survive. ### Reproduction From a built source checkout with locked dependencies installed, run the following. It uses system `zip`, `unzip`, and `jq`, creates every fixture locally, and isolates REA configuration. No executable, Xcode, provider, target execution, or decryption is required. ```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/Payload/Mobile.app" printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' \ '<plist version="1.0"><dict><key>CFBundleIdentifier</key><string>example.mobile</string></dict></plist>' \ > "$work_dir/Payload/Mobile.app/Info.plist" (cd "$work_dir" && zip -q -r -P fixture-only encrypted.ipa Payload) (cd "$work_dir" && zip -q -r readable.ipa Payload) unzip -l "$work_dir/encrypted.ipa" rm "$work_dir/Payload/Mobile.app/Info.plist" printf 'fixture resource\n' > "$work_dir/Payload/Mobile.app/resource.txt" (cd "$work_dir" && zip -q -r missing.ipa Payload) 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 "$@" } project_case() { case_name="$1" archive="$2" run_rea inspect-artifact "$archive" --json > "$work_dir/$case_name-inventory.json" jq '{inventory_evidence: [.normalized_result.substeps[0].evidence]}' \ "$work_dir/$case_name-inventory.json" > "$work_dir/$case_name-input.json" run_rea project-apple-application-graph "$work_dir/$case_name-input.json" --json \ > "$work_dir/$case_name-projection.json" jq --arg case "$case_name" '.normalized_result | {case:$case, platforms, bundles: [.bundles[] | {path, info_plist_path, signing_paths}], unavailable: [.unavailable_components[].path], coverage, framework_limitations: [.limitations[] | select(contains("versioned framework"))]}' \ "$work_dir/$case_name-projection.json" } for case_name in encrypted readable missing; do project_case "$case_name" "$work_dir/$case_name.ipa" done ``` For the related macOS variant, continue in the same shell: ```sh mkdir -p "$work_dir/Desk.app/Contents/Frameworks/Kit.framework/Versions/A/Resources" \ "$work_dir/Desk.app/Contents/_CodeSignature" printf '<plist version="1.0"><dict/></plist>\n' > "$work_dir/Desk.app/Contents/Info.plist" cp "$work_dir/Desk.app/Contents/Info.plist" \ "$work_dir/Desk.app/Contents/Frameworks/Kit.framework/Versions/A/Resources/Info.plist" cp "$work_dir/Desk.app/Contents/Info.plist" \ "$work_dir/Desk.app/Contents/_CodeSignature/CodeResources" (cd "$work_dir" && zip -q -r -P fixture-only desk-encrypted.zip Desk.app) (cd "$work_dir" && zip -q -r desk-readable.zip Desk.app) project_case desk-encrypted "$work_dir/desk-encrypted.zip" project_case desk-readable "$work_dir/desk-readable.zip" ``` The independent verifier ran these five fixture cases at the baseline. The submission recheck ran them again on current main; all ten inspection/projection calls exited 0 in each run. The snippets consolidate the executed commands into a shared helper. ### Expected behavior Both IPA variants should retain `Payload/Mobile.app/Info.plist` and the documented shallow-app `ios` platform. Both macOS variants should retain their inventoried app/framework plist and CodeResources paths. Unread contents should retain unavailable identity and partial coverage. The missing-plist control should keep a null plist path and no platform. The [Apple guide](https://github.com/morluto/rea/blob/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f/docs/apple-application-analysis.md#L63-L70) supports encrypted members as unavailable components. Its [platform and anatomy contract](https://github.com/morluto/rea/blob/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f/docs/apple-application-analysis.md#L103-L111) derives these facts from paths. Its [framework selection rule](https://github.com/morluto/rea/blob/d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f/docs/apple-application-analysis.md#L147-L159) selects the plist for one real version and states that projection does not read plists. ### Impact and scope P2: supported unavailable-member inventories lose known metadata/signing paths and the shallow platform derived from them. One framework limitation becomes inaccurate. The original inventory and `unavailable_components` retain the exact paths, so callers can recover them manually and reconstruct bundle associations. Real-world prevalence, other unread-content causes, and MCP execution were not tested. No data mutation or security boundary crossing was observed. ### REA version `rea-agents` 6.7.0; verified baseline `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Rebuilt and reproduced on fetched `origin/main` at `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1; arm64; system Info-ZIP 3.0. No external analysis provider involved. ### Target and artifact context Self-built ZIP-format IPAs and macOS ZIPs containing inert plist/resource data. CodeResources contains placeholder plist bytes; this establishes signing-path classification only, with no valid-signature claim. Projection receives the actual inventory Evidence produced by the public inspector. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The encrypted IPA returned: ```json { "platforms": [], "bundles": [{"path": "Payload/Mobile.app", "info_plist_path": null, "signing_paths": []}], "unavailable": ["Payload/Mobile.app/Info.plist"], "coverage": {"status": "partial", "inventory_complete": true} } ``` The readable IPA returned `platforms: ["ios"]` and `info_plist_path: "Payload/Mobile.app/Info.plist"`. The missing-plist control returned no platform and a null plist path. `unzip -l` independently lists the encrypted archive's plist. The encrypted macOS case retained all three metadata/signing paths in `unavailable_components`, but returned null app/framework plist paths and empty signing arrays. It also returned: ```text A versioned framework has several version directories or no Versions/*/Resources/Info.plist; the Info.plist that Versions/Current selects is unknown because symlink targets are not inventoried. ``` The readable macOS control returned both plist paths and `Desk.app/Contents/_CodeSignature/CodeResources`, without that limitation. Evidence provenance is observed; local verification is reproduced through the built public CLI on both revisions. ### Typed outcome or recovery expected Retain known positive file-path facts in bundle anatomy while preserving unavailable content identity, partial coverage, and unknown executable classification. Keep the missing-plist control and genuine multiple-framework-version ambiguity unchanged. No decryption or plist-semantic interpretation is needed to retain paths. ### Analysis or suspected cause Source-derived reasoning: `src/domain/mobileApplicationGraphProjection.ts:106-130` retains null-identity occurrences without a readable component. `src/domain/apple/appleBundleAnatomy.ts:182-200` excludes those files from its name index. Named-file selection at lines 300-307 and shallow platform derivation at lines 364-375 consume that index. `src/domain/apple/appleApplication.ts:401-406` derives the framework limitation from the resulting null plist path. These source ranges are unchanged between the baseline and rechecked main. ### Additional context Still reproduces after [#4688](https://github.com/morluto/rea/issues/4688), fixed in `c192c5881` before this baseline. That fix correctly retains unavailable entries and partial coverage. Its reproduction leaves Info.plist readable and encrypts a dylib; the uncovered contract here is bundle anatomy for unread metadata/signing files. Closed [PR #4692](https://github.com/morluto/rea/pull/4692) covers that earlier omission and coverage fix. [#3359](https://github.com/morluto/rea/issues/3359), #4779, and closed PR #3505 concern file/directory identity; #1486 concerns case policy. These fixtures use canonical names and real directories, with no misleading Contents leaf. Open #4302 requests plist semantics, which this report does not require. Prepared with AI assistance and independent verification. No fix, successful restoration, or earlier known-good version has been established.
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#3472
No description provided.