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