Security
cookpit.org is a static site served over HTTPS with a strict set of response headers applied to every request. The headers are defined once in netlify.toml and reviewed in the open.
Response headers
| Header | Value | What it does |
|---|---|---|
Content-Security-Policy | default-src 'self'; script-src 'self' + SHA-256 hashes; object-src 'none'; base-uri 'self'; frame-ancestors 'self' | The load-bearing header. script-src ships no 'unsafe-inline' — the two inline scripts Docusaurus emits are pinned by hash, so an injected <script> cannot run. object-src 'none' and base-uri 'self' close the usual bypasses. |
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | Two-year HTTPS pin across every subdomain, preload-eligible. After the first visit the browser refuses to talk to cookpit.org over plain HTTP at all. |
X-Content-Type-Options | nosniff | Stops the browser from MIME-sniffing a response into something executable — a served .json or .md is treated as exactly its declared type. |
Referrer-Policy | strict-origin-when-cross-origin | Sends the full URL on same-origin navigations but only the origin cross-site, and nothing at all when downgrading to HTTP. No path leakage to third parties. |
Permissions-Policy | geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=() | Switches off powerful APIs the site never uses, and opts out of FLoC/Topics ad cohorting. |
frame-ancestors (via CSP) | 'self' | Clickjacking defence — the site can only be framed by itself. Supersedes the legacy X-Frame-Options header. |
How the A+ is held in place
The single thing standing between a typical site and an A+ on the Observatory is 'unsafe-inline' in script-src — a 20-point penalty. We don't ship it. The two inline bootstrap scripts Docusaurus emits (the colour-mode initialiser on every page, and the base-URL fallback banner on the homepage) are pinned by their SHA-256 hashes instead.
Hash-pinning a generated site is brittle on its own: a framework upgrade can change an inline script's bytes, its hash drifts, and the browser silently refuses to run it — a white screen that a passing npm run build would not catch. So check-csp-hashes.mjs runs in CI after every build: it re-scans the built HTML for inline scripts and fails the pipeline if any one of them is not already allowed by the CSP. The grade cannot silently regress.
Scope
This covers the static site at cookpit.org. The validation and attestation service at validator.cookpit.org is a separate, narrowly-scoped HTTP service: it holds uploaded files in memory for the duration of a single request, never writes them to disk and never logs them, and accepts cross-origin requests only from the cookpit.org origins.
Authenticity of an individual cooking file is a separate, stronger guarantee carried by the file itself: every authenticated .A.jsonld is Ed25519-signed, so tampering is detectable by anyone, offline, without trusting this site. If you build an app that consumes cookpit files, see Trusted — verifying a file in your app for the one rule, the pinned public key, and a drop-in verifier.