The gem will automatically apply several headers that are related to security. This includes:
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. CSP 1.1 Specification
- HTTP Strict Transport Security (HSTS) - Ensures the browser never visits the http version of a website. Protects from SSLStrip/Firesheep attacks. HSTS Specification
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. X-Frame-Options draft
- X-XSS-Protection - Cross site scripting heuristic filter for IE/Chrome
- X-Content-Type-Options - Prevent content type sniffing
This gem has integration with Rails, but works for any Ruby code. See the sinatra example section.
Add to your Gemfile
gem 'secure_headers'
And then execute:
$ bundle
Or install it yourself as:
$ gem install secure_headers
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.
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
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.
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 => true, :max_age => 20.years.to_i},
:x_frame_options => 'DENY',
:csp => false
)
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.
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'}
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'
}
}
}
# 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;"
script/style-nonce can be used to whitelist inline content. To do this, add "nonce" to your script/style-src configuration, then set the nonce attributes on the various tags.
*setting a nonce will also set 'unsafe-inline' for browsers that don't support nonces for backwards compatibility. 'unsafe-inline' is ignored if a nonce is present in a directive in compliant browsers.
:csp => {
:default_src => 'self',
:script_src => 'self nonce'
}
content-security-policy: default-src 'self'; script-src 'self' 'nonce-abc123' 'unsafe-inline'
<script nonce="<%= @content_security_policy_nonce %>">
console.log("whitelisted, will execute")
</script>
<script nonce="lol">
console.log("won't execute, not whitelisted")
</script>
<script>
console.log("won't execute, not whitelisted")
</script>
- 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 toforward_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!!!
map.csp_endpoint
If the csp reporting endpoint is clobbered by another route, add:
post SecureHeaders::ContentSecurityPolicy::FF_CSP_ENDPOINT => "content_security_policy#scribe"
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
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:
require 'secure_headers/padrino'
module Web
class App < Padrino::Application
register SecureHeaders::Padrino
get '/' do
set_csp_header
render 'index'
end
end
end
and in config/boot.rb
:
def before_load
::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
end
- Node.js (express) helmet and hood
- J2EE Servlet >= 3.0 highlines
- ASP.NET - NWebsec
- Python - django-csp + commonware
- Go - secureheader
- Neil Matatall @ndm - primary author.
- Nicholas Green @nickgreen - code contributions, main reviewer.
- 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.
Copyright 2013 Twitter, Inc.
Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0