SecureHeaders Build Status Code Climate

The gem will automatically apply several headers that are related to security. This includes:

This gem has integration with Rails, but works for any Ruby code. See the sinatra example section.

Installation

Add to your Gemfile

gem 'secure_headers'

And then execute:

$ bundle

Or install it yourself as:

$ gem install secure_headers

Usage

Functionality provided

  • ensure_security_headers: will set security-related headers automatically based on the configuration below.

By default, it will set all of the headers listed in the options section below unless specified.

Disabling

Use the standard skip_before_filter :filter_name, options mechanism. e.g. skip_before_filter :set_csp_header, :only => :tinymce_page

The following methods are going to be called, unless they are provided in a skip_before_filter block.

  • :set_csp_header
  • :set_hsts_header
  • :set_x_frame_options_header
  • :set_x_xss_protection_header
  • :set_x_content_type_options_header

Bonus Features

This gem makes a few assumptions about how you will use some features. For example:

  • It fills any blank directives with the value in :default_src Getting a default-src report is pretty useless. This way, you will always know what type of violation occurred. You can disable this feature by supplying :disable_fill_missing => true. This is referred to as the "effective-directive" in the spec, but is not well supported as of Nov 5, 2013.
  • Firefox does not support cross-origin CSP reports. If we are using Firefox, AND the value for :report_uri does not satisfy the same-origin requirements, we will instead forward to an internal endpoint (FF_CSP_ENDPOINT). This is also the case if :report_uri only contains a path, which we assume will be cross host. This endpoint will in turn forward the request to the value in :forward_endpoint without restriction. More information can be found in the "Note on Firefox handling of CSP" section.

Configuration

Place the following in an initializer (recommended):

::SecureHeaders::Configuration.configure do |config|
  config.hsts = {:max_age => 20.years.to_i, :include_subdomains => true}
  config.x_frame_options = 'DENY'
  config.x_content_type_options = "nosniff"
  config.x_xss_protection = {:value => 1, :mode => 'block'}
  config.csp = {
    :default_src => "https://* self",
    :frame_src => "https://* http://*.twimg.com http://itunes.apple.com",
    :img_src => "https://*",
    :report_uri => '//example.com/uri-directive'
  }
end

# and then simply include this in application_controller.rb
class ApplicationController < ActionController::Base
  ensure_security_headers
end

Or simply add it to application controller

ensure_security_headers(
  :hsts => {:include_subdomains, :x_frame_options => false},
  :x_frame_options => 'DENY',
  :csp => false)

Options for ensure_security_headers

To disable any of these headers, supply a value of false (e.g. :hsts => false), supplying nil will set the default value

Each header configuration can take a hash, or a string, or both. If a string is provided, that value is inserted verbatim. If a hash is supplied, a header will be constructed using the supplied options.

The Easy Headers

This configuration will likely work for most applications without modification.

:hsts             => {:max_age => 631138519, :include_subdomains => false}
:x_frame_options  => {:value => 'SAMEORIGIN'}
:x_xss_protection => {:value => 1, :mode => 'block'}  # set the :mode option to false to use "warning only" mode
:x_content_type_options => {:value => 'nosniff'}

Content Security Policy (CSP)

All browsers will receive the webkit csp header except Firefox, which gets its own header. See WebKit specification and Mozilla CSP specification

:csp => {
  :enforce     => false,        # sets header to report-only, by default
  # default_src is required!
  :default_src     => nil,      # sets the default-src/allow+options directives

  # Where reports are sent. Use protocol relative URLs if you are posting to the same domain (TLD+1). Use paths if you are posting to the application serving the header
  :report_uri  => '//mysite.example.com',

  # Send reports that cannot be sent across host here. These requests are sent
  # the server, not the browser. If no value is supplied, it will default to
  # the value in report_uri. Use this if you cannot use relative protocols mentioned above due to host mismatches.
  :forward_endpoint => 'https://internal.mylogaggregator.example.com'

  # these directives all take 'none', 'self', or a globbed pattern
  :img_src     => nil,
  :frame_src   => nil,
  :connect_src => nil,
  :font_src    => nil,
  :media_src   => nil,
  :object_src  => nil,
  :style_src   => nil,
  :script_src  => nil,

  # http additions will be appended to the various directives when
  # over http, relaxing the policy
  # e.g.
  # :csp => {
  #   :img_src => 'https://*',
  #   :http_additions => {:img_src => 'http//*'}
  # }
  # would produce the directive: "img-src https://* http://*;"
  # when over http, ignored for https requests
  :http_additions => {}

  # If you have enforce => true, you can use the `experiments` block to
  # also produce a report-only header. Values in this block override the
  # parent config for the report-only, and leave the enforcing header
  # unaltered. http_additions work the same way described above, but
  # are added to your report-only header as expected.
  :experimental => {
    :script_src => 'self',
    :img_src => 'https://mycdn.example.com',
    :http_additions {
      :img_src => 'http://mycdn.example.com'
    }
  }

  # script-nonce is an experimental feature of CSP 1.1 available in Chrome. It allows
  # you to whitelist inline script blocks. For more information, see
  # https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce
  :script_nonce => lambda { @script_nonce = SecureRandom.hex }
  # which can be used to whitelist a script block:
  # script_tag :nonce = @script_nonce { inline_script_call() }
}

Example CSP header config

# most basic example
:csp => {
  :default_src => "https://* inline eval",
  :report_uri => '/uri-directive'
}

> "default-src 'unsafe-inline' 'unsafe-eval' https://*; report-uri /uri-directive;"

# turn off inline scripting/eval
:csp => {
  :default_src => 'https://*',
  :report_uri => '/uri-directive'
}

> "default-src  https://*; report-uri /uri-directive;"

# Auction site wants to allow images from anywhere, plugin content from a list of trusted media providers (including a content distribution network), and scripts only from its server hosting sanitized JavaScript
:csp => {
  :default_src => 'self',
  :img_src => '*',
  :object_src => ['media1.com', 'media2.com', '*.cdn.com'],
  # alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
  :script_src => 'trustedscripts.example.com'
}
"default-src  'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"

Note on Firefox handling of CSP

Currently, Firefox does not support the w3c draft standard. So there are a few steps taken to make the two interchangeable.

  • CSP reports will not POST cross-origin. This sets up an internal endpoint in the application that will forward the request. Set the forward_endpoint value in the CSP section if you need to post cross origin for firefox. The internal endpoint that receives the initial request will forward the request to forward_endpoint

Adding the Firefox report forwarding endpoint

You need to add the following line to the TOP of confib/routes.rb This is an unauthenticated, unauthorized endpoint. Only do this if your report-uri is not on the same origin as your application!!!

Rails 2

map.csp_endpoint

Rails 3

If the csp reporting endpoint is clobbered by another route, add:

post SecureHeaders::ContentSecurityPolicy::FF_CSP_ENDPOINT => "content_security_policy#scribe"

Using with Sinatra

Here's an example using SecureHeaders for Sinatra applications:

require 'rubygems'
require 'sinatra'
require 'haml'
require 'secure_headers'

::SecureHeaders::Configuration.configure do |config|
  config.hsts = {:max_age => 99, :include_subdomains => true}
  config.x_frame_options = 'DENY'
  config.x_content_type_options = "nosniff"
  config.x_xss_protection = {:value => 1, :mode => false}
  config.csp = {
    :default_src => "https://* inline eval",
    :report_uri => '//example.com/uri-directive',
    :img_src => "https://* data:",
    :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
  }
end

class Donkey < Sinatra::Application
  include SecureHeaders
  set :root, APP_ROOT

  get '/' do
    set_csp_header
    haml :index
  end
end

Using with Padrino

You can use SecureHeaders for Padrino applications as well:

In your Gemfile:

  gem "secure_headers", :require => 'secure_headers'

then in your app.rb file you can:

module Web
  class App < Padrino::Application
    include SecureHeaders

    ::SecureHeaders::Configuration.configure do |config|
      config.hsts                   = {:max_age => 99, :include_subdomains => true}
      config.x_frame_options        = 'DENY'
      config.x_content_type_options = "nosniff"
      config.x_xss_protection       = {:value   => '1', :mode => false}
      config.csp                    = {
        :default_src => "https://* inline eval",
        :report_uri => '//example.com/uri-directive',
        :img_src => "https://* data:",
        :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
      }
    end

    get '/' do
      set_csp_header
      render 'index'
    end
  end
end

Similar libraries

Authors

  • Neil Matatall @ndm - primary author.
  • Nicholas Green @nickgreen - code contributions, main reviewer.

Acknowledgements

  • Justin Collins @presidentbeef & Jim O'Leary @jimio for reviews.
  • Ian Melven @imelven - Discussions/info about CSP in general, made us aware of the userCSP Mozilla extension.
  • Sumit Shah @omnidactyl - For being an eager guinea pig.
  • Chris Aniszczyk @cra - For running an awesome open source program at Twitter.

License

Copyright 2013 Twitter, Inc.

Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0