CSP Academy

← The ten steps · Step 5 of 10 · 6 minutes

Why allowlists fail

This is the part where CSP's critics are right, and it is worth understanding properly before the next step shows you the way out.

The instinct

Report-only mode hands you a list of origins your site loads. The obvious move is to put them in the policy:

script-src 'self' https://cdn.example.com https://www.googletagmanager.com https://widget.example.net

It works. The violations stop. It also does much less than it looks like it does, because a host allowlist is a promise about origins, not about content. It says "scripts may come from this host". It cannot say "and only the scripts I meant".

Four ways that goes wrong

1. A JSONP endpoint on an allowlisted host

JSONP works by reflecting your callback name into an executable response. If any host in your script-src has one, an attacker points a script tag at it with a callback of their choosing and the response is served — from an origin your policy trusts.

<script src="https://allowed.example/api?callback=alert(1)//"></script>

Large CDNs and platform APIs have historically been full of these. Your policy is only as strong as the least careful endpoint on the most permissive host in it.

2. A framework with a script gadget

Some libraries turn markup into code by design. AngularJS 1.x compiles {{ expressions }} found in the DOM. Vue's full build compiles the live innerHTML as a template. Knockout compiles data-bind attributes. If one of those is served from an allowlisted host, an attacker who can inject markup — not script, just markup — gets code execution without ever violating your policy.

3. A host that serves user content

Uploads, avatars, attachments. If the host that serves them is in script-src, an attacker uploads a file whose contents are JavaScript and loads it as a script. CSP checks the origin of a script, not its content type — a cross-origin script response is executed regardless of what Content-Type claims.

4. Wildcards and schemes

*.example.com covers every subdomain, including the forgotten marketing one running an old CMS. https: covers the entire web. These end up in policies because they make the violations stop, and they mean the directive has stopped doing anything.

The honest summary

Large-scale research on real policies found that the overwhelming majority of allowlist-based CSPs were bypassable, usually because of exactly these four things. That finding is not an argument against CSP; it is the argument that produced 'strict-dynamic', which is the subject of the next step.

What this does not mean

It does not mean allowlists are useless. connect-src, img-src, frame-src and style-src are all still worth constraining by host — the gadget problem is specific to script execution. It means that for script-src specifically, listing hosts is the wrong tool, and there is a better one.

You can do this now

Take the host list from your report-only run and ask one question about each: would I be comfortable if that host could execute arbitrary code on my site? Because that is what listing it in script-src grants. It is a clarifying question, and the answer is usually no.