[GH-ISSUE #6442] [Bug] decode-interface-builder reports a plain plist as a decoded nib with complete archive coverage #3856

Closed
opened 2026-10-31 00:44:41 +00:00 by bsakaguchi · 0 comments
Owner

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

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

node scripts/rea.mjs decode-interface-builder "$WORK/Plain.app" --json accepts an ordinary XML property-list dictionary named Plain.nib as a decoded Interface Builder document. It exits 0, returns a zero-object nib document and a scene node with evidence described as compiled document, and reports archive_decode as complete with no invalid-archive limitation.

The fixture contains only CFBundleIdentifier; it has no NIBArchive bytes, NSKeyedArchiver header/object table, or ibtool document fields. Object and connection facets also report complete with zero examined entries. Hierarchy and class facets are unsupported, so this is not a claim that every facet reports complete.

Reproduction

From a fresh checkout at 9eb829aca34be36ff112826337996f6139f82eff, prepare its locked dependencies and runtime:

npm ci && npm run build:cached

Then create an inert app bundle and invoke the CLI:

export WORK="$(mktemp -d)"
mkdir -p "$WORK/home" "$WORK/Plain.app/Contents/Resources"
cat > "$WORK/Plain.app/Contents/Info.plist" <<'PLIST'
<plist version="1.0"><dict><key>CFBundleIdentifier</key><string>org.example.plain</string></dict></plist>
PLIST
cat > "$WORK/Plain.app/Contents/Resources/Plain.nib" <<'PLIST'
<plist version="1.0"><dict><key>CFBundleIdentifier</key><string>org.example.plain</string></dict></plist>
PLIST
HOME="$WORK/home" node scripts/rea.mjs decode-interface-builder "$WORK/Plain.app" --json

The reduced plain-dictionary input reproduced in 2/2 CLI runs on the supplied prepared build. An equivalent larger fixture also reproduced in 2/2 runs. Dependency installation and rebuilding were preparation instructions, not rerun for these observations. No app executable, target execution or native analysis provider is needed.

Expected behavior

Recognize that the selected file is not an Interface Builder archive and retain an explicit invalid/unsupported outcome for its path. Do not present it as a successfully decoded compiled document with complete archive coverage.

The documented command contract describes reading compiled storyboard/nib resources and says unsupported archive forms remain explicit. The record parser contract covers ibtool dictionaries or compiled keyed archives.

Impact and scope

P2: a malformed, misnamed or repackaged resource can appear to be a decoded empty nib, giving callers false archive/object/connection completeness and compiled-document evidence. The demonstrated effect is limited to the synthetic plain XML dictionary; real-application frequency and other plist shapes are unknown. Manual inspection of the source bytes can identify the wrong format but does not restore truthful automatic coverage. No persistent corruption or security-boundary crossing was observed.

REA version

6.7.0 (source 9eb829aca3)

Runtime and platform

Node.js v26.12.1; macOS 27.0.1; arm64.

Target and artifact context

Synthetic Apple .app resource bundle containing a minimal Info.plist and an ordinary XML dictionary named Contents/Resources/Plain.nib. No executable or native provider.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed CLI output, reduced to relevant fields:

{
  "documents": [{
    "relative_path": "Contents/Resources/Plain.nib",
    "document_kind": "nib",
    "object_count": 0,
    "connection_count": 0,
    "hierarchy_complete": false
  }],
  "archive_decode": {
    "facet": "archive_decode",
    "status": "complete",
    "reason": null,
    "examined": 1,
    "omitted": 0
  },
  "truncated": false
}

Here archive_decode is the selected row from normalized_result.graph.coverage, and truncated is normalized_result.graph.truncated. The graph also contains one scene node for the resource. Limitations contain only generic notices, with no path-specific invalid/unsupported-archive explanation.

Discriminating controls, one CLI run each on the same bundle/path: replacing the resource with <bogus/> returns no documents or nodes, partial archive coverage with one_or_more_archives_invalid, and a path-specific plist decode limitation. Replacing it with an ibtool dictionary containing an NSButton named Build returns that control and object_count 1.

Typed outcome or recovery expected

Report the unrecognized archive form explicitly and keep archive coverage partial/unsupported rather than complete. A decoded-empty document must not stand in for failed format recognition. Preserve successful decoding of recognized archive forms.

Analysis or suspected cause

Source-derived: the analyzer appends any successfully parsed plist without first checking its Interface Builder shape. The record parser treats missing ibtool fields as empty after keyed-archive recognition fails. The coverage calculation then sees no invalid or omitted archive and reports complete.

Additional context

Searched open and closed issues. Related #5912 concerns budget retention after an actual decode rejection; #6066 concerns read/identity failures; #3958 concerns unresolved objects in recognized keyed archives; #3876 concerns integer precision. None describes accepting a generic dictionary as a compiled document.

NOT_ENOUGH_INFO: no last known good version or real-app prevalence established. Binary plist variants, MCP transport and a real ibtool-compiled nib were not executed. The ibtool control above is a synthetic supported dictionary, not a compiled native fixture.

Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6442 ### 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 `node scripts/rea.mjs decode-interface-builder "$WORK/Plain.app" --json` accepts an ordinary XML property-list dictionary named `Plain.nib` as a decoded Interface Builder document. It exits 0, returns a zero-object nib document and a scene node with evidence described as `compiled document`, and reports `archive_decode` as complete with no invalid-archive limitation. The fixture contains only `CFBundleIdentifier`; it has no NIBArchive bytes, NSKeyedArchiver header/object table, or ibtool document fields. Object and connection facets also report complete with zero examined entries. Hierarchy and class facets are unsupported, so this is not a claim that every facet reports complete. ### Reproduction From a fresh checkout at `9eb829aca34be36ff112826337996f6139f82eff`, prepare its locked dependencies and runtime: ```sh npm ci && npm run build:cached ``` Then create an inert app bundle and invoke the CLI: ```sh export WORK="$(mktemp -d)" mkdir -p "$WORK/home" "$WORK/Plain.app/Contents/Resources" cat > "$WORK/Plain.app/Contents/Info.plist" <<'PLIST' <plist version="1.0"><dict><key>CFBundleIdentifier</key><string>org.example.plain</string></dict></plist> PLIST cat > "$WORK/Plain.app/Contents/Resources/Plain.nib" <<'PLIST' <plist version="1.0"><dict><key>CFBundleIdentifier</key><string>org.example.plain</string></dict></plist> PLIST HOME="$WORK/home" node scripts/rea.mjs decode-interface-builder "$WORK/Plain.app" --json ``` The reduced plain-dictionary input reproduced in 2/2 CLI runs on the supplied prepared build. An equivalent larger fixture also reproduced in 2/2 runs. Dependency installation and rebuilding were preparation instructions, not rerun for these observations. No app executable, target execution or native analysis provider is needed. ### Expected behavior Recognize that the selected file is not an Interface Builder archive and retain an explicit invalid/unsupported outcome for its path. Do not present it as a successfully decoded compiled document with complete archive coverage. The [documented command contract](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/docs/native-investigation.md#L448-L461) describes reading compiled storyboard/nib resources and says unsupported archive forms remain explicit. The [record parser contract](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/apple/interfaceBuilderKeyedArchive.ts#L68-L88) covers ibtool dictionaries or compiled keyed archives. ### Impact and scope P2: a malformed, misnamed or repackaged resource can appear to be a decoded empty nib, giving callers false archive/object/connection completeness and compiled-document evidence. The demonstrated effect is limited to the synthetic plain XML dictionary; real-application frequency and other plist shapes are unknown. Manual inspection of the source bytes can identify the wrong format but does not restore truthful automatic coverage. No persistent corruption or security-boundary crossing was observed. ### REA version 6.7.0 (source 9eb829aca34be36ff112826337996f6139f82eff) ### Runtime and platform Node.js v26.12.1; macOS 27.0.1; arm64. ### Target and artifact context Synthetic Apple .app resource bundle containing a minimal Info.plist and an ordinary XML dictionary named Contents/Resources/Plain.nib. No executable or native provider. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed CLI output, reduced to relevant fields: ```json { "documents": [{ "relative_path": "Contents/Resources/Plain.nib", "document_kind": "nib", "object_count": 0, "connection_count": 0, "hierarchy_complete": false }], "archive_decode": { "facet": "archive_decode", "status": "complete", "reason": null, "examined": 1, "omitted": 0 }, "truncated": false } ``` Here `archive_decode` is the selected row from `normalized_result.graph.coverage`, and `truncated` is `normalized_result.graph.truncated`. The graph also contains one `scene` node for the resource. Limitations contain only generic notices, with no path-specific invalid/unsupported-archive explanation. Discriminating controls, one CLI run each on the same bundle/path: replacing the resource with `<bogus/>` returns no documents or nodes, partial archive coverage with `one_or_more_archives_invalid`, and a path-specific plist decode limitation. Replacing it with an ibtool dictionary containing an NSButton named Build returns that control and object_count 1. ### Typed outcome or recovery expected Report the unrecognized archive form explicitly and keep archive coverage partial/unsupported rather than complete. A decoded-empty document must not stand in for failed format recognition. Preserve successful decoding of recognized archive forms. ### Analysis or suspected cause Source-derived: the [analyzer appends any successfully parsed plist](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/artifacts/apple/InterfaceBuilderAnalysis.ts#L176-L208) without first checking its Interface Builder shape. The [record parser](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/apple/interfaceBuilderKeyedArchive.ts#L82-L88) treats missing ibtool fields as empty after keyed-archive recognition fails. The [coverage calculation](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/artifacts/apple/InterfaceBuilderAnalysis.ts#L333-L384) then sees no invalid or omitted archive and reports complete. ### Additional context Searched open and closed issues. Related #5912 concerns budget retention after an actual decode rejection; #6066 concerns read/identity failures; #3958 concerns unresolved objects in recognized keyed archives; #3876 concerns integer precision. None describes accepting a generic dictionary as a compiled document. NOT_ENOUGH_INFO: no last known good version or real-app prevalence established. Binary plist variants, MCP transport and a real ibtool-compiled nib were not executed. The ibtool control above is a synthetic supported dictionary, not a compiled native fixture.
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#3856
No description provided.