# Headers and CSP (https://docs.vitrinka.ai/security/headers)



## Every response [#baseline]

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 [#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:

1. the baseline on every response;
2. the workspace embed allowlist on `/w/` responses — `VITRINKA_FRAME_ANCESTORS`, space-separated origins allowed to frame workspace pages, empty meaning `'self'`;
3. 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 [#cors]

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 [#share]

`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 [#sandbox]

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.
