Security
The SDK applies defense in depth at every layer of the bridge — not a single check, but a chain of validations that must all pass before any application message is accepted. This page explains what the SDK protects, what it doesn't, and how to harden your integration.
For a working production configuration, see Use Cases & Recipes. For the handshake validation rules the parent enforces, see Wire Protocol.
Current Guarantees
These protections are enforced automatically by the SDK. You don't need to opt in — they apply to every bridge instance.
| Guarantee | How it works |
|---|---|
| Exact target origin | Parent-to-iframe postMessage calls always use an exact, validated origin. Wildcard origins ('*') are rejected at config time with CONFIG_UNSAFE_ORIGIN. |
| Inbound message validation | Every incoming message is checked against event.origin, event.source, session id, protocol name ('iframe-bridge'), protocol version (1), and envelope shape before being processed. |
| HTTPS-by-default | src URLs must use HTTPS, except for explicit localhost development mode controlled by allowInsecureLocalhost. |
| Unsafe scheme rejection | javascript:, data:, blob:, and srcdoc iframe URLs are rejected synchronously with CONFIG_INVALID_SRC. Embedded credentials in URLs are also rejected. |
| Bounded pre-ready queue | Operations called before handshake readiness are queued with a configurable limit (default: 50). Queue overflow throws QUEUE_LIMIT_EXCEEDED. The queue closes on handshake failure or destroy. |
| Complete cleanup | destroy() removes all SDK-owned listeners, timers, pending requests, event waits, and the owned iframe. Idempotent — safe to call multiple times. |
| Sanitized diagnostics | Diagnostic events do not include raw postMessage data or application payloads by default. Browser messageerror events are surfaced as MESSAGE_DESERIALIZATION_ERROR without raw message content. |
| Strict security profile | securityProfile: 'strict' converts risky-but-allowed configurations into hard errors: rejects insecure localhost mode, wildcard Permissions Policy grants, sandbox combinations that weaken isolation, and unbounded resize plugin axes. |
| Duplicate message handling | Only the first bridge:ready is accepted. Duplicate responses for the same requestId are ignored. Duplicate ready messages do not re-flush the queue or re-send bridge:connected. |
Non-Guarantees
The SDK is a transport and lifecycle layer — not a complete security solution. These concerns are outside its scope and remain your responsibility.
The SDK does not provide
- Authentication or authorization. The session id is correlation metadata for message routing. It is not a token, not a secret, and not proof of identity.
- Server-side validation. Client-side origin and envelope checks cannot prove the iframe application is uncompromised. Backend validation is still required.
- CSRF protection. The bridge layer does not prevent cross-site request forgery. Use standard CSRF defenses in your application layer.
- Runtime payload validation. TypeScript contract types (
createTypedIframeBridge) are compile-time only. They do not validate payloads at runtime and are not a security boundary. - Proof of remote processing.
sendEvent()resolves after posting — it does not confirm the iframe processed the message. Userequest()when acknowledgement is required. - Same-origin isolation. If the parent and iframe share an origin, the iframe can access the parent DOM, cookies, storage, and service worker scope. The SDK does not prevent same-origin access — that's a browser guarantee.
- Accessibility or focus management. The SDK does not trap focus, handle
Escape, set ARIA state, or restore focus. Implement those behaviors in your parent app and iframe app when using modals, drawers, or overlays.
If your parent page and the iframe share an origin (including same subdomain), the iframe can access window.parent.document, parent cookies, localStorage, and service workers. The SDK's origin validation still functions, but the iframe is inside your trust boundary. If the iframe app is operated by a different team or has a different security posture, use a separate subdomain (e.g., embed.example.com vs app.example.com) or sandbox the iframe.
What the SDK cannot detect
- An iframe that intentionally spoofs messages after a valid handshake.
- An iframe that loads a different document post-handshake (the exact
targetOriginprevents delivery to a different origin, and unexpected origins or sources are ignored). - A compromised parent page that manipulates the bridge configuration.
Security Profiles
The securityProfile option controls how aggressively the SDK enforces security-relevant configuration. Choose one based on your environment.
'development' (default) | 'strict' | |
|---|---|---|
Wildcard Permissions Policyallow: 'camera *' | Diagnostic warning | Throws CONFIG_UNSAFE_PERMISSIONS_POLICY |
Sandbox allow-scripts + allow-same-origin | Diagnostic warning | Throws CONFIG_UNSAFE_SANDBOX |
| Resize plugin without max bounds for active axes | Diagnostic warning | Throws CONFIG_INVALID_RESIZE |
allowInsecureLocalhost: true with strict | Allowed | Forced to false; setting true throws CONFIG_UNSAFE_ORIGIN |
When to use 'strict'
- Production deployments. Configuration mistakes should fail fast, not silently warn.
- CI and integration tests. Catch security misconfiguration before it reaches staging.
- Any deployment where you've reviewed and expect production-grade settings.
When to keep 'development'
- Local development with HTTP localhost servers.
- Sandboxed integrations where the
allow-scripts+allow-same-origincombination is intentionally reviewed and documented. - Experimentation and manual playgrounds.
import { createIframeBridge } from 'iframe-helper-sdk';
// Production: fail fast on unsafe configs
const bridge = createIframeBridge({
container: '#partner-frame',
src: 'https://partner.example/app',
securityProfile: 'strict',
});
In 'development' mode, warnings are only delivered when a diagnostics logger is configured. Use createDiagnosticRecorder to capture them during development.
Plugin trust boundary
Plugins are trusted parent-side code. The SDK validates origin, source window, session id, protocol, version, and envelope shape before dispatching plugin events, but plugin payload semantics remain plugin-specific.
For resizePlugin, treat dimensions as child-controlled layout input. Set maxWidthPx and/or maxHeightPx for every active axis before using it with partner or untrusted iframes. In development mode missing max bounds produce CONFIG_UNBOUNDED_RESIZE; in strict mode they fail fast with CONFIG_INVALID_RESIZE. See Resize Plugin for the full setup.
CSP Guidance
Content Security Policy prevents unauthorized iframe embedding at the browser level. Use it alongside the SDK's origin validation — CSP and the bridge check different things, and both matter.
The child iframe SDK (iframe-helper-sdk/child) adds child-side parent-origin validation for iframe apps that install the package. Its allowedParentOrigins?: readonly string[] | null option complements, but does not replace, the iframe app's CSP frame-ancestors response header.
Parent-side: restrict what you embed
Add a frame-src (or child-src) directive to your parent page's CSP header. This controls which origins the browser is allowed to load into iframes on your page.
Production cross-domain:
Content-Security-Policy: frame-src https://partner.example
Same-host:
Content-Security-Policy: frame-src 'self'
Multiple trusted origins:
Content-Security-Policy: frame-src https://partner.example https://portal.example
If you don't set frame-src, the browser falls back to default-src or allows all sources. This means an attacker who can inject HTML into your page can load an arbitrary iframe. Always restrict frame-src in production.
Iframe-side: restrict who can embed you
Your iframe application should also set a CSP header with frame-ancestors. This tells the browser which parent origins are allowed to embed your iframe.
Only your production parent:
Content-Security-Policy: frame-ancestors https://host.example
Multiple known embedders:
Content-Security-Policy: frame-ancestors https://host.example https://admin.example
Same-host embed only:
Content-Security-Policy: frame-ancestors 'self'
CSP controls which pages can load/embed the iframe. The SDK's origin, source, and session validation controls which messages are accepted over postMessage. Both layers are necessary — one doesn't replace the other. See Origin Validation for what the SDK checks.
Child SDK: restrict accepted parent origins
When your iframe app uses iframe-helper-sdk/child, configure allowedParentOrigins if the valid parent origins are known. For broad or public multi-embedder integrations where every parent origin cannot be enumerated, omitted or null can be acceptable only when paired with server-side/browser embedding controls such as CSP frame-ancestors and application/backend authorization.
import { createIframeChildBridge } from 'iframe-helper-sdk/child';
createIframeChildBridge({
allowedParentOrigins: ['https://host.example'],
});
Exact semantics:
| Value | Behavior |
|---|---|
| Omitted | Accepts the bootstrap parent origin. Appropriate only with server-side/browser embedding controls and backend authorization. |
null | Same as omitted: accepts the bootstrap parent origin. |
| Non-empty array | Requires exact origin match against the bootstrap parent origin. Best when valid embedders are known. |
| Empty array | Invalid configuration. |
The child SDK does not use wildcard target origins in normal operation. After it accepts the parent origin, it posts bridge:ready, child events, request responses, and plugin messages back to that exact origin.
The session id remains correlation metadata only. It is not auth, a token, a secret, or proof that the parent is trusted.
Full child-side guidance: Child Security.
Sandbox Guidance
The sandbox option applies the iframe sandbox attribute, which restricts what the iframe can do. Use it to reduce the impact of a compromised iframe application.
How sandbox tokens affect the bridge
| Token | Effect on bridge behavior |
|---|---|
allow-scripts | Required. Without it, the iframe cannot run JavaScript and the handshake will never complete. |
allow-same-origin | Treats the iframe as same-origin. Without it, the iframe sends event.origin === 'null' and the SDK rejects its messages because origins must be exact HTTP(S) values. With it, the iframe regains access to its origin's cookies and storage. |
allow-forms, allow-top-navigation, allow-popups, etc. | Do not affect bridge communication. Add only if the iframe genuinely needs them. |
The allow-scripts + allow-same-origin caveat
When both tokens are present, the sandbox is significantly weakened: the iframe can execute scripts and access same-origin resources (cookies, localStorage, sessionStorage). This is the browser-equivalent of running the iframe without a sandbox.
// Development: emits CONFIG_UNSAFE_SANDBOX warning
createIframeBridge({
container: '#sandboxed-frame',
src: 'https://partner.example/app',
sandbox: ['allow-scripts', 'allow-same-origin'],
securityProfile: 'development',
diagnostics: {
// Warning will appear here
},
});
// Strict: throws CONFIG_UNSAFE_SANDBOX synchronously
createIframeBridge({
sandbox: ['allow-scripts', 'allow-same-origin'],
securityProfile: 'strict', // ❌ Error
});
Sandbox behavior varies across browsers. Test your exact token combination in every browser you support. A token set that works in Chrome may break event.origin behavior in Firefox.
Recommended starting points
Maximum isolation (if the iframe only needs to display content, no scripts):
sandbox: ''; // No tokens — most restrictive
Scripts only, no origin access (the iframe runs scripts but can't access its own origin's cookies/storage):
sandbox: 'allow-scripts';
Without allow-same-origin, the iframe's event.origin is 'null'. The SDK rejects messages from 'null' origins. This configuration requires the iframe to communicate through a different channel, or it means the iframe is intentionally non-interactive.
Permissions Policy
The iframeAttributes.allow attribute controls which browser features the iframe can use — camera, microphone, geolocation, clipboard, fullscreen, and more.
Grant only what the iframe needs
Use the narrowest possible grant. Prefer origin-scoped values over wildcards.
// Good: scoped to the iframe's exact origin
createIframeBridge({
src: 'https://partner.example/app',
iframeAttributes: {
allow: 'clipboard-write https://partner.example',
},
});
// Bad: wildcard grant allows any iframe loaded at this URL
createIframeBridge({
src: 'https://partner.example/app',
iframeAttributes: {
allow: 'clipboard-write *',
},
});
Wildcard detection
The SDK detects wildcard values (any token containing * or 'src') in the allow attribute:
'development'profile: Emits aCONFIG_UNSAFE_PERMISSIONS_POLICYdiagnostic warning. The bridge still functions.'strict'profile: ThrowsCONFIG_UNSAFE_PERMISSIONS_POLICYsynchronously. The bridge is never created.
// Strict mode rejects wildcard grants
createIframeBridge({
securityProfile: 'strict',
iframeAttributes: {
allow: 'camera *; microphone *', // ❌ CONFIG_UNSAFE_PERMISSIONS_POLICY
},
});
If no allow attribute is set, the browser applies its default Permissions Policy. The SDK does not add any feature grants on its own.
Origin Validation
The SDK enforces strict origin validation at every level — from config parsing to message routing. Understanding these rules helps you debug handshake failures and avoid common misconfigurations.
What the SDK requires
Every origin value (src, targetOrigin, allowedOrigin, and the bootstrap parent origin) must:
- Be an exact origin —
https://partner.example:443, nothttps://partner.example/app - Use HTTPS — except localhost origins with
allowInsecureLocalhost: true - Be a real HTTP(S) origin — no
javascript:,data:,blob:,file:, or opaque origins - Not contain wildcards — no
*, no*.example.com - Not contain paths, query strings, hashes, or credentials
| Config value | Valid? | Reason |
|---|---|---|
https://partner.example | Yes | Exact HTTPS origin |
http://127.0.0.1:5174 | Yes (with allowInsecureLocalhost) | Explicit localhost dev mode |
https://partner.example/app | No | Contains a path |
https://*.example.com | No | Contains a wildcard |
* | No | Wildcard origin |
http://partner.example | No | HTTP on non-localhost |
https://user:pass@partner.example | No | Contains credentials |
targetOrigin vs allowedOrigin
These two options control opposite directions of message flow. They must be symmetric for the bridge to work.
| Option | Direction | What it controls |
|---|---|---|
targetOrigin | Parent → Iframe | Origin used in postMessage() calls from the parent to the iframe |
allowedOrigin | Iframe → Parent | Origin the parent accepts for inbound messages from the iframe |
If you leave both unset, the SDK derives them from src.origin. For most integrations, this is correct. Set them explicitly only when you know the iframe's messaging origin differs from its initial URL.
// Most common: derive from src
createIframeBridge({
src: 'https://partner.example/app',
// targetOrigin = 'https://partner.example' (derived)
// allowedOrigin = 'https://partner.example' (derived)
});
// Explicit: when the iframe redirects to a different origin for messaging
createIframeBridge({
src: 'https://partner.example/app',
targetOrigin: 'https://messages.partner.example',
allowedOrigin: 'https://messages.partner.example',
});
If targetOrigin does not match the iframe's actual origin, the browser silently drops the postMessage call. The bridge won't receive an error — it will time out with HANDSHAKE_TIMEOUT. If allowedOrigin does not match, inbound messages from the iframe are silently ignored.
Localhost development
The SDK never allows non-localhost HTTP origins. During development, set allowInsecureLocalhost: true to permit HTTP on localhost, 127.0.0.1, or [::1].
createIframeBridge({
src: 'http://127.0.0.1:5174',
allowInsecureLocalhost: true, // Required for HTTP localhost
securityProfile: 'development',
});
By default, allowInsecureLocalhost is true only when the parent page itself is on localhost. In all other cases, it defaults to false.
Never set allowInsecureLocalhost: true in production. The option is a development convenience and never permits HTTP on production domains.
Same-Origin Is Not Isolation
If the parent page and the iframe share an origin — including the same subdomain — the browser treats them as part of the same security context. The SDK cannot override this.
What same-origin means in practice
- The iframe can access
window.parent.documentand modify the parent DOM. - The iframe shares cookies with the parent (including
HttpOnlyif served from the same application). - The iframe shares
localStorage,sessionStorage, andindexedDBwith the parent origin. - The iframe can register service workers that control the parent's scope.
The SDK's message validation still works — invalid envelopes, wrong session ids, and mismatched protocol versions are still rejected. But the iframe doesn't need the bridge to interact with your page. It can access the DOM directly.
Recommendations
-
If the iframe is built by your own team and deployed together with the parent, same-origin may be acceptable. Keep origin, source, session, protocol, and version validation enabled regardless.
-
If the iframe is built by a different team or has a different security posture, host it on a separate subdomain (e.g.,
embed.example.comwhile the parent runs atapp.example.com). This makes it cross-origin and prevents DOM/cookie/storage access. -
If you must embed same-origin content from an external source, apply the strongest available
sandboxtokens and setsecurityProfile: 'strict'. However, no sandbox token can fully isolate same-origin content from the parent.
// Same-origin: the iframe can access the parent DOM
createIframeBridge({
src: 'https://app.example/embed',
// The iframe can read window.parent.document.cookie
});
Security Checklist
Use this checklist before deploying a bridge integration to production. Each item covers a specific concern and references the relevant configuration option or CSP directive.
Origins
-
srcuses HTTPS (not HTTP). →CONFIG_UNSAFE_ORIGINotherwise. -
targetOriginis an exact origin, not a wildcard or path. → Derived fromsrc.originby default. -
allowedOriginis an exact origin, not a wildcard or path. → Derived fromsrc.originby default. -
allowInsecureLocalhostisfalse(or not set) in production. → Defaults tofalseon non-localhost parents. - No wildcards, credentials, or paths in any configured origin. → Origin Validation
CSP
- Parent page sets
frame-srcto restrict which origins can be loaded into iframes. - Iframe application sets
frame-ancestorsto restrict which origins can embed it. - CSP directives use exact origins, not wildcards. → CSP Guidance
Security Profile
-
securityProfileis set to'strict'for production. → Catches wildcard permissions, unsafe sandbox, and HTTP localhost. - No
CONFIG_UNSAFE_SANDBOXorCONFIG_UNSAFE_PERMISSIONS_POLICYerrors at startup.
Sandbox
- If you use
sandbox, you've tested the exact token set in all target browsers. - You understand that
allow-scripts+allow-same-originweakens sandbox isolation. - Without
allow-same-origin, the iframe sendsevent.origin === 'null'and the bridge rejects it.
Permissions Policy
-
iframeAttributes.allowgrants only the features the iframe actually needs. - No wildcard grants (
*or'src') in theallowvalue — scoped to exact origins instead.
Referrer Policy
-
referrerPolicyis set to'no-referrer'for sensitive parent URLs (tenant ids, tokens, invitation links). →iframeAttributes.referrerPolicy - Or
'same-origin'when parent and iframe share an origin and referrer data is safe to send.
Bootstrap
- No secrets or tokens in
bootstrap.session.paramValue. The session id appears in the iframe URL and is visible to anyone inspecting the DOM. - Bootstrap parameters in the hash (
location: 'hash') if parent URL query strings contain sensitive data. - The iframe app validates the parent origin against its own allowlist — not blindly trusting the bootstrap parameter.
Diagnostics
- A diagnostics logger is configured to catch warnings (
CONFIG_UNSAFE_SANDBOX,CONFIG_UNSAFE_PERMISSIONS_POLICY) during development. - Diagnostic data is routed to your application monitoring in production.
- You understand that diagnostics do not include raw message payloads.
Next Steps
- Use Cases & Recipes — Copy-pasteable security configurations for production, local dev, sandboxed, and sensitive deployments.
- Configuration — Detailed reference for every security-related option (
securityProfile,sandbox,targetOrigin,allowedOrigin,allowInsecureLocalhost,iframeAttributes). - Wire Protocol — What the parent validates on every inbound message (origin, source, session, protocol, version, envelope).
- Troubleshooting — Diagnose handshake failures, origin mismatches, and CSP-related issues.