This commit was merged in pull request #37.
This commit is contained in:
42
TODO.md
42
TODO.md
@@ -18,7 +18,9 @@ dotfiles; `LICENSE` is the only mandated file still missing. Every external
|
|||||||
reference in the repo is now pinned by cryptographic hash (or, for the wrangler
|
reference in the repo is now pinned by cryptographic hash (or, for the wrangler
|
||||||
CLI install, an exact version), and the Hugo that builds the published site is a
|
CLI install, an exact version), and the Hugo that builds the published site is a
|
||||||
deliberate pinned version rather than whatever the base image's package repo
|
deliberate pinned version rather than whatever the base image's package repo
|
||||||
serves.
|
serves. The site now ships a Cloudflare Pages `_headers` file, so its response
|
||||||
|
security headers are declared in the repo instead of being whatever the edge
|
||||||
|
defaults to — unverified in production until the next deploy.
|
||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
@@ -28,6 +30,31 @@ remaining policy scaffold is otherwise complete.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-08-09: added `static/_headers` so Cloudflare Pages serves baseline
|
||||||
|
response security headers (closes #14). Hugo copies `static/` verbatim into
|
||||||
|
`public/`, which is the deploy root Pages reads the file from; this is the
|
||||||
|
first root-level `static/` in the repo, and the built tree confirms it unions
|
||||||
|
with the theme's rather than shadowing it — `public/css/style.css` and
|
||||||
|
`public/index.html` are byte-identical to the previous build and the static
|
||||||
|
file count goes 1 to 2. The live "before" was measured, not assumed:
|
||||||
|
Cloudflare already sends `X-Content-Type-Options` and `Referrer-Policy` by
|
||||||
|
default, so the substance here is `Strict-Transport-Security`,
|
||||||
|
`Content-Security-Policy`, `X-Frame-Options` and `Permissions-Policy`. The CSP
|
||||||
|
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
|
||||||
|
and no `style=`/`on*=` attribute, only the one inline `<style>` block
|
||||||
|
`baseof.html` fills by `readFile`. 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 and the computed body padding, tagline colour and link colour all come
|
||||||
|
from the theme CSS, framing the page from another origin is refused by
|
||||||
|
`frame-ancestors 'none'` (consistent with `X-Frame-Options: DENY`), and all
|
||||||
|
five named outbound links still navigate with status 200. 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, and `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.
|
||||||
- 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 /
|
||||||
@@ -178,6 +205,19 @@ 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, on
|
||||||
|
both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against
|
||||||
|
each must show `strict-transport-security` or `content-security-policy`.
|
||||||
|
Cloudflare Pages silently ignores a malformed `_headers`, and checking
|
||||||
|
`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`.
|
||||||
|
Both are owner calls: neither can be walked back inside the max-age window,
|
||||||
|
and `includeSubDomains` binds hostnames this repo does not control (#14)
|
||||||
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
||||||
canonical copy is clean under the shared prettier settings and future syncs
|
canonical copy is clean under the shared prettier settings and future syncs
|
||||||
are a straight byte copy
|
are a straight byte copy
|
||||||
|
|||||||
42
static/_headers
Normal file
42
static/_headers
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Cloudflare Pages response headers.
|
||||||
|
#
|
||||||
|
# Hugo copies static/ verbatim into public/, so this file lands at the
|
||||||
|
# deploy output root, which is where Pages reads it from. Pages consumes
|
||||||
|
# the file rather than publishing it. Values here override what
|
||||||
|
# Cloudflare would otherwise send.
|
||||||
|
#
|
||||||
|
# Every value below was checked against the built public/index.html, not
|
||||||
|
# copied from a template. That page loads nothing: no script, img, link,
|
||||||
|
# iframe, form, video, audio, object or embed element, no style= or on*=
|
||||||
|
# attribute. It has exactly one inline <style> block, which
|
||||||
|
# themes/loravega/layouts/_default/baseof.html fills with the whole of
|
||||||
|
# themes/loravega/static/css/style.css via readFile. That inlining is a
|
||||||
|
# deliberate theme design choice, and it is the sole reason style-src
|
||||||
|
# needs 'unsafe-inline'.
|
||||||
|
#
|
||||||
|
# img-src 'self' is kept even though the page has no images. Browsers
|
||||||
|
# request /favicon.ico unprompted and that fetch is governed by img-src;
|
||||||
|
# measured in Chrome, with this allowance the request is made and 404s,
|
||||||
|
# and without it the request is suppressed outright. Neither hurts
|
||||||
|
# today, but same-origin images are the one resource class this site
|
||||||
|
# would plausibly grow, and 'self' loosens nothing cross-origin.
|
||||||
|
#
|
||||||
|
# X-Content-Type-Options and Referrer-Policy are already sent by
|
||||||
|
# Cloudflare by default and are restated here on purpose. They are a
|
||||||
|
# default, not a guarantee, and this file is where the site's header
|
||||||
|
# posture is declared.
|
||||||
|
#
|
||||||
|
# Strict-Transport-Security deliberately carries neither preload nor
|
||||||
|
# includeSubDomains. preload is effectively irreversible and is the
|
||||||
|
# owner's call. includeSubDomains would bind every hostname under
|
||||||
|
# lora.vegas for a year, and it buys nothing today: www.lora.vegas is
|
||||||
|
# the only other name in DNS, it is served by this same Pages project,
|
||||||
|
# so this block sets HSTS on its responses directly.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Strict-Transport-Security: max-age=31536000
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
||||||
|
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'
|
||||||
Reference in New Issue
Block a user