Skip to content

The app frame is a srcdoc document with its own <meta> CSP - #146

Merged
lannbot merged 1 commit into
mainfrom
frame/srcdoc-meta-csp
Sep 5, 2026
Merged

lannbot merged 1 commit into
mainfrom
frame/srcdoc-meta-csp

Conversation

@lannbot

@lannbot lannbot commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

frame-backend.ts stops navigating the sandboxed iframe to ./frame.html and instead assembles the frame document as a srcdoc string — template + stylesheet + bundled frame.js fetched as text in the visor's realm — with a <meta http-equiv="Content-Security-Policy"> inserted first in <head>: default-src 'none'; script-src 'sha256-<hash of the inline script>'; style-src 'unsafe-inline'; img-src data:.

Ruling and measurements: #142.

  • A service worker never sees a sandboxed frame's navigation (both engines), so a real-URL skeleton is served raw by the host and cannot be pinned; srcdoc is pinned by value.
  • CSP policies compose: the meta's default-src 'none' makes the frame network-dead whatever the visor's inherited policy allows. Measured zero requests out; the meta-less control (today's shape) leaked fetch, sendBeacon and <img>.
  • The dedicated sandbox origin frame.html argued for is not required for confinement. NOTES' mechanics bullet is rewritten as the ruling; frame.html stays as the template so check-invariants.sh's greps keep their target.

Standing constraint recorded in frame-backend.ts: once the visor ships a header CSP, the same script hash must be emitted into its script-src (the inherited policy must also admit the inline script). Neither consumer sends one today.

Gates: deno check (demo, spikes/todomvc); check-invariants.sh; spikes/todomvc e2e 5/5; a direct Chromium probe of the built spike page — iframe has srcdoc, no src; meta first in <head>; about:srcdoc; in-frame fetch rejected and never reaches the server; a typed todo renders. The demo's own e2e lane needs MinIO, fetched linux-amd64 only — not runnable on this arm64 host.

Not touched, deliberately: spikes/todomvc/{web/frame-dioxus.html,host/dioxus-frame.ts} (same real-URL shape, slated for removal when the app instance moves into the frame) and the now-stale CORS rationale for frame.js in spikes/todomvc/justfile / demo/e2e/run.ts (frame-dioxus.js still needs it).

Automerge armed.

frame-backend.ts no longer navigates the sandboxed iframe to
./frame.html. It fetches the template, ./todomvc-app.css and the bundled
./frame.js as text in the visor's own realm, inlines them, and hands the
result to the iframe as `srcdoc`, with a
<meta http-equiv="Content-Security-Policy"> inserted as the first child
of <head>: `default-src 'none'; script-src 'sha256-<hash of the inline
script>'; style-src 'unsafe-inline'; img-src data:`.

Why (polyvisor#142, measured in Chromium and Firefox):

- A service worker never sees a sandboxed (opaque-origin) frame's
  navigation, so a real-URL skeleton is served raw by the host, outside
  the release-integrity path, with whatever headers it happens to send.
  It cannot be pinned. srcdoc content is pinned by value.
- CSP policies compose. The meta's `default-src 'none'` makes the frame
  network-dead regardless of the visor's inherited policy: zero requests
  leave the frame, against three (fetch, sendBeacon, <img>) for the
  meta-less control that is today's shape. The applier's URL validation
  stops being the sole defense.
- The dedicated sandbox origin frame.html's comment argued for is not
  required for confinement; it would buy only Firefox process
  isolation. The NOTES mechanics bullet is rewritten as the ruling.

Standing constraint, recorded in frame-backend.ts: the inline script must
also satisfy the visor's own header `script-src` once the visor ships
one, so the hash has to be emitted there from the same frame.js bytes.
Today neither consumer sends a header CSP.

Gates: deno check (demo, spikes/todomvc); demo/scripts/check-invariants;
spikes/todomvc e2e 5/5; a direct Chromium probe of the built spike page
(iframe has srcdoc and no src, meta first in <head>, frame location
about:srcdoc, in-frame fetch rejected and never reaches the server, a
typed todo renders). The demo's own e2e lane needs MinIO, which the
engine recipe fetches as linux-amd64 only; not runnable on this arm64
host.
@lannbot
lannbot enabled auto-merge September 5, 2026 18:32
@lannbot
lannbot merged commit 4c3d979 into main Sep 5, 2026
3 checks passed
@lannbot
lannbot deleted the frame/srcdoc-meta-csp branch September 5, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant