Run the CSP check that has never run - #8
Conversation
tests/csp-check.mjs existed to prove the strict CSP in _headers does not break the app: script-src carries no 'unsafe-inline', and the print path builds an iframe srcdoc with an inline <style> that this file exercises. Nothing invoked it. The only reference to it anywhere in the repo was prose in README.md:107, so a reader reasonably concluded CSP was covered in CI. It was not. It also could not have run if something had called it. Line 8 was const ROOT = '/home/user/markdownwizard'; an absolute path that exists on no Mac and no GitHub runner. ROOT now derives from import.meta.url, matching e2e.mjs beside it, which already did this. Wired into the test job after the e2e suite, where the browser and deps are already installed by the existing step, so this costs one extra node run and no new setup. Verified red before being trusted green, which is the only reason to believe the green: clean repo exit 0 — "CSP violations: none" inline <script> in index.html exit 1 — "script-src-elem blocked inline" The violation message names the directive and offers the sha256 hash, so a future failure is actionable rather than just red. Note a real behavioural change: the deploy job declares `needs: test`, so a CSP violation now blocks deployment rather than merely reporting. That is the point of the check, but it is a gate that did not exist before this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P5n34zWrrhGeihpHsCJE3K
|
Ready for review — attested against Read in full; at 17 lines there is no summary to hide behind. Two changes, and the second is what makes the first mean anything:
The reason I am flipping this quickly is the control, and I want it named because I ask for it constantly and rarely get it: the PR verified the check RED before trusting it green — an inline My gate concerns:
This also closes a finding Carrie raised independently on Michal merges. |
Refs: branched from
markdownwizardorigin/main9995aaa, fetched immediately before. Worktree; shared clone untouched.No collision with draft PR #7 (
feat/ds9-required-pages), which touches404.html,css/app.css,docs.html,index.html,privacy.html,sitemap-0.xml,support.html,terms.html,tests/e2e.mjs. This PR touchestests/csp-check.mjsand.github/workflows/deploy.yml— disjoint. Handed over rather than folded into #7 at that author's request.Two defects, both confirmed
1. Nothing invoked it.
git grep "csp-check" origin/mainreturns exactly one hit: prose inREADME.md:107. Zero references in any.ymlor.json. Thetestjob rannode e2e.mjsand nothing else.2. It could not have run anyway. Line 8 was
const ROOT = '/home/user/markdownwizard';— a path that exists on no Mac and no GitHub runner. Now derived fromimport.meta.url, the patterne2e.mjsbeside it already used.So the repo carried a written-down safety argument that had never been executed, and a README that told readers CSP was covered in CI.
Verified red before trusted green
This is the only reason to believe the pass. I ran it locally against the real
_headersCSP:CSP violations: none,console errors: none,preview rendered: yes<script>injected intoindex.htmlscript-src-elem blocked inlineThe failure message names the offending directive and offers the
sha256-…hash, so a future red is actionable rather than merely red.index.htmlwas restored —git statusshows exactly the two intended files.What it exercises:
script-src 'self' https://plausible.thompsonblack.uswith no'unsafe-inline', against a print path that builds an iframesrcdoccontaining an inline<style>.style-srcdoes permit'unsafe-inline', so the style is legitimate and the check confirms it stays legitimate.One behavioural change worth calling out before merge
The
deployjob declaresneeds: test. Adding this step totestmeans a CSP violation now blocks deployment, where previously it would not have been noticed at all. That is the intent, but it is a new gate and it should be a deliberate choice rather than a side effect — so flagging it rather than burying it.Cost is one extra
noderun; it sits after the e2e step where chromium and deps are already installed.Placement note
I put the step in the same job rather than a parallel one, because
Install test deps + browseris the expensive part and it is already paid for there. A separate job would double aplaywright install --with-deps chromiumfor one script.Draft per the gate.
🤖 Generated with Claude Code
https://claude.ai/code/session_01P5n34zWrrhGeihpHsCJE3K