Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

Ship a CSP, and make sure Jekyll actually copies it - #18

Merged
MichalAFerber merged 1 commit into
mainfrom
add/csp-header
Aug 11, 2026
Merged

MichalAFerber merged 1 commit into
mainfrom
add/csp-header

Conversation

@MichalAFerber

Copy link
Copy Markdown
Owner

gsamanager.org was the only site in the estate serving no Content-Security-Policy — found while sweeping all 30 sites for CSPs that silently block Plausible. Its analytics worked precisely because nothing restricted them.

The policy

Every source is something the site actually loads, read off the built output rather than assumed:

directive why
script-src Turnstile + self-hosted Plausible. 'unsafe-inline' for the one inline script driving the contact form.
style-src The Google Fonts stylesheet (Open Sans).
font-src fonts.gstatic.com, plus data:.
connect-src Plausible's event beacon and mailer.thompsonblack.us, which takes the contact POST.
frame-src Turnstile renders itself into an iframe.
img-src 'self' data: — a full-repo scan found no external image loads.

The part that would have failed silently

Jekyll drops underscore-prefixed files from _site. A _headers file sitting at the repo root looks correct, never reaches the build, and the site keeps serving no policy. It has to be named in include: beside .well-known:

include:
  - .well-known
  # Jekyll drops underscore-prefixed files; without this the CSP never ships.
  - _headers

Delete that line and you delete the CSP, with nothing to indicate it. Verified with a local bundle exec jekyll build — _site/_headers is present.

Worth checking after merge

default-src 'none' is unforgiving. Confirm the contact form still submits and Turnstile still renders — those are the two things this could break.

🤖 Generated with Claude Code

https://claude.ai/code/session_0119vXMJC7FKdDTYJkLVLusg

gsamanager.org was the only site in the estate serving no Content-Security-Policy
at all -- which is why its analytics worked: nothing restricted them.

Every source in the policy is something the site loads, read off the built output
rather than assumed: Turnstile and self-hosted Plausible for scripts, the Google
Fonts stylesheet for styles, fonts.gstatic.com for faces, Plausible's beacon and
the mailer for connections, and Turnstile again under frame-src because it
renders itself into an iframe. One inline script drives the contact form, hence
'unsafe-inline' on script-src. No external images anywhere in the repo, so
img-src stays 'self' data:.

The part that would have failed silently: Jekyll drops underscore-prefixed files
from _site, so `_headers` had to be named in `include:` beside `.well-known`.
Without that line the file sits in the repo looking correct and never reaches the
build, and the site keeps serving no policy. Delete the include line and you
delete the CSP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0119vXMJC7FKdDTYJkLVLusg
@MichalAFerber
MichalAFerber merged commit 6d580a2 into main Aug 11, 2026
1 check passed
@MichalAFerber
MichalAFerber deleted the add/csp-header branch August 11, 2026 23:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant