Content-Security-Policy now uses 'strict-dynamic'

From v3.3.0.beta1, Discourse implements a ‘strict-dynamic’ Content Security Policy (CSP). This will eliminate the need for manual CSP config, and will greatly improve compatibility with external tooling like tag-managers and advertising.

As a site administrator, you don’t need to do anything. The change will take effect automatically, and any external scripts you’re using will keep working.

No changes are required for themes. A small number of plugins [1] may need a small tweak for compatibility with this change (e.g. 1, 2).

Forums which previously disabled CSP for compatibility with external scripts should now be able to re-enable it without any issues or configuration effort.

For the technical details, check out this topic:

For now, it is still possible to switch back to the old system by disabling the ‘content security policy strict dynamic’ site setting. If you have any reason to do this, please let us know!


From v3.3.0.beta3, we’ve made the ‘strict-dynamic’ keyword a compulsory part of our CSP. The ‘content security policy strict dynamic’ site setting has been removed and the ‘content security policy script src’ site setting has been updated to only store valid values.

For admins, you should be able to find the previous value of the ‘content security policy script src’ site setting from your site’s Staff Action Logs (https://<site_url>/admin/logs/staff_action_logs?filters=%7B%22action_name%22%3A%22change_site_setting%22%2C%22action_id%22%3A3%2C%22subject%22%3A%22content_security_policy_script_src%22%7D - Replace <site_url> with your site’s base URL).


  1. technically: those which introduce <script> elements via register_html_builder or an erb template ↩︎

25 Likes

How can I configure 'unsafe-eval' after this change like before?

1 Like

No change there - you can still add ‘unsafe eval’ (with the quotes) to the content security script src site setting.

3 Likes

I saw that the description of the content security script src setting states that enabling content_security_policy_strict_dynamic will ignore this setting, so I came here to consult.

3 Likes

The description says

Host sources will be ignored when content_security_policy_strict_dynamic is enabled.

The important bit there is “Host sources”. ‘unsafe-inline’ is not a host source, so it is still supported.

That said, I totally agree this is confusing. Given the success of the strict-dynamic rollout, we’re planning to remove the old system. Once we do that, we can automatically remove all host-sources from the list, and things will be much simpler for admins. :rocket:

5 Likes

OK, thanks for the clarification.

4 Likes

David,

Just some clarification.

This new system presumably works well with:

  • inline scripts
  • full script sources that are hosted locally

But what about the case where remote scripts were invoked with “loadScript” and the full remote URL?

Correct me if I’m wrong but I don’t think there’s a nice way to handle that case?

So does that mean for those cases we need to instead rely on:

  • moving that invocation to an inline script OR
  • download the full source as a Theme asset?

For the former inline script case, I guess there’s no way of guaranteeing the script is loaded before using elements of it? (Like you could do within a .then after a loadScript)

2 Likes

strict-dynamic should work with remote scripts loaded via loadScript. In fact, that was the main reason for the switch: we no longer need to list every single external script URL up-front. That’s especially good for people running ads, which often pull in a ton of remote scripts.

Are you seeing errors with loadScript?

3 Likes

I was seeing some errors, but it might not be for this reason. Let me see if I can find an example.

I was mainly preparing myself for a stable upgrade that I know involves loadScript and remote scripts.

Can you indulge me and explain how remote scripts that are randomly invoked within the code with loadScript end up being “authorized” by the Dynamic mode CSP? Is there some magic going on?

3 Likes

Yup!

MDN has a nice explanation and examples.

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

So, as long as the original script is trusted (via a nonce), the browser allows it to load any other scripts without restriction. And then, since those scripts are trusted, they can load more!


There is one caveat, which is that scripts cannot be ‘parser inserted’. This prevents strict-dynamic being exploited for XSS attacks.

So for example, this would be ‘parser inserted’ and would be blocked:

document.head.appendChild("<script src='https://example.com/xss-attempt.js' />");

But, constructing the script element programmatically does not involve HTML parsing, is much less likely to be an XSS vector, and so it’s allowed:

const script = document.createElement("script");
script.src = "https://example.com/script.js"
document.head.appendChild(script);

^^ this is essentially how loadScript() works

3 Likes

Great link.

OK nearly got it, thank you!

So the nonce is included in the script tags rendered by loadScript too?

2 Likes

No, the nonce is only included on the original script tag rendered by Rails (e.g. core, plugin, or theme scripts).

That means the core/plugin/theme code is trusted, and is allowed to insert any other scripts. No nonce required here - the browser knows which script performed the insertion, and magically knows it can be trusted.

4 Likes

David, once again, thanks for your time!

4 Likes

6 posts were split to a new topic: CSP error when adding a script via a theme component