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.