Origins & content security
Allowed origins
Section titled “Allowed origins”Each questionnaire carries a list of origins allowed to embed and submit
it, edited in the editor’s Embed panel (https://app.example.com — scheme
- host, optional port, no path). An empty list allows every origin —
the editor shows a warning badge so openness is always a visible choice,
and the literal
*is rejected as redundant.
Enforcement is server-side, per request, against the browser-sent Origin
header:
- Mutating requests (creating a session, saving answers, completing,
uploading) from a disallowed origin are rejected with
403. - Reads return data but withhold the CORS
Access-Control-Allow-Originheader, so the browser blocks the embedding page from reading it — the embed shows its generic load error. - Requests without an
Originheader (curl, server-to-server) are allowed — origin checking is a browser-context control, not authentication. The security model is honest about this: origins keep other websites from embedding your questionnaire, nothing more.
Matching is exact and case-sensitive — https://app.example.com and
https://www.app.example.com are different origins; list each one you
serve from. The origin and user agent of the creating request are also
stored on each response (visible in the management API) for forensics.
Content-Security-Policy on your page
Section titled “Content-Security-Policy on your page”If your embedding page sets a CSP, the embed needs:
script-src https://unpkg.com # or your own host if you self-host the bundleconnect-src https://forms.your-domain.com # your api-basescript-src— whereverforms-engine.esm.jsloads from. Self-hosting the bundle on your own origin (copy the file next to your page) removes the third-party entry entirely and pins the version — reasonable hardening for production.connect-src— theapi-base, for all runtime calls including uploads.- No
frame-src,style-srcadditions, or cookies are needed: the component is not an iframe, styles live in its shadow DOM, and the API is cookie-less (the response id in sessionStorage is the only session state).
Serve the backend over HTTPS in production. Browsers block mixed content,
so an embed on an https:// page cannot call an http:// api-base at
all — terminate TLS in front of the backend (see
Reverse proxy & exposure).