secure_headers 2.2.0 → 2.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -1
  4. data/Gemfile +3 -2
  5. data/README.md +89 -18
  6. data/Rakefile +2 -2
  7. data/fixtures/{rails_3_2_12_no_init → rails_3_2_22}/Gemfile +2 -1
  8. data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/initializers/secure_headers.rb +2 -3
  9. data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/other_things_controller_spec.rb +1 -1
  10. data/fixtures/{rails_3_2_12 → rails_3_2_22_no_init}/Gemfile +2 -2
  11. data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +20 -0
  12. data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/other_things_controller_spec.rb +7 -1
  13. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +1 -1
  14. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +2 -3
  15. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +1 -1
  16. data/lib/secure_headers/headers/content_security_policy.rb +41 -41
  17. data/lib/secure_headers/headers/public_key_pins.rb +1 -0
  18. data/lib/secure_headers/headers/strict_transport_security.rb +1 -0
  19. data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
  20. data/lib/secure_headers/headers/x_download_options.rb +1 -0
  21. data/lib/secure_headers/headers/x_frame_options.rb +1 -0
  22. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -0
  23. data/lib/secure_headers/headers/x_xss_protection.rb +1 -0
  24. data/lib/secure_headers/version.rb +1 -1
  25. data/lib/secure_headers.rb +59 -27
  26. data/secure_headers.gemspec +1 -1
  27. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +2 -2
  28. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +64 -36
  29. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +1 -1
  30. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +1 -1
  31. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +1 -1
  32. data/spec/lib/secure_headers_spec.rb +51 -5
  33. data/spec/spec_helper.rb +8 -1
  34. metadata +72 -59
  35. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -6
  36. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/.rspec +0 -0
  37. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/README.rdoc +0 -0
  38. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/Rakefile +0 -0
  39. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/application_controller.rb +0 -0
  40. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/other_things_controller.rb +0 -0
  41. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/things_controller.rb +0 -0
  42. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/models/.gitkeep +0 -0
  43. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/layouts/application.html.erb +0 -0
  44. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/other_things/index.html.erb +0 -0
  45. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/things/index.html.erb +0 -0
  46. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/application.rb +0 -0
  47. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/boot.rb +0 -0
  48. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environment.rb +0 -0
  49. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environments/test.rb +0 -0
  50. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/routes.rb +0 -0
  51. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/script_hashes.yml +0 -0
  52. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config.ru +0 -0
  53. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/assets/.gitkeep +0 -0
  54. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/tasks/.gitkeep +0 -0
  55. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/log/.gitkeep +0 -0
  56. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/things_controller_spec.rb +0 -0
  57. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/spec_helper.rb +0 -0
  58. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/javascripts/.gitkeep +0 -0
  59. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/stylesheets/.gitkeep +0 -0
  60. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/plugins/.gitkeep +0 -0
  61. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/.rspec +0 -0
  62. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/README.rdoc +0 -0
  63. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/Rakefile +0 -0
  64. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/application_controller.rb +0 -0
  65. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/things_controller.rb +0 -0
  66. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/models/.gitkeep +0 -0
  67. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/layouts/application.html.erb +0 -0
  68. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/other_things/index.html.erb +0 -0
  69. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/things/index.html.erb +0 -0
  70. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/application.rb +0 -0
  71. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/boot.rb +0 -0
  72. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environment.rb +0 -0
  73. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environments/test.rb +0 -0
  74. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/routes.rb +0 -0
  75. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config.ru +0 -0
  76. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/assets/.gitkeep +0 -0
  77. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/tasks/.gitkeep +0 -0
  78. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/log/.gitkeep +0 -0
  79. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/things_controller_spec.rb +0 -0
  80. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/spec_helper.rb +0 -0
  81. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/javascripts/.gitkeep +0 -0
  82. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/stylesheets/.gitkeep +0 -0
  83. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/plugins/.gitkeep +0 -0
@@ -1,7 +1,32 @@
1
+ require "secure_headers/version"
2
+ require "secure_headers/header"
3
+ require "secure_headers/headers/public_key_pins"
4
+ require "secure_headers/headers/content_security_policy"
5
+ require "secure_headers/headers/x_frame_options"
6
+ require "secure_headers/headers/strict_transport_security"
7
+ require "secure_headers/headers/x_xss_protection"
8
+ require "secure_headers/headers/x_content_type_options"
9
+ require "secure_headers/headers/x_download_options"
10
+ require "secure_headers/headers/x_permitted_cross_domain_policies"
11
+ require "secure_headers/railtie"
12
+ require "secure_headers/hash_helper"
13
+ require "secure_headers/view_helper"
14
+
1
15
  module SecureHeaders
2
16
  SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
3
17
  HASHES_ENV_KEY = 'secure_headers.script_hashes'
4
18
 
19
+ ALL_HEADER_CLASSES = [
20
+ SecureHeaders::ContentSecurityPolicy,
21
+ SecureHeaders::StrictTransportSecurity,
22
+ SecureHeaders::PublicKeyPins,
23
+ SecureHeaders::XContentTypeOptions,
24
+ SecureHeaders::XDownloadOptions,
25
+ SecureHeaders::XFrameOptions,
26
+ SecureHeaders::XPermittedCrossDomainPolicies,
27
+ SecureHeaders::XXssProtection
28
+ ]
29
+
5
30
  module Configuration
6
31
  class << self
7
32
  attr_accessor :hsts, :x_frame_options, :x_content_type_options,
@@ -24,6 +49,27 @@ module SecureHeaders
24
49
  include InstanceMethods
25
50
  end
26
51
  end
52
+
53
+ def header_hash(options = nil)
54
+ ALL_HEADER_CLASSES.inject({}) do |memo, klass|
55
+ config = if options.is_a?(Hash) && options[klass::Constants::CONFIG_KEY]
56
+ options[klass::Constants::CONFIG_KEY]
57
+ else
58
+ ::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
59
+ end
60
+
61
+ unless klass == SecureHeaders::PublicKeyPins && !config.is_a?(Hash)
62
+ header = get_a_header(klass::Constants::CONFIG_KEY, klass, config)
63
+ memo[header.name] = header.value
64
+ end
65
+ memo
66
+ end
67
+ end
68
+
69
+ def get_a_header(name, klass, options)
70
+ return if options == false
71
+ klass.new(options)
72
+ end
27
73
  end
28
74
 
29
75
  module ClassMethods
@@ -39,6 +85,9 @@ module SecureHeaders
39
85
  end
40
86
 
41
87
  def ensure_security_headers options = {}
88
+ if RUBY_VERSION == "1.8.7"
89
+ warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
90
+ end
42
91
  self.secure_headers_options = options
43
92
  before_filter :prep_script_hash
44
93
  before_filter :set_hsts_header
@@ -50,12 +99,6 @@ module SecureHeaders
50
99
  before_filter :set_x_download_options_header
51
100
  before_filter :set_x_permitted_cross_domain_policies_header
52
101
  end
53
-
54
- # we can't use ||= because I'm overloading false => disable, nil => default
55
- # both of which trigger the conditional assignment
56
- def options_for(type, options)
57
- options.nil? ? ::SecureHeaders::Configuration.send(type) : options
58
- end
59
102
  end
60
103
 
61
104
  module InstanceMethods
@@ -80,7 +123,7 @@ module SecureHeaders
80
123
  end
81
124
 
82
125
  config = self.class.secure_headers_options[:csp] if config.nil?
83
- config = self.class.options_for :csp, config
126
+ config = secure_header_options_for :csp, config
84
127
 
85
128
  return if config == false
86
129
 
@@ -140,7 +183,7 @@ module SecureHeaders
140
183
 
141
184
  def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
142
185
  return unless request.ssl?
143
- config = self.class.options_for :hpkp, options
186
+ config = secure_header_options_for :hpkp, options
144
187
 
145
188
  return if config == false || config.nil?
146
189
 
@@ -158,12 +201,16 @@ module SecureHeaders
158
201
 
159
202
  private
160
203
 
204
+ # we can't use ||= because I'm overloading false => disable, nil => default
205
+ # both of which trigger the conditional assignment
206
+ def secure_header_options_for(type, options)
207
+ options.nil? ? ::SecureHeaders::Configuration.send(type) : options
208
+ end
209
+
161
210
  def set_a_header(name, klass, options=nil)
162
- options = self.class.options_for name, options
211
+ options = secure_header_options_for(name, options)
163
212
  return if options == false
164
-
165
- header = klass.new(options)
166
- set_header(header)
213
+ set_header(SecureHeaders::get_a_header(name, klass, options))
167
214
  end
168
215
 
169
216
  def set_header(name_or_header, value=nil)
@@ -176,18 +223,3 @@ module SecureHeaders
176
223
  end
177
224
  end
178
225
  end
179
-
180
-
181
- require "secure_headers/version"
182
- require "secure_headers/header"
183
- require "secure_headers/headers/public_key_pins"
184
- require "secure_headers/headers/content_security_policy"
185
- require "secure_headers/headers/x_frame_options"
186
- require "secure_headers/headers/strict_transport_security"
187
- require "secure_headers/headers/x_xss_protection"
188
- require "secure_headers/headers/x_content_type_options"
189
- require "secure_headers/headers/x_download_options"
190
- require "secure_headers/headers/x_permitted_cross_domain_policies"
191
- require "secure_headers/railtie"
192
- require "secure_headers/hash_helper"
193
- require "secure_headers/view_helper"
@@ -19,5 +19,5 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
  gem.add_development_dependency "rake"
22
- gem.post_install_message = "Warning: lambda config values will be broken until you add |controller|. e.g. :enforce => lambda { |controller| some_expression }"
22
+ gem.add_dependency "user_agent_parser"
23
23
  end
@@ -13,8 +13,8 @@ module SecureHeaders
13
13
  :disable_fill_missing => true,
14
14
  :default_src => 'https://*',
15
15
  :report_uri => '/csp_report',
16
- :script_src => 'inline eval https://* data:',
17
- :style_src => "inline https://* about:"
16
+ :script_src => "'unsafe-inline' 'unsafe-eval' https://* data:",
17
+ :style_src => "'unsafe-inline' https://* about:"
18
18
  }
19
19
  end
20
20
 
@@ -4,20 +4,21 @@ module SecureHeaders
4
4
  describe ContentSecurityPolicy do
5
5
  let(:default_opts) do
6
6
  {
7
- :disable_fill_missing => true,
8
7
  :default_src => 'https:',
9
8
  :report_uri => '/csp_report',
10
- :script_src => 'inline eval https: data:',
11
- :style_src => "inline https: about:"
9
+ :script_src => "'unsafe-inline' 'unsafe-eval' https: data:",
10
+ :style_src => "'unsafe-inline' https: about:"
12
11
  }
13
12
  end
13
+ let(:controller) { DummyClass.new }
14
14
 
15
15
  IE = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"
16
16
  FIREFOX = "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8"
17
17
  FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
18
18
  CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
19
19
  CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
20
-
20
+ SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"
21
+ OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
21
22
 
22
23
  def request_for user_agent, request_uri=nil, options={:ssl => false}
23
24
  double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
@@ -55,6 +56,23 @@ module SecureHeaders
55
56
  end
56
57
  end
57
58
 
59
+ it "exports a policy to JSON" do
60
+ policy = ContentSecurityPolicy.new(default_opts)
61
+ expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
62
+ expect(policy.to_json).to eq(expected)
63
+ end
64
+
65
+ it "imports JSON to build a policy" do
66
+ json1 = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"]})
67
+ json2 = %({"style-src":["'unsafe-inline'"],"img-src":["https:","data:"]})
68
+ json3 = %({"style-src":["https:","about:"]})
69
+ config = ContentSecurityPolicy.from_json(json1, json2, json3)
70
+ policy = ContentSecurityPolicy.new(config)
71
+
72
+ expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
73
+ expect(policy.to_json).to eq(expected)
74
+ end
75
+
58
76
  context "when using hash sources" do
59
77
  it "adds hashes and unsafe-inline to the script-src" do
60
78
  policy = ContentSecurityPolicy.new(default_opts.merge(:script_hashes => ['sha256-abc123']))
@@ -64,8 +82,7 @@ module SecureHeaders
64
82
 
65
83
  describe "#normalize_csp_options" do
66
84
  before(:each) do
67
- default_opts.delete(:disable_fill_missing)
68
- default_opts[:script_src] << ' self none'
85
+ default_opts[:script_src] << " 'self' 'none'"
69
86
  @opts = default_opts
70
87
  end
71
88
 
@@ -89,7 +106,7 @@ module SecureHeaders
89
106
 
90
107
  it "accepts procs for report-uris" do
91
108
  opts = {
92
- :default_src => 'self',
109
+ :default_src => "'self'",
93
110
  :report_uri => proc { "http://lambda/result" }
94
111
  }
95
112
 
@@ -101,7 +118,6 @@ module SecureHeaders
101
118
  opts = {
102
119
  :default_src => proc { "http://lambda/result" },
103
120
  :enforce => proc { true },
104
- :disable_fill_missing => proc { true }
105
121
  }
106
122
 
107
123
  csp = ContentSecurityPolicy.new(opts)
@@ -115,8 +131,7 @@ module SecureHeaders
115
131
 
116
132
  allow(controller).to receive(:current_user).and_return(user)
117
133
  opts = {
118
- :disable_fill_missing => true,
119
- :default_src => "self",
134
+ :default_src => "'self'",
120
135
  :enforce => lambda { |c| c.current_user.beta_testing? }
121
136
  }
122
137
  csp = ContentSecurityPolicy.new(opts, :controller => controller)
@@ -133,35 +148,26 @@ module SecureHeaders
133
148
  }.to raise_error(RuntimeError)
134
149
  end
135
150
 
136
- context "CSP level 2 directives" do
137
- let(:config) { {:default_src => 'self'} }
138
- ::SecureHeaders::ContentSecurityPolicy::Constants::NON_DEFAULT_SOURCES.each do |non_default_source|
139
- it "supports all level 2 directives" do
140
- directive_name = ::SecureHeaders::ContentSecurityPolicy.send(:symbol_to_hyphen_case, non_default_source)
141
- config.merge!({ non_default_source => "value" })
142
- csp = ContentSecurityPolicy.new(config, :request => request_for(CHROME))
143
- expect(csp.value).to match(/#{directive_name} value;/)
144
- end
145
- end
146
- end
147
-
148
151
  context "auto-whitelists data: uris for img-src" do
149
152
  it "sets the value if no img-src specified" do
150
- csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
153
+ csp = ContentSecurityPolicy.new({:default_src => "'self'"}, :request => request_for(CHROME))
151
154
  expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
152
155
  end
153
156
 
154
157
  it "appends the value if img-src is specified" do
155
- csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
158
+ csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'"}, :request => request_for(CHROME))
156
159
  expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
157
160
  end
158
- end
159
161
 
160
- it "fills in directives without values with default-src value" do
161
- options = default_opts.merge(:disable_fill_missing => false)
162
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
163
- value = "default-src https:; connect-src https:; font-src https:; frame-src https:; img-src https: data:; media-src https:; object-src https:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;"
164
- expect(csp.value).to eq(value)
162
+ it "doesn't add a duplicate data uri if img-src specifies it already" do
163
+ csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self' data:"}, :request => request_for(CHROME))
164
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
165
+ end
166
+
167
+ it "allows the user to disable img-src data: uris auto-whitelisting" do
168
+ csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'", :disable_img_src_data_uri => true}, :request => request_for(CHROME))
169
+ expect(csp.value).to eq("default-src 'self'; img-src 'self';")
170
+ end
165
171
  end
166
172
 
167
173
  it "sends the standard csp header if an unknown browser is supplied" do
@@ -184,18 +190,40 @@ module SecureHeaders
184
190
  end
185
191
 
186
192
  context "when using a nonce" do
187
- it "adds a nonce and unsafe-inline to the script-src value" do
188
- header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
193
+ it "adds a nonce and unsafe-inline to the script-src value when using chrome" do
194
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
195
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
196
+ end
197
+
198
+ it "adds a nonce and unsafe-inline to the script-src value when using firefox" do
199
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(FIREFOX), :controller => controller)
200
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
201
+ end
202
+
203
+ it "adds a nonce and unsafe-inline to the script-src value when using opera" do
204
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(OPERA), :controller => controller)
189
205
  expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
190
206
  end
191
207
 
208
+ it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do
209
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(SAFARI), :controller => controller)
210
+ expect(header.value).to include("script-src 'self' 'unsafe-inline'")
211
+ expect(header.value).not_to include("nonce")
212
+ end
213
+
214
+ it "does not add a nonce and unsafe-inline to the script-src value when using IE" do
215
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(IE), :controller => controller)
216
+ expect(header.value).to include("script-src 'self' 'unsafe-inline'")
217
+ expect(header.value).not_to include("nonce")
218
+ end
219
+
192
220
  it "adds a nonce and unsafe-inline to the style-src value" do
193
- header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
221
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
194
222
  expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
195
223
  end
196
224
 
197
225
  it "adds an identical nonce to the style and script-src directives" do
198
- header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
226
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce", :script_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
199
227
  nonce = header.nonce
200
228
  value = header.value
201
229
  expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
@@ -203,7 +231,7 @@ module SecureHeaders
203
231
  end
204
232
 
205
233
  it "does not add 'unsafe-inline' twice" do
206
- header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
234
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce 'unsafe-inline'"), :request => request_for(CHROME), :controller => controller)
207
235
  expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
208
236
  end
209
237
  end
@@ -251,7 +279,7 @@ module SecureHeaders
251
279
 
252
280
  describe ".add_to_env" do
253
281
  let(:controller) { double }
254
- let(:config) { {:default_src => 'self'} }
282
+ let(:config) { {:default_src => "'self'"} }
255
283
  let(:options) { {:controller => controller} }
256
284
 
257
285
  it "adds metadata to env" do
@@ -27,7 +27,7 @@ module SecureHeaders
27
27
  it "doesn't accept anything besides no-sniff" do
28
28
  expect {
29
29
  XContentTypeOptions.new("donkey")
30
- }.to raise_error
30
+ }.to raise_error(XContentTypeOptionsBuildError)
31
31
  end
32
32
  end
33
33
  end
@@ -25,7 +25,7 @@ module SecureHeaders
25
25
  it "doesn't accept anything besides noopen" do
26
26
  expect {
27
27
  XDownloadOptions.new("open")
28
- }.to raise_error
28
+ }.to raise_error(XDOBuildError)
29
29
  end
30
30
  end
31
31
  end
@@ -57,7 +57,7 @@ module SecureHeaders
57
57
  it "doesn't accept invalid values" do
58
58
  expect {
59
59
  XPermittedCrossDomainPolicies.new("open")
60
- }.to raise_error
60
+ }.to raise_error(XPCDPBuildError)
61
61
  end
62
62
  end
63
63
  end
@@ -1,10 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe SecureHeaders do
4
- class DummyClass
5
- include ::SecureHeaders
6
- end
7
-
8
4
  subject {DummyClass.new}
9
5
  let(:headers) {double}
10
6
  let(:response) {double(:headers => headers)}
@@ -131,7 +127,7 @@ describe SecureHeaders do
131
127
 
132
128
  it "saves the options to the env when using script hashes" do
133
129
  opts = {
134
- :default_src => 'self',
130
+ :default_src => "'self'",
135
131
  :script_hash_middleware => true
136
132
  }
137
133
  stub_user_agent(USER_AGENTS[:chrome])
@@ -159,6 +155,56 @@ describe SecureHeaders do
159
155
  end
160
156
  end
161
157
 
158
+ describe "SecureHeaders#header_hash" do
159
+ def expect_default_values(hash)
160
+ expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
161
+ expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
162
+ expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
163
+ expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
164
+ expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
165
+ expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
166
+ end
167
+
168
+ it "produces a hash of headers given a hash as config" do
169
+ hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:", :disable_fill_missing => true})
170
+ expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
171
+ expect_default_values(hash)
172
+ end
173
+
174
+ it "produces a hash with a mix of config values, override values, and default values" do
175
+ ::SecureHeaders::Configuration.configure do |config|
176
+ config.hsts = { :max_age => '123456'}
177
+ config.hpkp = {
178
+ :enforce => true,
179
+ :max_age => 1000000,
180
+ :include_subdomains => true,
181
+ :report_uri => '//example.com/uri-directive',
182
+ :pins => [
183
+ {:sha256 => 'abc'},
184
+ {:sha256 => '123'}
185
+ ]
186
+ }
187
+ end
188
+
189
+ hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:", :disable_fill_missing => true})
190
+ ::SecureHeaders::Configuration.configure do |config|
191
+ config.hsts = nil
192
+ config.hpkp = nil
193
+ end
194
+
195
+ expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
196
+ expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
197
+ expect(hash[HSTS_HEADER_NAME]).to eq("max-age=123456")
198
+ expect(hash[HPKP_HEADER_NAME]).to eq(%{max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains})
199
+ end
200
+
201
+ it "produces a hash of headers with default config" do
202
+ hash = SecureHeaders::header_hash
203
+ expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
204
+ expect_default_values(hash)
205
+ end
206
+ end
207
+
162
208
  describe "#set_x_frame_options_header" do
163
209
  it "sets the X-Frame-Options header" do
164
210
  should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
data/spec/spec_helper.rb CHANGED
@@ -3,8 +3,11 @@ require 'rspec'
3
3
 
4
4
  require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
5
5
 
6
- if defined?(Coveralls)
6
+ begin
7
+ require 'coveralls'
7
8
  Coveralls.wear!
9
+ rescue LoadError
10
+ # damn you 1.8.7
8
11
  end
9
12
 
10
13
  include ::SecureHeaders::PublicKeyPins::Constants
@@ -28,6 +31,10 @@ USER_AGENTS = {
28
31
  :safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
29
32
  }
30
33
 
34
+ class DummyClass
35
+ include ::SecureHeaders
36
+ end
37
+
31
38
  def should_assign_header name, value
32
39
  expect(response.headers).to receive(:[]=).with(name, value)
33
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-18 00:00:00.000000000 Z
11
+ date: 2015-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: user_agent_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: Security related headers all in one gem.
28
42
  email:
29
43
  - neil.matatall@gmail.com
@@ -39,60 +53,60 @@ files:
39
53
  - LICENSE
40
54
  - README.md
41
55
  - Rakefile
42
- - fixtures/rails_3_2_12/.rspec
43
- - fixtures/rails_3_2_12/Gemfile
44
- - fixtures/rails_3_2_12/README.rdoc
45
- - fixtures/rails_3_2_12/Rakefile
46
- - fixtures/rails_3_2_12/app/controllers/application_controller.rb
47
- - fixtures/rails_3_2_12/app/controllers/other_things_controller.rb
48
- - fixtures/rails_3_2_12/app/controllers/things_controller.rb
49
- - fixtures/rails_3_2_12/app/models/.gitkeep
50
- - fixtures/rails_3_2_12/app/views/layouts/application.html.erb
51
- - fixtures/rails_3_2_12/app/views/other_things/index.html.erb
52
- - fixtures/rails_3_2_12/app/views/things/index.html.erb
53
- - fixtures/rails_3_2_12/config.ru
54
- - fixtures/rails_3_2_12/config/application.rb
55
- - fixtures/rails_3_2_12/config/boot.rb
56
- - fixtures/rails_3_2_12/config/environment.rb
57
- - fixtures/rails_3_2_12/config/environments/test.rb
58
- - fixtures/rails_3_2_12/config/initializers/secure_headers.rb
59
- - fixtures/rails_3_2_12/config/routes.rb
60
- - fixtures/rails_3_2_12/config/script_hashes.yml
61
- - fixtures/rails_3_2_12/lib/assets/.gitkeep
62
- - fixtures/rails_3_2_12/lib/tasks/.gitkeep
63
- - fixtures/rails_3_2_12/log/.gitkeep
64
- - fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb
65
- - fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb
66
- - fixtures/rails_3_2_12/spec/spec_helper.rb
67
- - fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep
68
- - fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep
69
- - fixtures/rails_3_2_12/vendor/plugins/.gitkeep
70
- - fixtures/rails_3_2_12_no_init/.rspec
71
- - fixtures/rails_3_2_12_no_init/Gemfile
72
- - fixtures/rails_3_2_12_no_init/README.rdoc
73
- - fixtures/rails_3_2_12_no_init/Rakefile
74
- - fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb
75
- - fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb
76
- - fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb
77
- - fixtures/rails_3_2_12_no_init/app/models/.gitkeep
78
- - fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb
79
- - fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb
80
- - fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb
81
- - fixtures/rails_3_2_12_no_init/config.ru
82
- - fixtures/rails_3_2_12_no_init/config/application.rb
83
- - fixtures/rails_3_2_12_no_init/config/boot.rb
84
- - fixtures/rails_3_2_12_no_init/config/environment.rb
85
- - fixtures/rails_3_2_12_no_init/config/environments/test.rb
86
- - fixtures/rails_3_2_12_no_init/config/routes.rb
87
- - fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep
88
- - fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep
89
- - fixtures/rails_3_2_12_no_init/log/.gitkeep
90
- - fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb
91
- - fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb
92
- - fixtures/rails_3_2_12_no_init/spec/spec_helper.rb
93
- - fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep
94
- - fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep
95
- - fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep
56
+ - fixtures/rails_3_2_22/.rspec
57
+ - fixtures/rails_3_2_22/Gemfile
58
+ - fixtures/rails_3_2_22/README.rdoc
59
+ - fixtures/rails_3_2_22/Rakefile
60
+ - fixtures/rails_3_2_22/app/controllers/application_controller.rb
61
+ - fixtures/rails_3_2_22/app/controllers/other_things_controller.rb
62
+ - fixtures/rails_3_2_22/app/controllers/things_controller.rb
63
+ - fixtures/rails_3_2_22/app/models/.gitkeep
64
+ - fixtures/rails_3_2_22/app/views/layouts/application.html.erb
65
+ - fixtures/rails_3_2_22/app/views/other_things/index.html.erb
66
+ - fixtures/rails_3_2_22/app/views/things/index.html.erb
67
+ - fixtures/rails_3_2_22/config.ru
68
+ - fixtures/rails_3_2_22/config/application.rb
69
+ - fixtures/rails_3_2_22/config/boot.rb
70
+ - fixtures/rails_3_2_22/config/environment.rb
71
+ - fixtures/rails_3_2_22/config/environments/test.rb
72
+ - fixtures/rails_3_2_22/config/initializers/secure_headers.rb
73
+ - fixtures/rails_3_2_22/config/routes.rb
74
+ - fixtures/rails_3_2_22/config/script_hashes.yml
75
+ - fixtures/rails_3_2_22/lib/assets/.gitkeep
76
+ - fixtures/rails_3_2_22/lib/tasks/.gitkeep
77
+ - fixtures/rails_3_2_22/log/.gitkeep
78
+ - fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb
79
+ - fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb
80
+ - fixtures/rails_3_2_22/spec/spec_helper.rb
81
+ - fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep
82
+ - fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep
83
+ - fixtures/rails_3_2_22/vendor/plugins/.gitkeep
84
+ - fixtures/rails_3_2_22_no_init/.rspec
85
+ - fixtures/rails_3_2_22_no_init/Gemfile
86
+ - fixtures/rails_3_2_22_no_init/README.rdoc
87
+ - fixtures/rails_3_2_22_no_init/Rakefile
88
+ - fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb
89
+ - fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb
90
+ - fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb
91
+ - fixtures/rails_3_2_22_no_init/app/models/.gitkeep
92
+ - fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb
93
+ - fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb
94
+ - fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb
95
+ - fixtures/rails_3_2_22_no_init/config.ru
96
+ - fixtures/rails_3_2_22_no_init/config/application.rb
97
+ - fixtures/rails_3_2_22_no_init/config/boot.rb
98
+ - fixtures/rails_3_2_22_no_init/config/environment.rb
99
+ - fixtures/rails_3_2_22_no_init/config/environments/test.rb
100
+ - fixtures/rails_3_2_22_no_init/config/routes.rb
101
+ - fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep
102
+ - fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep
103
+ - fixtures/rails_3_2_22_no_init/log/.gitkeep
104
+ - fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb
105
+ - fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb
106
+ - fixtures/rails_3_2_22_no_init/spec/spec_helper.rb
107
+ - fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep
108
+ - fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep
109
+ - fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep
96
110
  - fixtures/rails_4_1_8/Gemfile
97
111
  - fixtures/rails_4_1_8/README.rdoc
98
112
  - fixtures/rails_4_1_8/Rakefile
@@ -156,8 +170,7 @@ homepage: https://github.com/twitter/secureheaders
156
170
  licenses:
157
171
  - Apache Public License 2.0
158
172
  metadata: {}
159
- post_install_message: 'Warning: lambda config values will be broken until you add
160
- |controller|. e.g. :enforce => lambda { |controller| some_expression }'
173
+ post_install_message:
161
174
  rdoc_options: []
162
175
  require_paths:
163
176
  - lib
@@ -173,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
186
  version: '0'
174
187
  requirements: []
175
188
  rubyforge_project:
176
- rubygems_version: 2.2.3
189
+ rubygems_version: 2.4.5.1
177
190
  signing_key:
178
191
  specification_version: 4
179
192
  summary: Add easily configured security headers to responses including content-security-policy,