diff --git a/docs/architecture/embeddability.md b/docs/architecture/embeddability.md index 2ba40f03c..98b7e29ba 100644 --- a/docs/architecture/embeddability.md +++ b/docs/architecture/embeddability.md @@ -55,3 +55,28 @@ Both are supported on **built** pages. The one "never iframe" rule applies only staging** script (`internal/staging.js`), which mounts elements directly because a dev-server iframe crosses realms (`instanceof` breaks). Built pages have no such problem — the generated staging (`staging-forge.js`) itself uses an iframe stage. + +## Direct-embed vs staging — the framing contract (serving layer) + +The built tree has TWO kinds of page, and they need DIFFERENT framing policy: + +| Surface | Paths | `frame-ancestors` | +|---|---|---| +| **Direct embed** (the product) | `//index.html`, `///index.html` — the bare map pages the manifest's `iframeUrl` points at | **`*`** — framable on any partner site (that's the whole point) | +| **Staging** (internal preview) | tree root `index.html`, per-bundle `/index.html`, the SPA shell | **`'self'`** — preview only | + +`frame-ancestors` can ONLY be set by an HTTP response header (HTML `` cannot set it), so +**the server/CDN owns this split — the bundler cannot enforce it.** Whoever serves the tree must +apply it. Rule of thumb: **a path with ≥2 directory segments under the tree root is a direct-embed +page → serve it framable.** + +Portable ways to implement the split, by host: +- **Traefik** (nuxtbase/go-base): a higher-priority router on `PathRegexp(^/plugin-assets/[^/]+/[^/]+/)` + sets `Content-Security-Policy: frame-ancestors *` + clears `X-Frame-Options`. (Live example: + `/data/coolify/proxy/dynamic/doxa-maps-embed.yaml`.) +- **Cloudflare Pages / Netlify**: a `_headers` file — `//*/* Content-Security-Policy: frame-ancestors *`. +- **Nginx**: `location ~ ^/[^/]+/[^/]+/ { add_header Content-Security-Policy "frame-ancestors *"; }` +- **GitHub Pages**: serves no custom headers → pages are framable by default (no frame-ancestors), which is already correct for direct embeds. + +If you serve the WHOLE tree with one blanket `frame-ancestors 'self'`, direct embeds break on +partner sites (the staging policy leaks onto the product pages). That is the one mistake to avoid.