[GH-ISSUE #3373] [Bug] Keyed archive inspection returns hexadecimal XML integers as zero #1241

Closed
opened 2026-10-16 21:03:13 +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/3373

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-keyed-archive reports the valid XML plist integer 0x2A as an observed scalar value of 0, although Apple's property-list parser reads the same bytes as 42. The result retains the original archive digest but carries the wrong object-table value in normalized_result.objects[1].value. A decimal encoding of the same value and a binary conversion of the same archive both return 42.

The XML command exits with status 0 and reports truncated: false. Its additional numeric limitation says that original number metadata could not be completely observed and decoded numbers may lose integer precision. That notice does not explain changing the small, exactly representable integer 42 to zero.

Reproduction

  1. Start from a clean source checkout at 8dde6370d0809ad04cda3342d4e6f290f48aa063 and prepare the runtime:

    npm ci
    npm run build:cached
    
  2. From the repository root, create the complete XML keyed archive and compare native parsing with REA inspection:

    cat > sample.plist <<'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>$version</key><integer>100000</integer>
    <key>$objects</key><array><string>$null</string><integer>0x2A</integer></array>
    <key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict>
    </dict></plist>
    XML
    /usr/bin/plutil -p sample.plist
    node scripts/rea.mjs inspect-keyed-archive sample.plist --json
    
  3. Convert the unchanged archive to a separate binary plist and inspect that control:

    /usr/bin/plutil -convert binary1 -o sample-binary.plist sample.plist
    node scripts/rea.mjs inspect-keyed-archive sample-binary.plist --json
    
  4. For the decimal XML control, replace only 0x2A with 42 in a separate copy and inspect it:

    sed 's/0x2A/42/' sample.plist > sample-decimal.plist
    node scripts/rea.mjs inspect-keyed-archive sample-decimal.plist --json
    

The native plutil -p command was observed to exit with status 0 and report $objects[1] as 42. Binary conversion succeeded, and REA's binary inspection exited with status 0 and returned object index 1 with value: 42. A separately executed decimal XML control that replaced only 0x2A with 42 also returned 42.

Expected behavior

Inspection must preserve the original object's value. Both encodings represent the integer 42, which is within JSON's exact integer range, so XML and binary results should each report object index 1 with value: 42.

The inspect_keyed_archive description in src/contracts/artifactToolContracts.ts:107 promises original object-table nodes and preserved identities. The native plutil -p sample.plist result and binary conversion independently establish that the fixture's original integer is 42. A generic possible-precision-loss notice does not account for changing this small integer to zero.

Impact and scope

Impact level: P2. Assessment status: supported. A supported XML archive produces a wrong scalar in observed Evidence. The demonstrated case changes the small integer 42 to zero, which can mislead downstream analysis. The observed scope is the supplied hexadecimal integer fixture; occurrence in real application archives was not measured.

Workaround status: available. Converting the unchanged input to a separate binary plist with plutil -convert binary1 -o sample-binary.plist sample.plist was executed successfully. REA then returned the correct integer while preserving the original object index and root reference. The conversion preserves the original file and is available on the tested macOS host.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0 on macOS 27.0.1, using the system plutil. Host architecture was not recorded in the reproduction evidence. No Hopper, Ghidra, or application runtime was involved.

Target and artifact context

A synthetic XML Foundation NSKeyedArchiver plist with $version: 100000, an object table containing $null and hexadecimal integer 0x2A, and a named root root whose CF$UID is 1. The binary and decimal XML controls represent the same scalar value. The fixture requires no analysis engine or target execution.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The XML command exits with status 0 and returns this object at normalized_result.objects[1]:

{"id":1,"kind":"scalar","class_id":null,"class_name":null,"status":"observed","value":0}

The result reports truncated: false. Native plutil -p reports $objects[1] as 42, and the binary and decimal XML controls return value: 42.

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete.

Typed outcome or recovery expected

Run the same hexadecimal XML fixture without rewriting it and require object index 1 to report the integer 42. Retain the decimal XML and binary controls, and require their scalar values to agree. Confirm that the original UID edge still targets object index 1 and that ordinary pagination keeps the original indices.

Analysis or suspected cause

Source-derived reasoning: the XML decode path in src/artifacts/apple/KeyedArchiveReader.ts:77 reaches src/domain/propertyListKeys.ts:114, which delegates to the installed plist decoder. Its integer branch in node_modules/plist/dist/parse.js:167 uses decimal-only decoding. Supplemental metadata collection at src/artifacts/apple/PlistNumberLiterals.ts:12 does not recognize the hexadecimal literal. This source path explains the difference between the observed hexadecimal XML result and the decimal XML and binary controls.

Additional context

  • #495, closed as completed, introduced Foundation keyed-archive object graphs; this report concerns a hexadecimal XML scalar value within that existing capability.
  • #2105, closed as completed, concerns XML depth preflight before recursive decoding; this report concerns a shallow archive that succeeds but returns the wrong integer.
  • #2751, closed as completed, concerns XML archive admission when the filename lacks .plist; this report concerns an admitted .plist archive's decoded value.
  • #1594, closed as completed, concerns omitted binary plist dictionary keys without an omission notice; this report concerns a small hexadecimal XML integer becoming zero.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3373 ### 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-keyed-archive` reports the valid XML plist integer `0x2A` as an observed scalar value of `0`, although Apple's property-list parser reads the same bytes as `42`. The result retains the original archive digest but carries the wrong object-table value in `normalized_result.objects[1].value`. A decimal encoding of the same value and a binary conversion of the same archive both return `42`. The XML command exits with status `0` and reports `truncated: false`. Its additional numeric limitation says that original number metadata could not be completely observed and decoded numbers may lose integer precision. That notice does not explain changing the small, exactly representable integer `42` to zero. ### Reproduction 1. Start from a clean source checkout at `8dde6370d0809ad04cda3342d4e6f290f48aa063` and prepare the runtime: ```sh npm ci npm run build:cached ``` 2. From the repository root, create the complete XML keyed archive and compare native parsing with REA inspection: ```sh cat > sample.plist <<'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>$version</key><integer>100000</integer> <key>$objects</key><array><string>$null</string><integer>0x2A</integer></array> <key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict> </dict></plist> XML /usr/bin/plutil -p sample.plist node scripts/rea.mjs inspect-keyed-archive sample.plist --json ``` 3. Convert the unchanged archive to a separate binary plist and inspect that control: ```sh /usr/bin/plutil -convert binary1 -o sample-binary.plist sample.plist node scripts/rea.mjs inspect-keyed-archive sample-binary.plist --json ``` 4. For the decimal XML control, replace only `0x2A` with `42` in a separate copy and inspect it: ```sh sed 's/0x2A/42/' sample.plist > sample-decimal.plist node scripts/rea.mjs inspect-keyed-archive sample-decimal.plist --json ``` The native `plutil -p` command was observed to exit with status `0` and report `$objects[1]` as `42`. Binary conversion succeeded, and REA's binary inspection exited with status `0` and returned object index `1` with `value: 42`. A separately executed decimal XML control that replaced only `0x2A` with `42` also returned `42`. ### Expected behavior Inspection must preserve the original object's value. Both encodings represent the integer `42`, which is within JSON's exact integer range, so XML and binary results should each report object index `1` with `value: 42`. The `inspect_keyed_archive` description in `src/contracts/artifactToolContracts.ts:107` promises original object-table nodes and preserved identities. The native `plutil -p sample.plist` result and binary conversion independently establish that the fixture's original integer is `42`. A generic possible-precision-loss notice does not account for changing this small integer to zero. ### Impact and scope Impact level: P2. Assessment status: supported. A supported XML archive produces a wrong scalar in observed Evidence. The demonstrated case changes the small integer `42` to zero, which can mislead downstream analysis. The observed scope is the supplied hexadecimal integer fixture; occurrence in real application archives was not measured. Workaround status: available. Converting the unchanged input to a separate binary plist with `plutil -convert binary1 -o sample-binary.plist sample.plist` was executed successfully. REA then returned the correct integer while preserving the original object index and root reference. The conversion preserves the original file and is available on the tested macOS host. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js `v26.10.0` on macOS `27.0.1`, using the system `plutil`. Host architecture was not recorded in the reproduction evidence. No Hopper, Ghidra, or application runtime was involved. ### Target and artifact context A synthetic XML Foundation `NSKeyedArchiver` plist with `$version: 100000`, an object table containing `$null` and hexadecimal integer `0x2A`, and a named root `root` whose `CF$UID` is `1`. The binary and decimal XML controls represent the same scalar value. The fixture requires no analysis engine or target execution. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The XML command exits with status `0` and returns this object at `normalized_result.objects[1]`: ```text {"id":1,"kind":"scalar","class_id":null,"class_name":null,"status":"observed","value":0} ``` The result reports `truncated: false`. Native `plutil -p` reports `$objects[1]` as `42`, and the binary and decimal XML controls return `value: 42`. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete. ### Typed outcome or recovery expected Run the same hexadecimal XML fixture without rewriting it and require object index `1` to report the integer `42`. Retain the decimal XML and binary controls, and require their scalar values to agree. Confirm that the original UID edge still targets object index `1` and that ordinary pagination keeps the original indices. ### Analysis or suspected cause Source-derived reasoning: the XML decode path in `src/artifacts/apple/KeyedArchiveReader.ts:77` reaches `src/domain/propertyListKeys.ts:114`, which delegates to the installed plist decoder. Its integer branch in `node_modules/plist/dist/parse.js:167` uses decimal-only decoding. Supplemental metadata collection at `src/artifacts/apple/PlistNumberLiterals.ts:12` does not recognize the hexadecimal literal. This source path explains the difference between the observed hexadecimal XML result and the decimal XML and binary controls. ### Additional context - [#495](https://github.com/morluto/rea/issues/495), closed as completed, introduced Foundation keyed-archive object graphs; this report concerns a hexadecimal XML scalar value within that existing capability. - [#2105](https://github.com/morluto/rea/issues/2105), closed as completed, concerns XML depth preflight before recursive decoding; this report concerns a shallow archive that succeeds but returns the wrong integer. - [#2751](https://github.com/morluto/rea/issues/2751), closed as completed, concerns XML archive admission when the filename lacks `.plist`; this report concerns an admitted `.plist` archive's decoded value. - [#1594](https://github.com/morluto/rea/issues/1594), closed as completed, concerns omitted binary plist dictionary keys without an omission notice; this report concerns a small hexadecimal XML integer becoming zero.
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:6234000202 --> @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.
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#1241
No description provided.