Every modern Rails app should have a Content Security Policy enabled.
Very compatible default
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
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|
(request.session.id || SecureRandom.base64(16)).to_s
end
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.
Livereload
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
unsafe_eval
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.
style_src
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
element.style = 'display: none;'
What will keep working is
element.style.display = '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 archive.org 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 archive.org snapshot you can use their endpoint, see the documentation Show archive.org snapshot for details. We also have a short card on how to enable CSP.