secure_headers 3.6.5 → 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 (56) 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 +16 -0
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +7 -4
  9. data/Guardfile +1 -0
  10. data/README.md +14 -18
  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 +9 -13
  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 +52 -20
  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 +70 -0
  21. data/lib/secure_headers/headers/policy_management.rb +112 -65
  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 +4 -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 +42 -26
  40. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  41. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  42. data/spec/lib/secure_headers/headers/policy_management_spec.rb +57 -10
  43. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  44. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
  45. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  46. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  47. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  48. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  49. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  50. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  51. data/spec/lib/secure_headers/middleware_spec.rb +29 -21
  52. data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
  53. data/spec/lib/secure_headers_spec.rb +92 -120
  54. data/spec/spec_helper.rb +10 -22
  55. data/upgrading-to-4-0.md +55 -0
  56. metadata +16 -6
data/lib/tasks/tasks.rake CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
2
3
  INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
3
4
  INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
@@ -73,7 +74,7 @@ namespace :secure_headers do
73
74
  end
74
75
  end
75
76
 
76
- File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, 'w') do |file|
77
+ File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, "w") do |file|
77
78
  file.write(script_hashes.to_yaml)
78
79
  end
79
80
 
@@ -1,10 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  Gem::Specification.new do |gem|
3
4
  gem.name = "secure_headers"
4
- gem.version = "3.6.5"
5
+ gem.version = "4.0.0"
5
6
  gem.authors = ["Neil Matatall"]
6
7
  gem.email = ["neil.matatall@gmail.com"]
7
- gem.description = 'Manages application of security headers with many safe defaults.'
8
+ gem.description = "Manages application of security headers with many safe defaults."
8
9
  gem.summary = 'Add easily configured security headers to responses
9
10
  including content-security-policy, x-frame-options,
10
11
  strict-transport-security, etc.'
@@ -15,5 +16,14 @@ Gem::Specification.new do |gem|
15
16
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
17
  gem.require_paths = ["lib"]
17
18
  gem.add_development_dependency "rake"
18
- gem.add_dependency "useragent"
19
+ gem.add_dependency "useragent", ">= 0.15.0"
20
+
21
+ # TODO: delete this after 4.1 is cut or a number of 4.0.x releases have occurred
22
+ gem.post_install_message = <<-POST_INSTALL
23
+
24
+ **********
25
+ :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
26
+ **********
27
+
28
+ POST_INSTALL
19
29
  end
@@ -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 Configuration do
@@ -70,7 +71,7 @@ module SecureHeaders
70
71
 
71
72
  it "allows you to override an override" do
72
73
  Configuration.override(:override) do |config|
73
- config.csp = { default_src: %w('self')}
74
+ config.csp = { default_src: %w('self'), script_src: %w('self')}
74
75
  end
75
76
 
76
77
  Configuration.override(:second_override, :override) do |config|
@@ -78,17 +79,17 @@ module SecureHeaders
78
79
  end
79
80
 
80
81
  original_override = Configuration.get(:override)
81
- expect(original_override.csp.to_h).to eq(default_src: %w('self'))
82
+ expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
82
83
  override_config = Configuration.get(:second_override)
83
84
  expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
84
85
  end
85
86
 
86
87
  it "deprecates the secure_cookies configuration" do
87
- expect(Kernel).to receive(:warn).with(/\[DEPRECATION\]/)
88
-
89
- Configuration.default do |config|
90
- config.secure_cookies = true
91
- end
88
+ expect {
89
+ Configuration.default do |config|
90
+ config.secure_cookies = true
91
+ end
92
+ }.to raise_error(ArgumentError)
92
93
  end
93
94
  end
94
95
  end
@@ -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 ClearSiteData do
@@ -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 ContentSecurityPolicy do
@@ -23,6 +24,10 @@ module SecureHeaders
23
24
  end
24
25
 
25
26
  describe "#value" do
27
+ it "uses a safe but non-breaking default value" do
28
+ expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
29
+ end
30
+
26
31
  it "discards 'none' values if any other source expressions are present" do
27
32
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
28
33
  expect(csp.value).not_to include("'none'")
@@ -46,13 +51,18 @@ module SecureHeaders
46
51
  expect(csp.value).to eq("default-src example.org")
47
52
  end
48
53
 
54
+ it "does not build directives with a value of OPT_OUT (and bypasses directive requirements)" do
55
+ csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), script_src: OPT_OUT)
56
+ expect(csp.value).to eq("default-src example.org")
57
+ end
58
+
49
59
  it "does not remove schemes from report-uri values" do
50
60
  csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
51
61
  expect(csp.value).to eq("default-src https:; report-uri https://example.org")
52
62
  end
53
63
 
54
64
  it "does not remove schemes when :preserve_schemes is true" do
55
- csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), :preserve_schemes => true)
65
+ csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), preserve_schemes: true)
56
66
  expect(csp.value).to eq("default-src https://example.org")
57
67
  end
58
68
 
@@ -86,25 +96,30 @@ module SecureHeaders
86
96
  expect(csp.value).to eq("default-src example.org")
87
97
  end
88
98
 
99
+ it "creates maximally strict sandbox policy when passed no sandbox token values" do
100
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
101
+ expect(csp.value).to eq("default-src example.org; sandbox")
102
+ end
103
+
104
+ it "creates maximally strict sandbox policy when passed true" do
105
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: true)
106
+ expect(csp.value).to eq("default-src example.org; sandbox")
107
+ end
108
+
109
+ it "creates sandbox policy when passed valid sandbox token values" do
110
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: %w(allow-forms allow-scripts))
111
+ expect(csp.value).to eq("default-src example.org; sandbox allow-forms allow-scripts")
112
+ end
113
+
89
114
  it "does not emit a warning when using frame-src" do
90
115
  expect(Kernel).to_not receive(:warn)
91
116
  ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
92
117
  end
93
118
 
94
- it "emits a warning when child-src and frame-src are supplied but are not equal" do
95
- expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
96
- ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
97
- end
98
-
99
- it "will still set inconsistent child/frame-src values to be less surprising" do
100
- expect(Kernel).to receive(:warn).at_least(:once)
101
- firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
102
- firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
103
- expect(firefox).not_to eq(firefox_transitional)
104
- expect(firefox).to match(/frame-src/)
105
- expect(firefox).not_to match(/child-src/)
106
- expect(firefox_transitional).to match(/child-src/)
107
- expect(firefox_transitional).not_to match(/frame-src/)
119
+ it "raises an error when child-src and frame-src are supplied but are not equal" do
120
+ expect {
121
+ ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
122
+ }.to raise_error(ArgumentError)
108
123
  end
109
124
 
110
125
  it "supports strict-dynamic" do
@@ -119,52 +134,53 @@ module SecureHeaders
119
134
  end.merge({
120
135
  block_all_mixed_content: true,
121
136
  upgrade_insecure_requests: true,
122
- reflected_xss: "block",
123
137
  script_src: %w(script-src.com),
124
- script_nonce: 123456
138
+ script_nonce: 123456,
139
+ sandbox: %w(allow-forms),
140
+ plugin_types: %w(application/pdf)
125
141
  })
126
142
  end
127
143
 
128
144
  it "does not filter any directives for Chrome" do
129
145
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
130
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
146
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
131
147
  end
132
148
 
133
149
  it "does not filter any directives for Opera" do
134
150
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
135
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
151
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
136
152
  end
137
153
 
138
154
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
139
155
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
140
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
156
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
141
157
  end
142
158
 
143
159
  it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
144
160
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
145
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
161
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
146
162
  end
147
163
 
148
164
  it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
149
165
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
150
- expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
166
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
151
167
  end
152
168
 
153
169
  it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
154
170
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
155
- expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
171
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
156
172
  end
157
173
 
158
174
  it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
159
175
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
160
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
176
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
161
177
  end
162
178
 
163
179
  it "falls back to standard Firefox defaults when the useragent version is not present" do
164
180
  ua = USER_AGENTS[:firefox].dup
165
181
  allow(ua).to receive(:version).and_return(nil)
166
182
  policy = ContentSecurityPolicy.new(complex_opts, ua)
167
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
183
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
168
184
  end
169
185
  end
170
186
  end
@@ -1,40 +1,46 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe Cookie do
5
6
  let(:raw_cookie) { "_session=thisisatest" }
6
7
 
7
- it "does not tamper with cookies when unconfigured" do
8
- cookie = Cookie.new(raw_cookie, {})
8
+ it "does not tamper with cookies when using OPT_OUT is used" do
9
+ cookie = Cookie.new(raw_cookie, OPT_OUT)
9
10
  expect(cookie.to_s).to eq(raw_cookie)
10
11
  end
11
12
 
13
+ it "applies httponly, secure, and samesite by default" do
14
+ cookie = Cookie.new(raw_cookie, nil)
15
+ expect(cookie.to_s).to eq("_session=thisisatest; secure; HttpOnly; SameSite=Lax")
16
+ end
17
+
12
18
  it "preserves existing attributes" do
13
- cookie = Cookie.new("_session=thisisatest; secure", secure: true)
19
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
14
20
  expect(cookie.to_s).to eq("_session=thisisatest; secure")
15
21
  end
16
22
 
17
23
  it "prevents duplicate flagging of attributes" do
18
- cookie = Cookie.new("_session=thisisatest; secure", secure: true)
24
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT)
19
25
  expect(cookie.to_s.scan(/secure/i).count).to eq(1)
20
26
  end
21
27
 
22
28
  context "Secure cookies" do
23
29
  context "when configured with a boolean" do
24
30
  it "flags cookies as Secure" do
25
- cookie = Cookie.new(raw_cookie, secure: true)
31
+ cookie = Cookie.new(raw_cookie, secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
26
32
  expect(cookie.to_s).to eq("_session=thisisatest; secure")
27
33
  end
28
34
  end
29
35
 
30
36
  context "when configured with a Hash" do
31
37
  it "flags cookies as Secure when whitelisted" do
32
- cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]})
38
+ cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]}, httponly: OPT_OUT, samesite: OPT_OUT)
33
39
  expect(cookie.to_s).to eq("_session=thisisatest; secure")
34
40
  end
35
41
 
36
42
  it "does not flag cookies as Secure when excluded" do
37
- cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] })
43
+ cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
38
44
  expect(cookie.to_s).to eq("_session=thisisatest")
39
45
  end
40
46
  end
@@ -43,19 +49,19 @@ module SecureHeaders
43
49
  context "HttpOnly cookies" do
44
50
  context "when configured with a boolean" do
45
51
  it "flags cookies as HttpOnly" do
46
- cookie = Cookie.new(raw_cookie, httponly: true)
52
+ cookie = Cookie.new(raw_cookie, httponly: true, secure: OPT_OUT, samesite: OPT_OUT)
47
53
  expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
48
54
  end
49
55
  end
50
56
 
51
57
  context "when configured with a Hash" do
52
58
  it "flags cookies as HttpOnly when whitelisted" do
53
- cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]})
59
+ cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]}, secure: OPT_OUT, samesite: OPT_OUT)
54
60
  expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
55
61
  end
56
62
 
57
63
  it "does not flag cookies as HttpOnly when excluded" do
58
- cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] })
64
+ cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
59
65
  expect(cookie.to_s).to eq("_session=thisisatest")
60
66
  end
61
67
  end
@@ -63,46 +69,52 @@ module SecureHeaders
63
69
 
64
70
  context "SameSite cookies" do
65
71
  it "flags SameSite=Lax" do
66
- cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } })
72
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
67
73
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
68
74
  end
69
75
 
70
76
  it "flags SameSite=Lax when configured with a boolean" do
71
- cookie = Cookie.new(raw_cookie, samesite: { lax: true})
77
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
72
78
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
73
79
  end
74
80
 
75
81
  it "does not flag cookies as SameSite=Lax when excluded" do
76
- cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } })
82
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
77
83
  expect(cookie.to_s).to eq("_session=thisisatest")
78
84
  end
79
85
 
80
86
  it "flags SameSite=Strict" do
81
- cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } })
87
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
82
88
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
83
89
  end
84
90
 
85
91
  it "does not flag cookies as SameSite=Strict when excluded" do
86
- cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] } })
92
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
87
93
  expect(cookie.to_s).to eq("_session=thisisatest")
88
94
  end
89
95
 
90
96
  it "flags SameSite=Strict when configured with a boolean" do
91
- cookie = Cookie.new(raw_cookie, samesite: { strict: true})
97
+ cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
92
98
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
93
99
  end
94
100
 
95
101
  it "flags properly when both lax and strict are configured" do
96
102
  raw_cookie = "_session=thisisatest"
97
- cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } })
103
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
98
104
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
99
105
  end
100
106
 
101
107
  it "ignores configuration if the cookie is already flagged" do
102
108
  raw_cookie = "_session=thisisatest; SameSite=Strict"
103
- cookie = Cookie.new(raw_cookie, samesite: { lax: true })
109
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
104
110
  expect(cookie.to_s).to eq(raw_cookie)
105
111
  end
112
+
113
+ it "samesite: true sets all cookies to samesite=lax" do
114
+ raw_cookie = "_session=thisisatest"
115
+ cookie = Cookie.new(raw_cookie, samesite: true, secure: OPT_OUT, httponly: OPT_OUT)
116
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
117
+ end
106
118
  end
107
119
  end
108
120
 
@@ -113,12 +125,18 @@ module SecureHeaders
113
125
  end.to raise_error(CookiesConfigError)
114
126
  end
115
127
 
116
- it "raises an exception when configured without a boolean/Hash" do
128
+ it "raises an exception when configured without a boolean(true or OPT_OUT)/Hash" do
117
129
  expect do
118
130
  Cookie.validate_config!(secure: "true")
119
131
  end.to raise_error(CookiesConfigError)
120
132
  end
121
133
 
134
+ it "raises an exception when configured with false" do
135
+ expect do
136
+ Cookie.validate_config!(secure: false)
137
+ end.to raise_error(CookiesConfigError)
138
+ end
139
+
122
140
  it "raises an exception when both only and except filters are provided" do
123
141
  expect do
124
142
  Cookie.validate_config!(secure: { only: [], except: [] })
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe ExpectCertificateTransparency do
6
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
7
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
8
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
9
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"") }
10
+ specify do
11
+ config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
12
+ header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
13
+ expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
14
+ end
15
+
16
+ context "with an invalid configuration" do
17
+ it "raises an exception when configuration isn't a hash" do
18
+ expect do
19
+ ExpectCertificateTransparency.validate_config!(%w(a))
20
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
21
+ end
22
+
23
+ it "raises an exception when max-age is not provided" do
24
+ expect do
25
+ ExpectCertificateTransparency.validate_config!(foo: "bar")
26
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
27
+ end
28
+
29
+ it "raises an exception with an invalid max-age" do
30
+ expect do
31
+ ExpectCertificateTransparency.validate_config!(max_age: "abc123")
32
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
33
+ end
34
+
35
+ it "raises an exception with an invalid enforce value" do
36
+ expect do
37
+ ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
38
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -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 PolicyManagement do
@@ -16,7 +17,7 @@ module SecureHeaders
16
17
  it "accepts all keys" do
17
18
  # (pulled from README)
18
19
  config = {
19
- # "meta" values. these will shaped the header, but the values are not included in the header.
20
+ # "meta" values. these will shape the header, but the values are not included in the header.
20
21
  report_only: true, # default: false
21
22
  preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
22
23
 
@@ -27,6 +28,7 @@ module SecureHeaders
27
28
  connect_src: %w(wss:),
28
29
  font_src: %w('self' data:),
29
30
  img_src: %w(mycdn.com data:),
31
+ manifest_src: %w(manifest.com),
30
32
  media_src: %w(utoob.com),
31
33
  object_src: %w('self'),
32
34
  script_src: %w('self'),
@@ -45,10 +47,22 @@ module SecureHeaders
45
47
 
46
48
  it "requires a :default_src value" do
47
49
  expect do
48
- ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
50
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
49
51
  end.to raise_error(ContentSecurityPolicyConfigError)
50
52
  end
51
53
 
54
+ it "requires a :script_src value" do
55
+ expect do
56
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self')))
57
+ end.to raise_error(ContentSecurityPolicyConfigError)
58
+ end
59
+
60
+ it "accepts OPT_OUT as a script-src value" do
61
+ expect do
62
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: OPT_OUT))
63
+ end.to_not raise_error
64
+ end
65
+
52
66
  it "requires :report_only to be a truthy value" do
53
67
  expect do
54
68
  ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
@@ -94,28 +108,60 @@ module SecureHeaders
94
108
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
95
109
  it "performs light validation on source lists" do
96
110
  expect do
97
- ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
111
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval), script_src: %w('self')))
112
+ end.to raise_error(ContentSecurityPolicyConfigError)
113
+ end
114
+
115
+ it "rejects anything not of the form allow-* as a sandbox value" do
116
+ expect do
117
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
118
+ end.to raise_error(ContentSecurityPolicyConfigError)
119
+ end
120
+
121
+ it "accepts anything of the form allow-* as a sandbox value " do
122
+ expect do
123
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
124
+ end.to_not raise_error
125
+ end
126
+
127
+ it "accepts true as a sandbox policy" do
128
+ expect do
129
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
130
+ end.to_not raise_error
131
+ end
132
+
133
+ it "rejects anything not of the form type/subtype as a plugin-type value" do
134
+ expect do
135
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
98
136
  end.to raise_error(ContentSecurityPolicyConfigError)
99
137
  end
138
+
139
+ it "accepts anything of the form type/subtype as a plugin-type value " do
140
+ expect do
141
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
142
+ end.to_not raise_error
143
+ end
100
144
  end
101
145
 
102
146
  describe "#combine_policies" do
103
147
  it "combines the default-src value with the override if the directive was unconfigured" do
104
148
  Configuration.default do |config|
105
149
  config.csp = {
106
- default_src: %w(https:)
150
+ default_src: %w(https:),
151
+ script_src: %w('self'),
107
152
  }
108
153
  end
109
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
154
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, style_src: %w(anothercdn.com))
110
155
  csp = ContentSecurityPolicy.new(combined_config)
111
156
  expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
112
- expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
157
+ expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
113
158
  end
114
159
 
115
160
  it "combines directives where the original value is nil and the hash is frozen" do
116
161
  Configuration.default do |config|
117
162
  config.csp = {
118
163
  default_src: %w('self'),
164
+ script_src: %w('self'),
119
165
  report_only: false
120
166
  }.freeze
121
167
  end
@@ -129,6 +175,7 @@ module SecureHeaders
129
175
  Configuration.default do |config|
130
176
  config.csp = {
131
177
  default_src: %w('self'),
178
+ script_src: %w('self'),
132
179
  report_only: false
133
180
  }.freeze
134
181
  end
@@ -140,14 +187,13 @@ module SecureHeaders
140
187
  ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
141
188
  expect(combined_config[directive]).to eq(%w("http://example.org))
142
189
  end
143
-
144
- ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
145
190
  end
146
191
 
147
192
  it "overrides the report_only flag" do
148
193
  Configuration.default do |config|
149
194
  config.csp = {
150
195
  default_src: %w('self'),
196
+ script_src: %w('self'),
151
197
  report_only: false
152
198
  }
153
199
  end
@@ -160,12 +206,13 @@ module SecureHeaders
160
206
  Configuration.default do |config|
161
207
  config.csp = {
162
208
  default_src: %w(https:),
209
+ script_src: %w('self'),
163
210
  block_all_mixed_content: false
164
211
  }
165
212
  end
166
213
  combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
167
214
  csp = ContentSecurityPolicy.new(combined_config)
168
- expect(csp.value).to eq("default-src https:; block-all-mixed-content")
215
+ expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
169
216
  end
170
217
 
171
218
  it "raises an error if appending to a OPT_OUT policy" do
@@ -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 PublicKeyPins do
@@ -8,7 +9,7 @@ module SecureHeaders
8
9
  specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
9
10
  specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
10
11
  specify do
11
- config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin1' }, { sha256: 'base64encodedpin2' }] }
12
+ config = { max_age: 1234, pins: [{ sha256: "base64encodedpin1" }, { sha256: "base64encodedpin2" }] }
12
13
  header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
13
14
  expect(PublicKeyPins.new(config).value).to eq(header_value)
14
15
  end
@@ -16,19 +17,19 @@ module SecureHeaders
16
17
  context "with an invalid configuration" do
17
18
  it "raises an exception when max-age is not provided" do
18
19
  expect do
19
- PublicKeyPins.validate_config!(foo: 'bar')
20
+ PublicKeyPins.validate_config!(foo: "bar")
20
21
  end.to raise_error(PublicKeyPinsConfigError)
21
22
  end
22
23
 
23
24
  it "raises an exception with an invalid max-age" do
24
25
  expect do
25
- PublicKeyPins.validate_config!(max_age: 'abc123')
26
+ PublicKeyPins.validate_config!(max_age: "abc123")
26
27
  end.to raise_error(PublicKeyPinsConfigError)
27
28
  end
28
29
 
29
- it 'raises an exception with less than 2 pins' do
30
+ it "raises an exception with less than 2 pins" do
30
31
  expect do
31
- config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin' }] }
32
+ config = { max_age: 1234, pins: [{ sha256: "base64encodedpin" }] }
32
33
  PublicKeyPins.validate_config!(config)
33
34
  end.to raise_error(PublicKeyPinsConfigError)
34
35
  end