Then they published the decision, the manual and the policy.
Every other page in this research quotes a blog post and a header. This one can point you at the decision record, the developer manual, the source of the policy itself and the header it produces — because GOV.UK builds in the open.
This is what www.gov.uk sends, with the analytics host lists trimmed:
content-security-policy: default-src 'self'; base-uri 'none'; object-src 'none'; script-src 'self' www.google-analytics.com … www.youtube.com 'nonce-kaWyTALQYs9ESXh1Y1N+xA=='; style-src 'self' www.gstatic.com; frame-ancestors 'self' *.publishing.service.gov.uk www.gov.uk; report-uri https://csp-reporter.publishing.service.gov.uk/report
Four things are worth pulling out. default-src 'self' is the floor, so anything not covered by a more specific directive has to come from GOV.UK itself. base-uri 'none' and object-src 'none' deny outright — the first stops an injected <base> tag quietly re-pointing every relative URL on the page, which is a trick most policies never close. There is a nonce on script-src. And there is a reporting endpoint, running on their own infrastructure.
Check it yourself. One command, no account, no special user agent:
curl -sI https://www.gov.uk/ | grep -i content-security-policy
The nonce will differ from the one above, because it is regenerated for every response.
GOV.UK records its technical decisions in a public repository of RFCs, described in their own words as “a forum to discuss and make technical decisions”. RFC 98 is the one that introduced CSP. It is marked accepted, the implementation is marked done, and you can read the whole thing.
Two motivations are given. The first is an extra defence against cross-site scripting, following an earlier incident in one of their own applications — named, in public, in the document that proposes the fix. The second is subtler: GOV.UK publishers write Govspeak markup which is converted to HTML and stored, so a tampered content store would be serving markup nobody wrote. Their reasoning is that CSP “mitigates against that by limiting the type of things the browser will run”.
The genuinely instructive part is the architectural choice. They configured CSP per application rather than once at the CDN, knowing that it meant a version bump across roughly fifteen applications, and they wrote down what they were buying:
They gave up “consistency across GOV.UK, and completeness” in exchange for “safety and incremental rollout”.
There is a second, quieter advantage they noted: a policy set in the application “works locally just like in production”. Set it at the CDN and every developer works all day against a page that behaves differently from the one your users get, which is how a policy ends up being something the team edits nervously rather than tests.
From their developer manual, and it is the single most useful sentence in this research:
CSP moved to enforce mode in Summer 2023, “following a number of years running in report-only mode”.
Years. Not a fortnight of nerves before someone signed it off — a deliberate period of running the policy without blocking anything, collecting what it would have blocked, and fixing what turned up.
It is worth being clear about why that is the professional answer rather than a slow one. Nobody knows everything their own pages load. Not the team that built them, not the person who owns the templates, and certainly not from reading the code: third-party tags load further tags, a marketing script arrives that nobody logged, an old page still references something that was decommissioned. Report-only is the only way to get that inventory from the one place that actually knows — a real browser, on a real page, in front of a real user.
Deploy an enforcing policy without doing that and you are not securing the site, you are guessing at it, in production, on your users.
The base policy is not copied into fifteen applications. It lives in govuk_app_config, a public, actively maintained library holding the directives that are “either global or common to many applications”, which each application then extends for its own needs.
So the policy is a dependency with a version number, reviewed like any other change, and a reader can go and look at the source and its history. Very few organisations in the world let you do that with a live security control.
It is also the same pattern arrived at independently elsewhere in this research: the policy belongs in shared code that applications inherit, not in a console somewhere. Twitter open-sourced secure_headers to do it, Dropbox put it in their templating layer, and login.gov keeps it in a version-controlled initializer.
Their manual sets out what receiving CSP reports actually involved. Violations are logged to Amazon S3, queried with Athena, and kept for thirty days. Because the volume of false positives is so high, they filter the most prolific browser-extension reports out of the logs by hand.
That is a sensible pipeline, built with the tools available, in public, on a public budget, and documented honestly — which is more than most organisations manage. It is also a real amount of engineering that produced no features, and it is worth saying what it bought and what it cost in the same breath.
The manual then records something no vendor would ever write down for you:
“It is likely we will remove the reporting functionality once a CSP is enforced.”
Cited reason: false positives. And this is the part that matters, so it is worth being precise — they have not done it. The endpoint is still in the header at the top of this page. They considered turning the visibility off, and kept it.
But the thought is the finding. When filtering reports is hard enough, teams do not go looking for better filtering. They start weighing up whether to keep the visibility at all. That is not a criticism of GOV.UK; it is the same conclusion reached independently by every organisation in this research:
| Who | When | What they wrote down |
|---|---|---|
| 2013 | Extensions generate “a lot of noise” | |
| GitHub | 2013 | Extension and bookmarklet violations made analysis nearly impossible |
| Dropbox | 2015 | “Sheer noise in the reports”; built a three-stage filter |
| GOV.UK | 2023 | Filter the most prolific by hand; considered removing reporting |
Four organisations, a decade apart, arriving at the same place. Dropbox wrote their filter up in detail →
Four things, before anyone else points them out.
The policy covers frontend applications serving www.gov.uk, and the manual names Whitehall as an exception. It is one estate, not the whole of government, and the page is about that estate.
script-src is not a pure nonce policyAlongside the nonce it carries a list of analytics and YouTube hosts. That is a normal, working policy rather than a textbook one, and presenting it as nonce-plus-strict-dynamic would be flattering it.
They run their own reporting service, and nothing on this page should be read as government endorsement of us or of anything else. It is a public rollout, cited as one.
Their own RFC calls CSP “an extra defence”, which is exactly right and better framing than we would have chosen for them. It sits on top of the work, not instead of it.
The objection this page answers is the one about scale: fifteen applications, a legacy estate, teams that do not all move together, and a public that notices when something breaks. That is a harder starting position than most companies have, and it is a good deal harder than yours.
The playbook they used is written down, and none of it is exotic. Run report-only and leave it running. Keep the base policy in shared code so applications inherit it rather than each inventing one. Roll out per application, so a mistake is one service rather than the estate. Then enforce, once the reports have stopped telling you things you did not know.
Every step of that depends on being able to read what comes back. GOV.UK built a pipeline to do it and still wrote down that the noise nearly cost them the visibility — which is the part that is now a product rather than a project.
That is what we run: the reports arrive filtered, grouped and attributed, so what you are reading is your own site rather than somebody's browser extension. See how CSP reporting works →
The policy stays yours, in your headers, enforced by every browser that visits, whether or not you have a relationship with us. Nothing routes through us and nothing runs on your pages.
Set one header and let real browsers tell you what your pages actually load, before anything gets blocked.