# Why a frame and a worker

> @faustine-ai/browser-sandbox documentation · https://browser-sandbox.faustine.ai/docs/why/ · source: README.md on GitHub (https://github.com/faustine-ai/browser-sandbox)

The code a model writes has to run somewhere with the user's session, or it
cannot do anything useful. Running it in your page with `new Function` gives
it everything your page has: `fetch` with cookies, `localStorage`, the DOM,
every global. Any text the model reads can then run script on your origin.

The alternatives, and why this library does not use them:

- **A Worker created from your page.** It has no DOM, but it is same-origin
  with your page: `fetch` inside it carries the user's cookies, and your CSP
  needs `worker-src blob:`.
- **SES / `lockdown` + `Compartment`.** A Compartment without `lockdown` is
  not a boundary; with it, your page's intrinsics are frozen, which breaks
  most apps. Fine as a hardening layer, not as the boundary.
- **QuickJS in WASM.** A real separate engine with its own memory and an
  interrupt handler. Costs a ~1 MB payload, a different JavaScript engine
  from the page's, and re-encoding `File`/`Blob` by hand.
- **An iframe alone.** A sandboxed frame has no cookies, but its window can
  still navigate (`location = "https://…?" + data`), and a synchronous loop
  cannot be stopped from outside.

A `<iframe sandbox="allow-scripts">` has an **opaque origin**: no cookies,
no storage, same-origin with nobody. Its document carries a CSP with
`connect-src 'none'`, and a Worker created from a `blob:` inside it
**inherits that CSP**: `fetch`, `XMLHttpRequest`, `WebSocket`,
`EventSource`, `sendBeacon`, `importScripts` and dynamic `import()` are all
refused by the browser before any code of ours runs. The worker has no DOM
and no navigable `location`, and `terminate()` ends a `while (true) {}`
mid-loop. The library adds a prelude that removes the same globals (so a
failure reads `fetch is not defined`), a bridge that only accepts the
message types it defines, and caps on the host side.
