Skip to content

www: ship security response headers - #34

Merged
z33b0t merged 1 commit into
mainfrom
www/security-headers
Jul 29, 2026
Merged

www: ship security response headers#34
z33b0t merged 1 commit into
mainfrom
www/security-headers

Conversation

@z33b0t

Copy link
Copy Markdown
Contributor

Adds CSP, HSTS, nosniff, Referrer-Policy and X-Frame-Options to the marketing site.

The issue's open question, answered

Confirm OpenNext actually serves _headers for the asset routes in your setup; if not, set them in next.config.ts via headers() instead.

It's both, and neither substitutes for the other. Measured against opennextjs-cloudflare preview (local workerd), counting how many of the five headers came back:

route_headers onlynext.config onlyboth
/ (Worker-rendered document)055
/favicon.svg (assets binding)505

Under OpenNext the document is rendered by the Worker, while /_next/static/* and public/ are served straight off the Cloudflare assets binding without the Worker running. So the _headers-only approach the issue sketches would have left the page — the one thing frame-ancestors protects — completely uncovered. Hence both, with a comment in each file pointing at the other.

On unsafe-inline

  • script-src: kept. The rendered document carries 12 inline <script> blocks (self.__next_f.push(...), Next's hydration payload). Hashes churn every build; nonces need middleware and would opt the page out of static rendering.
  • style-src: kept, but it may not be needed. The rendered document has zero <style> elements and zero style= attributes, and there are no style={{…}} props anywhere in www/src. So style-src 'self' would very likely work today. I left 'unsafe-inline' in because I couldn't do a real browser render check (no browser tooling in this session) and a wrong guess breaks the site's front page silently. Happy to tighten it if you'll eyeball the preview.

Verification

  • Both routes return all five headers from the local worker (curl -D -).
  • Every subresource the document requests is same-origin — JS/CSS chunks and three self-hosted .woff2 under /_next/static/. No data: URIs, no inline event handlers. The only absolute URLs are four anchor hrefs (github.com, lightware.dev), which are navigations and unaffected by this policy.
  • Not done: a browser render / devtools console check. The audit above is static.

Closes#22

No CSP, HSTS, nosniff, Referrer-Policy or frame-ancestors on the marketing
site. Low stakes — a static page with no auth, no cookies and no user input,
so there is no session to steal and no injection sink — but frame-ancestors
'none' is free clickjacking cover and the rest is a clean scanner report.
Set in two places, which is duplication but not redundancy. Under OpenNext the
document is rendered by the Worker while /_next/static/* and public/ are served
straight off the Cloudflare assets binding, so next.config's headers() covers
the first and public/_headers the second — and neither covers the other:
route _headers only next.config only
/ none all five
/favicon.svg all five none
Measured against `opennextjs-cloudflare preview`, not assumed; the issue's
suggested _headers-only approach would have left the actual page uncovered.
script-src keeps 'unsafe-inline' because Next inlines the hydration payload
(12 <script> blocks of self.__next_f.push in the rendered document); hashes
churn per build and nonces would deopt static rendering. HSTS is without
preload, deliberately.
Closes#22
@z33b0t

Copy link
Copy Markdown
ContributorAuthor

@z33b0t ready to merge.

All 6 checks green (CodeQL across all five languages). Note the site build did not run on this PR — www.yml has no pull_request trigger yet, which is #16 / PR #38. So the CSP change here is verified by my local opennextjs-cloudflare preview runs, not by CI.

Suggested order: merge #38 first, then this. That gets the site build running on PRs, and if you push a rebase here the headers change would get a real CI build before it deploys — which is precisely the hazard #16 describes, applied to this PR.

Two things flagged in the PR body worth a decision:

  • style-src 'unsafe-inline' is probably unnecessary (zero inline styles in the rendered document and no style={{…}} anywhere in www/src). I left it because I had no browser to confirm with.
  • The _headers + next.config.ts duplication is deliberate and measured — neither mechanism covers the other. Table's in the PR body.

@z33b0t

Copy link
Copy Markdown
ContributorAuthor

@z33b0t Green, and I'm holding this one deliberately rather than merging.

You asked for dependency order, and this sits behind #38 — which adds the pull_request trigger to www.yml. #38 is blocked on my token lacking workflow scope (see my comment there), so the chain is stuck at the first link.

Why it matters for this PR specifically: merging it now pushes a CSP straight to production having never been built by CI, and I couldn't do a browser render check either. Local opennextjs-cloudflare preview + curl is all the verification it has. That's exactly the first-execution-is-the-deploy hazard #16 is about, and it'd be a shame to demonstrate it on the PR that adds security headers.

Once #38 lands, push any commit here (or I will) so the site build runs on this PR, then it's safe to merge.

@z33b0t
z33b0t merged commit 0ad04eb into mainJul 29, 2026
6 checks passed
@z33b0t
z33b0t deleted the www/security-headers branch July 29, 2026 16:46
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Website ships no security response headers (CSP, HSTS, nosniff)

1 participant

@z33b0t