secure_headers 3.2.0 → 4.0.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +41 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +3 -0
  6. data/.ruby-version +1 -1
  7. data/.travis.yml +8 -4
  8. data/CHANGELOG.md +144 -1
  9. data/CODE_OF_CONDUCT.md +46 -0
  10. data/CONTRIBUTING.md +41 -0
  11. data/Gemfile +9 -4
  12. data/Guardfile +1 -0
  13. data/LICENSE +4 -199
  14. data/README.md +73 -344
  15. data/Rakefile +22 -18
  16. data/docs/HPKP.md +17 -0
  17. data/docs/cookies.md +64 -0
  18. data/docs/hashes.md +64 -0
  19. data/docs/named_overrides_and_appends.md +107 -0
  20. data/docs/per_action_configuration.md +133 -0
  21. data/docs/sinatra.md +25 -0
  22. data/lib/secure_headers/configuration.rb +116 -42
  23. data/lib/secure_headers/hash_helper.rb +2 -1
  24. data/lib/secure_headers/headers/clear_site_data.rb +55 -0
  25. data/lib/secure_headers/headers/content_security_policy.rb +128 -39
  26. data/lib/secure_headers/headers/content_security_policy_config.rb +162 -0
  27. data/lib/secure_headers/headers/cookie.rb +21 -3
  28. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  29. data/lib/secure_headers/headers/policy_management.rb +158 -68
  30. data/lib/secure_headers/headers/public_key_pins.rb +5 -4
  31. data/lib/secure_headers/headers/referrer_policy.rb +37 -0
  32. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  33. data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
  34. data/lib/secure_headers/headers/x_download_options.rb +3 -2
  35. data/lib/secure_headers/headers/x_frame_options.rb +2 -1
  36. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +3 -2
  37. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  38. data/lib/secure_headers/middleware.rb +16 -9
  39. data/lib/secure_headers/railtie.rb +7 -6
  40. data/lib/secure_headers/utils/cookies_config.rb +13 -12
  41. data/lib/secure_headers/view_helper.rb +15 -8
  42. data/lib/secure_headers.rb +139 -55
  43. data/lib/tasks/tasks.rake +7 -5
  44. data/secure_headers.gemspec +13 -3
  45. data/spec/lib/secure_headers/configuration_spec.rb +13 -12
  46. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +87 -0
  47. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +89 -15
  48. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  49. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  50. data/spec/lib/secure_headers/headers/policy_management_spec.rb +80 -42
  51. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  52. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +73 -0
  53. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  54. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  55. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  56. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  57. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  58. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  59. data/spec/lib/secure_headers/middleware_spec.rb +55 -17
  60. data/spec/lib/secure_headers/view_helpers_spec.rb +21 -8
  61. data/spec/lib/secure_headers_spec.rb +330 -58
  62. data/spec/spec_helper.rb +17 -25
  63. data/upgrading-to-3-0.md +13 -10
  64. data/upgrading-to-4-0.md +55 -0
  65. metadata +34 -7
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe SecureHeaders do
@@ -16,19 +17,30 @@ module SecureHeaders
16
17
 
17
18
  it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
18
19
  expect do
19
- SecureHeaders.opt_out_of_header(request, CSP::CONFIG_KEY)
20
+ SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
20
21
  end.to raise_error(Configuration::NotYetConfiguredError)
21
22
  end
22
23
 
24
+ it "raises and ArgumentError when referencing an override that has not been set" do
25
+ expect do
26
+ Configuration.default
27
+ SecureHeaders.use_secure_headers_override(request, :missing)
28
+ end.to raise_error(ArgumentError)
29
+ end
30
+
23
31
  describe "#header_hash_for" do
24
32
  it "allows you to opt out of individual headers via API" do
25
- Configuration.default
26
- SecureHeaders.opt_out_of_header(request, CSP::CONFIG_KEY)
33
+ Configuration.default do |config|
34
+ config.csp = { default_src: %w('self'), script_src: %w('self')}
35
+ config.csp_report_only = config.csp
36
+ end
37
+ SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
38
+ SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
27
39
  SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
28
40
  hash = SecureHeaders.header_hash_for(request)
29
- expect(hash['Content-Security-Policy-Report-Only']).to be_nil
30
- expect(hash['Content-Security-Policy']).to be_nil
31
- expect(hash['X-Content-Type-Options']).to be_nil
41
+ expect(hash["Content-Security-Policy-Report-Only"]).to be_nil
42
+ expect(hash["Content-Security-Policy"]).to be_nil
43
+ expect(hash["X-Content-Type-Options"]).to be_nil
32
44
  end
33
45
 
34
46
  it "Carries options over when using overrides" do
@@ -43,13 +55,27 @@ module SecureHeaders
43
55
 
44
56
  SecureHeaders.use_secure_headers_override(request, :api)
45
57
  hash = SecureHeaders.header_hash_for(request)
46
- expect(hash['X-Download-Options']).to be_nil
47
- expect(hash['X-Permitted-Cross-Domain-Policies']).to be_nil
48
- expect(hash['X-Frame-Options']).to be_nil
58
+ expect(hash["X-Download-Options"]).to be_nil
59
+ expect(hash["X-Permitted-Cross-Domain-Policies"]).to be_nil
60
+ expect(hash["X-Frame-Options"]).to be_nil
49
61
  end
50
62
 
51
63
  it "allows you to opt out entirely" do
52
- Configuration.default
64
+ # configure the disabled-by-default headers to ensure they also do not get set
65
+ Configuration.default do |config|
66
+ config.csp = { default_src: ["example.com"], script_src: %w('self') }
67
+ config.csp_report_only = config.csp
68
+ config.hpkp = {
69
+ report_only: false,
70
+ max_age: 10000000,
71
+ include_subdomains: true,
72
+ report_uri: "https://report-uri.io/example-hpkp",
73
+ pins: [
74
+ {sha256: "abc"},
75
+ {sha256: "123"}
76
+ ]
77
+ }
78
+ end
53
79
  SecureHeaders.opt_out_of_all_protection(request)
54
80
  hash = SecureHeaders.header_hash_for(request)
55
81
  ALL_HEADER_CLASSES.each do |klass|
@@ -75,27 +101,29 @@ module SecureHeaders
75
101
  SecureHeaders.override_content_security_policy_directives(request, default_src: %w(https:), script_src: %w('self'))
76
102
 
77
103
  hash = SecureHeaders.header_hash_for(request)
78
- expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
104
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
79
105
  expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
80
106
  end
81
107
 
82
108
  it "produces a UA-specific CSP when overriding (and busting the cache)" do
83
- config = Configuration.default do |config|
109
+ Configuration.default do |config|
84
110
  config.csp = {
85
111
  default_src: %w('self'),
86
- child_src: %w('self'), #unsupported by firefox
87
- frame_src: %w('self')
112
+ script_src: %w('self'),
113
+ child_src: %w('self')
88
114
  }
89
115
  end
90
116
  firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
91
117
 
92
118
  # append an unsupported directive
93
- SecureHeaders.override_content_security_policy_directives(firefox_request, plugin_types: %w(flash))
119
+ SecureHeaders.override_content_security_policy_directives(firefox_request, {plugin_types: %w(flash)})
94
120
  # append a supported directive
95
- SecureHeaders.override_content_security_policy_directives(firefox_request, script_src: %w('self'))
121
+ SecureHeaders.override_content_security_policy_directives(firefox_request, {script_src: %w('self')})
96
122
 
97
123
  hash = SecureHeaders.header_hash_for(firefox_request)
98
- expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
124
+
125
+ # child-src is translated to frame-src
126
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
99
127
  end
100
128
 
101
129
  it "produces a hash of headers with default config" do
@@ -118,10 +146,10 @@ module SecureHeaders
118
146
  config.hpkp = {
119
147
  max_age: 1_000_000,
120
148
  include_subdomains: true,
121
- report_uri: '//example.com/uri-directive',
149
+ report_uri: "//example.com/uri-directive",
122
150
  pins: [
123
- { sha256: 'abc' },
124
- { sha256: '123' }
151
+ { sha256: "abc" },
152
+ { sha256: "123" }
125
153
  ]
126
154
  }
127
155
  end
@@ -130,6 +158,10 @@ module SecureHeaders
130
158
  end
131
159
 
132
160
  context "content security policy" do
161
+ let(:chrome_request) {
162
+ Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
163
+ }
164
+
133
165
  it "appends a value to csp directive" do
134
166
  Configuration.default do |config|
135
167
  config.csp = {
@@ -140,66 +172,97 @@ module SecureHeaders
140
172
 
141
173
  SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
142
174
  hash = SecureHeaders.header_hash_for(request)
143
- expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
175
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
144
176
  end
145
177
 
146
- it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
178
+ it "child-src and frame-src must match" do
147
179
  Configuration.default do |config|
148
180
  config.csp = {
149
- default_src: %w('self')
181
+ default_src: %w('self'),
182
+ frame_src: %w(frame_src.com),
183
+ script_src: %w('self')
150
184
  }
151
185
  end
152
186
 
153
- expect(CSP).to receive(:idempotent_additions?).once
187
+ SecureHeaders.append_content_security_policy_directives(chrome_request, child_src: %w(child_src.com))
154
188
 
155
- # before an override occurs, the env is empty
156
- expect(request.env[SECURE_HEADERS_CONFIG]).to be_nil
189
+ expect {
190
+ SecureHeaders.header_hash_for(chrome_request)
191
+ }.to raise_error(ArgumentError)
192
+ end
157
193
 
158
- SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
159
- new_config = SecureHeaders.config_for(request)
160
- expect(new_config).to_not be(Configuration.get)
194
+ it "supports named appends" do
195
+ Configuration.default do |config|
196
+ config.csp = {
197
+ default_src: %w('self'),
198
+ script_src: %w('self')
199
+ }
200
+ end
201
+
202
+ Configuration.named_append(:moar_default_sources) do |request|
203
+ { default_src: %w(https:), style_src: %w('self')}
204
+ end
161
205
 
162
- SecureHeaders.override_content_security_policy_directives(request, script_src: %w(yet.anothercdn.com))
163
- current_config = SecureHeaders.config_for(request)
164
- expect(current_config).to be(new_config)
206
+ Configuration.named_append(:how_about_a_script_src_too) do |request|
207
+ { script_src: %w('unsafe-inline')}
208
+ end
209
+
210
+ SecureHeaders.use_content_security_policy_named_append(request, :moar_default_sources)
211
+ SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
212
+ hash = SecureHeaders.header_hash_for(request)
165
213
 
166
- SecureHeaders.header_hash_for(request)
214
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' 'unsafe-inline'; style-src 'self'")
167
215
  end
168
216
 
169
- it "doesn't allow you to muck with csp configs when a dynamic policy is in use" do
170
- default_config = Configuration.default
171
- expect { default_config.csp = {} }.to raise_error(NoMethodError)
217
+ it "appends a nonce to a missing script-src value" do
218
+ Configuration.default do |config|
219
+ config.csp = {
220
+ default_src: %w('self'),
221
+ script_src: %w('self')
222
+ }
223
+ end
172
224
 
173
- # config is frozen
174
- expect { default_config.send(:csp=, {}) }.to raise_error(RuntimeError)
225
+ SecureHeaders.content_security_policy_script_nonce(request) # should add the value to the header
226
+ hash = SecureHeaders.header_hash_for(chrome_request)
227
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/)
228
+ end
175
229
 
176
- SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
177
- new_config = SecureHeaders.config_for(request)
178
- expect { new_config.send(:csp=, {}) }.to raise_error(Configuration::IllegalPolicyModificationError)
230
+ it "appends a hash to a missing script-src value" do
231
+ Configuration.default do |config|
232
+ config.csp = {
233
+ default_src: %w('self'),
234
+ script_src: %w('self')
235
+ }
236
+ end
179
237
 
180
- expect do
181
- new_config.instance_eval do
182
- new_config.csp = {}
183
- end
184
- end.to raise_error(Configuration::IllegalPolicyModificationError)
238
+ SecureHeaders.append_content_security_policy_directives(request, script_src: %w('sha256-abc123'))
239
+ hash = SecureHeaders.header_hash_for(chrome_request)
240
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/)
185
241
  end
186
242
 
187
243
  it "overrides individual directives" do
188
244
  Configuration.default do |config|
189
245
  config.csp = {
190
- default_src: %w('self')
246
+ default_src: %w('self'),
247
+ script_src: %w('self')
191
248
  }
192
249
  end
193
250
  SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
194
251
  hash = SecureHeaders.header_hash_for(request)
195
- expect(hash[CSP::HEADER_NAME]).to eq("default-src 'none'")
252
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'; script-src 'self'")
196
253
  end
197
254
 
198
255
  it "overrides non-existant directives" do
199
- Configuration.default
256
+ Configuration.default do |config|
257
+ config.csp = {
258
+ default_src: %w(https:),
259
+ script_src: %w('self')
260
+ }
261
+ end
200
262
  SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
201
263
  hash = SecureHeaders.header_hash_for(request)
202
- expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; img-src data:")
264
+ expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
265
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:; script-src 'self'")
203
266
  end
204
267
 
205
268
  it "does not append a nonce when the browser does not support it" do
@@ -212,9 +275,9 @@ module SecureHeaders
212
275
  end
213
276
 
214
277
  safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
215
- nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
278
+ SecureHeaders.content_security_policy_script_nonce(safari_request)
216
279
  hash = SecureHeaders.header_hash_for(safari_request)
217
- expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
280
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
218
281
  end
219
282
 
220
283
  it "appends a nonce to the script-src when used" do
@@ -226,7 +289,6 @@ module SecureHeaders
226
289
  }
227
290
  end
228
291
 
229
- chrome_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
230
292
  nonce = SecureHeaders.content_security_policy_script_nonce(chrome_request)
231
293
 
232
294
  # simulate the nonce being used multiple times in a request:
@@ -235,7 +297,193 @@ module SecureHeaders
235
297
  SecureHeaders.content_security_policy_script_nonce(chrome_request)
236
298
 
237
299
  hash = SecureHeaders.header_hash_for(chrome_request)
238
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
300
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
301
+ end
302
+
303
+ it "uses a nonce for safari 10+" do
304
+ Configuration.default do |config|
305
+ config.csp = {
306
+ default_src: %w('self'),
307
+ script_src: %w(mycdn.com)
308
+ }
309
+ end
310
+
311
+ safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
312
+ nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
313
+ hash = SecureHeaders.header_hash_for(safari_request)
314
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
315
+ end
316
+
317
+ it "does not support the deprecated `report_only: true` format" do
318
+ expect {
319
+ Configuration.default do |config|
320
+ config.csp = {
321
+ default_src: %w('self'),
322
+ report_only: true
323
+ }
324
+ end
325
+ }.to raise_error(ArgumentError)
326
+ end
327
+
328
+ it "Raises an error if csp_report_only is used with `report_only: false`" do
329
+ expect do
330
+ Configuration.default do |config|
331
+ config.csp_report_only = {
332
+ default_src: %w('self'),
333
+ script_src: %w('self'),
334
+ report_only: false
335
+ }
336
+ end
337
+ end.to raise_error(ContentSecurityPolicyConfigError)
338
+ end
339
+
340
+ context "setting two headers" do
341
+ before(:each) do
342
+ Configuration.default do |config|
343
+ config.csp = {
344
+ default_src: %w('self'),
345
+ script_src: %w('self')
346
+ }
347
+ config.csp_report_only = config.csp
348
+ end
349
+ end
350
+
351
+ it "sets identical values when the configs are the same" do
352
+ Configuration.default do |config|
353
+ config.csp = {
354
+ default_src: %w('self'),
355
+ script_src: %w('self')
356
+ }
357
+ config.csp_report_only = {
358
+ default_src: %w('self'),
359
+ script_src: %w('self')
360
+ }
361
+ end
362
+
363
+ hash = SecureHeaders.header_hash_for(request)
364
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
365
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
366
+ end
367
+
368
+ it "sets different headers when the configs are different" do
369
+ Configuration.default do |config|
370
+ config.csp = {
371
+ default_src: %w('self'),
372
+ script_src: %w('self')
373
+ }
374
+ config.csp_report_only = config.csp.merge({script_src: %w(foo.com)})
375
+ end
376
+
377
+ hash = SecureHeaders.header_hash_for(request)
378
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
379
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' foo.com")
380
+ end
381
+
382
+ it "allows you to opt-out of enforced CSP" do
383
+ Configuration.default do |config|
384
+ config.csp = SecureHeaders::OPT_OUT
385
+ config.csp_report_only = {
386
+ default_src: %w('self'),
387
+ script_src: %w('self')
388
+ }
389
+ end
390
+
391
+ hash = SecureHeaders.header_hash_for(request)
392
+ expect(hash["Content-Security-Policy"]).to be_nil
393
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
394
+ end
395
+
396
+ it "allows appending to the enforced policy" do
397
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
398
+ hash = SecureHeaders.header_hash_for(request)
399
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
400
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
401
+ end
402
+
403
+ it "allows appending to the report only policy" do
404
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
405
+ hash = SecureHeaders.header_hash_for(request)
406
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
407
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
408
+ end
409
+
410
+ it "allows appending to both policies" do
411
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
412
+ hash = SecureHeaders.header_hash_for(request)
413
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
414
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
415
+ end
416
+
417
+ it "allows overriding the enforced policy" do
418
+ SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
419
+ hash = SecureHeaders.header_hash_for(request)
420
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src anothercdn.com")
421
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
422
+ end
423
+
424
+ it "allows overriding the report only policy" do
425
+ SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
426
+ hash = SecureHeaders.header_hash_for(request)
427
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
428
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src anothercdn.com")
429
+ end
430
+
431
+ it "allows overriding both policies" do
432
+ SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
433
+ hash = SecureHeaders.header_hash_for(request)
434
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src anothercdn.com")
435
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src anothercdn.com")
436
+ end
437
+
438
+ context "when inferring which config to modify" do
439
+ it "updates the enforced header when configured" do
440
+ Configuration.default do |config|
441
+ config.csp = {
442
+ default_src: %w('self'),
443
+ script_src: %w('self')
444
+ }
445
+ end
446
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
447
+
448
+ hash = SecureHeaders.header_hash_for(request)
449
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
450
+ expect(hash["Content-Security-Policy-Report-Only"]).to be_nil
451
+ end
452
+
453
+ it "updates the report only header when configured" do
454
+ Configuration.default do |config|
455
+ config.csp = OPT_OUT
456
+ config.csp_report_only = {
457
+ default_src: %w('self'),
458
+ script_src: %w('self')
459
+ }
460
+ end
461
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
462
+
463
+ hash = SecureHeaders.header_hash_for(request)
464
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
465
+ expect(hash["Content-Security-Policy"]).to be_nil
466
+ end
467
+
468
+ it "updates both headers if both are configured" do
469
+ Configuration.default do |config|
470
+ config.csp = {
471
+ default_src: %w(enforced.com),
472
+ script_src: %w('self')
473
+ }
474
+ config.csp_report_only = {
475
+ default_src: %w(reportonly.com),
476
+ script_src: %w('self')
477
+ }
478
+ end
479
+ SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
480
+
481
+ hash = SecureHeaders.header_hash_for(request)
482
+ expect(hash["Content-Security-Policy"]).to eq("default-src enforced.com; script-src 'self' anothercdn.com")
483
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src reportonly.com; script-src 'self' anothercdn.com")
484
+ end
485
+
486
+ end
239
487
  end
240
488
  end
241
489
  end
@@ -244,7 +492,7 @@ module SecureHeaders
244
492
  it "validates your hsts config upon configuration" do
245
493
  expect do
246
494
  Configuration.default do |config|
247
- config.hsts = 'lol'
495
+ config.hsts = "lol"
248
496
  end
249
497
  end.to raise_error(STSConfigError)
250
498
  end
@@ -252,7 +500,7 @@ module SecureHeaders
252
500
  it "validates your csp config upon configuration" do
253
501
  expect do
254
502
  Configuration.default do |config|
255
- config.csp = { CSP::DEFAULT_SRC => '123456' }
503
+ config.csp = { ContentSecurityPolicy::DEFAULT_SRC => "123456" }
256
504
  end
257
505
  end.to raise_error(ContentSecurityPolicyConfigError)
258
506
  end
@@ -260,7 +508,7 @@ module SecureHeaders
260
508
  it "raises errors for unknown directives" do
261
509
  expect do
262
510
  Configuration.default do |config|
263
- config.csp = { made_up_directive: '123456' }
511
+ config.csp = { made_up_directive: "123456" }
264
512
  end
265
513
  end.to raise_error(ContentSecurityPolicyConfigError)
266
514
  end
@@ -281,6 +529,14 @@ module SecureHeaders
281
529
  end.to raise_error(XContentTypeOptionsConfigError)
282
530
  end
283
531
 
532
+ it "validates your clear site data config upon configuration" do
533
+ expect do
534
+ Configuration.default do |config|
535
+ config.clear_site_data = 1
536
+ end
537
+ end.to raise_error(ClearSiteDataConfigError)
538
+ end
539
+
284
540
  it "validates your x_xss config upon configuration" do
285
541
  expect do
286
542
  Configuration.default do |config|
@@ -305,6 +561,14 @@ module SecureHeaders
305
561
  end.to raise_error(XPCDPConfigError)
306
562
  end
307
563
 
564
+ it "validates your referrer_policy config upon configuration" do
565
+ expect do
566
+ Configuration.default do |config|
567
+ config.referrer_policy = "lol"
568
+ end
569
+ end.to raise_error(ReferrerPolicyConfigError)
570
+ end
571
+
308
572
  it "validates your hpkp config upon configuration" do
309
573
  expect do
310
574
  Configuration.default do |config|
@@ -312,6 +576,14 @@ module SecureHeaders
312
576
  end
313
577
  end.to raise_error(PublicKeyPinsConfigError)
314
578
  end
579
+
580
+ it "validates your cookies config upon configuration" do
581
+ expect do
582
+ Configuration.default do |config|
583
+ config.cookies = { secure: "lol" }
584
+ end
585
+ end.to raise_error(CookiesConfigError)
586
+ end
315
587
  end
316
588
  end
317
589
  end
data/spec/spec_helper.rb CHANGED
@@ -1,35 +1,39 @@
1
- require 'rubygems'
2
- require 'rspec'
3
- require 'rack'
4
- require 'pry-nav'
5
-
6
- require 'coveralls'
1
+ # frozen_string_literal: true
2
+ require "rubygems"
3
+ require "rspec"
4
+ require "rack"
5
+ require "coveralls"
7
6
  Coveralls.wear!
8
7
 
9
- require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
8
+ require File.join(File.dirname(__FILE__), "..", "lib", "secure_headers")
10
9
 
11
10
 
12
11
 
13
12
  USER_AGENTS = {
14
- firefox: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
15
- chrome: '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',
16
- ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
17
- opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
13
+ edge: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
14
+ firefox: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1",
15
+ firefox46: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0",
16
+ chrome: "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",
17
+ ie: "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)",
18
+ opera: "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
18
19
  ios5: "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
19
20
  ios6: "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
20
21
  safari5: "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
21
22
  safari5_1: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
22
- 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"
23
+ 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",
24
+ safari10: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.11 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.11"
23
25
  }
24
26
 
25
27
  def expect_default_values(hash)
26
- expect(hash[SecureHeaders::CSP::HEADER_NAME]).to eq(SecureHeaders::CSP::DEFAULT_VALUE)
28
+ expect(hash[SecureHeaders::ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'")
27
29
  expect(hash[SecureHeaders::XFrameOptions::HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::DEFAULT_VALUE)
28
30
  expect(hash[SecureHeaders::XDownloadOptions::HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::DEFAULT_VALUE)
29
31
  expect(hash[SecureHeaders::StrictTransportSecurity::HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::DEFAULT_VALUE)
30
32
  expect(hash[SecureHeaders::XXssProtection::HEADER_NAME]).to eq(SecureHeaders::XXssProtection::DEFAULT_VALUE)
31
33
  expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
32
34
  expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
35
+ expect(hash[SecureHeaders::ReferrerPolicy::HEADER_NAME]).to be_nil
36
+ expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
33
37
  end
34
38
 
35
39
  module SecureHeaders
@@ -45,15 +49,3 @@ end
45
49
  def reset_config
46
50
  SecureHeaders::Configuration.clear_configurations
47
51
  end
48
-
49
- def capture_warning
50
- begin
51
- old_stderr = $stderr
52
- $stderr = StringIO.new
53
- yield
54
- result = $stderr.string
55
- ensure
56
- $stderr = old_stderr
57
- end
58
- result
59
- end