Skip to content

Add CSP + security.txt, PurgeCSS, and accessibility CI audit#6

Merged
jansroka merged 1 commit into
fix-ci-lockfilefrom
modernize-csp-a11y-purgecss
Jul 8, 2026
Merged

Add CSP + security.txt, PurgeCSS, and accessibility CI audit#6
jansroka merged 1 commit into
fix-ci-lockfilefrom
modernize-csp-a11y-purgecss

Conversation

@jansroka

@jansroka jansroka commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Implements three modernization recommendations (#5 security headers, #1 PurgeCSS, #4 a11y audit). Developed in parallel by three Sonnet subagents against non-overlapping files; shared files (package.json, ci.yml, _config.yml, lockfile) integrated in the final commit.

Base branch: this PR targets fix-ci-lockfile (PR #5) because it needs the committed package-lock.json for npm ci. Merge #5 first, then this retargets cleanly to main. (If #5 is already merged, I'll rebase onto main.)

#5 — Security headers + security.txt

  • Strict Content-Security-Policy + Referrer-Policy via <meta> tags in head.html (GitHub Pages can't set HTTP headers). Policy: default-src 'self', same-origin img/style/font, object-src 'none', frame-ancestors 'none', base-uri 'self', form-action 'self'.
    • style-src keeps 'unsafe-inline' — several Webflow-exported pages (projekte.html, offenerbrief-epa-2025.html) use inline style= attributes; a CSP that breaks styling is worse than none. Documented inline.
    • X-Content-Type-Options / X-Frame-Options are deliberately not emitted as meta tags (header-only directives) — noted in a comment; they'd need a header-capable front (e.g. Cloudflare).
  • /.well-known/security.txt (RFC 9116), contact kontakt@inoeg.de. Added .well-known to the _config.yml include: list — Jekyll excludes dot-dirs by default (verified against Jekyll 4.4.1 EntryFilter source).

#1 — PurgeCSS

  • purgecss.config.js + scripts/purge-css.js + a "Purge unused CSS" CI step that runs after jekyll build and rewrites only _site/assets/css/ (source assets/css/ untouched, so every build starts from the original export).
  • Conservative safelist covers Webflow runtime/state classes (w-*, w--*, wf-*) and the checkbox-toggled mobile nav, reasoned per-entry in comments. Over-purging is silent breakage, so the safelist errs toward keeping.
  • The 127 KB inog-website.webflow.css is the main target. Please spot-check all pages render correctly after the first CI purge — real per-page reduction is validated by CI, not by the single-page dev fixture.

#4 — Accessibility audit (pa11y-ci)

  • .pa11yci.json (WCAG2AA, axe + htmlcs runners) + test-a11y script that serves _site with http-server and runs pa11y-ci over all 8 pages (start-server-and-test for a race-free server startup; Chromium --no-sandbox for CI).
  • The CI step is NON-BLOCKING (continue-on-error: true). A real run against the built site showed 0/8 pages passing, with pre-existing errors:
    • <html> element missing a lang attribute
    • Document missing a non-empty <title> (on some Webflow-exported pages)
    • Image-only link missing alt text ("Links must have discernible text")
    • Frames (iframe) not testable by axe
  • Gating on these would make CI red on merge, so the check reports the debt on every PR. Follow-up: fix these, then drop continue-on-error to make it a hard gate. Some (missing title/lang) may be real defects on the raw Webflow pages and warrant investigation.

Verification

Prettier, YAML, JSON, and JS-config parsing all pass locally. The Jekyll build, PurgeCSS, and pa11y runs were exercised against real built output during development (subagents ran actual builds). A full build isn't runnable in the authoring env (offline), so the build itself is confirmed by CI on this PR.

🤖 Generated with Claude Code

Implements three modernization recommendations. The three changes were
developed in parallel by separate subagents against non-overlapping files;
shared files (package.json, ci.yml, _config.yml, lockfile) were integrated
here.

Security headers + security.txt (rec #5):
- Add a strict Content-Security-Policy and Referrer-Policy via <meta> tags in
  head.html (GitHub Pages cannot set HTTP headers). Policy is default-src
  'self' with same-origin CSS/img/font, object-src 'none', frame-ancestors
  'none'. style-src keeps 'unsafe-inline' because several Webflow-exported
  pages use inline style= attributes; a CSP that breaks styling is worse than
  none. X-Content-Type-Options / X-Frame-Options are intentionally NOT emitted
  as meta tags (header-only directives) — documented inline.
- Add /.well-known/security.txt (RFC 9116). Requires the .well-known dir in
  the _config.yml include list, since Jekyll excludes dot-dirs by default
  (verified against Jekyll 4.4.1 EntryFilter source).

PurgeCSS (rec #1):
- Add purgecss.config.js + scripts/purge-css.js and a "Purge unused CSS" CI
  step that runs after the Jekyll build and rewrites only the CSS in _site/
  (source assets/css/ is untouched). Conservative safelist covers Webflow
  runtime/state classes and the checkbox-toggled mobile nav. Fixture test
  showed the 127 KB inog-website.webflow.css dropping sharply; real per-page
  reduction will be validated by the first CI run.

Accessibility audit (rec #4):
- Add .pa11yci.json (WCAG2AA, axe + htmlcs runners) and a test-a11y script
  that serves _site via http-server and runs pa11y-ci over all 8 pages.
- The CI step is NON-BLOCKING (continue-on-error) for now: a real run against
  the built site showed 0/8 pages passing, with pre-existing errors including
  missing <title>/<html lang> on some Webflow-exported pages, an image-only
  link missing alt text, and untested iframes. Gating on these would make CI
  red on merge. The check reports the debt on every PR; remove
  continue-on-error once the baseline is fixed in a follow-up.

Verification: prettier, YAML, JSON, and JS-config parsing all pass locally.
The Jekyll build, PurgeCSS, and pa11y runs were exercised against real built
output during development; a full local build is not possible in the commit
environment (offline), so the build itself is confirmed by CI on this PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to 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.

1 participant