[GH-ISSUE #6682] [Bug] XML keyed archives invent empty-string values for dictionary keys with no value #4061

Closed
opened 2026-10-31 00:46:28 +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/6682

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 accepts an XML dictionary ending in <key>missing</key> without a value and emits {"missing":""} as an observed dictionary object. A control that explicitly stores <key>missing</key><string/> produces the same object value and status. The missing value receives no malformed-input or uncertainty notice.

This was executed through the source CLI command handler, using its real artifact provider and file reader. The published npm executable and MCP transport were not run.

Reproduction

From the source checkout identified below, with locked dependencies installed and the stated Node runtime, create a harmless archive and a source CLI entry point:

work=$(mktemp -d /tmp/rea-dictionary.XXXXXX)
cat > "$work/dangling.plist" <<'XML'
<?xml version="1.0" encoding="UTF-8"?><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>missing</key></dict></array><key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict></dict></plist>
XML
cat > "$work/cli.mts" <<'JS'
import { pathToFileURL } from 'node:url';
import { resolve } from 'node:path';
const { createCli } = await import(pathToFileURL(resolve('src/cli.ts')).href);
await createCli({}).serve(process.argv.slice(2));
JS
node --import ./node_modules/tsx/dist/loader.mjs "$work/cli.mts" \
  inspect-keyed-archive "$work/dangling.plist" --json

One affected execution returned exit 0 and the successful Evidence below. The exact archive payload above was exercised; the command uses a generic temporary directory. Two controls were also executed once each: adding <string/> after the missing key succeeded with the same projected object; adding a second key/value pair immediately after the valueless key failed with exit 1 and Unexpected <key> while parsing <dict/>. Keys and values must alternate. No reduction experiment or frequency beyond these three cases is claimed.

Expected behavior

Do not assert an observed empty string when the archive supplies no value. Reject the malformed dictionary with its missing-value reason, or retain an explicitly incomplete/unknown value if recovery is intended. An explicitly encoded empty string must remain distinguishable and supported.

Apple's PropertyList DTD requires dictionary contents to be key/value pairs. REA's own output limitation states that serialized fields are observed and that missing fields remain absent; the affected result contradicts those statements by manufacturing a value. This report concerns that concrete evidence-integrity failure, without requiring all permissive plist parsing to become DTD validation.

Impact and scope

A malformed XML archive can appear to contain an ordinary empty-string field, obscuring the difference between missing source data and an authored value. The demonstrated scope is a final valueless dictionary key inside one synthetic NSKeyedArchiver object. Other malformed forms, real-application prevalence, macOS inspect-plist, and binary plists were not tested. No target mutation, persistent corruption, or security boundary crossing was observed. Manual inspection reveals the missing value, but a faithful automatic REA recovery workflow was not established.

REA version

Source revision a7f967cf240eba8309cbf4526b2fc6338a52a617; package.json identifies 6.7.0. npm's latest published version was 6.7.0 when checked; that published artifact was not tested.

Runtime and platform

Node.js v24.22.1, tsx 4.23.15, Linux x86_64; locked checkout dependencies. No native decompiler or Apple host tools were used.

Target and artifact context

A self-authored XML Foundation archive with $archiver: NSKeyedArchiver, $version: 100000, two $objects entries, and a $top.root reference to the dictionary at index 1. Its dictionary contains a key element with no following value element.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Reduced fields from the affected CLI output:

{
  "operation": "inspect_keyed_archive",
  "confidence": "observed",
  "normalized_result": {
    "objects": [
      { "id": 0, "kind": "nil", "status": "nil", "value": "$null" },
      { "id": 1, "kind": "dictionary", "status": "observed", "value": { "missing": "" } }
    ],
    "truncated": false
  }
}

The explicit-empty control returned the same object at index 1. Both successful results had only the three standard keyed-archive limitations, including Missing fields remain absent in the raw serialized value. Neither reported the missing dictionary value. The consecutive-key control returned artifact_operation_failed, details.reason: format, and the alternating-keys error quoted above.

  • Expected source: Apple's dictionary pair grammar and the observed serialized-field/missing-field limitations described above.
  • Failure source: the source CLI results for the inline affected fixture and explicit-empty control.
  • Evidence provenance: observed CLI execution; the implementation explanation below is source-derived.
  • Local verification: reproduced.
  • Reproduction completeness: complete for the source CLI route; released executable and MCP not run.

Typed outcome or recovery expected

For the affected fixture, return a malformed-archive outcome or an explicit incomplete value, preserving the missing-value reason. The explicit <string/> control should still produce an observed empty string. Merely removing the key without a diagnostic would also lose source evidence; this is a proposed restoration check, not an implemented change.

Analysis or suspected cause

The shared XML dictionary decoder appends [key, ""] when the dictionary ends with an unpaired key. The archive reader consumes that decoded value, and the graph projector marks an ordinary dictionary as observed. This path is consistent with the executed results.

Additional context

Open and closed issue searches covered plist/keyed archives with missing value, dangling, empty string, and missing key/value terms. Related scalar-literal and root-identity reports do not describe this dictionary-end substitution. No last known good version was established. No bug reproduction was rerun after this draft was written.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The observed malformed XML archive produces a fabricated ordinary field value in successful observed Evidence. Scope is bounded to the demonstrated dictionary shape; no persistent changes were observed.
  • Workaround status: unverified
  • Workaround basis: Reading the XML exposes the defect, but no automatic REA workflow preserving the missing-value distinction was demonstrated.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6682 ## 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` accepts an XML dictionary ending in `<key>missing</key>` without a value and emits `{"missing":""}` as an `observed` dictionary object. A control that explicitly stores `<key>missing</key><string/>` produces the same object value and status. The missing value receives no malformed-input or uncertainty notice. This was executed through the source CLI command handler, using its real artifact provider and file reader. The published npm executable and MCP transport were not run. ## Reproduction From the source checkout identified below, with locked dependencies installed and the stated Node runtime, create a harmless archive and a source CLI entry point: ```sh work=$(mktemp -d /tmp/rea-dictionary.XXXXXX) cat > "$work/dangling.plist" <<'XML' <?xml version="1.0" encoding="UTF-8"?><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>missing</key></dict></array><key>$top</key><dict><key>root</key><dict><key>CF$UID</key><integer>1</integer></dict></dict></dict></plist> XML cat > "$work/cli.mts" <<'JS' import { pathToFileURL } from 'node:url'; import { resolve } from 'node:path'; const { createCli } = await import(pathToFileURL(resolve('src/cli.ts')).href); await createCli({}).serve(process.argv.slice(2)); JS node --import ./node_modules/tsx/dist/loader.mjs "$work/cli.mts" \ inspect-keyed-archive "$work/dangling.plist" --json ``` One affected execution returned exit 0 and the successful Evidence below. The exact archive payload above was exercised; the command uses a generic temporary directory. Two controls were also executed once each: adding `<string/>` after the `missing` key succeeded with the same projected object; adding a second key/value pair immediately after the valueless key failed with exit 1 and `Unexpected <key> while parsing <dict/>. Keys and values must alternate.` No reduction experiment or frequency beyond these three cases is claimed. ## Expected behavior Do not assert an observed empty string when the archive supplies no value. Reject the malformed dictionary with its missing-value reason, or retain an explicitly incomplete/unknown value if recovery is intended. An explicitly encoded empty string must remain distinguishable and supported. Apple's [PropertyList DTD](https://www.apple.com/DTDs/PropertyList-1.0.dtd) requires dictionary contents to be key/value pairs. REA's own output limitation states that serialized fields are observed and that missing fields remain absent; the affected result contradicts those statements by manufacturing a value. This report concerns that concrete evidence-integrity failure, without requiring all permissive plist parsing to become DTD validation. ## Impact and scope A malformed XML archive can appear to contain an ordinary empty-string field, obscuring the difference between missing source data and an authored value. The demonstrated scope is a final valueless dictionary key inside one synthetic NSKeyedArchiver object. Other malformed forms, real-application prevalence, macOS `inspect-plist`, and binary plists were not tested. No target mutation, persistent corruption, or security boundary crossing was observed. Manual inspection reveals the missing value, but a faithful automatic REA recovery workflow was not established. ## REA version Source revision `a7f967cf240eba8309cbf4526b2fc6338a52a617`; package.json identifies 6.7.0. npm's latest published version was 6.7.0 when checked; that published artifact was not tested. ## Runtime and platform Node.js v24.22.1, tsx 4.23.15, Linux x86_64; locked checkout dependencies. No native decompiler or Apple host tools were used. ## Target and artifact context A self-authored XML Foundation archive with `$archiver: NSKeyedArchiver`, `$version: 100000`, two `$objects` entries, and a `$top.root` reference to the dictionary at index 1. Its dictionary contains a key element with no following value element. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Reduced fields from the affected CLI output: ```json { "operation": "inspect_keyed_archive", "confidence": "observed", "normalized_result": { "objects": [ { "id": 0, "kind": "nil", "status": "nil", "value": "$null" }, { "id": 1, "kind": "dictionary", "status": "observed", "value": { "missing": "" } } ], "truncated": false } } ``` The explicit-empty control returned the same object at index 1. Both successful results had only the three standard keyed-archive limitations, including `Missing fields remain absent in the raw serialized value.` Neither reported the missing dictionary value. The consecutive-key control returned `artifact_operation_failed`, `details.reason: format`, and the alternating-keys error quoted above. - Expected source: Apple's dictionary pair grammar and the observed serialized-field/missing-field limitations described above. - Failure source: the source CLI results for the inline affected fixture and explicit-empty control. - Evidence provenance: observed CLI execution; the implementation explanation below is source-derived. - Local verification: reproduced. - Reproduction completeness: complete for the source CLI route; released executable and MCP not run. ## Typed outcome or recovery expected For the affected fixture, return a malformed-archive outcome or an explicit incomplete value, preserving the missing-value reason. The explicit `<string/>` control should still produce an observed empty string. Merely removing the key without a diagnostic would also lose source evidence; this is a proposed restoration check, not an implemented change. ## Analysis or suspected cause The shared [XML dictionary decoder](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/domain/propertyListXml.ts#L71-L90) appends `[key, ""]` when the dictionary ends with an unpaired key. The [archive reader](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/artifacts/apple/KeyedArchiveReader.ts#L79-L101) consumes that decoded value, and the [graph projector](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/domain/apple/keyedArchive.ts#L225-L251) marks an ordinary dictionary as observed. This path is consistent with the executed results. ## Additional context Open and closed issue searches covered plist/keyed archives with `missing value`, `dangling`, `empty string`, and missing key/value terms. Related scalar-literal and root-identity reports do not describe this dictionary-end substitution. No last known good version was established. No bug reproduction was rerun after this draft was written. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The observed malformed XML archive produces a fabricated ordinary field value in successful observed Evidence. Scope is bounded to the demonstrated dictionary shape; no persistent changes were observed. - Workaround status: unverified - Workaround basis: Reading the XML exposes the defect, but no automatic REA workflow preserving the missing-value distinction was demonstrated.
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#4061
No description provided.