Docs · 03 / 13

Frame modes: srcdoc and url

What matters in the frame document is its CSP, because the worker inherits it. The frame option says where that document comes from.

srcdoc: no server

createSandbox({ frame: { srcdoc: true } });

The library writes the document inline. Its <meta> CSP applies (policies intersect, so even if your page allows connect-src to your API, nothing inside the frame can reach it). The catch: a srcdoc document also inherits your page's CSP in full. So:

  • No CSP on your page: works.
  • A CSP with 'unsafe-inline' or 'unsafe-eval' missing from script-src, or a nonce-only script-src: the frame cannot boot. run resolves SandboxUnavailable; nothing falls back to your page.
  • A CSP that does allow inline scripts and eval but has no worker-src blob: (or child-src blob:): the frame boots but the worker cannot start; run resolves SandboxUnavailable with that cause.

url: a static document you serve

createSandbox({ frame: { url: '/sandbox' } });

The same document, served over HTTP with its own headers. Its CSP comes from the header and inherits nothing from your page, so it works on pages with a strict CSP. Your page needs one line: frame-src to wherever you serve it ('self' if it is on your origin). The document is static and cacheable (immutable); the library appends ?v=<hash> and checks the protocol version in the handshake.

Serving it:

// Next.js: app/sandbox/route.ts
import { frameDocument, frameHeaders } from '@faustine-ai/browser-sandbox/frame';
export function GET() {
  return new Response(frameDocument(), { headers: frameHeaders() });
}
// Express
import { frameDocument, frameHeaders } from '@faustine-ai/browser-sandbox/frame';
app.get('/sandbox', (_req, res) => res.set(frameHeaders()).send(frameDocument()));
# A static file: write it once at build time…
node -e "import('@faustine-ai/browser-sandbox/frame').then(m => process.stdout.write(m.frameDocument()))" > public/sandbox.html
# …and serve it with the headers (nginx)
location = /sandbox.html {
  add_header Content-Security-Policy "default-src 'none'; connect-src 'none'; img-src 'none'; script-src 'sha256-<hash>' 'unsafe-eval'; worker-src blob:; base-uri 'none'; form-action 'none'; frame-ancestors *" always;
  add_header X-Content-Type-Options nosniff always;
  add_header Referrer-Policy no-referrer always;
  add_header Cache-Control "public, max-age=31536000, immutable" always;
}

The exact header values, including the hash, are what frameHeaders() returns; print them once rather than copying from here. 'unsafe-eval' is required by the worker's AsyncFunction and is harmless in a document that has no network and no origin.

srcdoc url
Install npm i npm i + one static route
Network blocked inside yes (<meta> CSP) yes (header CSP)
Page without a CSP works works
Page with a strict CSP SandboxUnavailable works, add frame-src
Page with a lax CSP needs worker-src blob: works
Cache / version n/a immutable, ?v=, checked in the handshake

The library never falls from one mode to the other on its own.