Research

GOV.UK ran CSP in report-only for years.

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.

2023
Switched to enforce
~15
Apps, one at a time
4
Public artefacts to read
The Evidence

The header today

This is what www.gov.uk sends, with the analytics host lists trimmed:

www.gov.uk — HTTP response header
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:

Verify it yourself
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.

The Record

The decision, in public

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.

The Method

Years in report-only

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 Shape

The policy lives in a gem

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.

The Pattern

What the reports cost them

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
Twitter2013Extensions generate “a lot of noise”
GitHub2013Extension and bookmarklet violations made analysis nearly impossible
Dropbox2015“Sheer noise in the reports”; built a three-stage filter
GOV.UK2023Filter 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 →

Being Straight

What this page doesn't claim

Four things, before anyone else points them out.

This is not “the government uses CSP”

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 policy

Alongside 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.

GOV.UK is not a Report URI customer

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.

A policy is not a security programme

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 Point

What this means for you

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.

FAQ

Frequently asked questions

Their developer manual puts the switch to enforce mode in Summer 2023, "following a number of years running in report-only mode". That is the clearest published citation there is for report-only being the professional default rather than a hedge - you cannot safely block what you have not first measured.

They wrote the trade down in the decision record: they gave up "consistency across GOV.UK, and completeness" in exchange for "safety and incremental rollout". Roughly fifteen applications each took the change on their own schedule, and a policy set in the application also behaves the same locally as it does in production, which a CDN-only deployment does not.

No. Their manual records that it is "likely we will remove the reporting functionality once a CSP is enforced", citing false positives - but the endpoint is still in the live header today, pointing at a reporting service they run themselves. They considered dropping it and kept it.

No, and the manual is explicit about it. The policy applies to frontend applications serving www.gov.uk, with Whitehall named as an exception, rather than to the whole of government.

Yes. The base policy lives in the govuk_app_config gem, which is public and actively maintained, and individual applications extend it. You can read the policy source and every change ever made to it, which is true of almost no other organisation.

No, and nothing here should be read as government endorsement of anything. They run their own reporting service. The page exists because their rollout is the best-documented one available: the decision, the manual, the policy source and the live header are all public.

Report-only first. Exactly like they did.

Set one header and let real browsers tell you what your pages actually load, before anything gets blocked.