Adding CSP to Rails

Content Security Policy can be an effective way to prevent XSS attacks. If you aren’t familiar, here’s a great intro.

To get started with Rails, first add the header to all requests in your ApplicationController. We want to start by blocking content in development so we notice it, but only report it in production so nothing breaks.

before_action :set_csp

# use constants and freeze for performance
CSP_HEADER_NAME = (Rails.env.production? ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy").freeze
CSP_HEADER_VALUE = "default-src *; report-uri /csp_reports?report_only=#{CSP_HEADER_NAME.include?("Report-Only")}".freeze

def set_csp
  response.headers[CSP_HEADER_NAME] = CSP_HEADER_VALUE


Create a model to track reports.

rails g model CspReport

And in the migration, do:

class CreateCspReports < ActiveRecord::Migration
  def change
    create_table :csp_reports do |t|
      t.text :document_uri
      t.text :referrer
      t.text :violated_directive
      t.text :effective_directive
      t.text :original_policy
      t.text :blocked_uri
      t.integer :status_code
      t.text :user_agent
      t.boolean :report_only
      t.datetime :created_at

Add a controller to create the reports.

class CspReportsController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    report = JSON.parse(["csp-report"]
      document_uri: report["document-uri"],
      referrer: report["referrer"],
      violated_directive: report["violated-directive"],
      effective_directive: report["effective-directive"],
      original_policy: report["original-policy"],
      blocked_uri: report["blocked-uri"],
      status_code: report["status-code"],
      user_agent: request.user_agent,
      report_only: params[:report_only] == "true"
    head :ok

Don’t forget the route.

resources :csp_reports, only: [:create]

Enforcing the Policy

Once the reports stop, you’ll want to enforce the policy in production.

CSP_HEADER_NAME = "Content-Security-Policy".freeze

Testing New Policies

You can have both an enforced policy and a report only policy, so use this to your advantage when changing policies. Make the new policy report only for a bit before enforcing it.

before_action :set_csp_report_only

# use constants and freeze for performance
CSP_REPORT_ONLY_HEADER_NAME = "Content-Security-Policy-Report-Only".freeze
CSP_REPORT_ONLY_HEADER_VALUE = "default-src https:; report-uri /csp_reports?report_only=true".freeze

def set_csp_report_only

Published November 29, 2015

You might also enjoy

Scaling Reads

Anonymizing IPs in Ruby

Trying Out Vault for Postgres Credentials

All code examples are public domain.
Use them however you’d like (licensed under CC0).