secure_headers 3.7.2 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +8 -6
  6. data/CHANGELOG.md +2 -6
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +7 -4
  9. data/Guardfile +1 -0
  10. data/README.md +7 -19
  11. data/Rakefile +22 -18
  12. data/docs/cookies.md +16 -2
  13. data/docs/per_action_configuration.md +28 -0
  14. data/lib/secure_headers/configuration.rb +5 -12
  15. data/lib/secure_headers/hash_helper.rb +2 -1
  16. data/lib/secure_headers/headers/clear_site_data.rb +4 -3
  17. data/lib/secure_headers/headers/content_security_policy.rb +12 -15
  18. data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
  19. data/lib/secure_headers/headers/cookie.rb +21 -3
  20. data/lib/secure_headers/headers/expect_certificate_transparency.rb +1 -1
  21. data/lib/secure_headers/headers/policy_management.rb +14 -8
  22. data/lib/secure_headers/headers/public_key_pins.rb +4 -3
  23. data/lib/secure_headers/headers/referrer_policy.rb +1 -0
  24. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  25. data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
  26. data/lib/secure_headers/headers/x_download_options.rb +2 -1
  27. data/lib/secure_headers/headers/x_frame_options.rb +1 -0
  28. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
  29. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  30. data/lib/secure_headers/middleware.rb +10 -9
  31. data/lib/secure_headers/railtie.rb +7 -6
  32. data/lib/secure_headers/utils/cookies_config.rb +13 -12
  33. data/lib/secure_headers/view_helper.rb +2 -1
  34. data/lib/secure_headers.rb +2 -1
  35. data/lib/tasks/tasks.rake +2 -1
  36. data/secure_headers.gemspec +13 -3
  37. data/spec/lib/secure_headers/configuration_spec.rb +9 -8
  38. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +2 -1
  39. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +18 -18
  40. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  41. data/spec/lib/secure_headers/headers/policy_management_spec.rb +26 -11
  42. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  43. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
  44. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  45. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  46. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  47. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  48. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  49. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  50. data/spec/lib/secure_headers/middleware_spec.rb +29 -21
  51. data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
  52. data/spec/lib/secure_headers_spec.rb +92 -120
  53. data/spec/spec_helper.rb +9 -22
  54. data/upgrading-to-4-0.md +55 -0
  55. metadata +13 -6
@@ -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,
@@ -108,6 +109,7 @@ module SecureHeaders
108
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,42 +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
 
176
- it "appends child-src to frame-src" do
178
+ it "child-src and frame-src must match" do
177
179
  Configuration.default do |config|
178
180
  config.csp = {
179
181
  default_src: %w('self'),
180
- frame_src: %w(frame_src.com)
182
+ frame_src: %w(frame_src.com),
183
+ script_src: %w('self')
181
184
  }
182
185
  end
183
186
 
184
187
  SecureHeaders.append_content_security_policy_directives(chrome_request, child_src: %w(child_src.com))
185
- hash = SecureHeaders.header_hash_for(chrome_request)
186
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; child-src frame_src.com child_src.com")
187
- end
188
-
189
- it "appends frame-src to child-src" do
190
- Configuration.default do |config|
191
- config.csp = {
192
- default_src: %w('self'),
193
- child_src: %w(child_src.com)
194
- }
195
- end
196
188
 
197
- safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari6]))
198
- SecureHeaders.append_content_security_policy_directives(safari_request, frame_src: %w(frame_src.com))
199
- hash = SecureHeaders.header_hash_for(safari_request)
200
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src child_src.com frame_src.com")
189
+ expect {
190
+ SecureHeaders.header_hash_for(chrome_request)
191
+ }.to raise_error(ArgumentError)
201
192
  end
202
193
 
203
194
  it "supports named appends" do
204
195
  Configuration.default do |config|
205
196
  config.csp = {
206
- default_src: %w('self')
197
+ default_src: %w('self'),
198
+ script_src: %w('self')
207
199
  }
208
200
  end
209
201
 
210
202
  Configuration.named_append(:moar_default_sources) do |request|
211
- { default_src: %w(https:)}
203
+ { default_src: %w(https:), style_src: %w('self')}
212
204
  end
213
205
 
214
206
  Configuration.named_append(:how_about_a_script_src_too) do |request|
@@ -219,13 +211,14 @@ module SecureHeaders
219
211
  SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
220
212
  hash = SecureHeaders.header_hash_for(request)
221
213
 
222
- 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'")
223
215
  end
224
216
 
225
217
  it "appends a nonce to a missing script-src value" do
226
218
  Configuration.default do |config|
227
219
  config.csp = {
228
- default_src: %w('self')
220
+ default_src: %w('self'),
221
+ script_src: %w('self')
229
222
  }
230
223
  end
231
224
 
@@ -237,7 +230,8 @@ module SecureHeaders
237
230
  it "appends a hash to a missing script-src value" do
238
231
  Configuration.default do |config|
239
232
  config.csp = {
240
- default_src: %w('self')
233
+ default_src: %w('self'),
234
+ script_src: %w('self')
241
235
  }
242
236
  end
243
237
 
@@ -249,24 +243,26 @@ module SecureHeaders
249
243
  it "overrides individual directives" do
250
244
  Configuration.default do |config|
251
245
  config.csp = {
252
- default_src: %w('self')
246
+ default_src: %w('self'),
247
+ script_src: %w('self')
253
248
  }
254
249
  end
255
250
  SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
256
251
  hash = SecureHeaders.header_hash_for(request)
257
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'")
252
+ expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'; script-src 'self'")
258
253
  end
259
254
 
260
255
  it "overrides non-existant directives" do
261
256
  Configuration.default do |config|
262
257
  config.csp = {
263
- default_src: %w(https:)
258
+ default_src: %w(https:),
259
+ script_src: %w('self')
264
260
  }
265
261
  end
266
262
  SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
267
263
  hash = SecureHeaders.header_hash_for(request)
268
264
  expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
269
- 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'")
270
266
  end
271
267
 
272
268
  it "does not append a nonce when the browser does not support it" do
@@ -301,7 +297,7 @@ module SecureHeaders
301
297
  SecureHeaders.content_security_policy_script_nonce(chrome_request)
302
298
 
303
299
  hash = SecureHeaders.header_hash_for(chrome_request)
304
- 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'")
305
301
  end
306
302
 
307
303
  it "uses a nonce for safari 10+" do
@@ -315,25 +311,18 @@ module SecureHeaders
315
311
  safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
316
312
  nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
317
313
  hash = SecureHeaders.header_hash_for(safari_request)
318
- 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}'")
319
315
  end
320
316
 
321
- it "supports the deprecated `report_only: true` format" do
322
- expect(Kernel).to receive(:warn).once
323
-
324
- Configuration.default do |config|
325
- config.csp = {
326
- default_src: %w('self'),
327
- report_only: true
328
- }
329
- end
330
-
331
- expect(Configuration.get.csp).to eq(OPT_OUT)
332
- expect(Configuration.get.csp_report_only).to be_a(ContentSecurityPolicyReportOnlyConfig)
333
-
334
- hash = SecureHeaders.header_hash_for(request)
335
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to be_nil
336
- 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)
337
326
  end
338
327
 
339
328
  it "Raises an error if csp_report_only is used with `report_only: false`" do
@@ -341,6 +330,7 @@ module SecureHeaders
341
330
  Configuration.default do |config|
342
331
  config.csp_report_only = {
343
332
  default_src: %w('self'),
333
+ script_src: %w('self'),
344
334
  report_only: false
345
335
  }
346
336
  end
@@ -351,7 +341,8 @@ module SecureHeaders
351
341
  before(:each) do
352
342
  Configuration.default do |config|
353
343
  config.csp = {
354
- default_src: %w('self')
344
+ default_src: %w('self'),
345
+ script_src: %w('self')
355
346
  }
356
347
  config.csp_report_only = config.csp
357
348
  end
@@ -360,155 +351,136 @@ module SecureHeaders
360
351
  it "sets identical values when the configs are the same" do
361
352
  Configuration.default do |config|
362
353
  config.csp = {
363
- default_src: %w('self')
354
+ default_src: %w('self'),
355
+ script_src: %w('self')
364
356
  }
365
357
  config.csp_report_only = {
366
- default_src: %w('self')
358
+ default_src: %w('self'),
359
+ script_src: %w('self')
367
360
  }
368
361
  end
369
362
 
370
363
  hash = SecureHeaders.header_hash_for(request)
371
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
372
- 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'")
373
366
  end
374
367
 
375
368
  it "sets different headers when the configs are different" do
376
369
  Configuration.default do |config|
377
370
  config.csp = {
378
- default_src: %w('self')
371
+ default_src: %w('self'),
372
+ script_src: %w('self')
379
373
  }
380
- config.csp_report_only = config.csp.merge({script_src: %w('self')})
374
+ config.csp_report_only = config.csp.merge({script_src: %w(foo.com)})
381
375
  end
382
376
 
383
377
  hash = SecureHeaders.header_hash_for(request)
384
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
385
- 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")
386
380
  end
387
381
 
388
382
  it "allows you to opt-out of enforced CSP" do
389
383
  Configuration.default do |config|
390
384
  config.csp = SecureHeaders::OPT_OUT
391
385
  config.csp_report_only = {
392
- default_src: %w('self')
393
- }
394
- end
395
-
396
- hash = SecureHeaders.header_hash_for(request)
397
- expect(hash['Content-Security-Policy']).to be_nil
398
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
399
- end
400
-
401
- it "opts-out of enforced CSP when only csp_report_only is set" do
402
- expect(Kernel).to receive(:warn).once
403
- Configuration.default do |config|
404
- config.csp_report_only = {
405
- default_src: %w('self')
406
- }
407
- end
408
-
409
- hash = SecureHeaders.header_hash_for(request)
410
- expect(hash['Content-Security-Policy']).to be_nil
411
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
412
- end
413
-
414
- it "allows you to set csp_report_only before csp" do
415
- expect(Kernel).to receive(:warn).once
416
- Configuration.default do |config|
417
- config.csp_report_only = {
418
- default_src: %w('self')
386
+ default_src: %w('self'),
387
+ script_src: %w('self')
419
388
  }
420
- config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
421
389
  end
422
390
 
423
391
  hash = SecureHeaders.header_hash_for(request)
424
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' 'unsafe-inline'")
425
- 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'")
426
394
  end
427
395
 
428
396
  it "allows appending to the enforced policy" do
429
397
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
430
398
  hash = SecureHeaders.header_hash_for(request)
431
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
432
- 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'")
433
401
  end
434
402
 
435
403
  it "allows appending to the report only policy" do
436
404
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
437
405
  hash = SecureHeaders.header_hash_for(request)
438
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
439
- 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")
440
408
  end
441
409
 
442
410
  it "allows appending to both policies" do
443
411
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
444
412
  hash = SecureHeaders.header_hash_for(request)
445
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
446
- 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")
447
415
  end
448
416
 
449
417
  it "allows overriding the enforced policy" do
450
418
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
451
419
  hash = SecureHeaders.header_hash_for(request)
452
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
453
- 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'")
454
422
  end
455
423
 
456
424
  it "allows overriding the report only policy" do
457
425
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
458
426
  hash = SecureHeaders.header_hash_for(request)
459
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
460
- 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")
461
429
  end
462
430
 
463
431
  it "allows overriding both policies" do
464
432
  SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
465
433
  hash = SecureHeaders.header_hash_for(request)
466
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
467
- 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")
468
436
  end
469
437
 
470
438
  context "when inferring which config to modify" do
471
439
  it "updates the enforced header when configured" do
472
440
  Configuration.default do |config|
473
441
  config.csp = {
474
- default_src: %w('self')
442
+ default_src: %w('self'),
443
+ script_src: %w('self')
475
444
  }
476
445
  end
477
446
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
478
447
 
479
448
  hash = SecureHeaders.header_hash_for(request)
480
- expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
481
- 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
482
451
  end
483
452
 
484
453
  it "updates the report only header when configured" do
485
454
  Configuration.default do |config|
486
455
  config.csp = OPT_OUT
487
456
  config.csp_report_only = {
488
- default_src: %w('self')
457
+ default_src: %w('self'),
458
+ script_src: %w('self')
489
459
  }
490
460
  end
491
461
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
492
462
 
493
463
  hash = SecureHeaders.header_hash_for(request)
494
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
495
- 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
496
466
  end
497
467
 
498
468
  it "updates both headers if both are configured" do
499
469
  Configuration.default do |config|
500
470
  config.csp = {
501
- default_src: %w(enforced.com)
471
+ default_src: %w(enforced.com),
472
+ script_src: %w('self')
502
473
  }
503
474
  config.csp_report_only = {
504
- default_src: %w(reportonly.com)
475
+ default_src: %w(reportonly.com),
476
+ script_src: %w('self')
505
477
  }
506
478
  end
507
479
  SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
508
480
 
509
481
  hash = SecureHeaders.header_hash_for(request)
510
- expect(hash['Content-Security-Policy']).to eq("default-src enforced.com; script-src enforced.com anothercdn.com")
511
- 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")
512
484
  end
513
485
 
514
486
  end
@@ -520,7 +492,7 @@ module SecureHeaders
520
492
  it "validates your hsts config upon configuration" do
521
493
  expect do
522
494
  Configuration.default do |config|
523
- config.hsts = 'lol'
495
+ config.hsts = "lol"
524
496
  end
525
497
  end.to raise_error(STSConfigError)
526
498
  end
@@ -528,7 +500,7 @@ module SecureHeaders
528
500
  it "validates your csp config upon configuration" do
529
501
  expect do
530
502
  Configuration.default do |config|
531
- config.csp = { ContentSecurityPolicy::DEFAULT_SRC => '123456' }
503
+ config.csp = { ContentSecurityPolicy::DEFAULT_SRC => "123456" }
532
504
  end
533
505
  end.to raise_error(ContentSecurityPolicyConfigError)
534
506
  end
@@ -536,7 +508,7 @@ module SecureHeaders
536
508
  it "raises errors for unknown directives" do
537
509
  expect do
538
510
  Configuration.default do |config|
539
- config.csp = { made_up_directive: '123456' }
511
+ config.csp = { made_up_directive: "123456" }
540
512
  end
541
513
  end.to raise_error(ContentSecurityPolicyConfigError)
542
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",
@@ -50,15 +49,3 @@ end
50
49
  def reset_config
51
50
  SecureHeaders::Configuration.clear_configurations
52
51
  end
53
-
54
- def capture_warning
55
- begin
56
- old_stderr = $stderr
57
- $stderr = StringIO.new
58
- yield
59
- result = $stderr.string
60
- ensure
61
- $stderr = old_stderr
62
- end
63
- result
64
- 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.
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: 3.7.2
4
+ version: 4.0.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: 2017-10-03 00:00:00.000000000 Z
11
+ date: 2017-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.15.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.15.0
41
41
  description: Manages application of security headers with many safe defaults.
42
42
  email:
43
43
  - neil.matatall@gmail.com
@@ -49,6 +49,7 @@ files:
49
49
  - ".github/PULL_REQUEST_TEMPLATE.md"
50
50
  - ".gitignore"
51
51
  - ".rspec"
52
+ - ".rubocop.yml"
52
53
  - ".ruby-gemset"
53
54
  - ".ruby-version"
54
55
  - ".travis.yml"
@@ -108,11 +109,17 @@ files:
108
109
  - spec/lib/secure_headers_spec.rb
109
110
  - spec/spec_helper.rb
110
111
  - upgrading-to-3-0.md
112
+ - upgrading-to-4-0.md
111
113
  homepage: https://github.com/twitter/secureheaders
112
114
  licenses:
113
115
  - Apache Public License 2.0
114
116
  metadata: {}
115
- post_install_message:
117
+ post_install_message: |2+
118
+
119
+ **********
120
+ :wave: secure_headers 4.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/upgrading-to-4-0.md
121
+ **********
122
+
116
123
  rdoc_options: []
117
124
  require_paths:
118
125
  - lib
@@ -128,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
135
  version: '0'
129
136
  requirements: []
130
137
  rubyforge_project:
131
- rubygems_version: 2.5.2
138
+ rubygems_version: 2.6.11
132
139
  signing_key:
133
140
  specification_version: 4
134
141
  summary: Add easily configured security headers to responses including content-security-policy,