Mitigate XSS Attacks with Content Security Policy


What is Content Security Policy?

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement to distribution of malware.
Content Security Policy (CSP) - HTTP | MDN

XSS is still one of the most common web vulnerability – if someone else can run scripts on your site, it is not your site anymore.

Discourse mitigates XSS attacks with CSP, by allowing scripts only from trusted sources to load and execute. Discourse’s default policy employs a strict URL whitelist, and only allows sources that Discourse needs.

Currently, Discourse ships a CSP Level 2 policy with the following directives by default:

  • script-src specifies valid sources for JavaScripts
  • worker-src specifies valid sources for ServiceWorker scripts
  • object-src blocks the execution of plug-ins (Flash, Java, etc)
  • base-uri restricts the URLs for <base> element

The default policy will be expanded in future updates to include more directives.

Available Settings

  • content_security_policy: Turn on CSP (default off for existing sites, on for new instances)

  • content_security_policy_report_only: Turn on CSP Report-Only mode (default off)

    CSP Report Only mode checks and reports the site’s violations, without blocking them. This can be used to experiment with different policies, and monitor their effects.

  • content_security_policy_collect_reports: Log CSP violations reports in /logs (default off)


  • content_security_policy_script_src: Extend the default script-src by defining additional whitelisted script sources

How to Turn on CSP?

Flip the content_security_policy site setting, and viola, CSP is on!

However, since CSP will start blocking any non-whitelisted script sources immediately, I recommend the following steps to make sure that CSP is not blocking your site’s customizations:

  1. Turn on CSP Report-Only mode, and check for CSP violations.
  2. Extend the default CSP as needed
  3. Turn on Full CSP.

Are My Customizations CSP-Compliant?

1. Inline JavaScripts in Themes / Components

It is very common to have inline JavaScripts in themes:

<script type="text/discourse-plugin" version="0.8">
  api.replaceIcon('heart', 'thumbs-up');

Even though Discourse’s default CSP does not permit them, inline JavaScripts in themes / components are automatically extracted into an external file, so there are no changes required!

2. Inline on Event Handlers

<a href="#" onclick="doSomething()">Foo</a>

Unfortunately, inline on event handlers (e.g onclick, onload, etc) are also considered as inline scripts. It is recommended to add event listeners via JavaScript.

Here is an example of refactoring the above code:

<a href="#" id="bar">Foo</a>

  // if the element is already in the DOM
  document.getElementById('bar').addEventListener('click', doSomething);

  // otherwise, try event propagation
  $('#main').on('click', '#bar', doSomething);

3. <script> Tags Linking to External Resources

If your site’s customizations depend on third-party JavaScripts, you may either

  1. Copy and paste the script content into your theme / component, or
  2. Extend the default CSP by whitelisting the source.

4. Third-Party Script / Service Integration

Different integrations will have different requirements, but can be addressed similarly.

You could look up the integration’s recommended CSP whitelist, and extend the default CSP accordingly. I suggest simply turn on CSP Report Only mode in Discourse, and watch your console to determine which resources you’ll need to whitelist to make your integrations work.

This is especially important when using third-party script bundlers like Google Tag Manager or Segment, because these bundlers might load many third-party or inline scripts. (You might even end up adding 'unsafe-inline' when using Segment or GTM, even though this definition should be avoided as much as possible.)

Extending the Default CSP

1. Via Site Setting

Admins can extend their site’s CSP by defining additional script sources to be whitelisted in the content_security_policy_script_src site setting.

You can extend script-src with all valid source definitions, including hash-sources, and 'unsafe-inline' if you must.

2. In Plugins

If your plugin integrates or depends on third-party JavaScripts, you may extend the default CSP in plugin.rb with the extend_content_security_policy API.

# plugin.rb
  script_src: ['', ''],
  object_src: ['']

3. In Themes and Components

Define a list-type theme setting named extend_content_security_policy:

# settings.yml
  type: list
  default: "script_src:|script_src:"

And this is what it will look like in the UI:

Additional Resources

Discourse 2.2.0.beta6 Release Notes
Weird loading on Latest Topics
Report Only CSP Violations
Adsense Not Working after Recent Discourse Update
Discourse 2.2.0.beta9 Release Notes
Why Cookie Consent Doesn't Show Up?
"Refused to load the script" when adding adsense
Weird loading on Latest Topics
How to restart Discourse after server reboot?
(Jeff Atwood) #2

I added a note about this to our public file :tada:

(Penar Musaraj) #3

As of this commit, we’ve turned off CSP violations reports by default because the vast majority of the reported violations are false positives.

To illustrate this, here is a screenshot of logs from a site running Discourse with CSP enabled and reporting enabled (filtered using “CSP Violation”):

All of the reported violations are not related to the site’s code:

  • violations with ‘’ or ‘’ in the URL have nothing to do with Discourse, they’re likely coming from a browser extension
  • the Google Analytics violation reports are also not legitimate. They are triggered by Firefox in privacy mode, or Firefox with a privacy extension enabled (like DuckDuckGo Privacy Essentials).
  • Violations with ‘inline’, ‘data’ or ‘about’ are triggered by extensions as well. It’s not shown in the screenshot above, but these violations have some more details in the env tab of the log. In there, under script-sample, some of these violations had code like BlockAdBlock or window.klTabId_kis or AG_onLoad, which come from the AdBlock, Kaspersky, and AdGuard extensions, respectively. (I found this repo: CSP-useful/ at master · nico3333fr/CSP-useful · GitHub very useful in helping explain some of these reports.) Some of these violations will have safari-extension or user-script in the source-file variable (again, in env), so that points to Safari extensions as the culprit for the violation.

In other words, there’s a lot of noise in CSP violation reports, so it’s not useful to log them at all times. They might be helpful while you are configuring CSP, but the reporting should be off during the normal operation of a site.

A few final notes: if you site is using a tag manager (like Google Tag Manager or Segment) you need to load the site in your browser, and carefully examine the violations in the console. These tools load third-party scripts from third-party domains and/or inline scripts so you need to carefully whitelist each of them using the source URL or the hash of the inline script (Chrome usefully includes the hash of inline scripts in the console error statement).

If your site uses an advertising service (like Google Ad Manager, Adsense, etc.) you probably will have to use a very permissive policy:

In the screenshot above, the policy allows any script from a https: source and any inline script. (In the future, this might be replaced by the strict-dynamic keyword, but as of this writing, strict-dynamic isn’t supported by Safari or Edge.)

(Bhanu Sharma) #4

Do I have to configure something particular to enable cdn?

I’m into a situation where my forum will give WSOD because all the scripts from CDN are blocked (even after whitelisting the cdn domain)

(Penar Musaraj) #5

CDN should be configured automatically. Do you have any console error messages you can share?

(Bhanu Sharma) #6

I tried it again today and everything is blocked.

(Bhanu Sharma) #7

Site is in WSOD right now,
Is there a way to add cdn to CSP through console?

(Penar Musaraj) #8

You can disable CSP temporarily via the console by setting:

SiteSetting.content_security_policy = false

and then enabling CSP report only (which will only output issues in the console) and troubleshooting any issues you might have.

(Bhanu Sharma) #9

I actually did this


site seems to have come online since then but I’m still getting many warnings:

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Content Security Policy: Couldn’t parse invalid host //

Loading failed for the &lt;script&gt; with source “”. [](

Loading failed for the &lt;script&gt; with source “”. [](

Content Security Policy: The page’s settings blocked the loading of a resource at (“script-src”).

Content Security Policy: The page’s settings blocked the loading of a resource at (“script-src”).

I’ve created the following but still the warnings are there.

(Penar Musaraj) #10

You need to add https:// before the domain. And once you have added this domain on one line, you don’t need to add it again for each folder.

Also, something smells misconfigured about your CDN setup… normally, you don’t need to add the CDN path manually here.

(Bhanu Sharma) #11

I’m using steps as per this guide

Only additional setting that I’ve done is to enable SSL on the zone hostname through keycdn

(Bhanu Sharma) #12

so one potential issue I’ve found was that I was using // changing it to has made things better, there are no errors now

(Jeff Atwood) #13

Why aren’t we validating these if they must begin with a protocol?

(Bhanu Sharma) #15

If there had been a slight hint in app.yml I’d have got it right the first time :wink: I’d submit a PR with a copyedit in an hour so it makes clear.

here are the PRs:

(Sam Saffron) #16

Yeah protocol-less examples for our CDNs are not great any more, we should always show HTTPS in the samples, I think @supermathie may or may have not added this in the sample files many years ago before HTTPS was an internet requirement. I think it probably worth just going through the samples and doing a little cleanup here.

@supermathie can you take cleaning this up.

(Michael Brown) #18

Merged: Update CDN URL in samples to have a protocol · discourse/discourse_docker@303eb03 · GitHub