secure_headers 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/.travis.yml +3 -2
- data/Gemfile +4 -0
- data/Guardfile +1 -1
- data/HISTORY.md +14 -0
- data/README.md +2 -2
- data/Rakefile +41 -1
- data/app/controllers/content_security_policy_controller.rb +19 -10
- data/fixtures/rails_3_2_12/.rspec +1 -0
- data/fixtures/rails_3_2_12/Gemfile +14 -0
- data/fixtures/rails_3_2_12/Guardfile +14 -0
- data/fixtures/rails_3_2_12/README.rdoc +261 -0
- data/fixtures/rails_3_2_12/Rakefile +7 -0
- data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +5 -0
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +6 -0
- data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/app/models/thing.rb +3 -0
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +14 -0
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +1 -0
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +21 -0
- data/fixtures/rails_3_2_12/config.ru +4 -0
- data/fixtures/rails_3_2_12/config/application.rb +68 -0
- data/fixtures/rails_3_2_12/config/boot.rb +6 -0
- data/fixtures/rails_3_2_12/config/database.yml +25 -0
- data/fixtures/rails_3_2_12/config/environment.rb +5 -0
- data/fixtures/rails_3_2_12/config/environments/development.rb +37 -0
- data/fixtures/rails_3_2_12/config/environments/production.rb +67 -0
- data/fixtures/rails_3_2_12/config/environments/test.rb +37 -0
- data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +7 -0
- data/fixtures/rails_3_2_12/config/initializers/inflections.rb +15 -0
- data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +5 -0
- data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +7 -0
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +15 -0
- data/fixtures/rails_3_2_12/config/initializers/session_store.rb +8 -0
- data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +14 -0
- data/fixtures/rails_3_2_12/config/locales/en.yml +5 -0
- data/fixtures/rails_3_2_12/config/routes.rb +61 -0
- data/fixtures/rails_3_2_12/db/schema.rb +16 -0
- data/fixtures/rails_3_2_12/db/seeds.rb +7 -0
- data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +40 -0
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +47 -0
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +19 -0
- data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/.rspec +1 -0
- data/fixtures/rails_3_2_12_no_init/Gemfile +14 -0
- data/fixtures/rails_3_2_12_no_init/Guardfile +14 -0
- data/fixtures/rails_3_2_12_no_init/README.rdoc +261 -0
- data/fixtures/rails_3_2_12_no_init/Rakefile +7 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +3 -0
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +14 -0
- data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +1 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +17 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +6 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +21 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +5 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +5 -0
- data/fixtures/rails_3_2_12_no_init/config.ru +4 -0
- data/fixtures/rails_3_2_12_no_init/config/application.rb +68 -0
- data/fixtures/rails_3_2_12_no_init/config/boot.rb +6 -0
- data/fixtures/rails_3_2_12_no_init/config/database.yml +25 -0
- data/fixtures/rails_3_2_12_no_init/config/environment.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +37 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +67 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +37 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +15 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +8 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +14 -0
- data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +5 -0
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +61 -0
- data/fixtures/rails_3_2_12_no_init/db/schema.rb +16 -0
- data/fixtures/rails_3_2_12_no_init/db/seeds.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +40 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +44 -0
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +20 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
- data/lib/secure_headers.rb +19 -15
- data/lib/secure_headers/headers/content_security_policy.rb +54 -113
- data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +70 -0
- data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +72 -0
- data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +6 -0
- data/lib/secure_headers/headers/content_security_policy/webkit_browser_strategy.rb +9 -0
- data/lib/secure_headers/version.rb +1 -1
- data/{secure-headers.gemspec → secure_headers.gemspec} +0 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +72 -84
- data/travis.sh +10 -0
- metadata +93 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Rails3212::Application.routes.draw do
|
|
2
|
+
resources :things
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# The priority is based upon order of creation:
|
|
6
|
+
# first created -> highest priority.
|
|
7
|
+
|
|
8
|
+
# Sample of regular route:
|
|
9
|
+
# match 'products/:id' => 'catalog#view'
|
|
10
|
+
# Keep in mind you can assign values other than :controller and :action
|
|
11
|
+
|
|
12
|
+
# Sample of named route:
|
|
13
|
+
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
|
|
14
|
+
# This route can be invoked with purchase_url(:id => product.id)
|
|
15
|
+
|
|
16
|
+
# Sample resource route (maps HTTP verbs to controller actions automatically):
|
|
17
|
+
# resources :products
|
|
18
|
+
|
|
19
|
+
# Sample resource route with options:
|
|
20
|
+
# resources :products do
|
|
21
|
+
# member do
|
|
22
|
+
# get 'short'
|
|
23
|
+
# post 'toggle'
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# collection do
|
|
27
|
+
# get 'sold'
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
|
|
31
|
+
# Sample resource route with sub-resources:
|
|
32
|
+
# resources :products do
|
|
33
|
+
# resources :comments, :sales
|
|
34
|
+
# resource :seller
|
|
35
|
+
# end
|
|
36
|
+
|
|
37
|
+
# Sample resource route with more complex sub-resources
|
|
38
|
+
# resources :products do
|
|
39
|
+
# resources :comments
|
|
40
|
+
# resources :sales do
|
|
41
|
+
# get 'recent', :on => :collection
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
|
|
45
|
+
# Sample resource route within a namespace:
|
|
46
|
+
# namespace :admin do
|
|
47
|
+
# # Directs /admin/products/* to Admin::ProductsController
|
|
48
|
+
# # (app/controllers/admin/products_controller.rb)
|
|
49
|
+
# resources :products
|
|
50
|
+
# end
|
|
51
|
+
|
|
52
|
+
# You can have the root of your site routed with "root"
|
|
53
|
+
# just remember to delete public/index.html.
|
|
54
|
+
# root :to => 'welcome#index'
|
|
55
|
+
|
|
56
|
+
# See how all your routes lay out with "rake routes"
|
|
57
|
+
|
|
58
|
+
# This is a legacy wild controller route that's not recommended for RESTful applications.
|
|
59
|
+
# Note: This route will make all actions in every controller accessible via GET requests.
|
|
60
|
+
match ':controller(/:action(/:id))(.:format)'
|
|
61
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
5
|
+
#
|
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
|
7
|
+
# database schema. If you need to create the application database on another
|
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
|
11
|
+
#
|
|
12
|
+
# It's strongly recommended to check this file into your version control system.
|
|
13
|
+
|
|
14
|
+
ActiveRecord::Schema.define(:version => 0) do
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This file should contain all the record creation needed to seed the database with its default values.
|
|
2
|
+
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
|
|
3
|
+
#
|
|
4
|
+
# Examples:
|
|
5
|
+
#
|
|
6
|
+
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
|
7
|
+
# Mayor.create(name: 'Emanuel', city: cities.first)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe OtherThingsController do
|
|
4
|
+
describe "headers" do
|
|
5
|
+
before(:each) do
|
|
6
|
+
# Chrome
|
|
7
|
+
request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "sets the X-XSS-PROTECTION header" do
|
|
11
|
+
get :index
|
|
12
|
+
response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "sets the X-FRAME-OPTIONS header" do
|
|
16
|
+
get :index
|
|
17
|
+
response.headers['X-FRAME-OPTIONS'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "sets the X-WebKit-CSP header" do
|
|
21
|
+
get :index
|
|
22
|
+
response.headers['X-WebKit-CSP-Report-Only'].should == "default-src 'self'; img-src data:;"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#mock ssl
|
|
26
|
+
it "sets the STRICT-TRANSPORT-SECURITY header" do
|
|
27
|
+
request.env['HTTPS'] = 'on'
|
|
28
|
+
get :index
|
|
29
|
+
response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "using IE" do
|
|
33
|
+
it "sets the X-CONTENT-TYPE-OPTIONS header" do
|
|
34
|
+
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
35
|
+
get :index
|
|
36
|
+
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# This controller is meant to be something that inherits config from application controller
|
|
4
|
+
# all values are defaulted because no initializer is configured, and the values in app controller
|
|
5
|
+
# only provide csp => false
|
|
6
|
+
|
|
7
|
+
describe ThingsController do
|
|
8
|
+
describe "headers" do
|
|
9
|
+
before(:each) do
|
|
10
|
+
# Chrome
|
|
11
|
+
request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "sets the X-XSS-PROTECTION header" do
|
|
15
|
+
get :index
|
|
16
|
+
response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "sets the X-FRAME-OPTIONS header" do
|
|
20
|
+
get :index
|
|
21
|
+
response.headers['X-FRAME-OPTIONS'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "sets the X-WebKit-CSP header" do
|
|
25
|
+
get :index
|
|
26
|
+
response.headers['X-WebKit-CSP-Report-Only'].should == nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#mock ssl
|
|
30
|
+
it "sets the STRICT-TRANSPORT-SECURITY header" do
|
|
31
|
+
request.env['HTTPS'] = 'on'
|
|
32
|
+
get :index
|
|
33
|
+
response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "using IE" do
|
|
37
|
+
it "sets the X-CONTENT-TYPE-OPTIONS header" do
|
|
38
|
+
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
39
|
+
get :index
|
|
40
|
+
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'spork'
|
|
3
|
+
#uncomment the following line to use spork with the debugger
|
|
4
|
+
#require 'spork/ext/ruby-debug'
|
|
5
|
+
|
|
6
|
+
Spork.prefork do
|
|
7
|
+
# Loading more in this block will cause your tests to run faster. However,
|
|
8
|
+
# if you change any configuration or code from libraries loaded here, you'll
|
|
9
|
+
# need to restart spork for it take effect.
|
|
10
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
|
11
|
+
ENV["RAILS_ENV"] ||= 'test'
|
|
12
|
+
require File.expand_path("../../config/environment", __FILE__)
|
|
13
|
+
require 'rspec/rails'
|
|
14
|
+
require 'rspec/autorun'
|
|
15
|
+
# require 'ruby-debug'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Spork.each_run do
|
|
19
|
+
|
|
20
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/lib/secure_headers.rb
CHANGED
|
@@ -13,25 +13,25 @@ module SecureHeaders
|
|
|
13
13
|
class << self
|
|
14
14
|
def append_features(base)
|
|
15
15
|
base.module_eval do
|
|
16
|
-
@@secure_headers_options = nil
|
|
17
|
-
|
|
18
16
|
extend ClassMethods
|
|
19
17
|
include InstanceMethods
|
|
20
|
-
|
|
21
|
-
# jank?
|
|
22
|
-
def self.secure_headers_options=(opts)
|
|
23
|
-
@@secure_headers_options = opts
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def self.secure_headers_options
|
|
27
|
-
@@secure_headers_options
|
|
28
|
-
end
|
|
29
18
|
end
|
|
30
19
|
end
|
|
31
20
|
end
|
|
32
21
|
|
|
33
22
|
module ClassMethods
|
|
34
|
-
|
|
23
|
+
attr_writer :secure_headers_options
|
|
24
|
+
def secure_headers_options
|
|
25
|
+
if @secure_headers_options
|
|
26
|
+
@secure_headers_options
|
|
27
|
+
elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
|
|
28
|
+
superclass.secure_headers_options
|
|
29
|
+
else
|
|
30
|
+
{}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ensure_security_headers options = {}
|
|
35
35
|
self.secure_headers_options = options
|
|
36
36
|
before_filter :set_security_headers
|
|
37
37
|
end
|
|
@@ -44,7 +44,7 @@ module SecureHeaders
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
module InstanceMethods
|
|
47
|
-
def set_security_headers(options = self.class.secure_headers_options
|
|
47
|
+
def set_security_headers(options = self.class.secure_headers_options)
|
|
48
48
|
brwsr = Brwsr::Browser.new(:ua => request.env['HTTP_USER_AGENT'])
|
|
49
49
|
set_hsts_header(options[:hsts]) if request.ssl?
|
|
50
50
|
set_x_frame_options_header(options[:x_frame_options])
|
|
@@ -59,10 +59,10 @@ module SecureHeaders
|
|
|
59
59
|
options = self.class.options_for :csp, options
|
|
60
60
|
return if options == false
|
|
61
61
|
|
|
62
|
-
header = ContentSecurityPolicy.new(
|
|
62
|
+
header = ContentSecurityPolicy.new(options, :request => request)
|
|
63
63
|
set_header(header.name, header.value)
|
|
64
64
|
if options && options[:experimental] && options[:enforce]
|
|
65
|
-
header = ContentSecurityPolicy.new(
|
|
65
|
+
header = ContentSecurityPolicy.new(options, :experimental => true, :request => request)
|
|
66
66
|
set_header(header.name, header.value)
|
|
67
67
|
end
|
|
68
68
|
end
|
|
@@ -107,6 +107,10 @@ end
|
|
|
107
107
|
|
|
108
108
|
require "secure_headers/version"
|
|
109
109
|
require "secure_headers/headers/content_security_policy"
|
|
110
|
+
require "secure_headers/headers/content_security_policy/browser_strategy"
|
|
111
|
+
require "secure_headers/headers/content_security_policy/firefox_browser_strategy"
|
|
112
|
+
require "secure_headers/headers/content_security_policy/ie_browser_strategy"
|
|
113
|
+
require "secure_headers/headers/content_security_policy/webkit_browser_strategy"
|
|
110
114
|
require "secure_headers/headers/x_frame_options"
|
|
111
115
|
require "secure_headers/headers/strict_transport_security"
|
|
112
116
|
require "secure_headers/headers/x_xss_protection"
|
|
@@ -19,27 +19,39 @@ module SecureHeaders
|
|
|
19
19
|
end
|
|
20
20
|
include Constants
|
|
21
21
|
|
|
22
|
-
META
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
attr_reader :browser, :ssl_request, :report_uri, :request_uri
|
|
22
|
+
attr_accessor *META
|
|
23
|
+
attr_reader :browser, :ssl_request, :report_uri, :request_uri, :experimental, :config
|
|
26
24
|
|
|
27
|
-
alias :enforce? :enforce
|
|
28
25
|
alias :disable_chrome_extension? :disable_chrome_extension
|
|
29
26
|
alias :disable_fill_missing? :disable_fill_missing
|
|
30
27
|
alias :ssl_request? :ssl_request
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
# +options+ param contains
|
|
30
|
+
# :experimental use experimental block for config
|
|
31
|
+
# :ssl_request used to determine if http_additions should be used
|
|
32
|
+
# :request_uri used to determine if firefox should send the report directly
|
|
33
|
+
# or use the forwarding endpoint
|
|
34
|
+
# :ua the user agent (or just use Firefox/Chrome/MSIE/etc)
|
|
35
|
+
#
|
|
36
|
+
# :report used to determine what :ssl_request, :ua, and :request_uri are set to
|
|
37
|
+
def initialize(config=nil, options={})
|
|
34
38
|
@experimental = !!options.delete(:experimental)
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
if options[:request]
|
|
40
|
+
parse_request(options[:request])
|
|
41
|
+
else
|
|
42
|
+
@browser = Brwsr::Browser.new(:ua => options[:ua])
|
|
43
|
+
# fails open, assumes http. Bad idea? Will always include http additions.
|
|
44
|
+
# could also fail if not supplied.
|
|
45
|
+
@ssl_request = !!options.delete(:ssl)
|
|
46
|
+
# a nil value here means we always assume we are not on the same host,
|
|
47
|
+
# which causes all FF csp reports to go through the forwarder
|
|
48
|
+
@request_uri = options.delete(:request_uri)
|
|
39
49
|
end
|
|
50
|
+
|
|
51
|
+
configure(config) if config
|
|
40
52
|
end
|
|
41
53
|
|
|
42
|
-
def configure
|
|
54
|
+
def configure opts
|
|
43
55
|
@config = opts.dup
|
|
44
56
|
|
|
45
57
|
experimental_config = @config.delete(:experimental)
|
|
@@ -48,9 +60,8 @@ module SecureHeaders
|
|
|
48
60
|
@config.merge!(experimental_config)
|
|
49
61
|
end
|
|
50
62
|
|
|
51
|
-
parse_request request
|
|
52
63
|
META.each do |meta|
|
|
53
|
-
self.send(meta
|
|
64
|
+
self.send("#{meta}=", @config.delete(meta))
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
@report_uri = @config.delete(:report_uri)
|
|
@@ -61,45 +72,39 @@ module SecureHeaders
|
|
|
61
72
|
end
|
|
62
73
|
|
|
63
74
|
def name
|
|
64
|
-
|
|
65
|
-
STANDARD_HEADER_NAME
|
|
66
|
-
elsif browser.firefox?
|
|
67
|
-
# can't use supports_standard because FF18 does not support this part of the standard.
|
|
68
|
-
FIREFOX_CSP_HEADER_NAME
|
|
69
|
-
else
|
|
70
|
-
WEBKIT_CSP_HEADER_NAME
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
if !enforce || @experimental
|
|
74
|
-
base += "-Report-Only"
|
|
75
|
-
end
|
|
76
|
-
base
|
|
75
|
+
browser_strategy.name
|
|
77
76
|
end
|
|
78
77
|
|
|
79
78
|
def value
|
|
80
79
|
return @config if @config.is_a?(String)
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
if @config
|
|
82
|
+
build_value
|
|
83
|
+
else
|
|
84
|
+
browser_strategy.csp_header
|
|
83
85
|
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
def browser_strategy
|
|
91
|
+
@browser_strategy ||= BrowserStrategy.build(self)
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
def directives
|
|
89
|
-
|
|
90
|
-
browser.firefox? ? FIREFOX_DIRECTIVES : WEBKIT_DIRECTIVES
|
|
95
|
+
browser_strategy.directives
|
|
91
96
|
end
|
|
92
97
|
|
|
93
|
-
private
|
|
94
|
-
|
|
95
98
|
def build_value
|
|
96
99
|
fill_directives unless disable_fill_missing?
|
|
97
100
|
add_missing_chrome_extension_values unless disable_chrome_extension?
|
|
98
101
|
append_http_additions unless ssl_request?
|
|
99
102
|
|
|
100
|
-
header_value =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
header_value = [
|
|
104
|
+
build_impl_specific_directives,
|
|
105
|
+
generic_directives(@config),
|
|
106
|
+
report_uri_directive(@report_uri)
|
|
107
|
+
].join
|
|
103
108
|
|
|
104
109
|
#store the value for next time
|
|
105
110
|
@config = header_value
|
|
@@ -148,12 +153,7 @@ module SecureHeaders
|
|
|
148
153
|
end
|
|
149
154
|
|
|
150
155
|
def filter_unsupported_directives
|
|
151
|
-
|
|
152
|
-
# can't use supports_standard because FF18 does not support this part of the standard.
|
|
153
|
-
@config[:xhr_src] = @config.delete(:connect_src) if @config[:connect_src]
|
|
154
|
-
else
|
|
155
|
-
@config.delete(:frame_ancestors)
|
|
156
|
-
end
|
|
156
|
+
@config = browser_strategy.filter_unsupported_directives(@config)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
# translates 'inline','self', 'none' and 'eval' to their respective impl-specific values.
|
|
@@ -168,104 +168,45 @@ module SecureHeaders
|
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
-
# inline/eval => impl-specific values
|
|
172
171
|
def translate_inline_or_eval val
|
|
173
|
-
|
|
174
|
-
if browser.firefox?
|
|
175
|
-
val == 'inline' ? 'inline-script' : 'eval-script'
|
|
176
|
-
else
|
|
177
|
-
val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
|
|
178
|
-
end
|
|
172
|
+
browser_strategy.translate_inline_or_eval(val)
|
|
179
173
|
end
|
|
180
174
|
|
|
181
175
|
# if we have a forwarding endpoint setup and we are not on the same origin as our report_uri
|
|
182
176
|
# or only a path was supplied (in which case we assume cross-host)
|
|
183
177
|
# we need to forward the request for Firefox.
|
|
184
178
|
def normalize_reporting_endpoint
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
@report_uri = FF_CSP_ENDPOINT
|
|
188
|
-
else
|
|
189
|
-
@report_uri = nil
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
179
|
+
return unless browser_strategy.normalize_reporting_endpoint?
|
|
180
|
+
return unless !same_origin? || URI.parse(report_uri).host.nil?
|
|
193
181
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def build_impl_specific_directives
|
|
199
|
-
header_value = ""
|
|
200
|
-
default = expect_directive_value(:default_src)
|
|
201
|
-
# firefox 18 still requires the use of the options value, but can substitute default-src for allow
|
|
202
|
-
if browser.firefox?
|
|
203
|
-
header_value += build_firefox_specific_preamble(default) || ''
|
|
182
|
+
if forward_endpoint
|
|
183
|
+
@report_uri = FF_CSP_ENDPOINT
|
|
204
184
|
else
|
|
205
|
-
|
|
185
|
+
@report_uri = nil
|
|
206
186
|
end
|
|
207
|
-
|
|
208
|
-
header_value
|
|
209
187
|
end
|
|
210
188
|
|
|
211
|
-
def
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
header_value += "default-src #{default_src_value.join(" ")}; " if default_src_value.any?
|
|
215
|
-
elsif default_src_value
|
|
216
|
-
header_value += "allow #{default_src_value.join(" ")}; " if default_src_value.any?
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
options_directive = build_options_directive
|
|
220
|
-
header_value += "options #{options_directive.join(" ")}; " if options_directive.any?
|
|
221
|
-
header_value
|
|
189
|
+
def build_impl_specific_directives
|
|
190
|
+
default = expect_directive_value(:default_src)
|
|
191
|
+
browser_strategy.build_impl_specific_directives(default)
|
|
222
192
|
end
|
|
223
193
|
|
|
224
194
|
def expect_directive_value key
|
|
225
195
|
@config.delete(key) {|k| raise ContentSecurityPolicyBuildError.new("Expected to find #{k} directive value")}
|
|
226
196
|
end
|
|
227
197
|
|
|
228
|
-
# moves inline/eval values from script-src to options
|
|
229
|
-
# discards those values in the style-src directive
|
|
230
|
-
def build_options_directive
|
|
231
|
-
options_directive = []
|
|
232
|
-
@config.each do |directive, val|
|
|
233
|
-
next if val.is_a?(String)
|
|
234
|
-
new_val = []
|
|
235
|
-
val.each do |token|
|
|
236
|
-
if ['inline-script', 'eval-script'].include?(token)
|
|
237
|
-
# Firefox does not support blocking inline styles ATM
|
|
238
|
-
# https://bugzilla.mozilla.org/show_bug.cgi?id=763879
|
|
239
|
-
unless directive?(directive, "style_src") || options_directive.include?(token)
|
|
240
|
-
options_directive << token
|
|
241
|
-
end
|
|
242
|
-
else
|
|
243
|
-
new_val << token
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
@config[directive] = new_val
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
options_directive
|
|
250
|
-
end
|
|
251
|
-
|
|
252
198
|
def same_origin?
|
|
253
|
-
return
|
|
199
|
+
return unless report_uri && request_uri
|
|
254
200
|
|
|
255
201
|
origin = URI.parse(request_uri)
|
|
256
202
|
uri = URI.parse(report_uri)
|
|
257
203
|
uri.host == origin.host && origin.port == uri.port && origin.scheme == uri.scheme
|
|
258
204
|
end
|
|
259
205
|
|
|
260
|
-
def directive? val, name
|
|
261
|
-
val.to_s.casecmp(name) == 0
|
|
262
|
-
end
|
|
263
|
-
|
|
264
206
|
def report_uri_directive(report_uri)
|
|
265
|
-
report_uri
|
|
207
|
+
report_uri ? "report-uri #{report_uri};" : ''
|
|
266
208
|
end
|
|
267
209
|
|
|
268
|
-
|
|
269
210
|
def generic_directives(config)
|
|
270
211
|
header_value = ''
|
|
271
212
|
if config[:img_src]
|