Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
238: Content Security Policy in Rails
You've probably heard that Rails is "secure by default," and for the most part, it is. But escaping HTML isn't a silver bullet. If a malicious actor finds a way to inject a script—maybe through a third-party library you're using—they can steal session cookies or redirect your users. That's where Content Security Policy (CSP) comes in. It's essentially a "guest list" for your browser: if a resource isn't on the list, the browser refuses to load it.
Why do I need this if Rails already handles XSS?
Think of Rails' built-in escaping as your front door lock. It's great, but what happens if someone climbs through a window? CSP is like having a security guard inside the house who checks IDs. Even if an attacker manages to inject a <script> tag into your page, the browser will look at your CSP and say, "Wait, this script is coming from evil-hacker.com, and that's not on my approved list." Then, it simply refuses to execute the code.
Where do I actually define these rules in Rails?
Rails provides a dedicated initializer for this. If you haven't already, you can generate it, but most of the time you'll just create config/initializers/content_security_policy.rb. You define your policy using a DSL that tells Rails which HTTP headers to send.
Let's say your app uses Google Fonts and a Stripe payment widget. Your config would look something like this:
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, "https://fonts.gstatic.com"
policy.img_src :self, :https, "https://s3.amazonaws.com"
policy.script_src :self, :https, "https://js.stripe.com"
policy.style_src :self, :https, "https://fonts.googleapis.com"
# Specify an URI for violation reports
# policy.report_uri "/csp-violation-report-endpoint"
end
The :self keyword is crucial—it tells the browser that resources from your own domain are trusted. I usually start with default_src :self and then explicitly open up the specific holes I need for external APIs.
My inline scripts stopped working. How do I fix this without disabling CSP?
This is the most common point of frustration. By default, CSP blocks all inline scripts (like <script>alert('hi')</script>) because that's exactly how most XSS attacks work. You'll see a scary error in your browser console saying the script was blocked.
You could add :unsafe_inline to your script_src, but please don't. That basically defeats the purpose of having a CSP. Instead, use a nonce (a "number used once"). A nonce is a random string generated for every single request. If the script tag has the correct nonce, the browser lets it run.
First, tell Rails to use nonces in your initializer:
Rails.application.config.content_security_policy_nonce_generator = -> { SecureRandom.base64(16) }
Then, in your layout file, add the CSP meta tag helper:
<head>
<%= csp_meta_tag %>
...
</head>
Now, when you have an inline script you absolutely can't move to a separate file, you pass the nonce to it:
<script nonce="<%= content_security_policy_nonce %>">
console.log("This inline script is now trusted!");
</script>
I'm terrified of breaking the site. Is there a "safe" way to test this?
I've been there. Pushing a strict CSP to production and realizing you've accidentally blocked your analytics, your payment gateway, and half your CSS is a nightmare. The solution is report_only mode.
In your initializer, you can tell Rails to send the Content-Security-Policy-Report-Only header instead of the enforcement header:
Rails.application.config.content_security_policy_nonce_generator = -> { SecureRandom.base64(16) }
Rails.application.config.content_security_policy do |policy|
policy.report_only = true
# ... your rules ...
end
When report_only is true, the browser won't actually block anything. Instead, it will just log a warning to the console (or send a JSON report to your report_uri) telling you what would have been blocked. I always run my app in report-only mode for a few days in a staging environment to catch all the edge cases before flipping the switch to full enforcement.
📋 Practical Task
Exercise: Hardening a Stripe-Integrated Checkout Page
You are working on a Rails app that integrates Stripe for payments and uses Google Fonts for branding. Currently, the app has no CSP, making it vulnerable to XSS attacks.
Your Goal: Configure a Content Security Policy that allows the following, while blocking everything else:
- Resources from your own domain (
:self). - Scripts from
https://js.stripe.com. - Styles from
https://fonts.googleapis.com. - Fonts from
https://fonts.gstatic.com. - An inline script in the
application.html.erblayout that initializes a tracking variable.
Requirements:
- Create or modify
config/initializers/content_security_policy.rbto implement these rules. - Implement a nonce generator in the initializer.
- Update your
application.html.erbto include thecsp_meta_tag. - Apply the correct
nonceattribute to an inline<script>tag in your layout. - Ensure the policy is enforced (not in report-only mode).
There are no comments for now.