[PR #885] [MERGED] fix(plist): read XML plists containing a __proto__ key #2317

Closed
opened 2026-10-16 21:17:11 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/885
Author: @kaoru0822-kitauji
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/plist-prototype-keys


📝 Commits (1)

  • a08be51 fix(plist): read XML plists containing a proto key

📊 Changes

10 files changed (+278 additions, -28 deletions)

View changed files

📝 src/application/BinaryTargetResolver.ts (+9 -14)
📝 src/application/InterfaceBuilderAnalysis.ts (+3 -2)
📝 src/artifacts/KeyedArchiveReader.test.ts (+14 -0)
📝 src/artifacts/KeyedArchiveReader.ts (+16 -5)
➕ src/domain/propertyListKeys.test.ts (+53 -0)
➕ src/domain/propertyListKeys.ts (+88 -0)
📝 src/native/NativeMacOSProvider.ts (+2 -2)
📝 src/native/parsers/plist.ts (+20 -5)
➕ tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts (+53 -0)
📝 tests/boundary/filesystem/binaryTarget.test.ts (+20 -0)

📄 Description

Summary

__proto__ is a legal plist dictionary key (plutil -lint reports OK), but the plist package's XML decoder throws on it (__proto__ keys can lead to prototype pollution. More details on CVE-2022-22912). Every REA site that decodes XML plists failed on such a key:

Site Before
App target resolution (XML Info.plist, from #857) The app could not be opened at all: target_unavailable, app has no readable CFBundleExecutable. One unrelated <key>__proto__</key> is enough to keep an app out of every REA tool.
inspect_plist, plutil XML route (data/date/large-integer plists) app has no readable CFBundleExecutable / decode failure
inspect_plist, plutil JSON route key silently dropped, because zod record/object parsing strips own __proto__ keys
inspect_signature entitlements, XML inspect_keyed_archive, Interface Builder plists decode failure

A new shared reader, src/domain/propertyListKeys.ts, swaps each element key that decodes to __proto__ (literal, entity-encoded, or CDATA) for a unique placeholder before the plist decoder runs. Comments and CDATA text are consumed whole, so only real <key> elements are touched. The reader then omits those entries and returns how many it dropped. REA results cannot carry the key, because validation strips it, so it is reported rather than invented:

  • inspect_plist (both routes) and inspect_keyed_archive (XML) add the limitation 1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key.
  • Target resolution, entitlements, and Interface Builder decoding no longer fail. They read the other entries.

Prototype safety is unchanged: entries are rebuilt with Object.fromEntries, and the omitted value is never assigned.

Not covered: the plist package's binary decoder assigns dict["__proto__"] = value, which silently drops the key or replaces the object's prototype. Binary keyed archives and binary NIB plists are therefore unchanged by this PR. Binary Info.plist and inspect_plist go through plutil and are covered.

No schema or catalog change.

Verification

  • tests/boundary/filesystem/binaryTarget.test.ts: an app opens when its XML Info.plist also holds a literal or entity-encoded __proto__ entry, including one whose value contains a forged CFBundleExecutable. Fails on main.
  • New tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts (darwin, real plutil): JSON-expressible and data-bearing plists inspect successfully with the limitation, and the omitted value does not appear. Fails on main.
  • src/artifacts/KeyedArchiveReader.test.ts: an XML archive with a __proto__ entry decodes and reports the limitation. Fails on main.
  • New src/domain/propertyListKeys.test.ts covers the literal, CDATA, and entity spellings. It also checks that comments, CDATA strings, __proto__ with spaces, and the placeholder spelling itself stay intact.
  • npm run check:changed (22 files, 164 tests), npm run docs:check, npm run verify:keyed-archive (real Foundation), and the native plist/signature/keyed-archive/provider boundary tests all pass. 7 NIB/storyboard tests fail identically on unmodified main on this host, because this Mac has only the Command Line Tools and no ibtool.

No Hopper/Ghidra/IDA verification is needed.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/885 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/7/2026 **Status:** ✅ Merged **Merged:** 10/7/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/plist-prototype-keys` --- ### 📝 Commits (1) - [`a08be51`](https://github.com/morluto/rea/commit/a08be51e9f0d23bd46b66bbdb88dbda44bc54c88) fix(plist): read XML plists containing a __proto__ key ### 📊 Changes **10 files changed** (+278 additions, -28 deletions) <details> <summary>View changed files</summary> 📝 `src/application/BinaryTargetResolver.ts` (+9 -14) 📝 `src/application/InterfaceBuilderAnalysis.ts` (+3 -2) 📝 `src/artifacts/KeyedArchiveReader.test.ts` (+14 -0) 📝 `src/artifacts/KeyedArchiveReader.ts` (+16 -5) ➕ `src/domain/propertyListKeys.test.ts` (+53 -0) ➕ `src/domain/propertyListKeys.ts` (+88 -0) 📝 `src/native/NativeMacOSProvider.ts` (+2 -2) 📝 `src/native/parsers/plist.ts` (+20 -5) ➕ `tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts` (+53 -0) 📝 `tests/boundary/filesystem/binaryTarget.test.ts` (+20 -0) </details> ### 📄 Description ## Summary `__proto__` is a legal plist dictionary key (`plutil -lint` reports OK), but the `plist` package's XML decoder throws on it (`__proto__ keys can lead to prototype pollution. More details on CVE-2022-22912`). Every REA site that decodes XML plists failed on such a key: | Site | Before | | --- | --- | | App target resolution (XML `Info.plist`, from #857) | **The app could not be opened at all:** `target_unavailable`, `app has no readable CFBundleExecutable`. One unrelated `<key>__proto__</key>` is enough to keep an app out of every REA tool. | | `inspect_plist`, plutil XML route (data/date/large-integer plists) | `app has no readable CFBundleExecutable` / decode failure | | `inspect_plist`, plutil JSON route | key **silently dropped**, because zod record/object parsing strips own `__proto__` keys | | `inspect_signature` entitlements, XML `inspect_keyed_archive`, Interface Builder plists | decode failure | A new shared reader, `src/domain/propertyListKeys.ts`, swaps each element key that decodes to `__proto__` (literal, entity-encoded, or CDATA) for a unique placeholder before the `plist` decoder runs. Comments and CDATA text are consumed whole, so only real `<key>` elements are touched. The reader then omits those entries and returns how many it dropped. REA results cannot carry the key, because validation strips it, so it is reported rather than invented: - `inspect_plist` (both routes) and `inspect_keyed_archive` (XML) add the limitation `1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key.` - Target resolution, entitlements, and Interface Builder decoding no longer fail. They read the other entries. Prototype safety is unchanged: entries are rebuilt with `Object.fromEntries`, and the omitted value is never assigned. **Not covered:** the `plist` package's *binary* decoder assigns `dict["__proto__"] = value`, which silently drops the key or replaces the object's prototype. Binary keyed archives and binary NIB plists are therefore unchanged by this PR. Binary `Info.plist` and `inspect_plist` go through `plutil` and are covered. No schema or catalog change. ## Verification - `tests/boundary/filesystem/binaryTarget.test.ts`: an app opens when its XML `Info.plist` also holds a literal or entity-encoded `__proto__` entry, including one whose value contains a forged `CFBundleExecutable`. Fails on `main`. - New `tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts` (darwin, real plutil): JSON-expressible and data-bearing plists inspect successfully with the limitation, and the omitted value does not appear. Fails on `main`. - `src/artifacts/KeyedArchiveReader.test.ts`: an XML archive with a `__proto__` entry decodes and reports the limitation. Fails on `main`. - New `src/domain/propertyListKeys.test.ts` covers the literal, CDATA, and entity spellings. It also checks that comments, CDATA strings, ` __proto__ ` with spaces, and the placeholder spelling itself stay intact. - `npm run check:changed` (22 files, 164 tests), `npm run docs:check`, `npm run verify:keyed-archive` (real Foundation), and the native plist/signature/keyed-archive/provider boundary tests all pass. 7 NIB/storyboard tests fail identically on unmodified `main` on this host, because this Mac has only the Command Line Tools and no `ibtool`. No Hopper/Ghidra/IDA verification is needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-16 21:17:11 +00:00
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#2317
No description provided.