Working with Multiple Content-Security-Policy Headers
Christopher Davis
- 2 minutes read - 278 words
CSP headers are easy to misconfigure, and the result is a site that silently blocks scripts, styles, or API calls you never intended to restrict. The trap: multiple Content-Security-Policy headers do not merge. The browser applies the most restrictive policy and ignores the rest.
Servers may send multiple CSP headers, but there is a catch:
Adding additional policies can only further restrict the capabilities of the protected resource.
I had wrongly assumed that I could pretty up my nginx configuration by splitting up the various -src directives into separate add_header lines.
This doesn’t work because subsequent Content-Security-Policy headers may only get more restrictive. So a separate header with a sane default-src 'self' is already pretty restrictive and will override any other script-src directives in later (or earlier) headers that are more lenient. Combining everything into a single Content-Security-Policy header works just fine, however.
In other words, multiple Content-Security-Policy headers do not combine together. The most restrictive header is favored. Always. I had assumed they would combine at the directive level, but that’s not the case.
Non-Working Example
default-src is restrictive and connect-src allows wider permissions, so only default-src is used.
add_header Content-Security-Policy "default-src 'self'";
add_header Content-Security-Policy "connect-src 'self' https://api.example.com";Working Example
This one works fine because it’s a single CSP header with all directives combined:
add_header Content-Security-Policy "default-src 'self'; connect-src 'self' https://api.example.com";Making Nginx Configuration Pretty
I know the above examples seem trivial, but I was dealing with some very long lines for a CSP and ended up using this solution to split the lines. The sha384- bit is to allow Google Tag Manager’s initial script tag to load.