[GH-ISSUE #8407] [Bug] decode-interface-builder omits a nib with an empty DOCTYPE internal subset as aggregate decode budget exhaustion #6590

Open
opened 2026-11-01 21:12:29 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Plastic-time on GitHub (Nov 1, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8407

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

decode-interface-builder omits a 694-byte XML keyed-archive nib whose DOCTYPE carries an empty internal subset ([]). Coverage and limitations attribute the omission to exhaustion of the aggregate decode budget:

coverage: {"facet":"archive_decode","status":"partial","reason":"aggregate_decode_budget_exhausted","examined":1,"omitted":1}
limitations: "Panel.nib: omitted because aggregate decode budget exhausted."

When the [] is removed and nothing else changes, the same bundle reports archive_decode complete with 0 omitted. inspect-keyed-archive decodes the exact bytes of the failing nib without error.

The subset declares nothing, so nothing can expand. The pinned XML decoder also never expands internally declared entities. A reference to one fails as undefined entity; see the evidence below. The refusal therefore guards against an expansion that cannot happen, and it reports a budget overrun for a sub-kilobyte archive.

Reproduction

All steps were run with the CLI built from main (npm run build:cached, then node scripts/rea.mjs ...). Each case was run at least once, and repeated runs gave the same result.

  1. Create Example.app/Info.plist with an ordinary DOCTYPE:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0"><dict><key>CFBundleIdentifier</key><string>com.example.demo</string><key>CFBundleName</key><string>Example</string><key>CFBundlePackageType</key><string>APPL</string></dict></plist>
    
  2. Create Example.app/Panel.nib. It is a minimal XML NSKeyedArchiver archive, and its DOCTYPE ends in []:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" []>
    <plist version="1.0"><dict><key>$archiver</key><string>NSKeyedArchiver</string><key>$objects</key><array><string>$null</string><dict><key>$class</key><dict><key>CF$UID</key><integer>2</integer></dict><key>title</key><string>Build</string></dict><dict><key>$classname</key><string>NSButton</string><key>$classes</key><array><string>NSButton</string><string>NSObject</string></array></dict></array><key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict><key>$version</key><integer>100000</integer></dict></plist>
    
  3. Run rea decode-interface-builder Example.app --format json. The nib is omitted with aggregate_decode_budget_exhausted.

  4. Delete [] from Panel.nib and run step 3 again. archive_decode is complete, with 0 omitted.

  5. Run rea inspect-keyed-archive Panel.nib --format json on the step 2 file. It exits 0 and decodes the xml-plist archive.

Variants with the same outcome: the subset [ ], the comment-only subset [<!-- no declarations -->], and [<!ENTITY unused "x">] with no reference to the entity. Each is omitted by decode-interface-builder and decoded by inspect-keyed-archive.

Expected behavior

One of two outcomes, at the maintainers' discretion:

  1. An internal subset that the decoder neither expands nor needs is decoded, as inspect_keyed_archive already does. The tests appear to intend this. InterfaceBuilderDecodeBudget.test.ts preflights inert DOCTYPE examples "without treating it as entity expansion". Its refusal case is titled "continues to refuse an actual internal entity declaration before decoding", and that case uses a declared and referenced entity. #2967 likewise describes the retained refusal as one for "real internal entity subsets".
  2. If every internal subset stays refused by policy, the refusal says so: an unsupported-DTD or format reason, not aggregate_decode_budget_exhausted with a budget figure.

I have not established which outcome the maintainers prefer.

Impact and scope

Observed: one Interface Builder archive is silently reported as omitted for a resource reason, and graph coverage becomes partial. A caller that follows the limitation would conclude the aggregate budget was too small. No budget setting can admit the input, because the refusal does not depend on size.

The same preflight (preflightPropertyListDecode) has other callers. This is from reading the source only; none of these paths were run:

  • Native inspect_plist XML decoding (src/native/parsers/plist.ts, parsePlistXmlSource). It maps the failure to AnalysisResourceConstraintError with reason memory and a fixed allowance "(no runtime override)". That path needs plutil, so a macOS host.
  • src/application/AppBundleExecutable.ts:570. In my test, an Info.plist with [] did not change the decode-interface-builder result, so I could not observe that path from this command.

Real-world frequency is unknown. Xcode-generated plists do not normally carry an internal subset.

REA version

6.8.0 (rea --version), source commit 6e9ee97a6 on main

Runtime and platform

Node 24.22.1; Windows 11 x64 (10.0.26200)

Target and artifact context

Synthetic XML NSKeyedArchiver nib (694 bytes) in a minimal iOS-layout .app (Info.plist at the bundle root)

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

To separate the decoder from the preflight, I loaded both from the built dist and ran them directly. The calls were parseXmlPropertyList(text) from dist/domain/propertyListKeys.js and preflightPropertyListDecode(bytes, 16 MiB) from dist/artifacts/apple/PropertyListDecodeBudget.js:

a-control.plist (691 bytes)        DOCTYPE without subset
  decoder:   ok
  preflight: ok
b-empty.plist (694 bytes)          []
  decoder:   ok
  preflight: PropertyListDecodeBudgetFailure: XML plist internal entity expansion exceeds the property list decode budget
c-space.plist (695 bytes)          [ ]
  decoder:   ok
  preflight: (same failure)
d-comment.plist (718 bytes)        [<!-- no declarations -->]
  decoder:   ok
  preflight: (same failure)
e-unused-entity.plist (714 bytes)  [<!ENTITY unused "x">]
  decoder:   ok
  preflight: (same failure)
f-used-entity.plist (711 bytes)    [<!ENTITY t "Build">] and &t; in a <string>
  decoder:   Error: XML is not well-formed: 3:227: undefined entity.
  preflight: (same failure)

The CLI outputs for steps 3–5 are quoted under "Observed behavior". Here inspect-keyed-archive returned exit 0 for b–e. For f it failed with Cannot decode selected Foundation keyed archive: XML is not well-formed: 3:227: undefined entity.

Typed outcome or recovery expected

For b–e: archive_decode complete, as with inspect_keyed_archive. If the refusal is kept: an omission reason that names the unsupported DTD internal subset, not budget exhaustion.

Analysis or suspected cause

This is source-derived. In src/artifacts/apple/PropertyListDecodeBudget.ts, xmlPreflightTokens sets internalSubset for any unquoted [ inside a <!DOCTYPE declaration (around line 189), whatever the subset contains. estimateXmlPlistExpansion then throws fail("expansion", "XML plist internal entity expansion exceeds …") (lines 94–100) before any size estimate is made. InterfaceBuilderAnalysis.ts passes interfaceBuilderDecodeBudgetFailure as fail (line 213), which is how the refusal surfaces as aggregate_decode_budget_exhausted.

Additional context

Related but different: #2967 (inert declarations in comments, CDATA and processing instructions), #5540 / #5563 (an omission attributed to the wrong budget), #3949 (a false Interface Builder omission within the nesting limit).

Originally created by @Plastic-time on GitHub (Nov 1, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8407 ### 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 `decode-interface-builder` omits a 694-byte XML keyed-archive nib whose DOCTYPE carries an empty internal subset (`[]`). Coverage and limitations attribute the omission to exhaustion of the aggregate decode budget: ```text coverage: {"facet":"archive_decode","status":"partial","reason":"aggregate_decode_budget_exhausted","examined":1,"omitted":1} limitations: "Panel.nib: omitted because aggregate decode budget exhausted." ``` When the `[]` is removed and nothing else changes, the same bundle reports `archive_decode` `complete` with 0 omitted. `inspect-keyed-archive` decodes the exact bytes of the failing nib without error. The subset declares nothing, so nothing can expand. The pinned XML decoder also never expands internally declared entities. A reference to one fails as `undefined entity`; see the evidence below. The refusal therefore guards against an expansion that cannot happen, and it reports a budget overrun for a sub-kilobyte archive. ### Reproduction All steps were run with the CLI built from `main` (`npm run build:cached`, then `node scripts/rea.mjs ...`). Each case was run at least once, and repeated runs gave the same result. 1. Create `Example.app/Info.plist` with an ordinary DOCTYPE: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict><key>CFBundleIdentifier</key><string>com.example.demo</string><key>CFBundleName</key><string>Example</string><key>CFBundlePackageType</key><string>APPL</string></dict></plist> ``` 2. Create `Example.app/Panel.nib`. It is a minimal XML NSKeyedArchiver archive, and its DOCTYPE ends in ` []`: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" []> <plist version="1.0"><dict><key>$archiver</key><string>NSKeyedArchiver</string><key>$objects</key><array><string>$null</string><dict><key>$class</key><dict><key>CF$UID</key><integer>2</integer></dict><key>title</key><string>Build</string></dict><dict><key>$classname</key><string>NSButton</string><key>$classes</key><array><string>NSButton</string><string>NSObject</string></array></dict></array><key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict><key>$version</key><integer>100000</integer></dict></plist> ``` 3. Run `rea decode-interface-builder Example.app --format json`. The nib is omitted with `aggregate_decode_budget_exhausted`. 4. Delete ` []` from `Panel.nib` and run step 3 again. `archive_decode` is `complete`, with 0 omitted. 5. Run `rea inspect-keyed-archive Panel.nib --format json` on the step 2 file. It exits 0 and decodes the `xml-plist` archive. Variants with the same outcome: the subset `[ ]`, the comment-only subset `[<!-- no declarations -->]`, and `[<!ENTITY unused "x">]` with no reference to the entity. Each is omitted by `decode-interface-builder` and decoded by `inspect-keyed-archive`. ### Expected behavior One of two outcomes, at the maintainers' discretion: 1. An internal subset that the decoder neither expands nor needs is decoded, as `inspect_keyed_archive` already does. The tests appear to intend this. `InterfaceBuilderDecodeBudget.test.ts` preflights inert DOCTYPE examples "without treating it as entity expansion". Its refusal case is titled "continues to refuse an actual internal entity declaration before decoding", and that case uses a declared *and referenced* entity. #2967 likewise describes the retained refusal as one for "real internal entity subsets". 2. If every internal subset stays refused by policy, the refusal says so: an unsupported-DTD or format reason, not `aggregate_decode_budget_exhausted` with a budget figure. I have not established which outcome the maintainers prefer. ### Impact and scope Observed: one Interface Builder archive is silently reported as omitted for a resource reason, and graph coverage becomes `partial`. A caller that follows the limitation would conclude the aggregate budget was too small. No budget setting can admit the input, because the refusal does not depend on size. The same preflight (`preflightPropertyListDecode`) has other callers. This is from reading the source only; none of these paths were run: - Native `inspect_plist` XML decoding (`src/native/parsers/plist.ts`, `parsePlistXmlSource`). It maps the failure to `AnalysisResourceConstraintError` with reason `memory` and a fixed allowance "(no runtime override)". That path needs `plutil`, so a macOS host. - `src/application/AppBundleExecutable.ts:570`. In my test, an `Info.plist` with `[]` did not change the `decode-interface-builder` result, so I could not observe that path from this command. Real-world frequency is unknown. Xcode-generated plists do not normally carry an internal subset. ### REA version 6.8.0 (`rea --version`), source commit 6e9ee97a6 on `main` ### Runtime and platform Node 24.22.1; Windows 11 x64 (10.0.26200) ### Target and artifact context Synthetic XML NSKeyedArchiver nib (694 bytes) in a minimal iOS-layout `.app` (Info.plist at the bundle root) ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence To separate the decoder from the preflight, I loaded both from the built `dist` and ran them directly. The calls were `parseXmlPropertyList(text)` from `dist/domain/propertyListKeys.js` and `preflightPropertyListDecode(bytes, 16 MiB)` from `dist/artifacts/apple/PropertyListDecodeBudget.js`: ```text a-control.plist (691 bytes) DOCTYPE without subset decoder: ok preflight: ok b-empty.plist (694 bytes) [] decoder: ok preflight: PropertyListDecodeBudgetFailure: XML plist internal entity expansion exceeds the property list decode budget c-space.plist (695 bytes) [ ] decoder: ok preflight: (same failure) d-comment.plist (718 bytes) [<!-- no declarations -->] decoder: ok preflight: (same failure) e-unused-entity.plist (714 bytes) [<!ENTITY unused "x">] decoder: ok preflight: (same failure) f-used-entity.plist (711 bytes) [<!ENTITY t "Build">] and &t; in a <string> decoder: Error: XML is not well-formed: 3:227: undefined entity. preflight: (same failure) ``` The CLI outputs for steps 3–5 are quoted under "Observed behavior". Here `inspect-keyed-archive` returned exit 0 for `b`–`e`. For `f` it failed with `Cannot decode selected Foundation keyed archive: XML is not well-formed: 3:227: undefined entity.` ### Typed outcome or recovery expected For `b`–`e`: `archive_decode` `complete`, as with `inspect_keyed_archive`. If the refusal is kept: an omission reason that names the unsupported DTD internal subset, not budget exhaustion. ### Analysis or suspected cause This is source-derived. In `src/artifacts/apple/PropertyListDecodeBudget.ts`, `xmlPreflightTokens` sets `internalSubset` for any unquoted `[` inside a `<!DOCTYPE` declaration (around line 189), whatever the subset contains. `estimateXmlPlistExpansion` then throws `fail("expansion", "XML plist internal entity expansion exceeds …")` (lines 94–100) before any size estimate is made. `InterfaceBuilderAnalysis.ts` passes `interfaceBuilderDecodeBudgetFailure` as `fail` (line 213), which is how the refusal surfaces as `aggregate_decode_budget_exhausted`. ### Additional context Related but different: #2967 (inert declarations in comments, CDATA and processing instructions), #5540 / #5563 (an omission attributed to the wrong budget), #3949 (a false Interface Builder omission within the nesting limit).
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#6590
No description provided.