Skip to main content
This guide covers the security model of the GBGBridge Web SDK, including transport safety, content policies, origin allowlist hardening, token-exchange responsibilities, and production-deployment best practices.

Security model overview

GBGBridge runs entirely in the browser. The bridge is a structured-message layer on top of the browserโ€™s native postMessage channel โ€” it does not bypass any browser security mechanism. Three guarantees underpin the security model:
  1. 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.
  2. 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.
  3. 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-process postMessage 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 an http:// iframe will fail silently. Local development on http://localhost works in both directions because browsers treat localhost as a secure context.
  • HSTS on the journey origin if you control it. Forces HTTPS even on first contact.
  • CSP frame-src directive on the host page (covered below).

Origin allowlist

The allowedOrigins array passed to BridgeHost / BridgeHostProvider is the load-bearing security control. The SDK enforces it on three independent checks per inbound message:
  1. event.source !== iframe.contentWindow โ†’ drop silently.
  2. event.origin not in the normalized allowlist โ†’ drop silently.
  3. Envelope validation fails โ†’ drop silently.
And on outbound:
  1. targetOrigin resolved from iframe.src not in the allowlist โ†’ drop with console.warn.
This means a script on the host page that repoints 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.com and http://example.com are different origins.
  • Default ports omitted. https://example.com and https://example.com:443 are 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:8443 requires 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:
Avoid building allowlists from user-controlled input โ€” a malicious URL could expand the allowlist past your intended trust set.

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:
Three things to keep aligned:
  1. CSP frame-src lists both environments โ€” without it, the browser blocks the iframe load entirely.
  2. allowedOrigins is per-instance, scoped to the single environment in use โ€” narrower than CSP. Hard-coding a tighter allowlist than your CSP is intentional.
  3. iframe allow is environment-agnostic โ€” same permission set for production and staging.
If you switch environments at runtime, force a Provider remount (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 in frame-src (or child-src as a fallback for older browsers):
For multiple environments, list each explicitly. Avoid 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 iframe allow attribute to delegate the permissions your journey might need:
Even when your host handles capture via the bridge, set the appropriate 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 through HostIframeChannel.isValidEnvelope before reaching BridgeHost. The checks:
  • version is a string
  • correlationId is a string
  • type is "request", "response", or "event"
  • timestamp is a number
  • payload exists and is an object
  • payload.action is a non-empty string
Failures are dropped silently. Malformed envelopes never reach handlers, the delegate, or pendingRequests.

Action validation

Handlers route by exact string match on payload.action. Register handlers only for actions you expect. Unknown actions:
  • Go to pendingRequests (capped at 50; overflow dropped).
  • Fire delegate.onUnhandledRequest so you can decide whether to respond manually.

Data validation in handlers

The iframe is the less-trusted zone. Treat its payload.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:
  1. Backend mints the URL, not the browser. Use @gbg/go-core server-side to authenticate with GBG and obtain the tokenized journey URL. The host page receives only the URL.
  2. 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.
  3. The journey URL is a short-lived token. It typically expires within minutes and is single-use. Donโ€™t cache it in localStorage or pass it through unprotected channels.
For the full backend-to-frontend handoff pattern, see Journey URL.

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.
Report the current permission state in your 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. allowedOrigins contains only the journey origins you trust. No wildcards, no paths, no trailing slashes.
  • CSP frame-src. Host page CSP allows the journey origin(s). No frame-src *.
  • iframe allow attribute. Set the minimum browser permissions the journey needs (typically camera for capture flows).
  • Backend token exchange. @gbg/go-core runs server-side. Client secret never ships in browser code. See Journey URL.
  • Input validation in handlers. Every handler validates payload.data before 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