Skip to content

StackBlitz Embed — Cross-Origin Isolation Issue #37

@johnandrade18

Description

@johnandrade18

StackBlitz Embed — Cross-Origin Isolation Issue

Summary

@stackblitz/sdk.embedProject() fails with "Unable to run Embedded Project — Looks like this project is being embedded without proper isolation headers" even when the embedding page correctly sends all required COOP/COEP headers.

Environment

SDK version @stackblitz/sdk@1.11.0
Browser Chrome (latest)
Embed method sdk.embedProject() (POST via form → /run endpoint)
Template node (WebContainers)
Headers tested Both require-corp and credentialless

What Works

  • sdk.openProject({ newWindow: true }) — Opens StackBlitz in a new tab → runs correctly ✅
  • sdk.embedProject() with crossOriginIsolated: true but WITHOUT COOP/COEP on parent → connection refused (iframe can't load at all)
  • Parent page isolationwindow.crossOriginIsolated is true on the embedding page with either COEP value

Root Cause

The Problem

When crossOriginIsolated: true is passed to sdk.embedProject(), the SDK:

  1. Adds cross-origin-isolated to the iframe's allow attribute
  2. Adds corp=1 query parameter to the embed URL: https://stackblitz.com/run?corp=1&embed=1&...

The corp=1 parameter tells StackBlitz's server to serve the page with Cross-Origin-Embedder-Policy: require-corp (strict mode).

However, StackBlitz's own page loads cross-origin resources that lack Cross-Origin-Resource-Policy headers:

GET https://cdn.segment.com/analytics.js/v1/.../analytics.min.js
  → ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

POST https://www.google.com/ccm/collect?...
  → ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

GET https://json.schemastore.org/prettierrc
  → CORS error (No 'Access-Control-Allow-Origin' header)

GET https://json.schemastore.org/package
  → CORS error (No 'Access-Control-Allow-Origin' header)

GET https://json.schemastore.org/tsconfig
  → CORS error (No 'Access-Control-Allow-Origin' header)

These blocked resources prevent the WebContainer from initializing properly.

The Catch-22

Scenario Result
crossOriginIsolated: true + corp=1 StackBlitz serves COEP → its analytics/CDN resources blocked → WebContainer fails
crossOriginIsolated: false StackBlitz serves NO COEP → resources load fine → but WebContainer can't use SharedArrayBuffer

Console Errors

All from inside the StackBlitz iframe (cross-origin, stackblitz.com):

run:38 GET https://cdn.segment.com/analytics.js/v1/.../analytics.min.js
  net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

run:213 POST https://www.google.com/ccm/collect?...
  net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

run:1 Access to fetch at 'https://json.schemastore.org/prettierrc'
  blocked by CORS policy (No 'Access-Control-Allow-Origin' header)

run:1 Access to fetch at 'https://json.schemastore.org/package'
  blocked by CORS policy

run:1 Access to fetch at 'https://json.schemastore.org/tsconfig'
  blocked by CORS policy

Embedding Page Headers Tested

Both combinations were tested on the embedding page (Vite dev server via server.headers):

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp   # ❌ Same error
Cross-Origin-Embedder-Policy: credentialless # ❌ Same error

In both cases, window.crossOriginIsolated === true on the embedding page.

SDK Internals (v1.11.0)

Relevant code from @stackblitz/sdk/bundles/sdk.js:

// URL param generator (line 35)
crossOriginIsolated: (value) => trueParam("corp", value)

// Iframe allow attribute (lines 133-140)
function setFrameAllowList(target, frame, options = {}) {
  const allowList = target.allow?.split(";")?.map((key) => key.trim()) ?? [];
  if (options.crossOriginIsolated && !allowList.includes("cross-origin-isolated")) {
    allowList.push("cross-origin-isolated");
  }
  if (allowList.length > 0) {
    frame.allow = allowList.join("; ");
  }
}

The embed flow:

  1. embedProject() creates an iframe
  2. Writes an HTML form via frame.contentDocument.write(html)
  3. Form auto-submits via POST to https://stackblitz.com/run?corp=1&embed=1&...
  4. StackBlitz creates project and redirects (redirect may/may not preserve corp=1)
  5. Connection established via MessageChannel / postMessage

Related StackBlitz Issues

  • #2045 — Cross-origin isolation
  • #1739 — Embed isolation
  • #400 — SharedArrayBuffer
  • #5925 — WebContainer isolation
  • #296 — Firefox limitation

Suggested Fix (StackBlitz-side)

StackBlitz should either:

  1. Serve its own page with Cross-Origin-Embedder-Policy: credentialless instead of require-corp when corp=1 is present — this preserves window.crossOriginIsolated = true without blocking its own analytics/CDN resources.
  2. Add proper Cross-Origin-Resource-Policy: cross-origin headers to its CDN resources (segment.com, Google Tag Manager, schemastore.org).
  3. Provide an SDK option to control the COEP mode (require-corp vs credentialless).

Workaround

For now, use sdk.openProject({ newWindow: true }) instead of sdk.embedProject() — opens StackBlitz in a new tab where cross-origin isolation is not required.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions