Skip to content

fix(import): extend cross-origin MessageChannel validation to import frame [INT-783][ENG-4597][ENG-4598] - #130

Open
justinformentin wants to merge 2 commits into
mainfrom
jf/int-783-import-frame-apply-cross-origin-messagechannel-validation
Open

fix(import): extend cross-origin MessageChannel validation to import frame [INT-783][ENG-4597][ENG-4598]#130
justinformentin wants to merge 2 commits into
mainfrom
jf/int-783-import-frame-apply-cross-origin-messagechannel-validation

Conversation

@justinformentin

@justinformentin justinformentin commented Aug 7, 2026

Copy link
Copy Markdown

Summary

Extends the cross-origin MessageChannel validation from INT-697 / PR #129 to the import frame, and closes the organizationId/userId binding gap that allowed legacy callers to skip bundle binding checks.

PR #129 hardened the export-and-sign frame's TURNKEY_INIT_MESSAGE_CHANNEL handler against opaque origins and non-parent senders. The import frame had the same pre-existing gap and was not touched by PR #129.

Linear: INT-783 https://linear.app/turnkey/issue/INT-783

Security tickets addressed:
ENG-4598 (TKA-20260806-014) https://linear.app/turnkey/issue/ENG-4598 — "The hosted import frame accepts the first MessageChannel from any sender." This PR closes the channel-seizure vector by requiring the sender to be the direct parent with a concrete origin.
ENG-4597 - https://linear.app/turnkey/issue/ENG-4597

Fix 1: Cross-origin MessageChannel gate (ENG-4598)

Security ticket: ENG-4598 (TKA-20260806-014) — "The hosted import frame accepts the
first MessageChannel from any sender."

Gate now requires:

  • event.source === window.parent — only the direct parent frame may establish the channel
  • event.origin && event.origin !== "null" — opaque/sandboxed origins are rejected
  • event.ports?.length === 1 — exactly one transferred port required

Fix 2: Import bundle org/user binding (ENG-4597)

Security ticket: ENG-4597 (TKA-20260806-015) — "The hosted import frame accepts signed
import bundles without organization/user binding when legacy callers omit those fields."

onInjectImportBundle() previously logged a console.warn and skipped the binding check
when organizationId/userId was absent. Both now hard-reject (throw) on missing.
Existing mismatch checks unchanged.

Breaking change: iframe-stamper < v2.0.0 (or integrations omitting these fields) now
receive an ERROR response.

Validation

  • ESLint: 0 errors, 4 pre-existing no-console warnings (none from this change)
  • Prettier: all files pass
  • Jest: 25/25 tests pass (19 pre-existing + 6 new gate tests covering: accept valid case, reject non-parent source, reject opaque/null origin, reject empty origin, reject 0 ports, reject 2 ports)
  • Production build: compiled successfully, new bundle hash 45de86a592cd0d80ccd0

Reference

… import frame

Mirrors the TURNKEY_INIT_MESSAGE_CHANNEL gate added to export-and-sign in
PR #129 (#129). The import frame's handler
previously only checked event.ports?.[0]; it now also validates:
- event.source === window.parent (direct parent only)
- event.origin && event.origin !== "null" (concrete, non-opaque origin)
- event.ports?.length === 1 (exactly one transferred port)

Closes the identical class of vulnerability INT-697 fixed in export-and-sign.
Pre-existing gap; not introduced by PR #129.

Also adds six Jest tests for the new gate conditions and rebuilds the
production bundle (content hash: 45de86a592cd0d80ccd0).

Linear: INT-783 https://linear.app/turnkey/issue/INT-783
@justinformentin
justinformentin force-pushed the jf/int-783-import-frame-apply-cross-origin-messagechannel-validation branch from 207de18 to f1647ae Compare August 7, 2026 19:07
@justinformentin justinformentin changed the title fix(import): extend cross-origin MessageChannel validation to import frame (INT-783) fix(import): extend cross-origin MessageChannel validation to import frame [INT-783][ENG-4597][ENG-4598] Aug 10, 2026
…ing (ENG-4597)

Previously, onInjectImportBundle() in the v1.0.0 path would only log a
console.warn when organizationId or userId was omitted by the caller,
allowing signed import bundles to be accepted without org/user binding.

This change converts both warnings into hard errors:
- Missing organizationId → throws with message indicating iframe-stamper
  >= v2.0.0 is required
- Missing userId → throws with same guidance

The existing equality checks (signedData must match the supplied values)
are preserved unchanged. The now-obsolete TODO comments and console.warn
calls are removed.

Tests: new describe block 'onInjectImportBundle org/user binding
(import frame)' in import/index.test.js covers:
- missing organizationId (undefined and empty string)
- missing userId (undefined and empty string)
- mismatched organizationId / userId still throws
- valid matching bundle succeeds (BUNDLE_INJECTED)

Bundle rebuilt: index.bundle.808edbe601034931b90e.js
@justinformentin
justinformentin force-pushed the jf/int-783-import-frame-apply-cross-origin-messagechannel-validation branch from 1a7b31a to e0a04e0 Compare August 10, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant