secure_headers 1.1.1 → 1.4.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.
Files changed (80) hide show
  1. data/.ruby-gemset +1 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +2 -3
  4. data/Gemfile +2 -7
  5. data/HISTORY.md +47 -0
  6. data/README.md +74 -27
  7. data/app/controllers/content_security_policy_controller.rb +1 -0
  8. data/fixtures/rails_3_2_12/Gemfile +0 -8
  9. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -1
  10. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -3
  11. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
  12. data/fixtures/rails_3_2_12/config/application.rb +0 -54
  13. data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
  14. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +1 -0
  15. data/fixtures/rails_3_2_12/config/routes.rb +0 -57
  16. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +15 -14
  17. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +12 -12
  18. data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
  19. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
  20. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
  21. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
  22. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
  23. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
  24. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
  25. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
  26. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +12 -12
  27. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +12 -12
  28. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
  29. data/lib/secure_headers/headers/content_security_policy.rb +54 -35
  30. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  31. data/lib/secure_headers/headers/x_download_options.rb +39 -0
  32. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  33. data/lib/secure_headers/padrino.rb +14 -0
  34. data/lib/secure_headers/railtie.rb +6 -2
  35. data/lib/secure_headers/version.rb +1 -1
  36. data/lib/secure_headers.rb +9 -2
  37. data/spec/controllers/content_security_policy_controller_spec.rb +19 -19
  38. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +117 -85
  39. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
  40. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
  41. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
  42. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -12
  43. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
  44. data/spec/lib/secure_headers_spec.rb +38 -12
  45. data/spec/spec_helper.rb +10 -24
  46. metadata +9 -37
  47. data/.rvmrc +0 -1
  48. data/Guardfile +0 -10
  49. data/fixtures/rails_3_2_12/Guardfile +0 -14
  50. data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
  51. data/fixtures/rails_3_2_12/config/database.yml +0 -25
  52. data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
  53. data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
  54. data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
  55. data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
  56. data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
  57. data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
  58. data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
  59. data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
  60. data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
  61. data/fixtures/rails_3_2_12/db/schema.rb +0 -16
  62. data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
  63. data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
  64. data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
  65. data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
  66. data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
  67. data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
  68. data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
  69. data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
  70. data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
  71. data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
  72. data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
  73. data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
  74. data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
  75. data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
  76. data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
  77. data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
  78. data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
  79. data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
  80. data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
@@ -1,12 +1,8 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
3
  # Pick the frameworks you want:
4
- require "active_record/railtie"
5
4
  require "action_controller/railtie"
6
- require "action_mailer/railtie"
7
- require "active_resource/railtie"
8
5
  require "sprockets/railtie"
9
- # require "rails/test_unit/railtie"
10
6
 
11
7
  if defined?(Bundler)
12
8
  # If you precompile assets before deploying to production, use this line
@@ -17,52 +13,5 @@ end
17
13
 
18
14
  module Rails3212
19
15
  class Application < Rails::Application
20
- # Settings in config/environments/* take precedence over those specified here.
21
- # Application configuration should go into files in config/initializers
22
- # -- all .rb files in that directory are automatically loaded.
23
-
24
- # Custom directories with classes and modules you want to be autoloadable.
25
- # config.autoload_paths += %W(#{config.root}/extras)
26
-
27
- # Only load the plugins named here, in the order given (default is alphabetical).
28
- # :all can be used as a placeholder for all plugins not explicitly named.
29
- # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
30
-
31
- # Activate observers that should always be running.
32
- # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
33
-
34
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
35
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
36
- # config.time_zone = 'Central Time (US & Canada)'
37
-
38
- # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
39
- # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
40
- # config.i18n.default_locale = :de
41
-
42
- # Configure the default encoding used in templates for Ruby 1.9.
43
- config.encoding = "utf-8"
44
-
45
- # Configure sensitive parameters which will be filtered from the log file.
46
- config.filter_parameters += [:password]
47
-
48
- # Enable escaping HTML in JSON.
49
- config.active_support.escape_html_entities_in_json = true
50
-
51
- # Use SQL instead of Active Record's schema dumper when creating the database.
52
- # This is necessary if your schema can't be completely dumped by the schema dumper,
53
- # like if you have constraints or database-specific column types
54
- # config.active_record.schema_format = :sql
55
-
56
- # Enforce whitelist mode for mass assignment.
57
- # This will create an empty whitelist of attributes available for mass-assignment for all models
58
- # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
59
- # parameters by using an attr_accessible or attr_protected declaration.
60
- config.active_record.whitelist_attributes = true
61
-
62
- # Enable the asset pipeline
63
- config.assets.enabled = true
64
-
65
- # Version of your assets, change this if you want to expire all your assets
66
- config.assets.version = '1.0'
67
16
  end
68
17
  end
@@ -27,10 +27,10 @@ Rails3212::Application.configure do
27
27
  # Tell Action Mailer not to deliver emails to the real world.
28
28
  # The :test delivery method accumulates sent emails in the
29
29
  # ActionMailer::Base.deliveries array.
30
- config.action_mailer.delivery_method = :test
30
+ # config.action_mailer.delivery_method = :test
31
31
 
32
32
  # Raise exception on mass assignment protection for Active Record models
33
- config.active_record.mass_assignment_sanitizer = :strict
33
+ # config.active_record.mass_assignment_sanitizer = :strict
34
34
 
35
35
  # Print deprecation notices to the stderr
36
36
  config.active_support.deprecation = :stderr
@@ -1,61 +1,4 @@
1
1
  Rails3212::Application.routes.draw do
2
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
3
  match ':controller(/:action(/:id))(.:format)'
61
4
  end
@@ -1,44 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe OtherThingsController do
3
+ describe OtherThingsController, :type => :controller do
4
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
5
  it "sets the X-XSS-Protection header" do
11
6
  get :index
12
- response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
7
+ expect(response.headers['X-XSS-Protection']).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
13
8
  end
14
9
 
15
10
  it "sets the X-Frame-Options header" do
16
11
  get :index
17
- response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
12
+ expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
18
13
  end
19
14
 
20
15
  it "sets the X-WebKit-CSP header" do
21
16
  get :index
22
- response.headers['Content-Security-Policy-Report-Only'].should == "default-src 'self'; img-src data:;"
17
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src 'self' data:;")
23
18
  end
24
19
 
25
20
  #mock ssl
26
21
  it "sets the Strict-Transport-Security header" do
27
22
  request.env['HTTPS'] = 'on'
28
23
  get :index
29
- response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
24
+ expect(response.headers['Strict-Transport-Security']).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
25
+ end
26
+
27
+ it "sets the X-Download-Options header" do
28
+ get :index
29
+ expect(response.headers['X-Download-Options']).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
30
30
  end
31
31
 
32
32
  it "sets the X-Content-Type-Options header" do
33
33
  get :index
34
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
34
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
35
35
  end
36
36
 
37
37
  context "using IE" do
38
38
  it "sets the X-Content-Type-Options header" do
39
39
  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"
40
40
  get :index
41
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
41
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
42
42
  end
43
43
  end
44
44
  end
@@ -4,45 +4,45 @@ require 'spec_helper'
4
4
  # all values are defaulted because no initializer is configured, and the values in app controller
5
5
  # only provide csp => false
6
6
 
7
- describe ThingsController do
7
+ describe ThingsController, :type => :controller do
8
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
9
  it "sets the X-XSS-Protection header" do
15
10
  get :index
16
- response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
11
+ expect(response.headers['X-XSS-Protection']).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
17
12
  end
18
13
 
19
14
  it "sets the X-Frame-Options header" do
20
15
  get :index
21
- response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
16
+ expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
22
17
  end
23
18
 
24
19
  it "sets the X-WebKit-CSP header" do
25
20
  get :index
26
- response.headers['Content-Security-Policy-Report-Only'].should == nil
21
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq(nil)
27
22
  end
28
23
 
29
24
  #mock ssl
30
25
  it "sets the Strict-Transport-Security header" do
31
26
  request.env['HTTPS'] = 'on'
32
27
  get :index
33
- response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
28
+ expect(response.headers['Strict-Transport-Security']).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
29
+ end
30
+
31
+ it "sets the X-Download-Options header" do
32
+ get :index
33
+ expect(response.headers['X-Download-Options']).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
34
34
  end
35
35
 
36
36
  it "sets the X-Content-Type-Options header" do
37
37
  get :index
38
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
38
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
39
39
  end
40
40
 
41
41
  context "using IE" do
42
42
  it "sets the X-Content-Type-Options header" do
43
43
  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"
44
44
  get :index
45
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
45
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
46
46
  end
47
47
  end
48
48
  end
@@ -1,20 +1,5 @@
1
1
  require 'rubygems'
2
- require 'spork'
3
- #uncomment the following line to use spork with the debugger
4
- #require 'spork/ext/ruby-debug'
5
2
 
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
3
+ ENV["RAILS_ENV"] ||= 'test'
4
+ require File.expand_path("../../config/environment", __FILE__)
5
+ require 'rspec/rails'
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'base64'
2
3
 
3
4
  module SecureHeaders
4
5
  class ContentSecurityPolicyBuildError < StandardError; end
@@ -8,7 +9,7 @@ module SecureHeaders
8
9
  STANDARD_HEADER_NAME = "Content-Security-Policy"
9
10
  FF_CSP_ENDPOINT = "/content_security_policy/forward_report"
10
11
  DIRECTIVES = [:default_src, :script_src, :frame_src, :style_src, :img_src, :media_src, :font_src, :object_src, :connect_src]
11
- META = [:enforce, :http_additions, :disable_chrome_extension, :disable_fill_missing, :forward_endpoint]
12
+ META = [:disable_chrome_extension, :disable_fill_missing, :forward_endpoint]
12
13
  end
13
14
  include Constants
14
15
 
@@ -29,7 +30,9 @@ module SecureHeaders
29
30
  # :report used to determine what :ssl_request, :ua, and :request_uri are set to
30
31
  def initialize(config=nil, options={})
31
32
  @experimental = !!options.delete(:experimental)
33
+ warn "[DEPRECATION] 'experimental' config is removed in 2.0"
32
34
  @controller = options.delete(:controller)
35
+
33
36
  if options[:request]
34
37
  parse_request(options[:request])
35
38
  else
@@ -45,6 +48,10 @@ module SecureHeaders
45
48
  configure(config) if config
46
49
  end
47
50
 
51
+ def nonce
52
+ @nonce ||= SecureRandom.base64(32).chomp
53
+ end
54
+
48
55
  def configure(config)
49
56
  @config = config.dup
50
57
 
@@ -54,21 +61,26 @@ module SecureHeaders
54
61
  @config.merge!(experimental_config)
55
62
  end
56
63
 
64
+ # these values don't support lambdas because this needs to be rewritten
65
+ @http_additions = @config.delete(:http_additions)
66
+ @app_name = @config.delete(:app_name)
67
+
68
+ normalize_csp_options
69
+
57
70
  META.each do |meta|
58
71
  self.send("#{meta}=", @config.delete(meta))
59
72
  end
60
73
 
61
- @report_uri = @config.delete(:report_uri)
62
- @script_nonce = @config.delete(:script_nonce)
74
+ @enforce = !!@config.delete(:enforce)
75
+ @tag_report_uri = @config.delete(:tag_report_uri)
63
76
 
64
- normalize_csp_options
65
77
  normalize_reporting_endpoint
66
78
  fill_directives unless disable_fill_missing?
67
79
  end
68
80
 
69
81
  def name
70
82
  base = STANDARD_HEADER_NAME
71
- if !enforce || experimental
83
+ if !@enforce || experimental
72
84
  base += "-Report-Only"
73
85
  end
74
86
  base
@@ -89,16 +101,9 @@ module SecureHeaders
89
101
  raise "Expected to find default_src directive value" unless @config[:default_src]
90
102
  append_http_additions unless ssl_request?
91
103
  header_value = [
92
- # ensure default-src is first
93
- build_directive(:default_src),
94
104
  generic_directives(@config),
95
- report_uri_directive,
96
- script_nonce_directive,
97
- ].join
98
-
99
- #store the value for next time
100
- @config = header_value
101
- header_value.strip
105
+ report_uri_directive
106
+ ].join.strip
102
107
  rescue StandardError => e
103
108
  raise ContentSecurityPolicyBuildError.new("Couldn't build CSP header :( #{e}")
104
109
  end
@@ -115,20 +120,31 @@ module SecureHeaders
115
120
  end
116
121
 
117
122
  def append_http_additions
118
- return unless http_additions
119
- http_additions.each do |k, v|
123
+ return unless @http_additions
124
+ @http_additions.each do |k, v|
120
125
  @config[k] ||= []
121
126
  @config[k] << v
122
127
  end
123
128
  end
124
129
 
125
130
  def normalize_csp_options
126
- @config.each do |k,v|
127
- @config[k] = v.split if v.is_a? String
128
- @config[k] = @config[k].map do |val|
129
- translate_dir_value(val)
131
+ @config = @config.inject({}) do |hash, (key, value)|
132
+ # lambdas
133
+ config_val = value.respond_to?(:call) ? value.call : value
134
+ # space-delimeted strings
135
+ config_val = config_val.split if config_val.is_a? String
136
+ # array of strings
137
+ if config_val.respond_to?(:map) #skip booleans
138
+ config_val = config_val.map do |val|
139
+ translate_dir_value(val)
140
+ end.flatten.uniq
130
141
  end
142
+
143
+ hash[key] = config_val
144
+ hash
131
145
  end
146
+
147
+ @report_uri = @config.delete(:report_uri).join(" ") if @config[:report_uri]
132
148
  end
133
149
 
134
150
  # translates 'inline','self', 'none' and 'eval' to their respective impl-specific values.
@@ -138,6 +154,9 @@ module SecureHeaders
138
154
  # self/none are special sources/src-dir-values and need to be quoted in chrome
139
155
  elsif %{self none}.include?(val)
140
156
  "'#{val}'"
157
+ elsif val == 'nonce'
158
+ @controller.instance_variable_set(:@content_security_policy_nonce, nonce)
159
+ ["'nonce-#{nonce}'", "'unsafe-inline'"]
141
160
  else
142
161
  val
143
162
  end
@@ -153,16 +172,27 @@ module SecureHeaders
153
172
  end
154
173
 
155
174
  if forward_endpoint
175
+ warn "[DEPRECATION] forwarder is removed in 2.0"
156
176
  @report_uri = FF_CSP_ENDPOINT
157
177
  end
158
178
  end
179
+
180
+ if @tag_report_uri
181
+ @report_uri = "#{@report_uri}?enforce=#{@enforce}"
182
+ @report_uri += "&app_name=#{@app_name}" if @app_name
183
+ end
159
184
  end
160
185
 
161
186
  def same_origin?
162
187
  return unless report_uri && request_uri
163
188
 
164
- origin = URI.parse(request_uri)
165
- uri = URI.parse(report_uri)
189
+ begin
190
+ origin = URI.parse(request_uri)
191
+ uri = URI.parse(report_uri)
192
+ rescue URI::InvalidURIError
193
+ return false
194
+ end
195
+
166
196
  uri.host == origin.host && origin.port == uri.port && origin.scheme == uri.scheme
167
197
  end
168
198
 
@@ -180,26 +210,15 @@ module SecureHeaders
180
210
  "report-uri #{@report_uri};"
181
211
  end
182
212
 
183
- def script_nonce_directive
184
- return '' if @script_nonce.nil?
185
- nonce_value = if @script_nonce.is_a?(String)
186
- @script_nonce
187
- elsif @controller
188
- @controller.instance_exec(&@script_nonce)
189
- else
190
- @script_nonce.call
191
- end
192
- "script-nonce #{nonce_value};"
193
- end
194
-
195
213
  def generic_directives(config)
196
214
  header_value = ''
197
215
  if config[:img_src]
198
216
  config[:img_src] = config[:img_src] + ['data:'] unless config[:img_src].include?('data:')
199
217
  else
200
- config[:img_src] = ['data:']
218
+ config[:img_src] = config[:default_src] + ['data:']
201
219
  end
202
220
 
221
+ header_value = build_directive(:default_src)
203
222
  config.keys.sort_by{|k| k.to_s}.each do |k| # ensure consistent ordering
204
223
  header_value += build_directive(k)
205
224
  end
@@ -6,7 +6,7 @@ module SecureHeaders
6
6
  HSTS_HEADER_NAME = 'Strict-Transport-Security'
7
7
  HSTS_MAX_AGE = "631138519"
8
8
  DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
9
- VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?\z/i
9
+ VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
10
10
  MESSAGE = "The config value supplied for the HSTS header was invalid."
11
11
  end
12
12
  include Constants
@@ -31,6 +31,7 @@ module SecureHeaders
31
31
  max_age = @config.fetch(:max_age, HSTS_MAX_AGE)
32
32
  value = "max-age=" + max_age.to_s
33
33
  value += "; includeSubdomains" if @config[:include_subdomains]
34
+ value += "; preload" if @config[:preload]
34
35
 
35
36
  value
36
37
  end
@@ -0,0 +1,39 @@
1
+ module SecureHeaders
2
+ class XDOBuildError < StandardError; end
3
+ class XDownloadOptions < Header
4
+ module Constants
5
+ XDO_HEADER_NAME = "X-Download-Options"
6
+ DEFAULT_VALUE = 'noopen'
7
+ end
8
+ include Constants
9
+
10
+ def initialize(config = nil)
11
+ @config = config
12
+ validate_config unless @config.nil?
13
+ end
14
+
15
+ def name
16
+ XDO_HEADER_NAME
17
+ end
18
+
19
+ def value
20
+ case @config
21
+ when NilClass
22
+ DEFAULT_VALUE
23
+ when String
24
+ @config
25
+ else
26
+ @config[:value]
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def validate_config
33
+ value = @config.is_a?(Hash) ? @config[:value] : @config
34
+ unless value.casecmp(DEFAULT_VALUE) == 0
35
+ raise XDOBuildError.new("Value can only be nil or 'noopen'")
36
+ end
37
+ end
38
+ end
39
+ end
@@ -4,7 +4,7 @@ module SecureHeaders
4
4
  module Constants
5
5
  X_XSS_PROTECTION_HEADER_NAME = 'X-XSS-Protection'
6
6
  DEFAULT_VALUE = "1"
7
- VALID_X_XSS_HEADER = /\A[01](; mode=block)?\z/i
7
+ VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
8
8
  end
9
9
  include Constants
10
10
 
@@ -26,6 +26,7 @@ module SecureHeaders
26
26
  else
27
27
  value = @config[:value].to_s
28
28
  value += "; mode=#{@config[:mode]}" if @config[:mode]
29
+ value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
29
30
  value
30
31
  end
31
32
  end
@@ -0,0 +1,14 @@
1
+ module SecureHeaders
2
+ module Padrino
3
+ class << self
4
+ ##
5
+ # Main class that register this extension.
6
+ #
7
+ def registered(app)
8
+ app.extend SecureHeaders::ClassMethods
9
+ app.helpers SecureHeaders::InstanceMethods
10
+ end
11
+ alias :included :registered
12
+ end
13
+ end
14
+ end
@@ -3,7 +3,11 @@ if defined?(Rails::Railtie)
3
3
  module SecureHeaders
4
4
  class Railtie < Rails::Engine
5
5
  isolate_namespace ::SecureHeaders if defined? isolate_namespace # rails 3.0
6
- ActionController::Base.send :include, ::SecureHeaders
6
+ initializer "secure_headers.action_controller" do
7
+ ActiveSupport.on_load(:action_controller) do
8
+ include ::SecureHeaders
9
+ end
10
+ end
7
11
  end
8
12
  end
9
13
  else
@@ -34,4 +38,4 @@ else
34
38
  if defined? ActionController::Routing
35
39
  ActionController::Routing::RouteSet::Mapper.send :include, ::SecureHeaders::Routing::MapperExtensions
36
40
  end
37
- end
41
+ end
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "1.1.1"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -2,7 +2,7 @@ module SecureHeaders
2
2
  module Configuration
3
3
  class << self
4
4
  attr_accessor :hsts, :x_frame_options, :x_content_type_options,
5
- :x_xss_protection, :csp
5
+ :x_xss_protection, :csp, :x_download_options
6
6
 
7
7
  def configure &block
8
8
  instance_eval &block
@@ -38,6 +38,7 @@ module SecureHeaders
38
38
  before_filter :set_csp_header
39
39
  before_filter :set_x_xss_protection_header
40
40
  before_filter :set_x_content_type_options_header
41
+ before_filter :set_x_download_options_header
41
42
  end
42
43
 
43
44
  # we can't use ||= because I'm overloading false => disable, nil => default
@@ -55,6 +56,7 @@ module SecureHeaders
55
56
  set_x_frame_options_header(options[:x_frame_options])
56
57
  set_x_xss_protection_header(options[:x_xss_protection])
57
58
  set_x_content_type_options_header(options[:x_content_type_options])
59
+ set_x_download_options_header(options[:x_download_options])
58
60
  end
59
61
 
60
62
  # backwards compatibility jank, to be removed in 1.0. Old API required a request
@@ -99,6 +101,10 @@ module SecureHeaders
99
101
  set_a_header(:hsts, StrictTransportSecurity, options)
100
102
  end
101
103
 
104
+ def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
105
+ set_a_header(:x_download_options, XDownloadOptions, options)
106
+ end
107
+
102
108
  private
103
109
 
104
110
  def set_a_header(name, klass, options=nil)
@@ -120,7 +126,7 @@ module SecureHeaders
120
126
  end
121
127
  end
122
128
 
123
-
129
+ require "securerandom"
124
130
  require "secure_headers/version"
125
131
  require "secure_headers/header"
126
132
  require "secure_headers/headers/content_security_policy"
@@ -128,4 +134,5 @@ require "secure_headers/headers/x_frame_options"
128
134
  require "secure_headers/headers/strict_transport_security"
129
135
  require "secure_headers/headers/x_xss_protection"
130
136
  require "secure_headers/headers/x_content_type_options"
137
+ require "secure_headers/headers/x_download_options"
131
138
  require "secure_headers/railtie"