[GH-ISSUE #3892] [Bug] OpenStep keyed archives drop __proto__ entries without an omission limitation #1510

Closed
opened 2026-10-16 21:06:23 +00:00 by bsakaguchi · 1 comment
Owner

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

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

rea inspect-keyed-archive accepts an OpenStep-format keyed archive whose dictionary contains a __proto__ key, drops that entry, and reports no omission limitation. The same archive converted to XML reports 1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key. The OpenStep branch of the shared plist parser always returns omittedPrototypeKeys: 0.

The OpenStep inspection exits 0 and returns object 1 as {"kept":"visible"} with truncated: false. Neither the normalized result nor the enclosing Evidence limitations mention the missing __proto__ entry. Calling the shared parser directly on { __proto__ = x; kept = y; } returns omittedPrototypeKeys: 0.

Reproduction

  1. In a scratch directory, create an OpenStep keyed archive with a __proto__ key and an XML copy of it.
cat > openstep.plist <<'PLIST'
{
  "$archiver" = NSKeyedArchiver;
  "$version" = 100000;
  "$objects" = ("$null", { __proto__ = hidden; kept = visible; });
  "$top" = { root = payload; };
}
PLIST
/usr/bin/plutil -convert xml1 -o converted.plist openstep.plist
  1. Inspect both files and compare object 1, truncated, and omission limitations.
rea inspect-keyed-archive "$PWD/openstep.plist" --json > openstep-output.json
rea inspect-keyed-archive "$PWD/converted.plist" --json > converted-output.json
python3 - <<'PY'
import json
for name in ['openstep', 'converted']:
    value = json.load(open(name + '-output.json'))['normalized_result']
    print(name, json.dumps({'object_1': value['objects'][1]['value'], 'truncated': value['truncated'], 'omission_limitations': [s for s in value['limitations'] if 'omitt' in s]}))
PY

Expected behavior

The OpenStep result should disclose the dropped entry exactly as the XML result does, with the 1 dictionary entry keyed __proto__ was omitted ... limitation. PropertyListWithoutPrototypeKeys promises the decoded value plus how many __proto__ entries were omitted, and the explicit OpenStep branch returns that same contract.

Impact and scope

Accepted OpenStep-format archives lose a dictionary entry with no disclosure in their Evidence, so serialized-field evidence is presented as complete when it is not; occurrence in real application archives was not measured.

Workaround: available. On macOS, converting a copy to XML with plutil -convert xml1 and inspecting the copy reports the omission limitation, as observed above.

Reporter's impact estimate: P2 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64; Pinned dependency plist@5.0.0; the XML control uses macOS /usr/bin/plutil.

Target and artifact context

OpenStep-format NSKeyedArchiver property list

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: src/domain/propertyListKeys.ts:14 (PropertyListWithoutPrototypeKeys returns the value and the omitted __proto__ count) and src/domain/propertyListKeys.ts:94 (OpenStep branch returns that contract); src/artifacts/apple/KeyedArchiveReader.ts:112 converts a nonzero count into the caller-visible limitation
  • Failure source: public CLI output for the OpenStep and XML fixtures above; static cause at src/domain/propertyListKeys.ts:99 and :41 with node_modules/plist/dist/parse-openstep.js:69
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Observed output; both REA commands and plutil exited 0:

openstep {"object_1": {"kept": "visible"}, "truncated": false, "omission_limitations": []}
converted {"object_1": {"kept": "visible"}, "truncated": false, "omission_limitations": ["1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key."]}

An independently written XML fixture with the same content also reported a count of one.

Source-derived cause: the OpenStep branch calls omitPrototypeKeys(parse(text)) at src/domain/propertyListKeys.ts:99. The pinned plist@5.0.0 OpenStep decoder assigns obj[key] = value at node_modules/plist/dist/parse-openstep.js:69, so __proto__ sets the object's prototype instead of creating an own enumerable entry. The later Object.entries traversal at src/domain/propertyListKeys.ts:41 cannot see or count it. The CLI reaches the parser through decodeKeyedArchiveBytes at src/artifacts/apple/KeyedArchiveReader.ts:80.

Typed outcome or recovery expected

The inspect_keyed_archive result should carry the existing '1 dictionary entry keyed proto was omitted because REA results cannot represent that key.' limitation, as it does for XML and binary archives.

Restoration check:

For openstep.plist, the tested build returns omission_limitations: []; a fixed build should return the same 1 dictionary entry keyed __proto__ was omitted ... limitation that converted.plist returns today, while object 1 stays {"kept":"visible"}. An OpenStep archive without a __proto__ key should still report no omission limitation.

Analysis or suspected cause

Source-derived: src/domain/propertyListKeys.ts:99 counts omissions after plist@5.0.0's OpenStep decoder (node_modules/plist/dist/parse-openstep.js:69) has assigned obj['proto'], which sets the prototype instead of an own entry, so the Object.entries traversal at line 41 finds nothing to count.

Additional context

Closed #1594 fixed the same omission accounting for binary plists; this is the remaining OpenStep branch, not a reproduced binary regression. Open #3371 (CDATA in XML keys) and closed #1036 (prototype-named JSON members) cover different paths. The public guide describes XML and binary keyed archives; this report relies on the explicit OpenStep implementation contract and the CLI accepting the input. It concerns evidence accounting, not process-wide prototype pollution.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3892 ### 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 `rea inspect-keyed-archive` accepts an OpenStep-format keyed archive whose dictionary contains a `__proto__` key, drops that entry, and reports no omission limitation. The same archive converted to XML reports `1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key.` The OpenStep branch of the shared plist parser always returns `omittedPrototypeKeys: 0`. The OpenStep inspection exits 0 and returns object 1 as `{"kept":"visible"}` with `truncated: false`. Neither the normalized result nor the enclosing Evidence limitations mention the missing `__proto__` entry. Calling the shared parser directly on `{ __proto__ = x; kept = y; }` returns `omittedPrototypeKeys: 0`. ### Reproduction 1. In a scratch directory, create an OpenStep keyed archive with a `__proto__` key and an XML copy of it. ```sh cat > openstep.plist <<'PLIST' { "$archiver" = NSKeyedArchiver; "$version" = 100000; "$objects" = ("$null", { __proto__ = hidden; kept = visible; }); "$top" = { root = payload; }; } PLIST /usr/bin/plutil -convert xml1 -o converted.plist openstep.plist ``` 2. Inspect both files and compare object 1, `truncated`, and omission limitations. ```sh rea inspect-keyed-archive "$PWD/openstep.plist" --json > openstep-output.json rea inspect-keyed-archive "$PWD/converted.plist" --json > converted-output.json python3 - <<'PY' import json for name in ['openstep', 'converted']: value = json.load(open(name + '-output.json'))['normalized_result'] print(name, json.dumps({'object_1': value['objects'][1]['value'], 'truncated': value['truncated'], 'omission_limitations': [s for s in value['limitations'] if 'omitt' in s]})) PY ``` ### Expected behavior The OpenStep result should disclose the dropped entry exactly as the XML result does, with the `1 dictionary entry keyed __proto__ was omitted ...` limitation. `PropertyListWithoutPrototypeKeys` promises the decoded value plus how many `__proto__` entries were omitted, and the explicit OpenStep branch returns that same contract. ### Impact and scope Accepted OpenStep-format archives lose a dictionary entry with no disclosure in their Evidence, so serialized-field evidence is presented as complete when it is not; occurrence in real application archives was not measured. Workaround: available. On macOS, converting a copy to XML with `plutil -convert xml1` and inspecting the copy reports the omission limitation, as observed above. Reporter's impact estimate: P2 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64; Pinned dependency `plist@5.0.0`; the XML control uses macOS `/usr/bin/plutil`. ### Target and artifact context OpenStep-format NSKeyedArchiver property list ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: src/domain/propertyListKeys.ts:14 (`PropertyListWithoutPrototypeKeys` returns the value and the omitted `__proto__` count) and src/domain/propertyListKeys.ts:94 (OpenStep branch returns that contract); src/artifacts/apple/KeyedArchiveReader.ts:112 converts a nonzero count into the caller-visible limitation - Failure source: public CLI output for the OpenStep and XML fixtures above; static cause at src/domain/propertyListKeys.ts:99 and :41 with node_modules/plist/dist/parse-openstep.js:69 - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Observed output; both REA commands and `plutil` exited 0: ```text openstep {"object_1": {"kept": "visible"}, "truncated": false, "omission_limitations": []} converted {"object_1": {"kept": "visible"}, "truncated": false, "omission_limitations": ["1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key."]} ``` An independently written XML fixture with the same content also reported a count of one. Source-derived cause: the OpenStep branch calls `omitPrototypeKeys(parse(text))` at src/domain/propertyListKeys.ts:99. The pinned `plist@5.0.0` OpenStep decoder assigns `obj[key] = value` at node_modules/plist/dist/parse-openstep.js:69, so `__proto__` sets the object's prototype instead of creating an own enumerable entry. The later `Object.entries` traversal at src/domain/propertyListKeys.ts:41 cannot see or count it. The CLI reaches the parser through `decodeKeyedArchiveBytes` at src/artifacts/apple/KeyedArchiveReader.ts:80. ### Typed outcome or recovery expected The inspect_keyed_archive result should carry the existing '1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key.' limitation, as it does for XML and binary archives. Restoration check: For `openstep.plist`, the tested build returns `omission_limitations: []`; a fixed build should return the same `1 dictionary entry keyed __proto__ was omitted ...` limitation that `converted.plist` returns today, while object 1 stays `{"kept":"visible"}`. An OpenStep archive without a `__proto__` key should still report no omission limitation. ### Analysis or suspected cause Source-derived: src/domain/propertyListKeys.ts:99 counts omissions after plist@5.0.0's OpenStep decoder (node_modules/plist/dist/parse-openstep.js:69) has assigned obj['__proto__'], which sets the prototype instead of an own entry, so the Object.entries traversal at line 41 finds nothing to count. ### Additional context Closed #1594 fixed the same omission accounting for binary plists; this is the remaining OpenStep branch, not a reproduced binary regression. Open #3371 (CDATA in XML keys) and closed #1036 (prototype-named JSON members) cover different paths. The public guide describes XML and binary keyed archives; this report relies on the explicit OpenStep implementation contract and the CLI accepting the input. It concerns evidence accounting, not process-wide prototype pollution. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
Author
Owner

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

The inconsistency between branches is the real problem here: the XML path honors the proto omission limitation contract while the OpenStep path silently returns omittedPrototypeKeys: 0 and produces a normalized result that hides data without any indication. For an inspection tool whose whole point is faithful reconstruction, silently dropping a key (especially a security-sensitive one like proto) is worse than reporting it as omitted — consumers will assume the dict is complete. The fix should make the OpenStep branch route through the same prototype-key omission accounting as the XML branch and surface it in limitations, plus a regression fixture for an OpenStep archive containing proto.

<!-- gh-comment-id:6185569549 --> @XinwangTao commented on GitHub (Oct 15, 2026): The inconsistency between branches is the real problem here: the XML path honors the __proto__ omission limitation contract while the OpenStep path silently returns omittedPrototypeKeys: 0 and produces a normalized result that hides data without any indication. For an inspection tool whose whole point is faithful reconstruction, silently dropping a key (especially a security-sensitive one like __proto__) is worse than reporting it as omitted — consumers will assume the dict is complete. The fix should make the OpenStep branch route through the same prototype-key omission accounting as the XML branch and surface it in limitations, plus a regression fixture for an OpenStep archive containing __proto__.
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#1510
No description provided.