secure_headers 3.6.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +8 -4
  6. data/CHANGELOG.md +38 -1
  7. data/CODE_OF_CONDUCT.md +46 -0
  8. data/CONTRIBUTING.md +41 -0
  9. data/Gemfile +7 -4
  10. data/Guardfile +1 -0
  11. data/LICENSE +4 -199
  12. data/README.md +18 -20
  13. data/Rakefile +22 -18
  14. data/docs/cookies.md +16 -2
  15. data/docs/hashes.md +1 -1
  16. data/docs/per_action_configuration.md +28 -0
  17. data/lib/secure_headers/configuration.rb +30 -17
  18. data/lib/secure_headers/hash_helper.rb +2 -1
  19. data/lib/secure_headers/headers/clear_site_data.rb +16 -12
  20. data/lib/secure_headers/headers/content_security_policy.rb +54 -22
  21. data/lib/secure_headers/headers/content_security_policy_config.rb +46 -12
  22. data/lib/secure_headers/headers/cookie.rb +21 -3
  23. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  24. data/lib/secure_headers/headers/policy_management.rb +122 -67
  25. data/lib/secure_headers/headers/public_key_pins.rb +5 -4
  26. data/lib/secure_headers/headers/referrer_policy.rb +2 -1
  27. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  28. data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
  29. data/lib/secure_headers/headers/x_download_options.rb +3 -2
  30. data/lib/secure_headers/headers/x_frame_options.rb +2 -1
  31. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +3 -2
  32. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  33. data/lib/secure_headers/middleware.rb +10 -9
  34. data/lib/secure_headers/railtie.rb +7 -6
  35. data/lib/secure_headers/utils/cookies_config.rb +13 -12
  36. data/lib/secure_headers/view_helper.rb +4 -3
  37. data/lib/secure_headers.rb +7 -3
  38. data/lib/tasks/tasks.rake +2 -1
  39. data/secure_headers.gemspec +13 -3
  40. data/spec/lib/secure_headers/configuration_spec.rb +9 -8
  41. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +11 -27
  42. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +47 -26
  43. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  44. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  45. data/spec/lib/secure_headers/headers/policy_management_spec.rb +57 -10
  46. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  47. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
  48. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  49. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  50. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  51. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  52. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  53. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  54. data/spec/lib/secure_headers/middleware_spec.rb +29 -21
  55. data/spec/lib/secure_headers/view_helpers_spec.rb +5 -5
  56. data/spec/lib/secure_headers_spec.rb +106 -107
  57. data/spec/spec_helper.rb +10 -22
  58. data/upgrading-to-4-0.md +55 -0
  59. metadata +19 -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
@@ -30,16 +31,16 @@ module SecureHeaders
30
31
  describe "#header_hash_for" do
31
32
  it "allows you to opt out of individual headers via API" do
32
33
  Configuration.default do |config|
33
- config.csp = { default_src: %w('self')}
34
+ config.csp = { default_src: %w('self'), script_src: %w('self')}
34
35
  config.csp_report_only = config.csp
35
36
  end
36
37
  SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
37
38
  SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
38
39
  SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
39
40
  hash = SecureHeaders.header_hash_for(request)
40
- expect(hash['Content-Security-Policy-Report-Only']).to be_nil
41
- expect(hash['Content-Security-Policy']).to be_nil
42
- 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
43
44
  end
44
45
 
45
46
  it "Carries options over when using overrides" do
@@ -54,15 +55,15 @@ module SecureHeaders
54
55
 
55
56
  SecureHeaders.use_secure_headers_override(request, :api)
56
57
  hash = SecureHeaders.header_hash_for(request)
57
- expect(hash['X-Download-Options']).to be_nil
58
- expect(hash['X-Permitted-Cross-Domain-Policies']).to be_nil
59
- 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
60
61
  end
61
62
 
62
63
  it "allows you to opt out entirely" do
63
64
  # configure the disabled-by-default headers to ensure they also do not get set
64
65
  Configuration.default do |config|
65
- config.csp = { :default_src => ["example.com"] }
66
+ config.csp = { default_src: ["example.com"], script_src: %w('self') }
66
67
  config.csp_report_only = config.csp
67
68
  config.hpkp = {
68
69
  report_only: false,
@@ -105,9 +106,10 @@ module SecureHeaders
105
106
  end
106
107
 
107
108
  it "produces a UA-specific CSP when overriding (and busting the cache)" do
108
- config = Configuration.default do |config|
109
+ Configuration.default do |config|
109
110
  config.csp = {
110
111
  default_src: %w('self'),
112
+ script_src: %w('self'),
111
113
  child_src: %w('self')
112
114
  }
113
115
  end
@@ -144,10 +146,10 @@ module SecureHeaders
144
146
  config.hpkp = {
145
147
  max_age: 1_000_000,
146
148
  include_subdomains: true,
147
- report_uri: '//example.com/uri-directive',
149
+ report_uri: "//example.com/uri-directive",
148
150
  pins: [
149
- { sha256: 'abc' },
150
- { sha256: '123' }
151
+ { sha256: "abc" },
152
+ { sha256: "123" }
151
153
  ]
152
154
  }
153
155
  end
@@ -173,15 +175,32 @@ module SecureHeaders
173
175
  expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
174
176
  end
175
177
 
178
+ it "child-src and frame-src must match" do
179
+ Configuration.default do |config|
180
+ config.csp = {
181
+ default_src: %w('self'),
182
+ frame_src: %w(frame_src.com),
183
+ script_src: %w('self')
184
+ }
185
+ end
186
+
187
+ SecureHeaders.append_content_security_policy_directives(chrome_request, child_src: %w(child_src.com))
188
+
189
+ expect {
190
+ SecureHeaders.header_hash_for(chrome_request)
191
+ }.to raise_error(ArgumentError)
192
+ end
193
+
176
194
  it "supports named appends" do
177
195
  Configuration.default do |config|
178
196
  config.csp = {
179
- default_src: %w('self')
197
+ default_src: %w('self'),
198
+ script_src: %w('self')
180
199
  }
181
200
  end
182
201
 
183
202
  Configuration.named_append(:moar_default_sources) do |request|
184
- { default_src: %w(https:)}
203
+ { default_src: %w(https:), style_src: %w('self')}
185
204
  end
186
205
 
187
206
  Configuration.named_append(:how_about_a_script_src_too) do |request|
@@ -192,54 +211,58 @@ module SecureHeaders
192
211
  SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
193
212
  hash = SecureHeaders.header_hash_for(request)
194
213
 
195
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
214
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' 'unsafe-inline'; style-src 'self'")
196
215
  end
197
216
 
198
217
  it "appends a nonce to a missing script-src value" do
199
218
  Configuration.default do |config|
200
219
  config.csp = {
201
- default_src: %w('self')
220
+ default_src: %w('self'),
221
+ script_src: %w('self')
202
222
  }
203
223
  end
204
224
 
205
225
  SecureHeaders.content_security_policy_script_nonce(request) # should add the value to the header
206
226
  hash = SecureHeaders.header_hash_for(chrome_request)
207
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/
227
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/)
208
228
  end
209
229
 
210
230
  it "appends a hash to a missing script-src value" do
211
231
  Configuration.default do |config|
212
232
  config.csp = {
213
- default_src: %w('self')
233
+ default_src: %w('self'),
234
+ script_src: %w('self')
214
235
  }
215
236
  end
216
237
 
217
238
  SecureHeaders.append_content_security_policy_directives(request, script_src: %w('sha256-abc123'))
218
239
  hash = SecureHeaders.header_hash_for(chrome_request)
219
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/
240
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/)
220
241
  end
221
242
 
222
243
  it "overrides individual directives" do
223
244
  Configuration.default do |config|
224
245
  config.csp = {
225
- default_src: %w('self')
246
+ default_src: %w('self'),
247
+ script_src: %w('self')
226
248
  }
227
249
  end
228
250
  SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
229
251
  hash = SecureHeaders.header_hash_for(request)
230
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'")
252
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'; script-src 'self'")
231
253
  end
232
254
 
233
255
  it "overrides non-existant directives" do
234
256
  Configuration.default do |config|
235
257
  config.csp = {
236
- default_src: %w(https:)
258
+ default_src: %w(https:),
259
+ script_src: %w('self')
237
260
  }
238
261
  end
239
262
  SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
240
263
  hash = SecureHeaders.header_hash_for(request)
241
264
  expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
242
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:")
265
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:; script-src 'self'")
243
266
  end
244
267
 
245
268
  it "does not append a nonce when the browser does not support it" do
@@ -252,7 +275,7 @@ module SecureHeaders
252
275
  end
253
276
 
254
277
  safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
255
- nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
278
+ SecureHeaders.content_security_policy_script_nonce(safari_request)
256
279
  hash = SecureHeaders.header_hash_for(safari_request)
257
280
  expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
258
281
  end
@@ -274,7 +297,7 @@ module SecureHeaders
274
297
  SecureHeaders.content_security_policy_script_nonce(chrome_request)
275
298
 
276
299
  hash = SecureHeaders.header_hash_for(chrome_request)
277
- 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'")
278
301
  end
279
302
 
280
303
  it "uses a nonce for safari 10+" do
@@ -288,25 +311,18 @@ module SecureHeaders
288
311
  safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
289
312
  nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
290
313
  hash = SecureHeaders.header_hash_for(safari_request)
291
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
314
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
292
315
  end
293
316
 
294
- it "supports the deprecated `report_only: true` format" do
295
- expect(Kernel).to receive(:warn).once
296
-
297
- Configuration.default do |config|
298
- config.csp = {
299
- default_src: %w('self'),
300
- report_only: true
301
- }
302
- end
303
-
304
- expect(Configuration.get.csp).to eq(OPT_OUT)
305
- expect(Configuration.get.csp_report_only).to be_a(ContentSecurityPolicyReportOnlyConfig)
306
-
307
- hash = SecureHeaders.header_hash_for(request)
308
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to be_nil
309
- expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'")
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)
310
326
  end
311
327
 
312
328
  it "Raises an error if csp_report_only is used with `report_only: false`" do
@@ -314,6 +330,7 @@ module SecureHeaders
314
330
  Configuration.default do |config|
315
331
  config.csp_report_only = {
316
332
  default_src: %w('self'),
333
+ script_src: %w('self'),
317
334
  report_only: false
318
335
  }
319
336
  end
@@ -324,7 +341,8 @@ module SecureHeaders
324
341
  before(:each) do
325
342
  Configuration.default do |config|
326
343
  config.csp = {
327
- default_src: %w('self')
344
+ default_src: %w('self'),
345
+ script_src: %w('self')
328
346
  }
329
347
  config.csp_report_only = config.csp
330
348
  end
@@ -333,155 +351,136 @@ module SecureHeaders
333
351
  it "sets identical values when the configs are the same" do
334
352
  Configuration.default do |config|
335
353
  config.csp = {
336
- default_src: %w('self')
354
+ default_src: %w('self'),
355
+ script_src: %w('self')
337
356
  }
338
357
  config.csp_report_only = {
339
- default_src: %w('self')
358
+ default_src: %w('self'),
359
+ script_src: %w('self')
340
360
  }
341
361
  end
342
362
 
343
363
  hash = SecureHeaders.header_hash_for(request)
344
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
345
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
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'")
346
366
  end
347
367
 
348
368
  it "sets different headers when the configs are different" do
349
369
  Configuration.default do |config|
350
370
  config.csp = {
351
- default_src: %w('self')
371
+ default_src: %w('self'),
372
+ script_src: %w('self')
352
373
  }
353
- config.csp_report_only = config.csp.merge({script_src: %w('self')})
374
+ config.csp_report_only = config.csp.merge({script_src: %w(foo.com)})
354
375
  end
355
376
 
356
377
  hash = SecureHeaders.header_hash_for(request)
357
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
358
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self'")
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")
359
380
  end
360
381
 
361
382
  it "allows you to opt-out of enforced CSP" do
362
383
  Configuration.default do |config|
363
384
  config.csp = SecureHeaders::OPT_OUT
364
385
  config.csp_report_only = {
365
- default_src: %w('self')
366
- }
367
- end
368
-
369
- hash = SecureHeaders.header_hash_for(request)
370
- expect(hash['Content-Security-Policy']).to be_nil
371
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
372
- end
373
-
374
- it "opts-out of enforced CSP when only csp_report_only is set" do
375
- expect(Kernel).to receive(:warn).once
376
- Configuration.default do |config|
377
- config.csp_report_only = {
378
- default_src: %w('self')
379
- }
380
- end
381
-
382
- hash = SecureHeaders.header_hash_for(request)
383
- expect(hash['Content-Security-Policy']).to be_nil
384
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
385
- end
386
-
387
- it "allows you to set csp_report_only before csp" do
388
- expect(Kernel).to receive(:warn).once
389
- Configuration.default do |config|
390
- config.csp_report_only = {
391
- default_src: %w('self')
386
+ default_src: %w('self'),
387
+ script_src: %w('self')
392
388
  }
393
- config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
394
389
  end
395
390
 
396
391
  hash = SecureHeaders.header_hash_for(request)
397
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' 'unsafe-inline'")
398
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
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'")
399
394
  end
400
395
 
401
396
  it "allows appending to the enforced policy" do
402
397
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
403
398
  hash = SecureHeaders.header_hash_for(request)
404
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
405
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
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'")
406
401
  end
407
402
 
408
403
  it "allows appending to the report only policy" do
409
404
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
410
405
  hash = SecureHeaders.header_hash_for(request)
411
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
412
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
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")
413
408
  end
414
409
 
415
410
  it "allows appending to both policies" do
416
411
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
417
412
  hash = SecureHeaders.header_hash_for(request)
418
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
419
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
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")
420
415
  end
421
416
 
422
417
  it "allows overriding the enforced policy" do
423
418
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
424
419
  hash = SecureHeaders.header_hash_for(request)
425
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
426
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
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'")
427
422
  end
428
423
 
429
424
  it "allows overriding the report only policy" do
430
425
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
431
426
  hash = SecureHeaders.header_hash_for(request)
432
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
433
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
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")
434
429
  end
435
430
 
436
431
  it "allows overriding both policies" do
437
432
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
438
433
  hash = SecureHeaders.header_hash_for(request)
439
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
440
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
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")
441
436
  end
442
437
 
443
438
  context "when inferring which config to modify" do
444
439
  it "updates the enforced header when configured" do
445
440
  Configuration.default do |config|
446
441
  config.csp = {
447
- default_src: %w('self')
442
+ default_src: %w('self'),
443
+ script_src: %w('self')
448
444
  }
449
445
  end
450
446
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
451
447
 
452
448
  hash = SecureHeaders.header_hash_for(request)
453
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
454
- expect(hash['Content-Security-Policy-Report-Only']).to be_nil
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
455
451
  end
456
452
 
457
453
  it "updates the report only header when configured" do
458
454
  Configuration.default do |config|
459
455
  config.csp = OPT_OUT
460
456
  config.csp_report_only = {
461
- default_src: %w('self')
457
+ default_src: %w('self'),
458
+ script_src: %w('self')
462
459
  }
463
460
  end
464
461
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
465
462
 
466
463
  hash = SecureHeaders.header_hash_for(request)
467
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
468
- expect(hash['Content-Security-Policy']).to be_nil
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
469
466
  end
470
467
 
471
468
  it "updates both headers if both are configured" do
472
469
  Configuration.default do |config|
473
470
  config.csp = {
474
- default_src: %w(enforced.com)
471
+ default_src: %w(enforced.com),
472
+ script_src: %w('self')
475
473
  }
476
474
  config.csp_report_only = {
477
- default_src: %w(reportonly.com)
475
+ default_src: %w(reportonly.com),
476
+ script_src: %w('self')
478
477
  }
479
478
  end
480
479
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
481
480
 
482
481
  hash = SecureHeaders.header_hash_for(request)
483
- expect(hash['Content-Security-Policy']).to eq("default-src enforced.com; script-src enforced.com anothercdn.com")
484
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src reportonly.com; script-src reportonly.com anothercdn.com")
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")
485
484
  end
486
485
 
487
486
  end
@@ -493,7 +492,7 @@ module SecureHeaders
493
492
  it "validates your hsts config upon configuration" do
494
493
  expect do
495
494
  Configuration.default do |config|
496
- config.hsts = 'lol'
495
+ config.hsts = "lol"
497
496
  end
498
497
  end.to raise_error(STSConfigError)
499
498
  end
@@ -501,7 +500,7 @@ module SecureHeaders
501
500
  it "validates your csp config upon configuration" do
502
501
  expect do
503
502
  Configuration.default do |config|
504
- config.csp = { ContentSecurityPolicy::DEFAULT_SRC => '123456' }
503
+ config.csp = { ContentSecurityPolicy::DEFAULT_SRC => "123456" }
505
504
  end
506
505
  end.to raise_error(ContentSecurityPolicyConfigError)
507
506
  end
@@ -509,7 +508,7 @@ module SecureHeaders
509
508
  it "raises errors for unknown directives" do
510
509
  expect do
511
510
  Configuration.default do |config|
512
- config.csp = { made_up_directive: '123456' }
511
+ config.csp = { made_up_directive: "123456" }
513
512
  end
514
513
  end.to raise_error(ContentSecurityPolicyConfigError)
515
514
  end
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,11 @@
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
 
@@ -14,9 +13,9 @@ USER_AGENTS = {
14
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",
15
14
  firefox: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1",
16
15
  firefox46: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0",
17
- 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',
18
- ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
19
- opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
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",
20
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",
21
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",
22
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",
@@ -34,6 +33,7 @@ def expect_default_values(hash)
34
33
  expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
35
34
  expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
36
35
  expect(hash[SecureHeaders::ReferrerPolicy::HEADER_NAME]).to be_nil
36
+ expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
37
37
  end
38
38
 
39
39
  module SecureHeaders
@@ -49,15 +49,3 @@ end
49
49
  def reset_config
50
50
  SecureHeaders::Configuration.clear_configurations
51
51
  end
52
-
53
- def capture_warning
54
- begin
55
- old_stderr = $stderr
56
- $stderr = StringIO.new
57
- yield
58
- result = $stderr.string
59
- ensure
60
- $stderr = old_stderr
61
- end
62
- result
63
- end
@@ -0,0 +1,55 @@
1
+ ### Breaking Changes
2
+
3
+ The most likely change to break your app is the new cookie defaults. This is the first place to check. If you're using the default CSP, your policy will change but your app should not break. This should not break brand new projects using secure_headers either.
4
+
5
+ ## All cookies default to secure/httponly/SameSite=Lax
6
+
7
+ By default, *all* cookies will be marked as `SameSite=lax`,`secure`, and `httponly`. To opt-out, supply `OPT_OUT` as the value for `SecureHeaders.cookies` or the individual configs. Setting these values to `false` will raise an error.
8
+
9
+ ```ruby
10
+ # specific opt outs
11
+ config.cookies = {
12
+ secure: OPT_OUT,
13
+ httponly: OPT_OUT,
14
+ samesite: OPT_OUT,
15
+ }
16
+
17
+ # nuclear option, just make things work again
18
+ config.cookies = OPT_OUT
19
+ ```
20
+
21
+ ## script_src must be set
22
+
23
+ Not setting a `script_src` value means your policy falls back to whatever `default_src` (also required) is set to. This can be very dangerous and indicates the policy is too loose.
24
+
25
+ However, sometimes you really don't need a `script-src` e.g. API responses (`default-src 'none'`) so you can set `script_src: SecureHeaders::OPT_OUT` to work around this.
26
+
27
+ ## Default Content Security Policy
28
+
29
+ The default CSP has changed to be more universal without sacrificing too much security.
30
+
31
+ * Flash/Java disabled by default
32
+ * `img-src` allows data: images and favicons (among others)
33
+ * `style-src` allows inline CSS by default (most find it impossible/impractical to remove inline content today)
34
+ * `form-action` (not governed by `default-src`, practically treated as `*`) is set to `'self'`
35
+
36
+ Previously, the default CSP was:
37
+
38
+ `Content-Security-Policy: default-src 'self'`
39
+
40
+ The new default policy is:
41
+
42
+ `default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:`
43
+
44
+ ## CSP configuration
45
+
46
+ * Setting `report_only: true` in a CSP config will raise an error. Instead, set `csp_report_only`.
47
+ * Setting `frame_src` and `child_src` when values don't match will raise an error. Just use `frame_src`.
48
+
49
+ ## config.secure_cookies removed
50
+
51
+ Use `config.cookies` instead.
52
+
53
+ ## Supported ruby versions
54
+
55
+ We've dropped support for ruby versions <= 2.2. Sorry.