TLDR; Today's lesson in Content Security Policy: using a nonce or hash to allowlist a script does not allow that script to use 'javascript: urls', inline event handlers, or eval.
Today I had the opportunity to review how the Content Security Policy was working for a web application that I work on. I thought it was pretty locked down after implementing a nonce based strategy a few months ago. My assumption with using nonces was that the allowed script would be given free rein to do whatever it wanted in the browser. After all, it seemed like the nonce was telling the browser that the script was implicitly trusted.
Also, the description of 'strict-dynamic' in the MDN documentation on the script-src directive is a little vague, simply saying:
The strict-dynamic source expression specifies that the trust explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script. At the same time, any allow-list or source expressions such as 'self' or 'unsafe-inline' are ignored.
When it says that 'unsafe-inline' is ignored, I read that to mean that 'unsafe-inline' would be redundant when used in tandem with 'strict-dynamic' (at least for the scripts using the nonce). In reality though, 'strict-dynamic' blocks 'javascript: urls' and inline event handlers where 'unsafe-inline' does not.
I only learned this after an hour of scouring the internet for explanations as to why certain scripts loading on my site were still throwing CSP errors even though they contained the nonce attribute. I finally found this comment on bugzilla from six years ago that pointed me to the issue:
...if you use a nonce/hash it's not possible to use javascript: urls or inline event handlers. It's as if the policy didn't have unsafe-inline at all...
That's when I decided to try adding an 'unsafe-eval' to the script-src in my policy in addition to 'strict-dynamic' with a nonce to see if the 'eval' errors would go away. They did. BUT, as the bugzilla post referenced above also points out, you can't do this for 'unsafe-inline'. You can either use 'strict-dynamic' OR 'unsafe-inline', but you can't get the best of both worlds.
The intricacies and complexities of building a robust Content Security Policy for the modern web (especially while dealing with legacy dependencies) never ceases to amaze me!
Top comments (0)