From 54b6c2a7bc8f54e0f665bbf46accc664bd09115f Mon Sep 17 00:00:00 2001 From: simien Date: Sun, 13 Sep 2026 16:54:34 -0400 Subject: [PATCH] chore: send security headers with every response flield.com served no security headers at all. Cloudflare reads a _headers file from the assets directory at deploy and applies it, so this adds the four that fit a static site with no forms and no third-party code: nosniff, a referrer policy, a permissions policy that turns off the device APIs the app never asks for, and a content security policy locked to the site's own origin. The policy keeps 'unsafe-inline' for scripts and styles, since every page carries its script inline and nothing exists to mint a nonce. It still stops any script from another host, re-basing, plugins, and forms posting elsewhere. Cloudflare Web Analytics is the one outside party the dashboard injects, and both of its hosts are allowed. Checked by serving the repo locally with these exact headers: no violation events, PNG and SVG export, the GIF encoder and its worker, the redrawn favicon, the theme toggle, and the explainers' live canvases all ran as before. Co-Authored-By: Claude Fable 5.1 --- _headers | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 _headers diff --git a/_headers b/_headers new file mode 100644 index 0000000..0d75eed --- /dev/null +++ b/_headers @@ -0,0 +1,22 @@ +# Response headers for everything flield.com serves. Cloudflare reads this +# file at deploy time and does not serve it. Every page is static, has no +# form, and talks to nothing but its own origin, so the policy can say +# exactly that. +# +# The one exception is Cloudflare Web Analytics, which the dashboard +# injects at the edge as a script from static.cloudflareinsights.com that +# then posts to cloudflareinsights.com. Both hosts are allowed below; drop +# them if analytics is ever turned off. +# +# script-src carries 'unsafe-inline' because the app is one long inline +# script and the document pages each have one too. A nonce would need a +# server to mint it, and there is none. What the policy still buys is that +# no script from any other host runs, no page can be re-based, nothing +# embeds a plugin, and no form posts anywhere. img-src allows data: for +# the favicon the app redraws on every load and blob: for the exported +# frames; worker-src covers the vendored GIF encoder's worker. +/* + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=() + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; worker-src 'self' blob:; connect-src 'self' https://cloudflareinsights.com; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'