• Patrick Obasi
  • Security

Content Security Policy is one of the strongest tools against cross site scripting, and also one of the tools teams most often set up halfway. We have seen it happen many times, including in our own early projects. A team adds the header, something on the page breaks, they loosen the rule until it stops breaking, and six months later the policy is so loose that it barely protects anything. Looking back at our own rollout, the lesson was simple. This kind of policy only works if you treat it as ongoing work, not a setting you write once and forget.

The right starting point is to lock down where scripts, styles, and frames can load from, only allowing your own site by default, and then open small, specific exceptions only when you actually find you need them. We did not do this the first time. We started loose and tried to tighten it later, and tightening a policy that is already working in production feels risky. Nobody wants to be the person who breaks the site. Starting strict and loosening it on purpose, with each exception reviewed, is the only version of this that survives an actual deadline. We learned that the second time, the hard way.

The biggest practical block to a strict policy was years of inline click handlers and inline script blocks scattered through old pages, because a strict policy blocks all of these unless you allow unsafe inline code, which defeats the whole point. There is no shortcut here. The honest path is a slow change over several stages. Move inline handlers to a small set of shared listeners with an approved list of functions they are allowed to call. Move inline scripts into their own files, or mark them with a one time code. Only after that work is mostly done do you remove the unsafe inline allowance. We budgeted this as a project with several stages, not a one line change to a header, and that turned out to be the right call.

A policy built around a one time code, together with the strict dynamic setting, is much stronger than allowing unsafe inline code, but it adds actual complexity the moment your app opens pop up windows or uses frames. Each one needs its own valid code, created and passed through consistently, or that part of the page quietly breaks. We test every pop up, every modal window, and every frame in the app on its own, not just the main page, before we tighten anything in production.

Before we turn on a new or tighter policy, we run it first in report only mode and collect violation reports for at least a full business cycle. This is how we found the outside widget that loads a script from an address we did not expect, and the analytics tool that adds its own inline styles, before our users found these problems for us through a broken page.

A strong policy cuts down the damage from an attack a great deal, but it does not replace careful output encoding, input checks, or cleaning up any HTML you build from data a user supplied. We now treat this kind of policy as the net underneath those other defenses, not as a replacement for them. That is the one lesson from this whole process we would tell any team starting out today.


Maybeach Tech helps teams roll out an actual, enforced security policy without breaking their site. Get in touch and we will look at your current policy.

Related Post

Designing RBAC for Multi-Location Organizations

A permission model that works fine for a single location business almost always breaks the first tim...

Approval Workflows as Code: Designing Configurable Business Process Engines

Almost every business application we build eventually needs approval chains. A purchase order above ...