[GH-ISSUE #3359] [Bug] A regular Contents resource misclassifies a shallow IPA bundle as macOS #1226

Closed
opened 2026-10-16 21:03:01 +00:00 by bsakaguchi · 1 comment
Owner

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

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

project-apple-application-graph accepts the authenticated IPA inventory produced by inspect-artifact, but a regular resource file named Contents makes a shallow application report layout: "macos-deep" and platforms: ["macos"]. The bundle has no Contents/ directory and its Info.plist is at the shallow bundle root. The projection returns info_plist_path: null, hiding the valid metadata path while declaring complete inventory coverage.

Both public CLI commands exit successfully. The original inventory retains the Contents occurrence as entry_kind: "file" and retains the separate shallow Info.plist occurrence. The contrary layout appears when that authenticated inventory is projected. The public CLI reproduction was observed and locally reproduced with complete reproduction inputs.

Reproduction

  1. Start from a clean checkout of https://github.com/morluto/rea at 8dde6370d0809ad04cda3342d4e6f290f48aa063. From the repository root, install the locked dependencies and build the runtime:
npm ci
npm run build:cached
  1. Run the following from the repository root. It creates every input inline, uses a temporary working directory and an isolated configuration home, invokes both public commands, and prints only the relevant projection fields:
python3 - <<'PYTHON'
import json
import os
from pathlib import Path
import subprocess
import tempfile
import zipfile

with tempfile.TemporaryDirectory(prefix="rea-apple-probe-") as directory:
    work = Path(directory)
    (work / "home").mkdir()
    env = dict(os.environ, HOME=str(work / "home"))
    archive = work / "sample.ipa"
    with zipfile.ZipFile(archive, "w") as output:
        output.writestr("Payload/Mobile.app/Info.plist", "<plist><dict/></plist>")
        output.writestr("Payload/Mobile.app/Contents", "ordinary resource")
    inspected = subprocess.run(
        ["node", "scripts/rea.mjs", "inspect-artifact", str(archive),
         "--format", "json", "--full-output"],
        env=env, text=True, capture_output=True, check=True,
    )
    evidence = json.loads(inspected.stdout)["data"]
    request = work / "request.json"
    request.write_text(json.dumps({"inventory_evidence": [evidence]}))
    projected = subprocess.run(
        ["node", "scripts/rea.mjs", "project-apple-application-graph", str(request),
         "--format", "json", "--full-output"],
        env=env, text=True, capture_output=True, check=True,
    )
    result = json.loads(projected.stdout)["data"]["normalized_result"]
    print(json.dumps({"platforms": result["platforms"],
                      "bundles": result["bundles"],
                      "coverage": result["coverage"]}, indent=2))
PYTHON
  1. Read the printed bundle, platform, and coverage fields. The archive contains exactly two regular files, Payload/Mobile.app/Info.plist with content <plist><dict/></plist> and Payload/Mobile.app/Contents with content ordinary resource. No explicit directory entry or nested path establishes a Contents/ directory.

  2. Repeat step 2 with only the resource path Payload/Mobile.app/Contents changed to Payload/Mobile.app/Resource. This control was observed to restore layout: "shallow", info_plist_path: "Payload/Mobile.app/Info.plist", and platforms: ["ios"].

  3. For the deep-layout control, repeat step 2 with the Info.plist archive path changed to Payload/Mobile.app/Contents/Info.plist and omit the output.writestr("Payload/Mobile.app/Contents", "ordinary resource") line. A separate control containing this real hierarchy was observed to report macos-deep, macos, and the deep Info.plist path.

Expected behavior

The application remains shallow, its info_plist_path is Payload/Mobile.app/Info.plist, and platforms is ["ios"]. The macOS layout requires a Contents/ directory. A regular resource file with that name does not establish the directory convention.

docs/apple-application-analysis.md:95-99 defines each layout by its content directory. docs/apple-application-analysis.md:85-87 defines the macOS and shallow iOS platform rules.

Impact and scope

Impact level: P2, supported. The supported IPA projection returns incorrect application anatomy and platform metadata and omits the usable Info.plist path for this bounded input. The source files remain intact.

Workaround status: available. The public inspection's nested inventory retains both exact file paths and their entry kinds, so an analyst can determine the shallow layout and recover the Info.plist path manually without changing the input.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1. CPU architecture was not recorded. The reproduction uses the built local CLI and requires no analysis engine or target execution.

Target and artifact context

A synthetic ZIP-format IPA containing a shallow Payload/Mobile.app with exactly two regular resource files and no explicit directory entries. The script obtains authenticated inventory Evidence through inspect-artifact, writes the projection request as request.json, and submits it through project-apple-application-graph. The regular Contents resource and valid shallow Info.plist both remain present in the original inventory.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The observed projection returned:

{
  "platforms": ["macos"],
  "bundles": [{
    "path": "Payload/Mobile.app",
    "parent_path": null,
    "layout": "macos-deep",
    "role": "application",
    "role_basis": "path-convention",
    "info_plist_path": null,
    "executable_candidates": [],
    "signing_paths": []
  }],
  "coverage": {
    "status": "complete-within-inventory",
    "inventory_complete": true
  }
}

Changing only the regular resource's name from Contents to Resource restored layout: "shallow", the shallow Info.plist path, and platforms: ["ios"]. A separate control containing Payload/Mobile.app/Contents/Info.plist correctly reported macos-deep, macos, and that deep Info.plist path.

Typed outcome or recovery expected

Run the reproduction unchanged. The bundle must report shallow, preserve info_plist_path: "Payload/Mobile.app/Info.plist", and list ios. Retain the regular Contents resource in the inventory. The control with a real Contents/Info.plist hierarchy must still report macos-deep, macos, and its deep metadata path.

Analysis or suspected cause

Source-derived reasoning: src/domain/apple/appleBundleAnatomy.ts:236-240 selects layout from child names without distinguishing a file from a directory. src/domain/apple/appleBundleAnatomy.ts:197-215 indexes both file and directory child names while retaining directory membership separately. A regular file named Contents therefore satisfies the name lookup used for macos-deep, even though the authenticated inventory identifies it as a file. The public CLI output independently establishes the wrong layout, platform, and metadata path.

Additional context

Related completed issue #1486 concerns case policy for bundle anatomy names; this failure uses the exact spelling Contents and concerns its known file entry kind.

Related completed issue #3174 concerns omitted extensionless Mach-O components outside IPA application roots; this failure changes an application's layout, platform, and Info.plist path.

Related open issue #3321 concerns trailing app-bundle separators selecting the JavaScript CLI lane; this failure occurs during projection of an already authenticated IPA inventory.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3359 ### 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 `project-apple-application-graph` accepts the authenticated IPA inventory produced by `inspect-artifact`, but a regular resource file named `Contents` makes a shallow application report `layout: "macos-deep"` and `platforms: ["macos"]`. The bundle has no `Contents/` directory and its Info.plist is at the shallow bundle root. The projection returns `info_plist_path: null`, hiding the valid metadata path while declaring complete inventory coverage. Both public CLI commands exit successfully. The original inventory retains the `Contents` occurrence as `entry_kind: "file"` and retains the separate shallow Info.plist occurrence. The contrary layout appears when that authenticated inventory is projected. The public CLI reproduction was observed and locally reproduced with complete reproduction inputs. ### Reproduction 1. Start from a clean checkout of `https://github.com/morluto/rea` at `8dde6370d0809ad04cda3342d4e6f290f48aa063`. From the repository root, install the locked dependencies and build the runtime: ```sh npm ci npm run build:cached ``` 2. Run the following from the repository root. It creates every input inline, uses a temporary working directory and an isolated configuration home, invokes both public commands, and prints only the relevant projection fields: ```sh python3 - <<'PYTHON' import json import os from pathlib import Path import subprocess import tempfile import zipfile with tempfile.TemporaryDirectory(prefix="rea-apple-probe-") as directory: work = Path(directory) (work / "home").mkdir() env = dict(os.environ, HOME=str(work / "home")) archive = work / "sample.ipa" with zipfile.ZipFile(archive, "w") as output: output.writestr("Payload/Mobile.app/Info.plist", "<plist><dict/></plist>") output.writestr("Payload/Mobile.app/Contents", "ordinary resource") inspected = subprocess.run( ["node", "scripts/rea.mjs", "inspect-artifact", str(archive), "--format", "json", "--full-output"], env=env, text=True, capture_output=True, check=True, ) evidence = json.loads(inspected.stdout)["data"] request = work / "request.json" request.write_text(json.dumps({"inventory_evidence": [evidence]})) projected = subprocess.run( ["node", "scripts/rea.mjs", "project-apple-application-graph", str(request), "--format", "json", "--full-output"], env=env, text=True, capture_output=True, check=True, ) result = json.loads(projected.stdout)["data"]["normalized_result"] print(json.dumps({"platforms": result["platforms"], "bundles": result["bundles"], "coverage": result["coverage"]}, indent=2)) PYTHON ``` 3. Read the printed bundle, platform, and coverage fields. The archive contains exactly two regular files, `Payload/Mobile.app/Info.plist` with content `<plist><dict/></plist>` and `Payload/Mobile.app/Contents` with content `ordinary resource`. No explicit directory entry or nested path establishes a `Contents/` directory. 4. Repeat step 2 with only the resource path `Payload/Mobile.app/Contents` changed to `Payload/Mobile.app/Resource`. This control was observed to restore `layout: "shallow"`, `info_plist_path: "Payload/Mobile.app/Info.plist"`, and `platforms: ["ios"]`. 5. For the deep-layout control, repeat step 2 with the Info.plist archive path changed to `Payload/Mobile.app/Contents/Info.plist` and omit the `output.writestr("Payload/Mobile.app/Contents", "ordinary resource")` line. A separate control containing this real hierarchy was observed to report `macos-deep`, `macos`, and the deep Info.plist path. ### Expected behavior The application remains `shallow`, its `info_plist_path` is `Payload/Mobile.app/Info.plist`, and `platforms` is `["ios"]`. The macOS layout requires a `Contents/` directory. A regular resource file with that name does not establish the directory convention. `docs/apple-application-analysis.md:95-99` defines each layout by its content directory. `docs/apple-application-analysis.md:85-87` defines the macOS and shallow iOS platform rules. ### Impact and scope Impact level: P2, supported. The supported IPA projection returns incorrect application anatomy and platform metadata and omits the usable Info.plist path for this bounded input. The source files remain intact. Workaround status: available. The public inspection's nested inventory retains both exact file paths and their entry kinds, so an analyst can determine the shallow layout and recover the Info.plist path manually without changing the input. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1. CPU architecture was not recorded. The reproduction uses the built local CLI and requires no analysis engine or target execution. ### Target and artifact context A synthetic ZIP-format IPA containing a shallow `Payload/Mobile.app` with exactly two regular resource files and no explicit directory entries. The script obtains authenticated inventory Evidence through `inspect-artifact`, writes the projection request as `request.json`, and submits it through `project-apple-application-graph`. The regular `Contents` resource and valid shallow Info.plist both remain present in the original inventory. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The observed projection returned: ```text { "platforms": ["macos"], "bundles": [{ "path": "Payload/Mobile.app", "parent_path": null, "layout": "macos-deep", "role": "application", "role_basis": "path-convention", "info_plist_path": null, "executable_candidates": [], "signing_paths": [] }], "coverage": { "status": "complete-within-inventory", "inventory_complete": true } } ``` Changing only the regular resource's name from `Contents` to `Resource` restored `layout: "shallow"`, the shallow Info.plist path, and `platforms: ["ios"]`. A separate control containing `Payload/Mobile.app/Contents/Info.plist` correctly reported `macos-deep`, `macos`, and that deep Info.plist path. ### Typed outcome or recovery expected Run the reproduction unchanged. The bundle must report `shallow`, preserve `info_plist_path: "Payload/Mobile.app/Info.plist"`, and list `ios`. Retain the regular `Contents` resource in the inventory. The control with a real `Contents/Info.plist` hierarchy must still report `macos-deep`, `macos`, and its deep metadata path. ### Analysis or suspected cause Source-derived reasoning: `src/domain/apple/appleBundleAnatomy.ts:236-240` selects layout from child names without distinguishing a file from a directory. `src/domain/apple/appleBundleAnatomy.ts:197-215` indexes both file and directory child names while retaining directory membership separately. A regular file named `Contents` therefore satisfies the name lookup used for `macos-deep`, even though the authenticated inventory identifies it as a file. The public CLI output independently establishes the wrong layout, platform, and metadata path. ### Additional context Related completed issue [#1486](https://github.com/morluto/rea/issues/1486) concerns case policy for bundle anatomy names; this failure uses the exact spelling `Contents` and concerns its known file entry kind. Related completed issue [#3174](https://github.com/morluto/rea/issues/3174) concerns omitted extensionless Mach-O components outside IPA application roots; this failure changes an application's layout, platform, and Info.plist path. Related open issue [#3321](https://github.com/morluto/rea/issues/3321) concerns trailing app-bundle separators selecting the JavaScript CLI lane; this failure occurs during projection of an already authenticated IPA inventory.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Fixed on main in 149a0fc06c. Adapted @bnkaiteam's directory-identity proposal and source regressions from #3505, extending the structural fix to metadata-file selection and root admission. This is not a PR merge or readiness attestation.

The owner now indexes directories (explicit and implied) separately from authenticated files. Root discovery, nested bundles, layout, metadata, executable candidates and signing paths consume those facts. Mixed child-name interpretation and later membership repairs were removed. Kind is selected before case preference; original spelling and the explicitly selected .app directory rule remain intact.

Observed validation:

  • Eight new source assertions and three public CLI layout assertions fail before the layout fix. A separate public CLI control reproduces #4779's false Fake.app root before its fix.
  • All 57 focused checks pass after the complete fix, including five built CLI workflows, real inventory, explicit/implied directories, file/symlink roots, metadata/signature shadowing, case collisions, versioned frameworks, unread-file preservation and production MCP projection.
  • Build, check:fast/typecheck/lint, docs:check (five README checks) and pre-push checks pass. Two final read-only reviews found no actionable defects.

30 completed tiny source projection tasks used five genuine authored ZIP inventories. First processes were recorded separately from two alternating repeated processes. Repeated-process medians below are observations, not evidence of a speed improvement. Peak RSS covers the known single Node process family (no children); tokens use cl100k_base estimates for full source replies.

Task Reply tokens Elapsed ms CPU ms Peak MB
shallow-resource 541→549 48.73→48.82 61.68→61.95 75.40→75.67
deep-control 566→566 49.46→49.96 62.40→63.81 75.46→75.42
case-shadow 581→590 48.88→48.55 61.94→61.69 75.24→75.22
versioned-control 829→829 48.76→49.16 61.95→61.73 75.42→75.46
false-root 447→436 49.35→48.61 62.59→61.68 75.25→75.51

Ordinary deep and versioned control replies, including projection IDs, remain byte-identical. Defect-case facts and IDs intentionally change. True-cold, whole agent/catalog, full public-task and large-input costs were not measured. Only harmless authored fixtures were inspected: no target execution, real provider, published npm or heavy profiling claim.

Research: Apple's directory-based bundle model; DeepWiki CoreFoundation layout research used for orientation, with REA's opaque-symlink and inventory rules determining this implementation.

<!-- gh-comment-id:6233574693 --> @morluto commented on GitHub (Oct 17, 2026): Fixed on main in 149a0fc06c3c67ee45624e6da14dfb0754473758. Adapted @bnkaiteam's directory-identity proposal and source regressions from #3505, extending the structural fix to metadata-file selection and root admission. This is not a PR merge or readiness attestation. The owner now indexes directories (explicit and implied) separately from authenticated files. Root discovery, nested bundles, layout, metadata, executable candidates and signing paths consume those facts. Mixed child-name interpretation and later membership repairs were removed. Kind is selected before case preference; original spelling and the explicitly selected `.app` directory rule remain intact. Observed validation: - Eight new source assertions and three public CLI layout assertions fail before the layout fix. A separate public CLI control reproduces #4779's false `Fake.app` root before its fix. - All 57 focused checks pass after the complete fix, including five built CLI workflows, real inventory, explicit/implied directories, file/symlink roots, metadata/signature shadowing, case collisions, versioned frameworks, unread-file preservation and production MCP projection. - Build, check:fast/typecheck/lint, docs:check (five README checks) and pre-push checks pass. Two final read-only reviews found no actionable defects. 30 completed tiny source projection tasks used five genuine authored ZIP inventories. First processes were recorded separately from two alternating repeated processes. Repeated-process medians below are observations, not evidence of a speed improvement. Peak RSS covers the known single Node process family (no children); tokens use cl100k_base estimates for full source replies. | Task | Reply tokens | Elapsed ms | CPU ms | Peak MB | | --- | --- | --- | --- | --- | | shallow-resource | 541→549 | 48.73→48.82 | 61.68→61.95 | 75.40→75.67 | | deep-control | 566→566 | 49.46→49.96 | 62.40→63.81 | 75.46→75.42 | | case-shadow | 581→590 | 48.88→48.55 | 61.94→61.69 | 75.24→75.22 | | versioned-control | 829→829 | 48.76→49.16 | 61.95→61.73 | 75.42→75.46 | | false-root | 447→436 | 49.35→48.61 | 62.59→61.68 | 75.25→75.51 | Ordinary deep and versioned control replies, including projection IDs, remain byte-identical. Defect-case facts and IDs intentionally change. True-cold, whole agent/catalog, full public-task and large-input costs were not measured. Only harmless authored fixtures were inspected: no target execution, real provider, published npm or heavy profiling claim. Research: [Apple's directory-based bundle model](https://developer.apple.com/documentation/bundleresources/placing-content-in-a-bundle); DeepWiki CoreFoundation layout research used for orientation, with REA's opaque-symlink and inventory rules determining this implementation.
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#1226
No description provided.