[GH-ISSUE #4042] [Bug] XML plist real values lose CDATA content during keyed-archive inspection #1604

Closed
opened 2026-10-16 21:07:49 +00:00 by bsakaguchi · 6 comments
Owner

Originally created by @JFSAS on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4042

Before submitting

  • I searched open and closed issues and related PRs.
  • The reproduction uses self-authored input and contains no credentials or private target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

A valid XML plist real split across text and CDATA is returned with the wrong numeric value by both inspect-keyed-archive and stdio MCP inspect_keyed_archive:

Real element Native plutil REA
<real>1.25</real> 1.25 1.25
<real>1<![CDATA[.25]]></real> 1.25 1
<real><![CDATA[1.25]]></real> 1.25 {"$plist_type":"real","value":null}

The last case adds a non-finite-real limitation even though the source is finite. The mixed case is returned as observed and untruncated. Its generic integer-precision/type-metadata limitation does not explain the incorrect real value.

Reproduction

Save this archive as sample.plist:

<plist version="1.0"><dict>
<key>$archiver</key><string>NSKeyedArchiver</string>
<key>$version</key><integer>100000</integer>
<key>$objects</key><array><string>$null</string><dict>
<key>value</key><real>1<![CDATA[.25]]></real>
</dict></array>
<key>$top</key><dict><key>root</key><dict>
<key>CF$UID</key><integer>1</integer>
</dict></dict>
</dict></plist>
plutil -extract '$objects.1.value' raw -o - sample.plist
rea inspect-keyed-archive sample.plist --json

The first command prints 1.25; REA returns objects[1].value.value: 1. Replacing the real with the pure-CDATA spelling reproduces the unknown-real result. For MCP, open the same file with open_binary, then call inspect_keyed_archive with default arguments.

Expected behavior

Equivalent supported XML and binary representations retain the same real value. A native plutil -convert binary1 conversion of the same archive already returns 1.25 through both REA adapters.

Impact and scope

Confirmed on self-authored archives through the public compiled CLI and real stdio MCP. Original bytes and Evidence SHA-256 remain intact, but the decoded value is wrong. Frequency in real application archives is NOT_ENOUGH_INFO; no widespread or urgent failure is claimed. Windows/Linux were not tested.

REA version

Source 337d2e33c5aae40f20c64584c341a3593420b8e0; also reproduced on 6688ed6db.

Runtime and platform

macOS 15.7.3 arm64; Node 24.18.0; npm 11.16.0. Native plutil is the independent control. No disassembler or target execution is involved.

Interface and earliest failure stage

CLI and MCP; shared XML value decoding, before graph/Evidence projection.

Relevant evidence and source-derived cause

Four representations across both adapters gave four correct controls and four failing exact-value checks. The focused public regression fails on the base through both adapters. Source digests match the retained Evidence and before/after files are identical.

The pinned plist 5.0.0 real decoder concatenates only text nodes. REA's shared parseXmlPropertyList passes that value through; the separate XML number scanner treats CDATA as incomplete. The producing boundary should own both character data and numeric facts, rather than patching downstream consumers.

Related #3371 / #3426 repair dictionary keys; #3373 / #3475 repair hexadecimal integers. Their current patches do not cover real character data, but touch the same boundary and need coordination.

Prepared with AI assistance; the reported reproductions and controls were executed locally.

Originally created by @JFSAS on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4042 ### Before submitting - [x] I searched open and closed issues and related PRs. - [x] The reproduction uses self-authored input and contains no credentials or private target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior A valid XML plist real split across text and CDATA is returned with the wrong numeric value by both `inspect-keyed-archive` and stdio MCP `inspect_keyed_archive`: | Real element | Native `plutil` | REA | | --- | --- | --- | | `<real>1.25</real>` | `1.25` | `1.25` | | `<real>1<![CDATA[.25]]></real>` | `1.25` | `1` | | `<real><![CDATA[1.25]]></real>` | `1.25` | `{"$plist_type":"real","value":null}` | The last case adds a non-finite-real limitation even though the source is finite. The mixed case is returned as observed and untruncated. Its generic integer-precision/type-metadata limitation does not explain the incorrect real value. ### Reproduction Save this archive as `sample.plist`: ```xml <plist version="1.0"><dict> <key>$archiver</key><string>NSKeyedArchiver</string> <key>$version</key><integer>100000</integer> <key>$objects</key><array><string>$null</string><dict> <key>value</key><real>1<![CDATA[.25]]></real> </dict></array> <key>$top</key><dict><key>root</key><dict> <key>CF$UID</key><integer>1</integer> </dict></dict> </dict></plist> ``` ```sh plutil -extract '$objects.1.value' raw -o - sample.plist rea inspect-keyed-archive sample.plist --json ``` The first command prints `1.25`; REA returns `objects[1].value.value: 1`. Replacing the real with the pure-CDATA spelling reproduces the unknown-real result. For MCP, open the same file with `open_binary`, then call `inspect_keyed_archive` with default arguments. ### Expected behavior Equivalent supported XML and binary representations retain the same real value. A native `plutil -convert binary1` conversion of the same archive already returns `1.25` through both REA adapters. ### Impact and scope Confirmed on self-authored archives through the public compiled CLI and real stdio MCP. Original bytes and Evidence SHA-256 remain intact, but the decoded value is wrong. Frequency in real application archives is **NOT_ENOUGH_INFO**; no widespread or urgent failure is claimed. Windows/Linux were not tested. ### REA version Source `337d2e33c5aae40f20c64584c341a3593420b8e0`; also reproduced on `6688ed6db`. ### Runtime and platform macOS 15.7.3 arm64; Node 24.18.0; npm 11.16.0. Native `plutil` is the independent control. No disassembler or target execution is involved. ### Interface and earliest failure stage CLI and MCP; shared XML value decoding, before graph/Evidence projection. ### Relevant evidence and source-derived cause Four representations across both adapters gave four correct controls and four failing exact-value checks. The focused public regression fails on the base through both adapters. Source digests match the retained Evidence and before/after files are identical. The pinned `plist` 5.0.0 real decoder concatenates only text nodes. REA's shared `parseXmlPropertyList` passes that value through; the separate XML number scanner treats CDATA as incomplete. The producing boundary should own both character data and numeric facts, rather than patching downstream consumers. Related #3371 / #3426 repair dictionary keys; #3373 / #3475 repair hexadecimal integers. Their current patches do not cover real character data, but touch the same boundary and need coordination. Prepared with AI assistance; the reported reproductions and controls were executed locally.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Confirmed — the mixed text+CDATA spelling returning 1 while native plutil returns 1.25 is a clean signal, and the pure-CDATA case going to {"value":null} shows the decoder really drops character data rather than just mis-parsing it. The producing-boundary framing (parseXmlPropertyList / the XML number scanner should own both character-data and numeric facts, not the downstream adapters) is the right direction — patching consumers would just shift the truncation around. e31f5c5 / #4047 looks aligned with that; just make sure the fix coordinates with #3371/#3426/#3373/#3475 as noted, since they touch the same XML-value boundary and a repeated-patch conflict would be easy to miss.

<!-- gh-comment-id:6188835174 --> @XinwangTao commented on GitHub (Oct 15, 2026): Confirmed — the mixed text+CDATA spelling returning 1 while native plutil returns 1.25 is a clean signal, and the pure-CDATA case going to {"value":null} shows the decoder really drops character data rather than just mis-parsing it. The producing-boundary framing (parseXmlPropertyList / the XML number scanner should own both character-data and numeric facts, not the downstream adapters) is the right direction — patching consumers would just shift the truncation around. e31f5c5 / #4047 looks aligned with that; just make sure the fix coordinates with #3371/#3426/#3373/#3475 as noted, since they touch the same XML-value boundary and a repeated-patch conflict would be easy to miss.
Author
Owner

@matbrgz commented on GitHub (Oct 16, 2026):

Cross-referencing with earlier XML/plist keyed-archive parsing defects #3371 ([Bug] CDATA in XML keyed-archive keys truncates root and field names) and #3373 ([Bug] Keyed archive inspection returns hexadecimal XML integers as zero). All three target the XML/plist deserializer in inspect-keyed-archive where CDATA blocks and non-decimal numbers are mishandled.

Consolidated under Cluster K (XML Keyed-Archive Deserialization & CDATA Handling) in tracker #4330.

<!-- gh-comment-id:6207086549 --> @matbrgz commented on GitHub (Oct 16, 2026): Cross-referencing with earlier XML/plist keyed-archive parsing defects #3371 (`[Bug] CDATA in XML keyed-archive keys truncates root and field names`) and #3373 (`[Bug] Keyed archive inspection returns hexadecimal XML integers as zero`). All three target the XML/plist deserializer in `inspect-keyed-archive` where CDATA blocks and non-decimal numbers are mishandled. Consolidated under **Cluster K (XML Keyed-Archive Deserialization & CDATA Handling)** in tracker #4330.
Author
Owner

@XinwangTao commented on GitHub (Oct 16, 2026):

Cluster K grouping looks right — #3371/#3373 share the same XML/plist deserializer boundary. Good that #4047 and #4054 are both tracking this; either landing will close the loop.

<!-- gh-comment-id:6207539120 --> @XinwangTao commented on GitHub (Oct 16, 2026): Cluster K grouping looks right — #3371/#3373 share the same XML/plist deserializer boundary. Good that #4047 and #4054 are both tracking this; either landing will close the loop.
Author
Owner

@JFSAS commented on GitHub (Oct 16, 2026):

@XinwangTao Thanks for confirming the grouping. Could you review #4047 and advise whether it is ready to merge?

Since #4054 also targets this issue, your assessment of which approach should land would help coordinate the two PRs. #4047 includes the original CLI/MCP regressions and the follow-up fix for CDATA entity expansion; the validation evidence and remaining limitations are documented in the PR.

Please let us know if any changes or additional verification are needed before merging.

<!-- gh-comment-id:6218255254 --> @JFSAS commented on GitHub (Oct 16, 2026): @XinwangTao Thanks for confirming the grouping. Could you review #4047 and advise whether it is ready to merge? Since #4054 also targets this issue, your assessment of which approach should land would help coordinate the two PRs. #4047 includes the original CLI/MCP regressions and the follow-up fix for CDATA entity expansion; the validation evidence and remaining limitations are documented in the PR. Please let us know if any changes or additional verification are needed before merging.
Author
Owner

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

Fixed on main in 7aa4b98c9.

The shared boundary now interprets values and number facts together from the validated, depth-bounded XML DOM. Hexadecimal integers retain their value; real text and CDATA are combined. Keyed archives, Interface Builder and native plist projections consume those parsed facts. The second XML parse, key aliases/source-location rewriting, artifact XML scanner and native private XML scanner were removed. Source bytes, digests, references, scalar identity, prototype omissions and unsupported mixed-integer limitations remain covered.

Adapted @apitoagents's relevant hexadecimal regression and literal-parsing proposal from #3475. Executed native plutil controls established valid signed-hex and real-CDATA cases; native binary conversion independently confirmed the updated real-value expectations. Native plutil rejects the synthetic 401-digit integer overflow input in #4789: preserving exact integer identity for REA's accepted input does not claim native-format support. A source-only native JSON metadata collision control was also repaired; this does not claim plutil emits CDATA in its canonical conversion output.

Validation: 274 focused checks passed across 30 files, one skipped; build, check:fast, docs:check and commit/push hooks passed. New built CLI/actual stdio MCP numeric controls failed eight baseline checks before the fix. Source golden, exact-number, UID/reference, pagination, malformed-XML, depth, prototype and shared-consumer controls passed. Two read-only reviews are clear after fixing the newly reported overflow provenance gap.

48 small completed CLI comparisons covered keyed XML/binary and native inspect-plist safe/JSON-unsafe/XML-unsafe workflows. Ordinary keyed and all three native full replies and Evidence IDs stayed byte for byte equal. Recorded full-reply token estimates, elapsed and wait4 CPU time, OS process peak and sampled process-family memory, separating first and repeated processes. No statistical speed claim; subprocess CPU inclusion and short-lived memory peaks have measurement limits. Whole-agent/catalog/MCP, large-input and failure costs remain unmeasured. No heavy profiler, investigated-target execution or real deep-provider verification.

<!-- gh-comment-id:6234000590 --> @morluto commented on GitHub (Oct 17, 2026): Fixed on main in 7aa4b98c9. The shared boundary now interprets values and number facts together from the validated, depth-bounded XML DOM. Hexadecimal integers retain their value; real text and CDATA are combined. Keyed archives, Interface Builder and native plist projections consume those parsed facts. The second XML parse, key aliases/source-location rewriting, artifact XML scanner and native private XML scanner were removed. Source bytes, digests, references, scalar identity, prototype omissions and unsupported mixed-integer limitations remain covered. Adapted @apitoagents's relevant hexadecimal regression and literal-parsing proposal from #3475. Executed native plutil controls established valid signed-hex and real-CDATA cases; native binary conversion independently confirmed the updated real-value expectations. Native plutil rejects the synthetic 401-digit integer overflow input in #4789: preserving exact integer identity for REA's accepted input does not claim native-format support. A source-only native JSON metadata collision control was also repaired; this does not claim plutil emits CDATA in its canonical conversion output. Validation: 274 focused checks passed across 30 files, one skipped; build, check:fast, docs:check and commit/push hooks passed. New built CLI/actual stdio MCP numeric controls failed eight baseline checks before the fix. Source golden, exact-number, UID/reference, pagination, malformed-XML, depth, prototype and shared-consumer controls passed. Two read-only reviews are clear after fixing the newly reported overflow provenance gap. 48 small completed CLI comparisons covered keyed XML/binary and native inspect-plist safe/JSON-unsafe/XML-unsafe workflows. Ordinary keyed and all three native full replies and Evidence IDs stayed byte for byte equal. Recorded full-reply token estimates, elapsed and wait4 CPU time, OS process peak and sampled process-family memory, separating first and repeated processes. No statistical speed claim; subprocess CPU inclusion and short-lived memory peaks have measurement limits. Whole-agent/catalog/MCP, large-input and failure costs remain unmeasured. No heavy profiler, investigated-target execution or real deep-provider verification.
Author
Owner

@XinwangTao commented on GitHub (Oct 18, 2026):

Thanks — 7aa4b98 landing on main settles the #4047 vs #4054 coordination question JFSAS raised: the shared-boundary fix in main supersedes both PRs, so there's no need to choose between them anymore. The validation evidence looks solid (hex integers keep their value, real text + CDATA combined, single depth-bounded DOM pass). Closing this out is reasonable — happy to re-verify against a release build once it ships.

<!-- gh-comment-id:6244122311 --> @XinwangTao commented on GitHub (Oct 18, 2026): Thanks — 7aa4b98 landing on main settles the #4047 vs #4054 coordination question JFSAS raised: the shared-boundary fix in main supersedes both PRs, so there's no need to choose between them anymore. The validation evidence looks solid (hex integers keep their value, real text + CDATA combined, single depth-bounded DOM pass). Closing this out is reasonable — happy to re-verify against a release build once it ships.
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#1604
No description provided.