Commit graph

2 commits

Author SHA1 Message Date
pre-commit-ci[bot]
60b002f1ea [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-05-25 14:00:43 +00:00
Daniel Han
5171bcc991 Studio: serve HTML preview from a same-origin route so inline scripts run
Closes the long-documented follow-up. Inline <script> and onclick handlers
inside the assistant's ```html fence were dead under the previous srcdoc
path because Chromium inherits the embedder CSP (script-src 'self') for
srcdoc / data: / blob: iframes per HTML / CSP3. The only browser-supported
escape is a same-origin URL whose response headers carry an overriding CSP.

Backend: new POST /api/preview/html stashes the source for 10 min behind a
192-bit random token; GET /api/preview/html/{id} serves the snippet with
default-src 'none' + script-src 'unsafe-inline' + frame-ancestors 'self' +
X-Frame-Options SAMEORIGIN so the host chat page can iframe it but third
parties cannot. The GET is intentionally unauthenticated because browsers
do not attach Authorization to iframe subresource loads -- the unguessable
URL token is the authorisation. Eviction caps the in-memory store at 256
entries per worker; TTL sweep runs on each access.

Frontend: HtmlPreview now POSTs the source on mount, holds about:blank
until the URL arrives, then sets iframe src to the returned path. The
iframe sandbox stays "allow-scripts allow-modals allow-popups" with NO
allow-same-origin / allow-top-navigation, so even though the URL is
same-origin the iframe document is treated as a unique opaque origin
(cannot reach parent storage / DOM, cannot navigate the host page).
A srcdoc fallback kicks in if the POST fails so the layout still renders.

Tests:
* 9 new backend cases pin auth gating on POST, the unauth GET path,
  CSP shape, X-Frame-Options override, TTL expiry, oldest-first eviction,
  and per-call token uniqueness.
* Frontend vitest mocks the fetch round-trip; two existing tests rewritten
  to await data-preview-state=ready, plus a new failing-fetch case that
  exercises the srcdoc fallback (so a future regression there is loud).

Updates the in-host-CSP comment in main.py to reflect that the
"same-origin backend route" follow-up is now landed.
2026-05-25 14:00:06 +00:00