Conversation
…to-subdomains, back-button shim [Q1] is a yes: app-like artifact capability is in scope, so the isolation track lands. Three pieces, all on infrastructure that already ships (the origin split + vanity subdomains were live) — the missing middle that makes an SPA export actually WORK, not just render: - Host-aware CSP (headersFor/ISOLATED_RAW_HEADERS in http.ts): an artifact-bound host (a subdomain serving ONE artifact at its root) now gets `allow-same-origin`, so localStorage/IndexedDB persist, History/ pushState works, and service workers can register. Granted ONLY there — the shared /raw sandbox and workspace domains (`<host>/<ref>`, many artifacts per host) keep the opaque wall, so one artifact still can't read another's storage. Wired in app.ts domain mode (artifact-bound = isolated:true, workspace domain = false) through a new serveContent `isolated` param; /raw stays default-false. - Auto-assigned isolated subdomains (after-publish.ts): the first time an SPA bundle goes live, it gets its own unguessable subdomain automatically — no more owner-typed vanity name required for the case that's actually broken without isolation (client routing). The label is a salted hash of the artifact id: deterministic (a concurrent publish derives the SAME host, so the second write is an idempotent no-op), unguessable (80 bits, server-salted, never sequential). Best-effort in the background, gated to SPA bundles with no existing artifact-bound host; skipped entirely when domain mode or the salt is unconfigured. Threaded through all three afterPublish call sites (route, MCP, restore). - Back-button/History shim (history-shim.ts): with real History access an EMBEDDED artifact's in-iframe router would otherwise push onto the reader's parent-tab history. The shim downgrades pushState→replaceState inside a frame (no-op top-level), so routing still works but doesn't hijack the embedder's Back button. Injected only on isolated-origin HTML serves, same delivery as MARKS_SCRIPT. Tests: origin-isolation.test.ts (6) — headersFor grant matrix; end-to-end that an artifact-bound subdomain serves allow-same-origin + the shim while the same artifact on /raw serves neither; auto-subdomain logic (SPA gets one, plain page/static bundle get none, idempotent, respects an existing vanity name, off without config); label determinism + DNS-label shape. Full suites green: core 357, db 203, api 954, mcp 27; typecheck all tiers; d1-schema + openapi + web api-types already regenerated in the prior commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ment Follow-ups from the branch's adversarial security review (verdict: safe to merge, no code blockers) — the boundary itself traced correct, these close the two things it flagged: - The one real misconfiguration risk: because per-artifact subdomain hosts now get the `allow-same-origin` grant, DERIVE_SUBDOMAIN_BASE sharing a registrable domain with the app/auth-cookie host would let an artifact inject a Domain-scoped cookie the app trusts. Added sharesRegistrableDomain (a pure, tested label-suffix predicate) + a loud startup warning in node.ts (mirrors the existing DERIVE_SANDBOX_URL warning; never crashes a running instance). The intended prod config (derived.app vs derive.to) passes clean. - Corrected the ISOLATED_RAW_HEADERS comment: SOP — not the PSL — is what isolates each artifact's localStorage/IndexedDB (distinct subdomains are distinct origins, true today pre-registration). What the PSL actually adds is closing the Domain=<base> cross-artifact COOKIE channel; register before relying on subdomain isolation at scale. Tests: sharesRegistrableDomain matrix (same-host / sub-of / parent-of / stray dots flagged; separate registrable domain + non-suffix shared label + null pass), plus an assertion that the isolated CSP grants neither top-navigation nor popup-escape. api 956 green, typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of #430 (2 of 3). Rebased clean of the reflow change: serving keeps today's auto-reflow default everywhere.
SPA bundles (client routing, storage, service workers) are broken on the shared opaque sandbox today. This gives each one its own isolated origin:
headersFor(isolated): artifact-bound subdomain hosts (one artifact at root) getallow-same-originadded to the sandbox CSP;/raw/*and workspace domains stay opaque. No top-navigation or popup-escape granted.d<20 hex>= 80-bit salted hash, deterministic so racing publishes are idempotent; best-effort, self-heals on next publish).HISTORY_SHIMinjected only on isolated origins: an embedded artifact downgradespushStatetoreplaceStateso an iframe can't grow the embedder's tab history.DERIVE_SUBDOMAIN_BASEshares a registrable domain with the app host (the Domain-cookie injection footgun).Known, accepted residual until PSL registration lands: sibling isolated artifacts share a registrable domain, so
Domain=<base>cookies are readable across them (artifact-to-artifact only; the app lives on a separate registrable domain). The PSL plan is tracked separately.8 e2e/unit tests: grant matrix, registrable-domain guard matrix, subdomain-vs-/raw serving, auto-assignment gating + idempotence + vanity-name respect.
🤖 Generated with Claude Code