| name | embed-csp-and-iframe-sandboxing |
| description | Configure CSP `frame-ancestors`, iframe `sandbox` attributes, postMessage origin checks, and web-component shadow-DOM boundaries for embedded dashboards. Invoked by `ravenclaude-core/security-reviewer` when a diff touches embed-auth flow; generated alongside dashboard code by `dashboard-builder`. |
Skill: embed-csp-and-iframe-sandboxing
Invoked by: ravenclaude-core/security-reviewer (mandatory for any embed-iframe / web-component change). Generated by dashboard-builder.
When to invoke: designing the iframe / web-component boundary that contains an embedded dashboard. Reviewing CSP headers. Verifying postMessage origin checks. Web-component shadow-DOM boundary concerns.
Output: correct CSP frame-ancestors directive; iframe sandbox attributes; postMessage origin allowlist; web-component boundary documented.
The three concerns
1. CSP frame-ancestors (the host side)
The host application (e.g., ravenpower.net or the client's web app) needs a CSP header that either allows the dashboard to be embedded into specific origins, or forbids embedding entirely if the dashboard is not third-party-facing.
Content-Security-Policy: frame-ancestors 'self' https://client-app.example.com https://*.client-tenant.example.com;
'self' — the host's own origin
- Explicit origins — the third-party apps allowed to embed
'none' — forbid embedding entirely (use for admin-only internal dashboards)
- Wildcards — allow sub-domains; use sparingly
X-Frame-Options: SAMEORIGIN is the legacy fallback; modern browsers honor frame-ancestors when both are present.
2. iframe sandbox attribute (the embed side)
When the host page renders the dashboard via iframe, the sandbox attribute scopes what the iframe can do:
<iframe
src="https://dashboards.example.com/embed/abc123"
sandbox="allow-scripts allow-same-origin allow-forms"
...
></iframe>
| Sandbox flag | Effect |
|---|
allow-scripts | iframe can execute JS (usually required for interactive dashboards) |
allow-same-origin | iframe content can be treated as same-origin if the URL matches (required for cookies / localStorage scoped to the embed origin) |
allow-forms | iframe can submit forms (filtering, export) |
allow-popups | iframe can open new windows (PDF export, share link) |
allow-popups-to-escape-sandbox | popups don't inherit the sandbox |
Default: start with sandbox="allow-scripts allow-same-origin" and add flags only when needed. No sandbox attribute at all = full permissions, which is wrong.
3. postMessage origin checks (the message-passing boundary)
When the iframe and host page need to communicate (resize signals, filter changes, theme updates), they use window.postMessage(). Both sides must verify the origin:
window.addEventListener('message', (event) => {
if (event.origin !== 'https://dashboards.example.com') return;
if (typeof event.data !== 'object' || !event.data.type) return;
});
window.parent.postMessage({ type: 'resize', height: 600 }, 'https://host-app.example.com');
Anti-patterns:
event.origin not checked
postMessage(..., '*') — broadcasts to any listener; leaks data
- Trusting
event.data shape without validation
Web components (alternative to iframes)
For tools like Embeddable.com that use web components rather than iframes, the boundary becomes the shadow-DOM boundary:
<dashboard-embed
api-key="<short-lived-token>"
tenant-id="from-server"
></dashboard-embed>
- Shadow DOM scopes CSS and DOM access — the host page can't reach into the dashboard's shadow root, the dashboard can't reach out
- Custom-element attributes (like
api-key) are visible in the host's DOM — pass short-lived tokens, never long-lived secrets
- Event communication via
dispatchEvent + addEventListener on the custom element
Tool-specific patterns
Apache Superset
- Embed SDK manages an iframe internally; the host page only needs
frame-ancestors allowing Superset's origin and the <superset-embedded-dashboard> custom element
- Guest token is short-lived (configurable; default ~1 hour — recommend 5-15 min for production)
Metabase
- Static embed: signed iframe URL;
frame-ancestors covers it
- Interactive Embedding (Pro+): SDK + JWT flow with parameterized iframes
Power BI Embedded
- Embed via
powerbi-client JS library; the library handles the iframe internally
- CSP must allow
frame-ancestors for *.powerbi.com (or the F-SKU embed URL); Microsoft also requires CSP nonce values for the embed page's inline scripts
Cube (with custom React UI)
- No iframe — direct REST/SQL API calls from the React app to Cube
- CSP
connect-src directive must allow the Cube API origin
- No
frame-ancestors concern unless Cube itself is being embedded somewhere
Embeddable.com
- Web-component-based; shadow-DOM boundary is the primary container
- API key passed as element attribute (must be short-lived)
Required CSP companions
Beyond frame-ancestors, the host page should set:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{server-generated}';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
connect-src 'self' https://api.cube.example.com;
frame-src 'self' https://dashboards.example.com;
frame-ancestors 'self' https://client-app.example.com;
frame-src (or child-src) — what the host is allowed to load inside iframes
connect-src — what the host's JS is allowed to fetch from (Cube API, JWT-issuer endpoint)
script-src 'nonce-...' — nonce-based script allowlist (more secure than unsafe-inline)
Anti-patterns this skill flags
frame-ancestors set to * (allows embedding anywhere — embed-jacking risk)
- iframe with no
sandbox attribute (full permissions)
sandbox="allow-scripts allow-same-origin allow-top-navigation" (allow-top-navigation lets the iframe redirect the parent page — phishing risk)
postMessage(..., '*') instead of explicit target origin
event.origin not checked in message handlers
- Host page that doesn't set CSP headers at all (the security review skips entirely)
- Long-lived API keys passed as web-component attributes (visible in host DOM; short-lived tokens only)
- CSP with
unsafe-eval enabled because "Cube needs it" — Cube doesn't; verify the requirement
- Power BI Embedded engagement that forgets the CSP nonce values Microsoft requires
- Web-component embed with no shadow-DOM (defeats the boundary)
References