diff --git a/.env b/.env index 34740107..ee0e1f9a 100644 --- a/.env +++ b/.env @@ -21,12 +21,6 @@ VITE_SAHAJCLOUD_URL="https://cloud.sydevelopers.com" # #130. Set them on every Workers Builds trigger that builds the app; the runbook is in # docs/environment.md. -# Cloudflare Turnstile site key for the report-issue form. A SITE key is public by -# design (the secret half lives in SahajCloud), so it belongs here rather than in -# .env.local. This is Cloudflare's always-passes TEST key — dev, Ladle and CI use it; -# production overrides it with the real key as a BUILD variable on its Workers Builds -# trigger, and a production build fails if it does not (#259, docs/environment.md). -VITE_TURNSTILE_SITE_KEY="1x00000000000000000000AA" # Where a FRAMED embed sends a visitor when its frame is too small for the interface (#161). # A frame cannot expand — `position: fixed` resolves against the frame — so the compact card's diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e55bd12..0e7268e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,20 @@ cover everything a host would notice since the widget was first deployed. ### Changed +- ⚠ **BREAKING: your CSP needs `frame-src https://cloud.sydevelopers.com`, and the three + `challenges.cloudflare.com` entries can go.** ([#343], [SahajCloud#1072]) The captcha that + gates every form now renders in a small frame `cloud.sydevelopers.com` serves — the origin + already in your `connect-src` — instead of in your document. A policy that does not allow the + frame shows the error screen, and the console names the fix. + + ⚠ **The captcha renders only on a domain your client record lists** — the same list that + already gates the API. Any other domain shows the error screen. An iframe embed needs nothing + listed: there the challenge's parent is the widget's own page. + + **Onboarding a site needs no Turnstile step**: Turnstile checks the hostname of the document + that renders the challenge, which is now always `cloud.sydevelopers.com`. No script of + Cloudflare's runs in your document, and your `Permissions-Policy` needs nothing for the captcha. + - **A repeat embed report turns into at most one write in any 6 h window.** ([#160]) The widget still reports on every page load, and an unchanged report inside that window is answered without a write. This guide and the embedding guide had both published the window as one hour. @@ -844,6 +858,8 @@ must-revalidate`, pinned rather than left to the CDN default. The production dom [#312]: https://github.com/sydevs/SahajAtlasWeb/pull/312 [#320]: https://github.com/sydevs/SahajAtlasWeb/issues/320 [#326]: https://github.com/sydevs/SahajAtlasWeb/issues/326 +[#343]: https://github.com/sydevs/SahajAtlasWeb/issues/343 +[SahajCloud#1072]: https://github.com/sydevs/SahajCloud/issues/1072 [#322]: https://github.com/sydevs/SahajAtlasWeb/issues/322 [#329]: https://github.com/sydevs/SahajAtlasWeb/pull/329 [#325]: https://github.com/sydevs/SahajAtlasWeb/issues/325 diff --git a/README.md b/README.md index 6480be5e..a56f5b5b 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,9 @@ SahajCloud's: - **`react-circle-flags.pages.dev`** serves the country flag SVGs (`referrer-policy: no-referrer`). **`challenges.cloudflare.com`** loads Turnstile as soon as the interface mounts, not when a visitor opens a form - (#182). Both are in the CSP table in + (#182) — from a challenge page SahajCloud serves, not from the host's page + (#343), so the host allows that page under `frame-src` instead. Both are in + the CSP table in [`docs/embedding.md`](docs/embedding.md#content-security-policy). None of these has a script-URL opt-out (#149) — each is built so it does not diff --git a/docs/embedding.md b/docs/embedding.md index 0ee41d76..877b5292 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -239,6 +239,7 @@ Some things are **not** parameters, on purpose: | ---------------------------------- | ---------------------------------------------------------------------- | | Display name, brand colours | your client record | | The path prefix for `routing=path` | your client record — the same value your canonical URLs are built from | +| The domains you embed on | your client record — the API refuses reads from anywhere else, and the captcha renders nowhere else | Identity is the same on every page you embed on, so making it per-embed only risks two of your pages disagreeing about what the product is called. The routing prefix is worse than merely @@ -793,15 +794,21 @@ and `cloud.sydevelopers.com` is in `connect-src` already, for the widget's own r If your page sends a CSP, this is what the widget needs added to it. **It is a list of additions to your policy, not a policy** — your own sources still have to be in there. +**The captcha needs nothing of yours but a listed domain.** Every form in the widget is behind a +Cloudflare Turnstile challenge that `cloud.sydevelopers.com` serves, renders and validates, so it +needs no key or account of yours. It renders only on a domain your client record lists ([What you +configure in the CMS instead](#what-you-configure-in-the-cms-instead)) — tell us every domain you +embed on — and the `frame-src` line below is your policy's whole part. + ``` -script-src https://sahajatlas.com https://api.mapbox.com https://challenges.cloudflare.com https://cdn.usefathom.com +script-src https://sahajatlas.com https://api.mapbox.com https://cdn.usefathom.com worker-src blob: child-src blob: style-src 'unsafe-inline' font-src https://sahajatlas.com img-src data: https://api.mapbox.com https://imagedelivery.net https://cloud.sydevelopers.com https://react-circle-flags.pages.dev -connect-src https://cloud.sydevelopers.com https://api.mapbox.com https://events.mapbox.com https://ipwho.is https://challenges.cloudflare.com https://cdn.usefathom.com https://*.sentry.io -frame-src https://challenges.cloudflare.com +connect-src https://cloud.sydevelopers.com https://api.mapbox.com https://events.mapbox.com https://ipwho.is https://cdn.usefathom.com https://*.sentry.io +frame-src https://cloud.sydevelopers.com ``` **One line per directive, deliberately: CSP ignores every repeat of a directive name after the @@ -814,7 +821,6 @@ believing you allowed something you hadn't. | -------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `script-src` | the widget's origin | The `