Custom Software · Security · Production

Report-only is the rollout, not a courtesy: shipping a strict CSP to a live platform

Published · Updated

Security Misconfiguration moved from #5 to #2 in OWASP's 2025 Top 10. A strict CSP is enforced in the browser, after your response has left, which is why the rollout order matters more than the policy text.

OWASP's 2025 Top 10 moves Security Misconfiguration from #5 in 2021 to #2 in 2025. Content Security Policy sits squarely in that category, and it behaves unlike most hardening work: the server only states the policy; the browser enforces it, long after the response has left. Get a directive wrong and nothing errors on your side. The page returns 200 and scripts simply stop running.

A strict CSP is a template change, not a header change

MDN is specific about what 'strict' means: to control script loading as a mitigation against XSS, recommended practice is to use nonce- or hash-based fetch directives. That is not an allowlist of domains. A nonce means the code that renders a script tag and the code that sets the response header must agree on the same value for the same response. On a server-rendered platform one process owns both, which is the cheapest place to make that guarantee hold.

Report-only mode is the plan

CSP can be deployed in report-only mode: the policy is not enforced, but violations are sent to the reporting endpoint specified in the policy. Treat that as the rollout stage rather than a nicety. Real traffic enumerates what your pages actually load, including consent managers, analytics tags and inline handlers that differ by market and locale. A staging environment with no third-party tags will report a clean policy that breaks on contact with production.

Violation reports need an owner, not a bucket

OWASP puts it plainly: great logging with no alerting is of minimal value in identifying security incidents. A reporting endpoint that accumulates JSON nobody reads turns report-only into a delay rather than a measurement. Route the reports into the same alerting path as application errors, and alert on new combinations of directive and blocked resource rather than on raw volume, so that one noisy browser extension does not drown the signal you deployed for.

Every response, on every domain

MDN states that the header should be set on all responses to all requests, not just the main document. On multi-domain deployments that becomes an architectural requirement rather than a configuration detail. Our Tatano Energy platform serves four country domains indexed separately from one codebase, across seven languages. A policy defined per host drifts; a policy emitted by shared middleware is inherited by every domain the codebase answers for, including the ones added later.

Decide what happens when the policy cannot be generated

A10:2025, Mishandling of Exceptional Conditions, is a new category in the 2025 Top 10. A nonce-based policy has an exceptional path: the value could not be generated, or the template rendered without it. There are two honest answers — fail the response or serve it without the policy — and only one of them is a security decision you can defend. Pick it deliberately and write it down next to the middleware.

CSP is not doing the two jobs people hope it is

MDN is direct: setting a CSP is not an alternative to sanitising input. It is also not access control. Broken Access Control maintains its position at #1 as the most serious application security risk, and the contributed data indicates that, on average, 3.73% of applications tested had one or more of the 40 Common Weakness Enumerations in that category. A header that governs script loading says nothing about who may read a record.

Measure what the policy costs at the first byte

TTFB measures the time between starting navigation and when the first byte of a response begins to arrive. Good values are 0.8 seconds or less, and poor values are greater than 1.8 seconds. It is not a Core Web Vitals metric, so meeting that threshold is not absolutely necessary provided it does not impede the metrics that matter, but per-response policy generation happens on the server. Compare before and after rather than assuming it is free.

Launch is the cheap moment

Our Abbys Consult platform launched with hardened security headers and clean search signals from day one, with time to first byte under 200 ms and zero technical SEO issues at launch. Headers set before any third-party script exists cost nothing to negotiate. The same policy introduced after two years of accumulated marketing tags means auditing every one of them, which is the same work plus an inventory nobody kept.

For an existing SME platform, the sequence that makes a strict CSP survivable is narrow: emit the header from shared middleware on all responses, start in report-only, wire the reporting endpoint to alerting rather than storage, keep it there until new violation types stop arriving, define the failure path for nonce generation, then enforce. For anything being built now, set the policy before the first third-party script lands and skip the retrofit entirely.

Sources

OWASP — Top 10:2025 Introduction — https://top10.owasp.org/2025/0x00_2025-Introduction/

MDN Web Docs — Content Security Policy (CSP) — https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP

web.dev (Google Chrome) — Time to First Byte (TTFB) — https://web.dev/articles/ttfb

Neurolinks case study — Four markets, one codebase — https://neurolinks.be/work/tatano-energy

Neurolinks case study — A consulting firm, dressed for trust — https://neurolinks.be/work/abbys-consult

Working on a project where these methods apply?