The process of developing web applications demands a thorough understanding of the fundamentals of web security. Every year, security threats get more complex, and so to mitigate this, software companies are consolidating advanced and robust security solutions. Content Security Policy, or CSP, is one such measure.
This article aims to make the concept of content security more accessible by briefly defining what CSP is, demonstrating how to enable CSP in Rails, examining common errors you might encounter, and helping you address them.
If you happen to have no experience developing in Ruby on Rails or you're just testing the waters, we highly recommend you take some time to explore this article and get acquainted with it. We'll address some features that might not be immediately clear unless you have some background in Rails.
Content Security Policy
Content Security Policy is a collection of policies or directions that your browser enforces on webpages when requested.
While loading a page, the browser has to request and render content and code—a lot of it. This process is standard and usually harmless as pretty much all modern websites are complex in nature and comprised of lots of lines of HTML, CSS, JavaScript, and other resources like images and files that the code references. The referenced code could be from the same origin (requested domain) or another origin; browsers don't distinguish. The browsers then have to process and execute these resources without any malicious code or access data not belonging to the website in question.
To ensure security, the server provides a CSP through the response header to guarantee that the browser executes only valid resources. This security layer helps mitigate attacks that take advantage of vulnerabilities like cross-site scripting (XSS) and injection attacks by providing an allowlist of trusted resources.
Let's see what a Content Security Policy header would look like:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self';
Notice that each section in the header is relevant to a specific kind of source.
Additionally, the default 'self' directive states that only resources from the same origin must be executed. You can specify domains that you want to retrieve resources from by putting their URLs next to 'self'. Additionally, you can allow all domains by setting '*' (but don't do this unless you absolutely have to).
How to Enable Rails Content Security Policy
Now that we're more familiar with Content Security Policy and know how it looks, let's see it in our code.
To implement CSP in Rails, you first have to check which version of Rails you're running. Rails 5.2 added CSP support, so you're already implementing CSP in your application if you're running on 5.2 or above. If you're on 5.2 or above, your policies settings lie in the application config file like below.
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https, :unsafe_inline
policy.report_uri "/csp-violation-report-endpoint"
end
If, however, you're below 5.2, you can use the SecureHeaders gem to add CSP to legacy applications. Then you can proceed to your initializer file containing the CSP directives (usually called 'csp.rb') and add the code if it doesn't already exist.
# config/initializers/csp.rb
SecureHeaders::Configuration.default do |config|
config.csp = {
default_src: %w(https: 'self'),
font_src: %w('self' data: https:),
img_src: %w('self' https: data:),
object_src: %w('none'),
script_src: %w(https:),
style_src: %w('self' https: 'unsafe-inline')
}
end
Now you can check that all response headers contain the CSP configuration mentioned above if they didn't before. The browser will enforce any changes, most likely breaking your page and displaying many alerts.
Proper Content Security Policies require a decent number of changes and testing. So, for now, let's address the immediate errors while still having a functional site, and that's where the 'Content-Security-Policy-Report-Only' alternative will be helpful.
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy_report_only = true
Using "report only," makes the browser no longer enforce the directives but continue to display the corresponding violation alerts. This behavior is helpful for development environments where the platform's security is not essential, but the developer needs to be aware of any violations to address them adequately.
Common CSP Errors
Seeing all these alerts in the browser developer console can be scary, but don't worry. Addressing them is very simple once you understand what your site is demanding. The report will be your guide to improve the policy directive and make the necessary updates.
Our first step should be to confirm that the resources the browser is reporting are, in fact, legitimate and necessary for the proper functioning of the application. For example, we can see that our application is requesting the following:
https://code.jquery.com/jquery-3.5.1.min.js
https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js
https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf
All these are valid resources that come from trusted origins.
Once you have the list of resources at hand, you can add them to the allowlist of each respective source as follows:
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, %w(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf)
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, %w(https://code.jquery.com/jquery-3.5.1.min.js https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js)
policy.style_src :self, %w(https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css)
policy.report_uri "/csp-violation-report-endpoint"
end
By doing this, most of the alerts go away.
Furthermore, if you want to enable all resources from a specific domain, you can do so by specifying the domain in the directive.
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, %w(https://cdnjs.cloudflare.com)
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, %w(https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com https://cdn.jsdelivr.net)
policy.style_src :self, %w(https://stackpath.bootstrapcdn.com https://cdnjs.cloudflare.com)
policy.report_uri "/csp-violation-report-endpoint"
end
But what about the inline code in our HTML? Well, we have a few options to approach these violations.
Move all inline code and inline styles to a file. This way, you can ensure the security of your application and keep it consistent.
Move the code to a tag and get its hash key. This hash key is generated by hashing the code inside the tag with a SHA256 algorithm. Conveniently, this hash is calculated for you by the alert itself so that you can add it directly to the directive, telling the browser that the code in this tag is allowed.
Use a 'nonce' tag attribute and add it to the corresponding tag. This solution will essentially serve the same purpose but must be updated with each request with some server-side code.
Ensuring Content Security
The process of securing our applications can be a very complicated and long journey. It's easy to end up lost in a rabbit hole of articles and forums. Nevertheless, we must address security issues and exploits as much as possible.
Rails is a manageable and friendly platform to build robust and flexible applications. It makes the work of securing against Content Security Policy exploits very straightforward. No need to tamper with complex configurations or scary settings.
If you want to learn more about securing other apps from threats, you can find articles here in our blog. So, for example, if you wish to make sure your NodeJS application is secure or you want to learn how to implement similar solutions in other technologies, you can find all you need here.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.