Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers the security model of GBGBridge, including transport safety, content policies, and best practices for production deployments.

Security Model Overview

GBGBridge operates within the iOS WebKit security model. The bridge is a thin messaging layer β€” it does not bypass any platform security mechanisms. All communication between the web journey and the native host goes through WebKit’s WKScriptMessageHandler (incoming) and evaluateJavaScript (outgoing).

Trust Boundaries

  • Native host code runs with full app permissions. Handlers can access any iOS API.
  • Web content in WKWebView runs in a sandboxed web process. It cannot access native APIs except through the bridge message handler.
  • The bridge is the controlled interface between these two zones.

Transport Security

Bridge messages never traverse the network β€” they ride on WebKit’s IPC channels β€” but the journey URL itself does, so transport security has two distinct concerns: how messages move between native and web (covered first) and how the journey is loaded over HTTPS (covered second).

Message Channel

Messages travel over WebKit’s internal IPC mechanism:
  • Web β†’ Native: window.webkit.messageHandlers.gbgBridge.postMessage() β€” uses WebKit’s built-in script message handler. This is a same-process or inter-process call managed by WebKit, not a network request.
  • Native β†’ Web: webView.evaluateJavaScript() β€” executes JavaScript in the WebView’s context.
Neither channel traverses the network. Messages are memory-to-memory within the device.

Network Security (Journey Loading)

The web journey itself is loaded over the network. To ensure network security:
  • Always use HTTPS in production. iOS App Transport Security (ATS) enforces this by default.
  • Do not disable ATS globally. If you need local development access, use the scoped exception:
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>
  • Pin certificates if your security requirements demand it. Implement WKNavigationDelegate.webView(_:didReceive:completionHandler:) for custom certificate validation.

Content Security

Bootstrap Script Injection

The bridge bootstrap script is injected at document start via WKUserScript. This runs before any web content loads, ensuring the window.GBGBridge namespace exists when the journey code initializes. The default bootstrap script is minimal:
window.GBGBridge = window.GBGBridge || {};
window.GBGBridge.receive = window.GBGBridge.receive || function(){};

Custom Bootstrap Scripts

If you provide a custom bootstrapScript in BridgeConfiguration, ensure it does not:
  • Expose sensitive native data to the web context.
  • Define functions that bypass the structured message protocol.
  • Include inline secrets, tokens, or API keys.
// Good: Minimal configuration
let config = BridgeConfiguration(
    hostVersion: "1.0.0",
    capabilities: [...],
    bootstrapScript: """
    window.GBGBridge = window.GBGBridge || {};
    window.GBGBridge.receive = window.GBGBridge.receive || function(){};
    window.GBGBridge.config = { theme: 'dark' };
    """
)

// Bad: Exposes secrets
let config = BridgeConfiguration(
    hostVersion: "1.0.0",
    capabilities: [...],
    bootstrapScript: """
    window.GBGBridge = window.GBGBridge || {};
    window.GBGBridge.apiKey = '\(apiKey)';  // DO NOT DO THIS
    """
)

Frame Isolation

By default, BridgeWebViewConfigurator injects the bootstrap script and message handler with forMainFrameOnly: true. This means only the top-level frame can access the bridge β€” iframes (including untrusted third-party content) cannot call window.webkit.messageHandlers.gbgBridge.postMessage() or invoke native capabilities. If your journey architecture requires iframe bridging (e.g., the verification flow runs inside an iframe), you will need to build a custom configurator that sets forMainFrameOnly: false and add origin validation in your handlers to ensure only trusted frames can invoke capabilities.

Message Validation

Incoming Message Decoding

BridgeHost decodes incoming messages using JSONDecoder. If a message doesn’t conform to the BridgeMessage structure, it is rejected and lastError is set. Malformed messages never reach handlers or the delegate.

Action Validation

Handlers are routed by exact string match on the action field. Register handlers only for actions you expect. Unexpected actions go to pendingRequests where you can inspect and respond to them.

Data Validation in Handlers

Always validate the data payload in your handlers. The web content is a less-trusted zone β€” treat incoming data the same way you would treat user input.
public func handle(request: BridgeMessage, responder: BridgeResponder) async {
    // Validate required fields
    guard let side = request.payload.data?["side"],
          case .string(let sideValue) = side,
          ["front", "back"].contains(sideValue) else {
        responder.respond(
            status: .error,
            data: nil,
            error: BridgeErrorPayload(
                code: "INVALID_PARAMS",
                message: "Missing or invalid 'side' parameter. Expected 'front' or 'back'.",
                recoverable: true
            )
        )
        return
    }

    // Proceed with validated data
    await performCapture(side: sideValue)
}

Permission Management

Native Permissions

GBGBridge itself does not request any iOS permissions. Your capability handlers are responsible for requesting and checking permissions as needed.
Check permission status before performing the operation, and return a clear error if permission is denied.
import AVFoundation

public func handle(request: BridgeMessage, responder: BridgeResponder) async {
    let status = AVCaptureDevice.authorizationStatus(for: .video)

    switch status {
    case .authorized:
        await performCapture(responder: responder)

    case .notDetermined:
        let granted = await AVCaptureDevice.requestAccess(for: .video)
        if granted {
            await performCapture(responder: responder)
        } else {
            responder.respond(
                status: .error,
                data: nil,
                error: BridgeErrorPayload(
                    code: "CAMERA_DENIED",
                    message: "Camera access was denied. Please enable it in Settings.",
                    recoverable: true
                )
            )
        }

    case .denied, .restricted:
        responder.respond(
            status: .error,
            data: nil,
            error: BridgeErrorPayload(
                code: "CAMERA_DENIED",
                message: "Camera access is not available. Please enable it in Settings.",
                recoverable: true
            )
        )

    @unknown default:
        break
    }
}

Info.plist Requirements

Add usage descriptions for every permission your handlers might request:
PermissionKey
CameraNSCameraUsageDescription
NFCNSNFCReaderUsageDescription
Photo LibraryNSPhotoLibraryUsageDescription
LocationNSLocationWhenInUseUsageDescription
Missing usage descriptions cause a runtime crash when the permission is requested.

Production Checklist

Before shipping your integration:
  • HTTPS only β€” Journey URL uses HTTPS. No global ATS exceptions.
  • No secrets in bootstrap scripts β€” Custom bootstrap scripts contain no API keys or tokens.
  • Input validation β€” All handlers validate incoming data payloads.
  • Permission handling β€” Handlers check and request permissions before accessing protected resources.
  • Info.plist entries β€” Usage descriptions for all permissions handlers might request.
  • Error information β€” Error responses do not leak internal implementation details.
  • Frame policy β€” Considered whether forMainFrameOnly should be true for your use case.
  • Certificate pinning β€” Evaluated whether certificate pinning is required for your security posture.

Next Steps