Read more

A reasonable default CSP for Rails projects

Tobias Kraze
November 15, 2023Software engineer at makandra GmbH

Every modern Rails app should have a Content Security Policy enabled.

Very compatible default

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show snapshot

The following "default" is a minimal policy that should

  • "just work" for almost all applications
  • give you most of the benefits of a CSP

In your config/initializers/content_security_policy.rb, set

Rails.application.config.content_security_policy do |policy|
  policy.object_src :none
  policy.script_src :unsafe_eval, :strict_dynamic, :https # Browsers with support for "'strict-dynamic'" will ignore "https:"
  policy.base_uri :self

  # policy.default_src :self
  # policy.style_src :self
  # policy.font_src :self, :data
  # policy.img_src :self, :data
  # policy.connect_src :self, *(["ws://localhost:#{Rack::LiveReload::BodyProcessor::LIVERELOAD_PORT}/livereload"] if defined?(Rack::LiveReload))

  # Specify URI for violation reports
  # if Rails.env.staging? || Rails.env.production?
  #   policy.report_uri "/csp-violation-report-endpoint"
  # end

# Use nonces for script tags.
Rails.application.config.content_security_policy_nonce_directives = %w[script-src]

# Avoid changing nonces for the same session to not break e-tags.
Rails.application.config.content_security_policy_nonce_generator = lambda do |request|
  ( || SecureRandom.base64(16)).to_s

Then, find all occurences of javascript_include_tag, javascript_tag, javascript_pack_tag etc. and add nonce: true.

Also, make sure you don't have any other inline scripts in your application that do not use javascript_tag.

Why this works

The main point of this CSP is to disallow user content to insert any kind of script tags. All script tags (whether inline or with src) will now need an unguessable nonce attribute.

If any of your JavaScript inserts a script tag programmatically (for example a third-party chat widget or similar), the strict-dynamic will permit this.


Any setup using rack-livereload will probably break, since the livereload JS is loaded without nonces. You can replace it with livereload-js, see our Livereload + esbuild guide as an example.

Possible improvements


Try removing the :unsafe_eval from your script source. Whether this is a problem depends on whether any JS library uses it. It is okay to remain there if required.


Try adding in the policy.style_src :self.

There is a chance this will break because some JS library uses inline styles incorrectly.

As a reminder, this will disallow

  • inline styles you set in your html
  • inline styles set by JS libraries using = 'display: none;'

What will keep working is = 'none'

default_src / connect_src

Try adding in the other commented lines. default_src (or if set connect_src) would disallow XHR / fetch requests to other domains.

Note that in development this could break some "Live Reload" solutions; you can see the required config for Rack::LiveReload above.

CSP reporting

If you are still worried that your CSP will break something, you can set an URI to report CSP violations. In this case, every time a browser refuses to do something because of a CSP, it will contact the URI which has to implement a specific API Show snapshot .

You can even configure Rails to not enforce a CSP, but only send reports for violations:

config.content_security_policy_report_only = true

If you use Sentry Show snapshot you can use their endpoint, see the documentation Show snapshot for details.

Tobias Kraze
November 15, 2023Software engineer at makandra GmbH
Posted by Tobias Kraze to makandra dev (2023-11-15 10:47)