feat: add baseline browser security headers #296

Merged
erik merged 1 commit from feat/task-03ba3b00-browser-security-headers into main 2026-10-05 05:26:06 -05:00
Owner

Summary

  • Apply CSP, DENY framing protection, nosniff, strict-origin-when-cross-origin and minimal permissions globally before setup gates/routes, including redirects/errors. Preserve response bodies/types and cross-origin media use; do not add isolation headers.
  • Use existing Hono secureHeaders/NONCE and request-local storage. Explicitly nonce trusted template scripts/styles only; never rewrite arbitrary HTML. Scripts require fresh 128-bit nonces, including external scripts. No unsafe-inline, unsafe-eval or broad script-origin allowances. HTML is no-store to avoid nonce reuse through caches; API/media/feed cache policies stay unchanged.
  • Default-deny CSP with same-origin forms/connections/fonts/styles/manifests, no frames/plugins/workers. Intentionally allow HTTPS image/media origins for existing remote/federated content, data/blob image previews, and exact configured HTTP(S) S3_PUBLIC_URL origins.
  • Swagger uses nonced stable-major-5 bundle/initializer, a docs-only style package-path exception, same-origin OpenAPI/API calls, and no external validator. Unnonced scripts from the shared CDN are still blocked.
  • HSTS defaults off. Positive SECURITY_HSTS_MAX_AGE requires production and explicit valid public HTTPS DNS SECURITY_PUBLIC_ORIGIN. Never inferred from request/forwarded headers; no subdomain/preload opt-in. Document controlled TLS proxy/backend assumptions.
  • Generated README/environment reference link shipped policy/customization/upgrade guidance. Archive regression confirms source/docs ship. Regenerated CSS adds only the inline utility detected in new source/docs.

Verification

  • New baseline tests initially failed (19 failures before implementation); final 30 unit cases cover response types, setup/liveness, concurrent nonces, trusted/untrusted renderers, scoped docs/media origins, errors, HSTS configuration and spoofed forwarded headers.
  • Existing LOCAL ONLY ActivityPub actor integration assertion confirms security headers while preserving document/type/signing-key fields.
  • All 35 LOCAL ONLY browser tests pass (29 existing + 6 new), including live search/color mode, blob image previews, actual virtual-device WebAuthn registration/login, real Swagger CDN/OpenAPI and Try it out health request, framing protection, and blocked inline handlers/scripts/styles/foreign connections. Only isolated in-memory fixtures and Chromium virtual passkeys; no personal passkeys, OS prompts, real uploads or deployed-site changes.
  • ./scripts/pre-pr.sh passes CSS, formatting, lint, type checks and all 1,163 local tests (533 CLI, 580 site, 50 package).
  • npm run test:ci passes (527 CLI, 501 site, 50 package); git diff checks clean.
  • Evidence: /tmp/slugkit-security-headers-red.log, /tmp/slugkit-security-pre-pr.log, /tmp/slugkit-security-unit-ci.log, /tmp/slugkit-security-browser-all.log, /tmp/slugkit-security-css-diff.json.

No dependencies, workflow changes, runtime provisioning in CI, or deployed-site rollout. NEVER browser/container/federation integration tests in CI or release workflows.

Task: #task-03ba3b00
Deployment follow-ups: erikvancraddock.com task-dae6192b; mustang-news task-91db9c08.

Awaiting CI, independent review and explicit human merge approval.

## Summary - Apply CSP, DENY framing protection, nosniff, strict-origin-when-cross-origin and minimal permissions globally before setup gates/routes, including redirects/errors. Preserve response bodies/types and cross-origin media use; do not add isolation headers. - Use existing Hono secureHeaders/NONCE and request-local storage. Explicitly nonce trusted template scripts/styles only; never rewrite arbitrary HTML. Scripts require fresh 128-bit nonces, including external scripts. No unsafe-inline, unsafe-eval or broad script-origin allowances. HTML is no-store to avoid nonce reuse through caches; API/media/feed cache policies stay unchanged. - Default-deny CSP with same-origin forms/connections/fonts/styles/manifests, no frames/plugins/workers. Intentionally allow HTTPS image/media origins for existing remote/federated content, data/blob image previews, and exact configured HTTP(S) S3_PUBLIC_URL origins. - Swagger uses nonced stable-major-5 bundle/initializer, a docs-only style package-path exception, same-origin OpenAPI/API calls, and no external validator. Unnonced scripts from the shared CDN are still blocked. - HSTS defaults off. Positive SECURITY_HSTS_MAX_AGE requires production and explicit valid public HTTPS DNS SECURITY_PUBLIC_ORIGIN. Never inferred from request/forwarded headers; no subdomain/preload opt-in. Document controlled TLS proxy/backend assumptions. - Generated README/environment reference link shipped policy/customization/upgrade guidance. Archive regression confirms source/docs ship. Regenerated CSS adds only the inline utility detected in new source/docs. ## Verification - New baseline tests initially failed (19 failures before implementation); final 30 unit cases cover response types, setup/liveness, concurrent nonces, trusted/untrusted renderers, scoped docs/media origins, errors, HSTS configuration and spoofed forwarded headers. - Existing LOCAL ONLY ActivityPub actor integration assertion confirms security headers while preserving document/type/signing-key fields. - All 35 LOCAL ONLY browser tests pass (29 existing + 6 new), including live search/color mode, blob image previews, actual virtual-device WebAuthn registration/login, real Swagger CDN/OpenAPI and Try it out health request, framing protection, and blocked inline handlers/scripts/styles/foreign connections. Only isolated in-memory fixtures and Chromium virtual passkeys; no personal passkeys, OS prompts, real uploads or deployed-site changes. - ./scripts/pre-pr.sh passes CSS, formatting, lint, type checks and all 1,163 local tests (533 CLI, 580 site, 50 package). - npm run test:ci passes (527 CLI, 501 site, 50 package); git diff checks clean. - Evidence: /tmp/slugkit-security-headers-red.log, /tmp/slugkit-security-pre-pr.log, /tmp/slugkit-security-unit-ci.log, /tmp/slugkit-security-browser-all.log, /tmp/slugkit-security-css-diff.json. No dependencies, workflow changes, runtime provisioning in CI, or deployed-site rollout. NEVER browser/container/federation integration tests in CI or release workflows. Task: #task-03ba3b00 Deployment follow-ups: erikvancraddock.com task-dae6192b; mustang-news task-91db9c08. Awaiting CI, independent review and explicit human merge approval.
feat: add baseline browser security headers
All checks were successful
CI / build-lint-test (pull_request) Successful in 56s
bca4012ee0
Authorize only explicitly nonced trusted scripts, preserve required image and Swagger behavior, and keep HSTS an operator-managed production HTTPS opt-in. Cover nonce isolation and browser compatibility locally without adding integration tests to CI.

Task: #task-03ba3b00
Author
Owner

Review update

  • PR: #296 (bca4012ee0) is open against main; reviewed the Forgejo diff, task task-03ba3b00, and the provided verification artifacts.
  • Result: approved.
  • Follow-up: wait for explicit human merge approval.

Summary

The PR implements a global browser security header baseline in template/site/src/security/headers.ts, wires per-request trusted nonces into existing template/script/style entry points, keeps Swagger/API docs working with a narrow docs-only exception, and documents HSTS/proxy customization for generated sites. The changes align with the task scope and avoid CI workflow/runtime provisioning changes.

Acceptance criteria

  1. met — Representative HTML, API, media, feed, and federation responses include the intended headers. The middleware is registered globally before setup gates/routes, unit coverage checks HTML/API/static/media/RSS/errors/redirects, and the ActivityPub route integration adds actor response header assertions.
  2. met — CSP blocks unapproved script, frame, and resource origins. The policy defaults to default-src 'none', requires nonces for scripts, blocks frame/object/base/worker/style/script attributes, and the local browser evidence covers blocked inline handlers/scripts/styles, foreign script/style/connect, and frame embedding.
  3. met — Existing color-mode scripts, live search, passkeys, profile image previews, and API docs continue to work. Trusted nonce helpers are applied to the affected templates, Swagger receives nonced bundle/initializer rendering, and browser evidence covers color mode, live search, blob previews, virtual-authenticator passkeys, and real Swagger OpenAPI/Try it out behavior.
  4. met — Local HTTP development does not emit an unsafe HSTS policy. HSTS defaults to off, requires production plus explicit HTTPS public origin and positive max-age, and tests assert no inference from request or forwarded headers.
  5. met — Production HTTPS responses emit the configured transport policy. The HSTS configuration path validates production HTTPS DNS origins and emits the configured max-age, with invalid max-age/origin cases covered.
  6. met — Automated tests assert header values and representative browser behavior. Added deterministic security unit coverage plus local-only ActivityPub/browser assertions; provided evidence shows pre-PR checks, npm run test:ci, and local visual tests passed without adding integration/browser/container tests to CI.
  7. met — Generated-site documentation explains secure customization of the policy. The generated/root READMEs, .env.example, packaged-file regression, and src/security/README.md document CSP allowances, nonce wiring, media exceptions, HSTS/proxy assumptions, and upgrade guidance.

Blocking issues

  • None.

Warnings

  • None.

Final verdict

Approved. Merge remains gated on explicit human approval.

### Review update - PR: #296 (`bca4012ee0`) is open against `main`; reviewed the Forgejo diff, task `task-03ba3b00`, and the provided verification artifacts. - Result: approved. - Follow-up: wait for explicit human merge approval. ### Summary The PR implements a global browser security header baseline in `template/site/src/security/headers.ts`, wires per-request trusted nonces into existing template/script/style entry points, keeps Swagger/API docs working with a narrow docs-only exception, and documents HSTS/proxy customization for generated sites. The changes align with the task scope and avoid CI workflow/runtime provisioning changes. ### Acceptance criteria 1. **met** — Representative HTML, API, media, feed, and federation responses include the intended headers. The middleware is registered globally before setup gates/routes, unit coverage checks HTML/API/static/media/RSS/errors/redirects, and the ActivityPub route integration adds actor response header assertions. 2. **met** — CSP blocks unapproved script, frame, and resource origins. The policy defaults to `default-src 'none'`, requires nonces for scripts, blocks frame/object/base/worker/style/script attributes, and the local browser evidence covers blocked inline handlers/scripts/styles, foreign script/style/connect, and frame embedding. 3. **met** — Existing color-mode scripts, live search, passkeys, profile image previews, and API docs continue to work. Trusted nonce helpers are applied to the affected templates, Swagger receives nonced bundle/initializer rendering, and browser evidence covers color mode, live search, blob previews, virtual-authenticator passkeys, and real Swagger OpenAPI/Try it out behavior. 4. **met** — Local HTTP development does not emit an unsafe HSTS policy. HSTS defaults to off, requires production plus explicit HTTPS public origin and positive max-age, and tests assert no inference from request or forwarded headers. 5. **met** — Production HTTPS responses emit the configured transport policy. The HSTS configuration path validates production HTTPS DNS origins and emits the configured `max-age`, with invalid max-age/origin cases covered. 6. **met** — Automated tests assert header values and representative browser behavior. Added deterministic security unit coverage plus local-only ActivityPub/browser assertions; provided evidence shows pre-PR checks, `npm run test:ci`, and local visual tests passed without adding integration/browser/container tests to CI. 7. **met** — Generated-site documentation explains secure customization of the policy. The generated/root READMEs, `.env.example`, packaged-file regression, and `src/security/README.md` document CSP allowances, nonce wiring, media exceptions, HSTS/proxy assumptions, and upgrade guidance. ### Blocking issues - None. ### Warnings - None. ### Final verdict Approved. Merge remains gated on explicit human approval.
erik merged commit fdfd33ea48 into main 2026-10-05 05:26:06 -05:00
erik deleted branch feat/task-03ba3b00-browser-security-headers 2026-10-05 05:26:06 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
erik/slugkit!296
No description provided.