You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The bridge currently authenticates the counterparty window correctly — IframeHostAdapter checks event.origin and event.source, and the client filters window messages through allowedOrigins. This stops external frames/windows from spoofing messages.
What origin checks cannot address is the same-context listener: any script already running in the parent or child page (third-party tags, ads, a compromised dependency) can register window.addEventListener('message', ...) and observe all bridge traffic, or call the bridge directly. Transport confidentiality against an attacker with script execution is not fully solvable, but the library currently offers neither the standard surface-reduction mechanism nor a last-line-of-defense hook on the host side.
Proposed changes
1. MessageChannel upgrade for the iframe transport
IframeHostAdapter / IframeClientAdapter exchange every message on the shared window listener. Instead:
Perform a single window-level handshake that transfers a MessagePort to the child.
Route all subsequent traffic through the port.
Ports are held in closures, so a window-level message listener added by any other script sees nothing after the handshake. This is the same model Comlink uses. (Not applicable to the RN transport — there is no MessageChannel across the native boundary.)
BridgeHost has registerHandler() and observe-only onCall() lifecycle events, but no interception point that can reject a request before it reaches a handler. The client has Axios-style interceptors; the host's handleMessage path has no symmetric hook.
Add a guard (or host request interceptor) that runs before handler dispatch, so apps can enforce policies like:
allowlist the WebView's current URL before serving any action
restrict sensitive actions to specific screens/origins
reject with a typed BridgeError (e.g. UNAUTHORIZED)
The policy content is the app's responsibility; providing the place to put it is the library's.
File: packages/core/src/host/BridgeHost.ts (guard check at the top of handleMessage)
3. SECURITY.md — documented threat model
State explicitly what the library defends against and what it delegates:
Reduced by this issue: same-context eavesdropping on the iframe transport (MessageChannel), unauthorized actions reaching handlers (host guard).
Delegated to the app: not sending secrets over the bridge (scoped/one-time tokens instead), third-party script hygiene (CSP, dependency review).
Non-goals
Encrypting bridge payloads — an attacker with script execution can call the bridge directly; encryption adds no real boundary.
MessageChannel for the React Native transport — not supported across the native boundary; the host guard (release: v0.1.0 (retry) #2) is the mitigation there.
Acceptance criteria
Iframe client/host communicate over a MessagePort after handshake; a window-level listener registered post-handshake observes no bridge traffic (integration test).
Handshake failure falls back or errors explicitly — no silent hang.
BridgeHost guard can reject a request before handler dispatch; rejection reaches the client as a typed BridgeError (unit test).
SECURITY.md documents the threat model above.
No breaking changes to the public definePlugin / client API.
Background
The bridge currently authenticates the counterparty window correctly —
IframeHostAdapterchecksevent.originandevent.source, and the client filters window messages throughallowedOrigins. This stops external frames/windows from spoofing messages.What origin checks cannot address is the same-context listener: any script already running in the parent or child page (third-party tags, ads, a compromised dependency) can register
window.addEventListener('message', ...)and observe all bridge traffic, or call the bridge directly. Transport confidentiality against an attacker with script execution is not fully solvable, but the library currently offers neither the standard surface-reduction mechanism nor a last-line-of-defense hook on the host side.Proposed changes
1. MessageChannel upgrade for the iframe transport
IframeHostAdapter/IframeClientAdapterexchange every message on the sharedwindowlistener. Instead:MessagePortto the child.Ports are held in closures, so a window-level
messagelistener added by any other script sees nothing after the handshake. This is the same model Comlink uses. (Not applicable to the RN transport — there is no MessageChannel across the native boundary.)Files:
packages/core/src/adapters/IframeHostAdapter.ts,packages/core/src/adapters/IframeClientAdapter.ts2. Host-side request guard
BridgeHosthasregisterHandler()and observe-onlyonCall()lifecycle events, but no interception point that can reject a request before it reaches a handler. The client has Axios-style interceptors; the host'shandleMessagepath has no symmetric hook.Add a guard (or host request interceptor) that runs before handler dispatch, so apps can enforce policies like:
BridgeError(e.g.UNAUTHORIZED)The policy content is the app's responsibility; providing the place to put it is the library's.
File:
packages/core/src/host/BridgeHost.ts(guard check at the top ofhandleMessage)3. SECURITY.md — documented threat model
State explicitly what the library defends against and what it delegates:
Non-goals
Acceptance criteria
MessagePortafter handshake; a window-level listener registered post-handshake observes no bridge traffic (integration test).BridgeHostguard can reject a request before handler dispatch; rejection reaches the client as a typedBridgeError(unit test).SECURITY.mddocuments the threat model above.definePlugin/ client API.