iframe-embed-contracts
Installation
SKILL.md
Iframe embed contracts
An iframe integration has two applications and one browser boundary. Treat the parent and guest as separate deploys with an explicit protocol: who may embed whom, which capabilities are delegated, how readiness and messages are authenticated, how size and navigation are synchronized, and what happens when storage or the frame disappears.
Checklist
- Classify both sides before editing. Record whether you own the parent, guest, or both; the exact origins in every environment; whether the frame is same-origin, cross-origin,
srcdoc, or sandboxed to an opaque origin; and whether the guest must support more than one parent. Do not infer a cross-origin frame's health from DOM access that the same-origin policy intentionally blocks. - Inspect the delivered frame policy, not only JSX. Capture the final
<iframe>attributes and the guest response headers. The parent controls where frames may load (frame-src), while the guest controls who may embed it (frame-ancestors, withX-Frame-Optionsonly as a legacy fallback).frame-ancestorsis HTTP-header-only and does not fall back todefault-src; a meta CSP cannot prove the production embedding contract. - Make readiness a replayable handshake.
loadis not success: browsers fire it even when the frame's resource failed, and a guest can post READY before the parent listener attaches. Register the listener before settingsrc, let the guest emit{type, version, instanceId}READY, let the parent answer with bounded init/config, and keep a timeout/diagnostic state instead of a permanent spinner. If either side may reload or navigate, make READY and init idempotent. - Authenticate every message in both directions. Send with an exact
targetOrigin; on receive, verifyevent.origin,event.source === iframe.contentWindow(or the expected parent window), message type/version, and payload schema before acting. An origin allowlist alone is insufficient when several frames share it. A sandboxedsrcdoc/opaque guest reports originnull; if that architecture is unavoidable, bind the channel to the exactsourceplus an unguessable per-instance nonce or transferredMessagePort, and expose minimal capabilities rather than accepting everynullsender. - Grant the minimum sandbox and feature capabilities. Start from a restricted
sandboxand add only required tokens. For same-origin content,allow-scriptsplusallow-same-originlets the guest remove its sandbox, so untrusted active content belongs on a separate origin. Browser features need both layers: the responsePermissions-Policysets the outer ceiling and the iframeallowattribute may narrow/delegate within it. Test forms, popups, downloads, fullscreen, camera/microphone, clipboard, and top navigation only if the product actually needs them. - Treat dynamic sizing as a bounded protocol. A cross-origin parent cannot read guest layout. Reserve an initial box to avoid CLS, observe the guest's actual content box with
ResizeObserver, coalesce messages to one animation frame, include an instance/sequence id, and clamp the height in the parent to sane min/max values. Do not use the guest document's viewport-boundedscrollHeightas the only shrink signal: after the parent grows the iframe, that value can stay pinned to the larger viewport and create a parent resize -> guest measurement feedback loop. Test growth and shrink, fonts/images/async content, hidden-to-visible transitions, and mobile widths. Use a maintained resizer library when its protocol fits rather than copying a partial snippet. - Design for partitioned storage and blocked third-party cookies. An embedded guest may not see the same cookies/storage it sees top-level. Prefer an explicit parent-to-guest session exchange with short-lived, audience-bound data over long-lived tokens in the URL. If unpartitioned cookie access is essential, the Storage Access API is permission- and user-activation-dependent; provide a top-level sign-in or recovery path and test denial.
- Make navigation and teardown observable. Remove
messagelisteners,ResizeObservers, timers, and channels when the frame unmounts or the instance id changes. Revalidate origin/source after guest navigation. Expose guest error/timeout states to the parent, but do not treat the iframeloadevent as authoritative failure evidence. - Preserve accessibility and performance contracts. Give every meaningful frame a concise
title; reserve dimensions; lazy-load only below-the-fold, non-critical frames; and provide an equivalent link/fallback when the embedded surface is not the only way to complete a critical task.
Quick probes
Use matches as leads, then trace the parent and guest together: