# Security model

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

Every row is one or more tests in `test/browser/escape.spec.ts`, run in
Chromium, Firefox and WebKit on every change. The list is the contract; a
claim without a test is not made.

| Claim | What the tests do |
|---|---|
| No network from the code | `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `sendBeacon`, `importScripts`, dynamic `import()`, nested `Worker` / `SharedWorker`, `WebTransport`, `fetch` reached through the prototype chain, `new Function("return fetch")`, indirect `eval`: each ends as an error, and the fixture server never receives a request. |
| No DOM, no host page | `window`, `document`, `parent`, `localStorage`, `indexedDB`, `caches`, `BroadcastChannel` are undefined; `location` is gone, assigning it creates a property and navigates nothing. |
| Only named functions run | A call outside the map runs nothing on the host; the tree has no `constructor` and a `null` prototype; function sources are the few-byte wrappers. |
| Nothing but clone crosses | A function or a Proxy in a value → `CloneError`; `File` / `Blob` arrive intact. |
| The channel is private | The code cannot reach the port (`typeof port`, the own properties of `self`); `self.postMessage` reaches a frame that does not listen; the frame accepts one `init` from its parent and ignores messages without the nonce; the page accepts the handshake only from the frame it created. |
| Loops stop | `while (true) {}`, a never-resolving promise, memory growth: the run ends at the watchdog or the clock, the worker is gone, the page stayed responsive; `signal.abort()` ends a run in flight. |
| Caps hold | 21 calls under `maxOps: 20` → the 21st ends the run; a 2 KB value under `maxOutputBytes: 1000` never arrives; a 2 KB input is refused; 500 log lines under `maxLogLines: 50` keep 50 and count 450. |
| Approval cannot be skipped | A marked function under `deny` never runs; under `pause` it runs only after `approve`; under `collect` it is deferred, observation cuts the run, `resume` replays recorded results without re-executing, and a call that differs from the approved one is `ReplayMismatch`. |
| Isolation between runs | A global set in one run is `undefined` in the next. |
| Host CSP | `url` mode boots on a page with `script-src 'nonce-…'` and no `'unsafe-eval'`; `srcdoc` on that page is `SandboxUnavailable`; on a page whose CSP has no `worker-src blob:`, `srcdoc` is `SandboxUnavailable` with the cause and `url` works. |
| No fallback | A frame that cannot load (404) is `SandboxUnavailable`; nothing is compiled in the page. |

What the sandbox does **not** do:

- It does not make your functions safe. Whatever `orders.cancel` does with
  the input it receives is your code's responsibility; the sandbox only
  guarantees the code can call nothing else and that marked calls wait for
  a human.
- It is not a DOM sandbox for your page. Your page is as trusted as it
  always was; the library runs in it.
- `srcdoc` mode inherits your page's CSP (see [Frame modes](https://browser-sandbox.faustine.ai/docs/frame-modes.md)).
- It does not hide timing: the code can measure how long a function takes.

Found a hole? See [SECURITY.md](https://github.com/faustine-ai/browser-sandbox/blob/main/SECURITY.md).
