Headers and CSP
The header set on every response, the single owner of the content security policy, and the origin that share links live on.
Every response
The outermost handler wrap — it covers the application, the share host and the licence-gate short-circuits — sets X-Content-Type-Options: nosniff, a referrer policy, a permissions policy, the content security policy's frame-ancestors, X-Robots-Tag: noindex, and Strict-Transport-Security whenever BASE_URL is https. On a plain-http deployment behind your own TLS terminator, set HSTS at the proxy.
There is deliberately no X-Frame-Options header: it cannot express an allowlist and would fight the CSP directive that does.
One owner of the CSP
The application emits every content security policy header; the reverse proxy in front of it must not add one. Browsers intersect multiple policies — each must independently allow a load — so a second policy at the edge silently negates a deliberate allowance.
Three layers, each setting rather than adding the header, so a response carries exactly one enforced policy:
- the baseline on every response;
- the workspace embed allowlist on
/w/responses —VITRINKA_FRAME_ANCESTORS, space-separated origins allowed to frame workspace pages, empty meaning'self'; - the artifact and set-file sandboxes, which serve user-supplied HTML in an opaque origin under their own complete policy.
VITRINKA_CSP is report-only by default: the enforced surface stays frame-ancestors, while the full baseline is delivered as a report-only policy and violations post to /csp-report on every origin that emits it, as structured, redacted telemetry. enforce promotes the baseline to the enforced header — flip it once the report stream has been quiet for a representative window on your deployment.
Cross-origin access
The API is same-origin. A wildcard Access-Control-Allow-Origin: * is answered only on static assets, raw files inside the artifact sandbox, and the anonymous device-link and contact-form endpoints, and a browser never exposes a cookie-authenticated response to another origin under a wildcard. Two publishable credentials are answered cross-origin, each echoing only an origin it allows and each confined to its own ingest routes: bug-report keys, and recorder tokens — an admin-minted key answers its own origin list, a device-linked recorder its browser extension or a page matching one of the workspace's project domain rules — a production rule only when it opts in to the in-app recorder. VITRINKA_REFERENCE_ORIGINS names exact https origins that may access private reference collections with the browser session, on /api/v1/reference-library only; it never widens the session cookie or opens other workspace APIs.
The share origin
VITRINKA_SHARE_ORIGIN names a dedicated host for board share links. Requests on that host are served by a share-only surface that never accepts a session cookie, a bearer token or the control plane, so a shared board cannot become a foothold into the workspace. Empty keeps share links on BASE_URL.
Sandboxed documents
User-supplied HTML artifacts and set files are served in a sandboxed browsing context with an opaque origin under a restrictive policy of their own. They cannot read the workspace's cookies or storage, and the application baseline is exempted for them so a sandboxed document does not flood the report stream with violations it is meant to produce.