Security model overview
GBGBridge runs entirely in the browser. The bridge is a structured-message layer on top of the browserโs nativepostMessage channel โ it does not bypass any browser security mechanism. Three guarantees underpin the security model:
- Cross-origin isolation โ The host page and the journey iframe are separate origins. Same-origin policy prevents either from reading the otherโs DOM, cookies, or storage. The bridge is the only sanctioned channel between them.
- Origin allowlist โ The SDK enforces a strict allowlist on both inbound and outbound messages. Messages to or from origins not in the allowlist are silently dropped.
- Envelope validation โ Every inbound message is validated against the protocol envelope before reaching any handler. Malformed messages are dropped before they can be routed.
Trust boundaries
- Host page code runs with full access to the hostโs origin (cookies, storage, network).
- Journey iframe content runs in a sandboxed cross-origin context. It cannot access the host pageโs DOM, storage, or APIs โ only the bridge.
- The bridge is the controlled interface. Every message crosses an origin boundary and is validated by the SDK before reaching either side.
- Your backend does the token exchange and returns a tokenized journey URL. Credentials never reach the browser.
Transport security
Bridge messages travel over the browserโs in-processpostMessage channel โ they never leave the device. Network security applies separately to the journey URL load and any backend calls.
Message channel
Both directions stay inside the browserโs process. There is no IPC across processes (browser-internal), and no network call between iframe and host.
Network security (journey URL load)
The journey URL is loaded over the network. Apply standard web transport hygiene:- HTTPS in production. Browsers block mixed content โ an
https://host page loading anhttp://iframe will fail silently. Local development onhttp://localhostworks in both directions because browsers treatlocalhostas a secure context. - HSTS on the journey origin if you control it. Forces HTTPS even on first contact.
- CSP
frame-srcdirective on the host page (covered below).
Origin allowlist
TheallowedOrigins array passed to BridgeHost / BridgeHostProvider is the load-bearing security control. The SDK enforces it on three independent checks per inbound message:
event.source !== iframe.contentWindowโ drop silently.event.originnot in the normalized allowlist โ drop silently.- Envelope validation fails โ drop silently.
targetOriginresolved fromiframe.srcnot in the allowlist โ drop withconsole.warn.
iframe.src to a hostile origin cannot use the bridge to leak data or invoke host capabilities โ the outbound check fails and the inbound source check fails.
Origin allowlist enforcement is defence in depth: this is the host-page half of the work shipped under GGO-13990 (US1 + US2). The iframe-side journey enforces the same allowlist against
event.origin of inbound messages from the host. Both ends of the bridge validate origin independently โ neither half can be skipped by a script on the other side.Format rules
- Exact origins only. No paths, no trailing slashes, no wildcards.
- Match scheme.
https://example.comandhttp://example.comare different origins. - Default ports omitted.
https://example.comandhttps://example.com:443are the same; the SDK normalizes both to the canonical form, but write the canonical form yourself to avoid drift. - Non-default ports required.
https://example.com:8443requires the explicit port. - Empty arrays throw.
new BridgeHost({ allowedOrigins: [], ... })raises at construction. There is no implicit โallow allโ โ this is by design.
Multiple environments
If your host serves both production and staging, list both explicit origins. If the journey URL might be on yet a third origin (e.g., a per-tenant subdomain), build the allowlist dynamically:Complete multi-environment example
For a host page that serves both production and staging, three controls work together. All three reference the same set of journey origins:- CSP
frame-srclists both environments โ without it, the browser blocks the iframe load entirely. allowedOriginsis per-instance, scoped to the single environment in use โ narrower than CSP. Hard-coding a tighter allowlist than your CSP is intentional.- iframe
allowis environment-agnostic โ same permission set for production and staging.
key={env}) so the allowedOrigins snapshot rebuilds with the new origin. Donโt just change the allowedOrigins prop โ itโs snapshotted at attach time.
Content Security Policy (CSP)
If your host page sets a CSP header, the journey origin must appear inframe-src (or child-src as a fallback for older browsers):
frame-src * in production โ it disables a meaningful defence-in-depth layer.
If the journey itself loads sub-resources from third-party origins (analytics, fonts), those are constrained by the journeyโs CSP, not yours. Coordinate with the journey team if you see CSP failures in the iframe.
Iframe permission delegation
Browser permissions (camera, microphone, geolocation, etc.) are not inherited by iframes by default. Use the iframeallow attribute to delegate the permissions your journey might need:
allow values โ the journey may invoke getUserMedia for a fallback flow (e.g., if capability.query returns supported: false for camera.document).
Use the least-permissive set of permissions your integration actually requires. Donโt include geolocation or microphone if the journey doesnโt use them.
No bootstrap script injection
Unlike iOS (WKUserScript) and Android (WebViewClient.onPageStarted + addJavascriptInterface), the Web SDK does not inject any script into the journey iframe. Cross-origin restrictions would prevent it anyway โ the host page cannot execute JavaScript inside a cross-origin iframe.
The journey iframe loads its own SDK (@gbgplc-internal/ggo-native-bridge and its React variant) as part of its bundle. The host and iframe agree on the wire protocol; neither party can modify the otherโs runtime.
This is a hard browser constraint, not a design choice โ but it also means thereโs no bootstrap configuration to manage, and no risk of accidentally exposing host secrets via injected globals.
Message validation
Two layers of validation apply to every inbound message: shape validation at the transport layer, and payload validation inside your own handlers. Both matter โ one keeps malformed messages out of your handlers, the other keeps malicious payloads from acting on your handlerโs assumptions.Envelope validation
Every inbound message goes throughHostIframeChannel.isValidEnvelope before reaching BridgeHost. The checks:
versionis a stringcorrelationIdis a stringtypeis"request","response", or"event"timestampis a numberpayloadexists and is an objectpayload.actionis a non-empty string
pendingRequests.
Action validation
Handlers route by exact string match onpayload.action. Register handlers only for actions you expect. Unknown actions:
- Go to
pendingRequests(capped at 50; overflow dropped). - Fire
delegate.onUnhandledRequestso you can decide whether to respond manually.
Data validation in handlers
The iframe is the less-trusted zone. Treat itspayload.data the same way you would treat any external input โ validate every field your handler reads.
Token exchange and credential safety
The journey URL is typically a single-use, signed URL minted by your backend. Three rules:- Backend mints the URL, not the browser. Use
@gbg/go-coreserver-side to authenticate with GBG and obtain the tokenized journey URL. The host page receives only the URL. - Credentials never reach the browser.
@gbg/go-coreโs OAuth flow uses a client secret. Exposing it in browser code lets anyone mint sessions against your tenant. Always run that flow server-side. - The journey URL is a short-lived token. It typically expires within minutes and is single-use. Donโt cache it in
localStorageor pass it through unprotected channels.
Browser permission handling
GBGBridge itself does not request any browser permissions. Your capability handlers are responsible for requesting and checking permissions before performing the operation.capabilities declaration when possible โ the journey uses permissionState to surface a permission prompt before triggering the request.
Production checklist
Before shipping your integration:- HTTPS only. Journey URL is HTTPS. Host page is HTTPS in production. No mixed content.
- Exact origin allowlist.
allowedOriginscontains only the journey origins you trust. No wildcards, no paths, no trailing slashes. - CSP
frame-src. Host page CSP allows the journey origin(s). Noframe-src *. - iframe
allowattribute. Set the minimum browser permissions the journey needs (typicallycamerafor capture flows). - Backend token exchange.
@gbg/go-coreruns server-side. Client secret never ships in browser code. See Journey URL. - Input validation in handlers. Every handler validates
payload.databefore acting on it. - No secrets in bridge messages. Events and responses contain no tokens, API keys, or personally-identifying data the journey doesnโt already have.
- Error responses sanitized. Error messages donโt leak internal implementation details (stack traces, file paths, internal IDs).
- Multiple environments separated. Staging and production journeys have distinct origins and distinct allowlists.
- Detach on teardown. When the iframe is removed,
host.detach()(or provider unmount) runs to release the message listener.
Next steps
- Journey URL โ Backend token exchange and journey URL construction.
- Integration Checklist โ Pre-flight checks before shipping.
- Troubleshooting โ Diagnosing security-related issues.
- API Reference โ Configuration and setup API.