[PR #5786] [MERGED] fix(apple): refuse invalid binary dates and unknown sub-millisecond instants #7060

Closed
opened 2026-11-01 21:45:04 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5786
Author: @morluto
Created: 10/21/2026
Status: ✅ Merged
Merged: 10/21/2026
Merged by: @morluto

Base: main ← Head: cursor/binary-keyed-archive-dates-f474


📝 Commits (3)

  • f9e41e5 fix(apple): refuse invalid binary dates and unknown sub-millisecond instants
  • 0ee7d42 fix(apple): share one decoder copy and bound binary date payloads
  • 481c118 test(apple): refuse out-of-region dates through the keyed reader

📊 Changes

10 files changed (+439 additions, -11 deletions)

View changed files

📝 docs/native-investigation.md (+6 -1)
➕ src/artifacts/apple/BinaryPlistDate.test.ts (+20 -0)
➕ src/artifacts/apple/BinaryPlistDate.ts (+18 -0)
📝 src/artifacts/apple/InterfaceBuilderAnalysis.ts (+15 -2)
📝 src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts (+50 -0)
📝 src/artifacts/apple/InterfaceBuilderDecodeBudget.ts (+37 -4)
📝 src/artifacts/apple/KeyedArchiveReader.test.ts (+228 -1)
📝 src/artifacts/apple/KeyedArchiveReader.ts (+5 -1)
📝 src/artifacts/apple/PlistNumberLiterals.test.ts (+37 -0)
📝 src/artifacts/apple/PlistNumberLiterals.ts (+23 -2)

📄 Description

Summary

Fixes #5769. Fixes #5770.

inspect-keyed-archive treated a binary date marker 0x30 as the observed instant 2001-01-01T00:00:00.100Z, and it truncated a valid 0x33 date at 0.1005 seconds after the 2001 epoch to that same timestamp with no date limitation. Invalid markers are now a typed format failure. Sub-millisecond instants stay the existing unknown date (iso8601: null) with a counted limitation. The canonical 0.100 second control stays 2001-01-01T00:00:00.100Z.

Problem and expected behavior

Binary plist dates are marker 0x33 plus a big-endian Float64 count of seconds since 2001-01-01. The pinned plist@5.0.0 decoder reads eight bytes for every type-3 marker and builds a JavaScript Date from seconds * 1000, which keeps only milliseconds.

The reachable 0x30 fixture must be refused. The 0.1005 second fixture must not be reported as a different known instant. The 0.100 second control must remain known.

Change and scope

The shared binary expansion preflight rejects a reachable type-3 marker other than 0x33. Date markers and their eight-byte payloads must end at the offset table even when other decoder-bounds checks accept trailing bytes. Each accepted date payload is judged from its own float64 seconds. When plist@5's Date construction does not denote those exact seconds, that payload is replaced with NaN on the same parser-input copy that rewrites wide integers, so the existing unknown-date projection applies. The caller's buffer is returned when neither rewrite is needed. Distinct instants that share one rounded millisecond stay independent. Keyed-archive and Interface Builder plist decoding both use this path. Original archive bytes are not modified.

Not included: numeric markers other than dates, XML dates, and native inspect_plist (plutil). Ordinary bugfix within existing binary plist boundaries; maintainer scope decision is not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: binary plist expansion preflight and the keyed-archive / Interface Builder plist decode boundary, before plist.parseBinary.
  • Contribution scope fit and linked maintainer decision: not applicable (bugfix inside existing read-only plist inspection).
  • CLI and MCP/tool-catalog contract: unchanged schemas. Malformed dates fail with artifact_operation_failed / reason: format. Unrepresentable dates use the existing unknown-date object and limitation.
  • Provider, bridge, target-format, or platform compatibility: binary property lists on every host that decodes them in process.
  • Evidence, artifact, provenance, or reconstruction contract: invalid markers are not observed timestamps. Unrepresentable instants stay unknown. Archive digests still identify the original bytes.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): read-only inspection. No target execution or file mutation.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/artifacts/apple/KeyedArchiveReader.test.ts, src/artifacts/apple/BinaryPlistDate.test.ts, src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts, src/artifacts/apple/PlistNumberLiterals.test.ts.
  • Base reproduction or other evidence: the issue's Python plistlib fixtures. Before the fix, marker 0x30 decoded and 0.1005 seconds was returned as 2001-01-01T00:00:00.100Z.
  • User-visible CLI/MCP output (if applicable): built scripts/rea.mjs and stdio MCP open_binary + inspect_keyed_archive on the same three fixtures, recorded on f9e41e5f9. The follow-up commits do not change those outcomes.
    • valid (39f08db8…, marker 0x33, payload 3fb999999999999a): exit 0, object 1 {"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100Z"}, no date limitation.
    • invalid-date-marker (ac5696b8…, marker 0x30): exit 1, {"code":"artifact_operation_failed","details":{"operation":"inspect_keyed_archive","reason":"format","detail":"Cannot decode selected Foundation keyed archive: binary plist date marker 0x30 is invalid"}}.
    • submillisecond (ab69ba6e…, payload 3fb9ba5e353f7cee): exit 0, object 1 {"$plist_type":"date","iso8601":null} and 1 date value(s) are reported as { "$plist_type": "date", "iso8601": null } because the decoder could not represent their timestamps.
    • MCP returned the same three outcomes. This run did not separately execute the harness Ajv check of the advertised SDK schema.
  • Remaining proof gaps: plutil was not re-run (this host has no macOS plutil). No real Interface Builder document beyond the synthetic .nib plist fixture. GitHub CI on 481c1183f has not completed.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • On 0ee7d420a: npm run test:local -- src/artifacts/apple/BinaryPlistDate.test.ts src/artifacts/apple/KeyedArchiveReader.test.ts src/artifacts/apple/PlistNumberLiterals.test.ts src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts — 44 passed.
  • On 481c1183f: npm run test:local -- src/artifacts/apple/KeyedArchiveReader.test.ts — 28 passed.
  • npm run check:fast — typecheck and lint succeeded on 0ee7d420a and again on 481c1183f.
  • npx oxfmt --check on the touched files — formatted.
  • On f9e41e5f9, still the user-visible proof for the three fixtures: npm run test:local -- src/application/AppBundleExecutable.test.ts src/domain/apple/keyedArchive.test.ts src/domain/apple/interfaceBuilderGraph.test.ts src/domain/apple/plistValue.test.ts — 46 passed; npm run build:cached; CLI and MCP replay above.

Compatibility, safety, and release

  • Breaking changes or migration steps: callers that accepted marker 0x30 as a date now receive a format failure. Callers that treated a sub-millisecond binary date as its truncated millisecond instant now receive an unknown date plus the counted limitation. Canonical millisecond dates are unchanged. No configuration migration.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable. plutil was not available here.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: read-only local plist inspection. No new execution, injection, or mutation.

Author readiness

Human owner: @morluto

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.
Open in Web Open in Cursor 

🔄 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/5786 **Author:** [@morluto](https://github.com/morluto) **Created:** 10/21/2026 **Status:** ✅ Merged **Merged:** 10/21/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `cursor/binary-keyed-archive-dates-f474` --- ### 📝 Commits (3) - [`f9e41e5`](https://github.com/morluto/rea/commit/f9e41e5f9f46c89e4130d371b5c4fdba373ca671) fix(apple): refuse invalid binary dates and unknown sub-millisecond instants - [`0ee7d42`](https://github.com/morluto/rea/commit/0ee7d420a20db665aae476c471fc16968279a9fc) fix(apple): share one decoder copy and bound binary date payloads - [`481c118`](https://github.com/morluto/rea/commit/481c1183ff47efeb68cbc3f7b19f4f139b6a5432) test(apple): refuse out-of-region dates through the keyed reader ### 📊 Changes **10 files changed** (+439 additions, -11 deletions) <details> <summary>View changed files</summary> 📝 `docs/native-investigation.md` (+6 -1) ➕ `src/artifacts/apple/BinaryPlistDate.test.ts` (+20 -0) ➕ `src/artifacts/apple/BinaryPlistDate.ts` (+18 -0) 📝 `src/artifacts/apple/InterfaceBuilderAnalysis.ts` (+15 -2) 📝 `src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts` (+50 -0) 📝 `src/artifacts/apple/InterfaceBuilderDecodeBudget.ts` (+37 -4) 📝 `src/artifacts/apple/KeyedArchiveReader.test.ts` (+228 -1) 📝 `src/artifacts/apple/KeyedArchiveReader.ts` (+5 -1) 📝 `src/artifacts/apple/PlistNumberLiterals.test.ts` (+37 -0) 📝 `src/artifacts/apple/PlistNumberLiterals.ts` (+23 -2) </details> ### 📄 Description <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary Fixes #5769. Fixes #5770. `inspect-keyed-archive` treated a binary date marker `0x30` as the observed instant `2001-01-01T00:00:00.100Z`, and it truncated a valid `0x33` date at `0.1005` seconds after the 2001 epoch to that same timestamp with no date limitation. Invalid markers are now a typed format failure. Sub-millisecond instants stay the existing unknown date (`iso8601: null`) with a counted limitation. The canonical `0.100` second control stays `2001-01-01T00:00:00.100Z`. ## Problem and expected behavior Binary plist dates are marker `0x33` plus a big-endian Float64 count of seconds since 2001-01-01. The pinned `plist@5.0.0` decoder reads eight bytes for every type-3 marker and builds a JavaScript `Date` from `seconds * 1000`, which keeps only milliseconds. The reachable `0x30` fixture must be refused. The `0.1005` second fixture must not be reported as a different known instant. The `0.100` second control must remain known. ## Change and scope The shared binary expansion preflight rejects a reachable type-3 marker other than `0x33`. Date markers and their eight-byte payloads must end at the offset table even when other decoder-bounds checks accept trailing bytes. Each accepted date payload is judged from its own float64 seconds. When `plist@5`'s `Date` construction does not denote those exact seconds, that payload is replaced with NaN on the same parser-input copy that rewrites wide integers, so the existing unknown-date projection applies. The caller's buffer is returned when neither rewrite is needed. Distinct instants that share one rounded millisecond stay independent. Keyed-archive and Interface Builder plist decoding both use this path. Original archive bytes are not modified. Not included: numeric markers other than dates, XML dates, and native `inspect_plist` (`plutil`). Ordinary bugfix within existing binary plist boundaries; maintainer scope decision is not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: binary plist expansion preflight and the keyed-archive / Interface Builder plist decode boundary, before `plist.parseBinary`. - Contribution scope fit and linked maintainer decision: not applicable (bugfix inside existing read-only plist inspection). - CLI and MCP/tool-catalog contract: unchanged schemas. Malformed dates fail with `artifact_operation_failed` / `reason: format`. Unrepresentable dates use the existing unknown-date object and limitation. - Provider, bridge, target-format, or platform compatibility: binary property lists on every host that decodes them in process. - Evidence, artifact, provenance, or reconstruction contract: invalid markers are not observed timestamps. Unrepresentable instants stay unknown. Archive digests still identify the original bytes. - Target selection, authority, upstream behavior, and cleanup details (if applicable): read-only inspection. No target execution or file mutation. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/artifacts/apple/KeyedArchiveReader.test.ts`, `src/artifacts/apple/BinaryPlistDate.test.ts`, `src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts`, `src/artifacts/apple/PlistNumberLiterals.test.ts`. - Base reproduction or other evidence: the issue's Python `plistlib` fixtures. Before the fix, marker `0x30` decoded and `0.1005` seconds was returned as `2001-01-01T00:00:00.100Z`. - User-visible CLI/MCP output (if applicable): built `scripts/rea.mjs` and stdio MCP `open_binary` + `inspect_keyed_archive` on the same three fixtures, recorded on `f9e41e5f9`. The follow-up commits do not change those outcomes. - valid (`39f08db8…`, marker `0x33`, payload `3fb999999999999a`): exit 0, object 1 `{"$plist_type":"date","iso8601":"2001-01-01T00:00:00.100Z"}`, no date limitation. - invalid-date-marker (`ac5696b8…`, marker `0x30`): exit 1, `{"code":"artifact_operation_failed","details":{"operation":"inspect_keyed_archive","reason":"format","detail":"Cannot decode selected Foundation keyed archive: binary plist date marker 0x30 is invalid"}}`. - submillisecond (`ab69ba6e…`, payload `3fb9ba5e353f7cee`): exit 0, object 1 `{"$plist_type":"date","iso8601":null}` and `1 date value(s) are reported as { "$plist_type": "date", "iso8601": null } because the decoder could not represent their timestamps.` - MCP returned the same three outcomes. This run did not separately execute the harness Ajv check of the advertised SDK schema. - Remaining proof gaps: `plutil` was not re-run (this host has no macOS `plutil`). No real Interface Builder document beyond the synthetic `.nib` plist fixture. GitHub CI on `481c1183f` has not completed. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - On `0ee7d420a`: `npm run test:local -- src/artifacts/apple/BinaryPlistDate.test.ts src/artifacts/apple/KeyedArchiveReader.test.ts src/artifacts/apple/PlistNumberLiterals.test.ts src/artifacts/apple/InterfaceBuilderDecodeBudget.test.ts` — 44 passed. - On `481c1183f`: `npm run test:local -- src/artifacts/apple/KeyedArchiveReader.test.ts` — 28 passed. - `npm run check:fast` — typecheck and lint succeeded on `0ee7d420a` and again on `481c1183f`. - `npx oxfmt --check` on the touched files — formatted. - On `f9e41e5f9`, still the user-visible proof for the three fixtures: `npm run test:local -- src/application/AppBundleExecutable.test.ts src/domain/apple/keyedArchive.test.ts src/domain/apple/interfaceBuilderGraph.test.ts src/domain/apple/plistValue.test.ts` — 46 passed; `npm run build:cached`; CLI and MCP replay above. ## Compatibility, safety, and release - Breaking changes or migration steps: callers that accepted marker `0x30` as a date now receive a format failure. Callers that treated a sub-millisecond binary date as its truncated millisecond instant now receive an unknown date plus the counted limitation. Canonical millisecond dates are unchanged. No configuration migration. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. `plutil` was not available here. - Package or release metadata impact: none. - Security, privacy, process, or containment review: read-only local plist inspection. No new execution, injection, or mutation. ## Author readiness Human owner: @morluto - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-9b6c9db7-1d5b-5352-95ec-34ec5265f474?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-9b6c9db7-1d5b-5352-95ec-34ec5265f474&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:45:04 +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#7060
No description provided.