CSP and Security Header Review for Production Websites
CSP constrains browser resource loading after an injection flaw; it does not sanitize input. A production policy must reflect real assets, inline code, third-party widgets, and report handling.
Deploy in two passes
Start report-only, group violations by directive, remove unnecessary inline behavior, then enforce. Example: nonce every server-rendered script rather than add unsafe-inline. Example: allow a payment frame only through frame-src, not broad https:.
Header deployment checklist
| Header | Purpose | Verification |
|---|---|---|
| CSP | script and frame restriction | injected script blocked |
| HSTS | HTTPS preference | redirect and max-age |
| X-Content-Type-Options | MIME sniffing control | mislabeled asset refused |
| frame-ancestors | clickjacking control | hostile frame denied |
OWASP CSP guidance and MDN CSP explain directives.
Decision rule: enforcement policy must be tested against critical user journeys before rollout.
Repair
Replace inline handlers, generate nonces per response, narrow sources, and retain reports without sensitive payloads. CSP is defense in depth, not a replacement for output encoding. Web testing can validate browser controls.
Move from observation to enforcement
Deploy report-only policy first and classify violations by route, directive, browser, and user journey. Remove unused inline behavior instead of adding unsafe allowances. Dynamic responses can use unpredictable per-response nonces; static scripts can use maintained hashes. A narrow baseline often includes default-src ‘self’, object-src ‘none’, and deliberate base-uri and frame-ancestors decisions, but each directive must match application needs.
Verify headers on normal pages, errors, and authenticated routes. Test an injected external script, inline event handler, hostile frame, mislabeled asset, and critical journey. Keep reports free of sensitive request data. Owner: web platform owner. Pass: expected assets work and disallowed behavior is blocked in target browsers. Fail: broad exception masks violation or critical flow breaks. Exceptions include directive, affected route, reason, compensating control, owner, approval, and expiry.
Configuration capture
Record exact deployed header, not an abbreviated ticket summary. For dynamic pages, show nonce differs between two responses and appears only on intended elements. For static pages, retain script hash and change process. Test headers through CDN, origin, cache, error, and authenticated paths so an unprotected alternate response is not missed.
Browser policy evidence
Expected output: route, full CSP header, report-only or enforce mode, nonce or hash method, browser version, blocked directive, journey result. This browser capture separates a policy block from a broken asset deployment.
Worked failure: A templated page reflects an inline handler. Enforcement must block that handler while the approved nonce-bearing script still loads.
Edge case: A third-party script change can invalidate a static hash. Treat the broken journey as a review signal, not a reason to add unsafe-inline.
Closure test: Load an error page and an authenticated route after CDN purge. Closure passes when both return intended policy and framing is denied.
Policy rollout ledger
Start with a route inventory, not a copied header. Record scripts, styles, frames, workers, fonts, images, forms, and third-party domains used by critical journeys. A report-only violation should identify the directive and route, then be classified as required asset, obsolete code, injection symptom, or policy bug. Do not approve a host wildcard because one unknown report appeared. Every added source needs named feature owner and removal review date.
Nonce generation needs entropy from server-side cryptography and a fresh value for each response. Never reuse a cached nonce across visitors. Hash policies need a build step that changes when script bytes change, including minifier output. Test a page with intentionally injected script and check browser console or report endpoint for blocked execution. For frame protection, test both same-origin permitted embedding if needed and hostile cross-origin embedding.
For each policy exception, capture feature, source expression, owner, and removal condition. Re-test policy after template or bundler changes. Expected result is no new inline execution and no report-only violation from approved journey.
CSP reports are untrusted browser input. Store only fields needed to group directive, document URI category, blocked URI category, and user agent family; avoid accepting report bodies as trusted diagnostics. Rate-limit report collection and authenticate internal report endpoints where deployment supports it. Review report volume after enforcement because a sudden drop can mean collector failure rather than policy success. A policy owner should periodically remove sources no longer used by current bundles.
Final policy check compares enforced headers from deployed responses with approved bundle behavior. Expected result: approved scripts and styles load without widening execution sources. Edge case: cache serves an older HTML document with newer assets; test both versions during rollout. Close only after report collection and direct browser checks show policy matches current application paths.