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:
| Permission | Key |
|---|
| Camera | NSCameraUsageDescription |
| NFC | NSNFCReaderUsageDescription |
| Photo Library | NSPhotoLibraryUsageDescription |
| Location | NSLocationWhenInUseUsageDescription |
Missing usage descriptions cause a runtime crash when the permission is requested.
Production Checklist
Before shipping your integration:
Next Steps