Compare commits

..

1 Commits

Author SHA1 Message Date
clawbot
5f998c6e70 Add a Cloudflare Pages _headers file with security headers (closes #14)
All checks were successful
check / check (push) Successful in 9s
Hugo copies static/ verbatim into public/, so static/_headers lands at
the deploy output root, which is where Pages reads it from. This is the
first root-level static/ in the repo; Hugo unions it with the theme's
static/ per path rather than shadowing it, and the built tree confirms
that: public/css/style.css and public/index.html are byte-identical to
the previous build and the static file count goes from 1 to 2.

The live "before" was measured rather than assumed. Cloudflare already
sends X-Content-Type-Options and Referrer-Policy by default, so those
two lines are restatements; the substance is Strict-Transport-Security,
Content-Security-Policy, X-Frame-Options and Permissions-Policy, none of
which the site sends today.

Every value is checked against the built page, which loads nothing: no
script, img, link, iframe, form or media element, no style= and no on*=
attribute. It has exactly one inline <style> block, filled by readFile
in baseof.html. So default-src 'none' with style-src 'unsafe-inline' is
both achievable and tight, and 'unsafe-inline' is required by, and only
by, that deliberate inlining. There is no script-src allowance because
there are no scripts. X-Frame-Options: DENY and frame-ancestors 'none'
agree.

HSTS carries neither preload nor includeSubDomains. www.lora.vegas is
the only other name in DNS and it is served by this same Pages project,
so this file sets HSTS on its responses directly; includeSubDomains
would instead bind every future subdomain for a year, with no way to
walk it back inside the max-age window without also dropping the apex
protection.

Verified in a headless Chrome against a local server that parses the
committed _headers and applies it as real response headers: zero CSP
violations, the inlined stylesheet parses to 17 rules with the computed
body padding, tagline colour and link colour all coming from the theme
CSS, framing from another origin refused by frame-ancestors, and all
five named outbound links still navigating with status 200.

Whether Pages actually parses the file cannot be verified from here.
Pages silently ignores a malformed _headers, so the green build proves
nothing about it; that check belongs after the next deploy and must be
made on Strict-Transport-Security or Content-Security-Policy, since
X-Content-Type-Options would pass either way. It has to be run against
both lora.vegas and www.lora.vegas: dropping includeSubDomains rests on
www being served by this same Pages project, which was established from
identical response bodies rather than from the Cloudflare dashboard.
2026-08-09 17:01:59 +00:00

18
TODO.md
View File

@@ -42,7 +42,7 @@ remaining policy scaffold is otherwise complete.
`Content-Security-Policy`, `X-Frame-Options` and `Permissions-Policy`. The CSP `Content-Security-Policy`, `X-Frame-Options` and `Permissions-Policy`. The CSP
is `default-src 'none'` with `style-src 'unsafe-inline'`, which the built page is `default-src 'none'` with `style-src 'unsafe-inline'`, which the built page
supports exactly: it has no script, img, link, iframe, form or media element supports exactly: it has no script, img, link, iframe, form or media element
and no `style=`/`on*=` attribute, only the one inline `&lt;style&gt;` block and no `style=`/`on*=` attribute, only the one inline `<style>` block
`baseof.html` fills by `readFile`. Verified in a headless Chrome against a `baseof.html` fills by `readFile`. Verified in a headless Chrome against a
local server that parses the committed `_headers` and applies it as real local server that parses the committed `_headers` and applies it as real
response headers: zero CSP violations, the inlined stylesheet parses to 17 response headers: zero CSP violations, the inlined stylesheet parses to 17
@@ -54,6 +54,7 @@ remaining policy scaffold is otherwise complete.
DNS and it is served by this same Pages project, so this file sets HSTS on its DNS and it is served by this same Pages project, so this file sets HSTS on its
responses directly, and `includeSubDomains` would instead bind every future responses directly, and `includeSubDomains` would instead bind every future
subdomain for a year with no way to walk it back inside the max-age window subdomain for a year with no way to walk it back inside the max-age window
without also dropping the apex protection.
- 2026-08-09: restructured `README.md` into the canonical section set (closes - 2026-08-09: restructured `README.md` into the canonical section set (closes
#11): a Description first line, then Getting Started, Entrypoints, Rationale, #11): a Description first line, then Getting Started, Entrypoints, Rationale,
Design, TODO, License, Author. The non-standard About / Contributing / Design, TODO, License, Author. The non-standard About / Contributing /
@@ -204,11 +205,16 @@ remaining policy scaffold is otherwise complete.
here (#20) here (#20)
- Move the deploy container to a pinned node 22 so the wrangler pin can advance - Move the deploy container to a pinned node 22 so the wrangler pin can advance
past 4.86.0 (#21) past 4.86.0 (#21)
- After the next deploy, confirm the `_headers` file actually took effect: - After the next deploy, confirm the `_headers` file actually took effect, on
`curl -sSI https://lora.vegas/` must show `strict-transport-security` or both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against
`content-security-policy`. Cloudflare Pages silently ignores a malformed each must show `strict-transport-security` or `content-security-policy`.
`_headers`, and checking `x-content-type-options` would pass either way Cloudflare Pages silently ignores a malformed `_headers`, and checking
because the edge sends it regardless (#14) `x-content-type-options` would pass either way because the edge sends it
regardless. `www` has to be checked too and not just the apex: dropping
`includeSubDomains` rests on `www.lora.vegas` being served by this same Pages
project, which was established behaviourally from identical response bodies
rather than from the Cloudflare dashboard. If `www` turns out not to be
covered, the `includeSubDomains` decision has to be revisited (#14)
- Decide the HSTS `includeSubDomains` and `preload` posture for `lora.vegas`. - Decide the HSTS `includeSubDomains` and `preload` posture for `lora.vegas`.
Both are owner calls: neither can be walked back inside the max-age window, Both are owner calls: neither can be walked back inside the max-age window,
and `includeSubDomains` binds hostnames this repo does not control (#14) and `includeSubDomains` binds hostnames this repo does not control (#14)