secure_headers 3.9.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 (56) hide show
  1. checksums.yaml +5 -5
  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 -22
  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 +18 -5
  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 +10 -19
  18. data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
  19. data/lib/secure_headers/headers/cookie.rb +22 -10
  20. data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -2
  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 +17 -18
  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 +15 -29
  40. data/spec/lib/secure_headers/headers/cookie_spec.rb +58 -37
  41. data/spec/lib/secure_headers/headers/{expect_certificate_transparency_spec.rb → expect_certificate_spec.rb} +3 -3
  42. data/spec/lib/secure_headers/headers/policy_management_spec.rb +26 -11
  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 +9 -22
  55. data/upgrading-to-4-0.md +55 -0
  56. metadata +16 -8
@@ -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,58 +49,72 @@ 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
62
68
  end
63
69
 
64
70
  context "SameSite cookies" do
65
- %w(None Lax Strict).each do |flag|
66
- it "flags SameSite=#{flag}" do
67
- cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } })
68
- expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
69
- end
71
+ it "flags SameSite=Lax" do
72
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
73
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
74
+ end
70
75
 
71
- it "flags SameSite=#{flag} when configured with a boolean" do
72
- cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true })
73
- expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
74
- end
76
+ it "flags SameSite=Lax when configured with a boolean" do
77
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
78
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
79
+ end
75
80
 
76
- it "does not flag cookies as SameSite=#{flag} when excluded" do
77
- cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } })
78
- expect(cookie.to_s).to eq("_session=thisisatest")
79
- end
81
+ it "does not flag cookies as SameSite=Lax when excluded" do
82
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
83
+ expect(cookie.to_s).to eq("_session=thisisatest")
84
+ end
85
+
86
+ it "flags SameSite=Strict" do
87
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
88
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
89
+ end
90
+
91
+ it "does not flag cookies as SameSite=Strict when excluded" do
92
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
93
+ expect(cookie.to_s).to eq("_session=thisisatest")
80
94
  end
81
95
 
82
96
  it "flags SameSite=Strict when configured with a boolean" do
83
- cookie = Cookie.new(raw_cookie, samesite: { strict: true})
97
+ cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
84
98
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
85
99
  end
86
100
 
87
101
  it "flags properly when both lax and strict are configured" do
88
102
  raw_cookie = "_session=thisisatest"
89
- 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)
90
104
  expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
91
105
  end
92
106
 
93
107
  it "ignores configuration if the cookie is already flagged" do
94
108
  raw_cookie = "_session=thisisatest; SameSite=Strict"
95
- cookie = Cookie.new(raw_cookie, samesite: { lax: true })
109
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
96
110
  expect(cookie.to_s).to eq(raw_cookie)
97
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
98
118
  end
99
119
  end
100
120
 
@@ -105,12 +125,18 @@ module SecureHeaders
105
125
  end.to raise_error(CookiesConfigError)
106
126
  end
107
127
 
108
- 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
109
129
  expect do
110
130
  Cookie.validate_config!(secure: "true")
111
131
  end.to raise_error(CookiesConfigError)
112
132
  end
113
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
+
114
140
  it "raises an exception when both only and except filters are provided" do
115
141
  expect do
116
142
  Cookie.validate_config!(secure: { only: [], except: [] })
@@ -123,15 +149,10 @@ module SecureHeaders
123
149
  end.to raise_error(CookiesConfigError)
124
150
  end
125
151
 
126
- cookie_options = %w(none lax strict).map(&:to_sym)
127
- cookie_options.each do |flag|
128
- (cookie_options - [flag]).each do |other_flag|
129
- it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
130
- expect do
131
- Cookie.validate_config!(samesite: { flag => true, other_flag => true})
132
- end.to raise_error(CookiesConfigError)
133
- end
134
- end
152
+ it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
153
+ expect do
154
+ Cookie.validate_config!(samesite: { lax: true, strict: true})
155
+ end.to raise_error(CookiesConfigError)
135
156
  end
136
157
 
137
158
  it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
@@ -3,13 +3,13 @@ require "spec_helper"
3
3
 
4
4
  module SecureHeaders
5
5
  describe ExpectCertificateTransparency do
6
- specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce, max-age=1234") }
6
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
7
7
  specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
8
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\"") }
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
10
  specify do
11
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\""
12
+ header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
13
13
  expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
14
14
  end
15
15
 
@@ -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
 
@@ -32,7 +33,6 @@ module SecureHeaders
32
33
  object_src: %w('self'),
33
34
  script_src: %w('self'),
34
35
  style_src: %w('unsafe-inline'),
35
- worker_src: %w(worker.com),
36
36
  base_uri: %w('self'),
37
37
  form_action: %w('self' github.com),
38
38
  frame_ancestors: %w('none'),
@@ -47,10 +47,22 @@ module SecureHeaders
47
47
 
48
48
  it "requires a :default_src value" do
49
49
  expect do
50
- ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
50
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
51
51
  end.to raise_error(ContentSecurityPolicyConfigError)
52
52
  end
53
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
+
54
66
  it "requires :report_only to be a truthy value" do
55
67
  expect do
56
68
  ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
@@ -96,7 +108,7 @@ module SecureHeaders
96
108
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
97
109
  it "performs light validation on source lists" do
98
110
  expect do
99
- 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')))
100
112
  end.to raise_error(ContentSecurityPolicyConfigError)
101
113
  end
102
114
 
@@ -135,19 +147,21 @@ module SecureHeaders
135
147
  it "combines the default-src value with the override if the directive was unconfigured" do
136
148
  Configuration.default do |config|
137
149
  config.csp = {
138
- default_src: %w(https:)
150
+ default_src: %w(https:),
151
+ script_src: %w('self'),
139
152
  }
140
153
  end
141
- 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))
142
155
  csp = ContentSecurityPolicy.new(combined_config)
143
156
  expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
144
- 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")
145
158
  end
146
159
 
147
160
  it "combines directives where the original value is nil and the hash is frozen" do
148
161
  Configuration.default do |config|
149
162
  config.csp = {
150
163
  default_src: %w('self'),
164
+ script_src: %w('self'),
151
165
  report_only: false
152
166
  }.freeze
153
167
  end
@@ -161,6 +175,7 @@ module SecureHeaders
161
175
  Configuration.default do |config|
162
176
  config.csp = {
163
177
  default_src: %w('self'),
178
+ script_src: %w('self'),
164
179
  report_only: false
165
180
  }.freeze
166
181
  end
@@ -172,14 +187,13 @@ module SecureHeaders
172
187
  ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
173
188
  expect(combined_config[directive]).to eq(%w("http://example.org))
174
189
  end
175
-
176
- ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
177
190
  end
178
191
 
179
192
  it "overrides the report_only flag" do
180
193
  Configuration.default do |config|
181
194
  config.csp = {
182
195
  default_src: %w('self'),
196
+ script_src: %w('self'),
183
197
  report_only: false
184
198
  }
185
199
  end
@@ -192,12 +206,13 @@ module SecureHeaders
192
206
  Configuration.default do |config|
193
207
  config.csp = {
194
208
  default_src: %w(https:),
209
+ script_src: %w('self'),
195
210
  block_all_mixed_content: false
196
211
  }
197
212
  end
198
213
  combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
199
214
  csp = ContentSecurityPolicy.new(combined_config)
200
- 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'")
201
216
  end
202
217
 
203
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
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe ReferrerPolicy do
5
6
  specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
6
- specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
7
+ specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
7
8
 
8
9
  context "valid configuration values" do
9
10
  it "accepts 'no-referrer'" do
@@ -61,7 +62,7 @@ module SecureHeaders
61
62
  end
62
63
  end
63
64
 
64
- context 'invlaid configuration values' do
65
+ context "invlaid configuration values" do
65
66
  it "doesn't accept invalid values" do
66
67
  expect do
67
68
  ReferrerPolicy.validate_config!("open")
@@ -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 StrictTransportSecurity do
@@ -10,19 +11,19 @@ module SecureHeaders
10
11
  context "with a string argument" do
11
12
  it "raises an exception with an invalid max-age" do
12
13
  expect do
13
- StrictTransportSecurity.validate_config!('max-age=abc123')
14
+ StrictTransportSecurity.validate_config!("max-age=abc123")
14
15
  end.to raise_error(STSConfigError)
15
16
  end
16
17
 
17
18
  it "raises an exception if max-age is not supplied" do
18
19
  expect do
19
- StrictTransportSecurity.validate_config!('includeSubdomains')
20
+ StrictTransportSecurity.validate_config!("includeSubdomains")
20
21
  end.to raise_error(STSConfigError)
21
22
  end
22
23
 
23
24
  it "raises an exception with an invalid format" do
24
25
  expect do
25
- StrictTransportSecurity.validate_config!('max-age=123includeSubdomains')
26
+ StrictTransportSecurity.validate_config!("max-age=123includeSubdomains")
26
27
  end.to raise_error(STSConfigError)
27
28
  end
28
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 XContentTypeOptions do
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe XDownloadOptions do
5
6
  specify { expect(XDownloadOptions.make_header).to eq([XDownloadOptions::HEADER_NAME, XDownloadOptions::DEFAULT_VALUE]) }
6
- specify { expect(XDownloadOptions.make_header('noopen')).to eq([XDownloadOptions::HEADER_NAME, 'noopen']) }
7
+ specify { expect(XDownloadOptions.make_header("noopen")).to eq([XDownloadOptions::HEADER_NAME, "noopen"]) }
7
8
 
8
9
  context "invalid configuration values" do
9
10
  it "accepts noopen" 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 XFrameOptions do
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe XPermittedCrossDomainPolicies do
5
6
  specify { expect(XPermittedCrossDomainPolicies.make_header).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "none"]) }
6
- specify { expect(XPermittedCrossDomainPolicies.make_header('master-only')).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, 'master-only']) }
7
+ specify { expect(XPermittedCrossDomainPolicies.make_header("master-only")).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "master-only"]) }
7
8
 
8
9
  context "valid configuration values" do
9
10
  it "accepts 'all'" do
@@ -36,7 +37,7 @@ module SecureHeaders
36
37
  end
37
38
  end
38
39
 
39
- context 'invlaid configuration values' do
40
+ context "invlaid configuration values" do
40
41
  it "doesn't accept invalid values" do
41
42
  expect do
42
43
  XPermittedCrossDomainPolicies.validate_config!("open")
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe XXssProtection do
5
6
  specify { expect(XXssProtection.make_header).to eq([XXssProtection::HEADER_NAME, XXssProtection::DEFAULT_VALUE]) }
6
- specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, '1; mode=block; report=https://www.secure.com/reports']) }
7
+ specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, "1; mode=block; report=https://www.secure.com/reports"]) }
7
8
 
8
9
  context "with invalid configuration" do
9
10
  it "should raise an error when providing a string that is not valid" do
@@ -19,7 +20,7 @@ module SecureHeaders
19
20
  context "when using a hash value" do
20
21
  it "should allow string values ('1' or '0' are the only valid strings)" do
21
22
  expect do
22
- XXssProtection.validate_config!('1')
23
+ XXssProtection.validate_config!("1")
23
24
  end.not_to raise_error
24
25
  end
25
26
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "spec_helper"
2
3
 
3
4
  module SecureHeaders
@@ -19,8 +20,8 @@ module SecureHeaders
19
20
  config.hpkp = {
20
21
  max_age: 10000000,
21
22
  pins: [
22
- {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
23
- {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
23
+ {sha256: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
24
+ {sha256: "73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"}
24
25
  ],
25
26
  report_uri: "https://#{report_host}/example-hpkp"
26
27
  }
@@ -54,68 +55,75 @@ module SecureHeaders
54
55
  expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
55
56
  end
56
57
 
57
- context "secure_cookies" do
58
+ context "cookies" do
58
59
  context "cookies should be flagged" do
59
60
  it "flags cookies as secure" do
60
- capture_warning do
61
- Configuration.default { |config| config.secure_cookies = true }
62
- end
61
+ Configuration.default { |config| config.cookies = {secure: true, httponly: OPT_OUT, samesite: OPT_OUT} }
63
62
  request = Rack::Request.new("HTTPS" => "on")
64
63
  _, env = cookie_middleware.call request.env
65
- expect(env['Set-Cookie']).to eq("foo=bar; secure")
64
+ expect(env["Set-Cookie"]).to eq("foo=bar; secure")
66
65
  end
67
66
  end
68
67
 
68
+ it "allows opting out of cookie protection with OPT_OUT alone" do
69
+ Configuration.default { |config| config.cookies = OPT_OUT}
70
+
71
+ # do NOT make this request https. non-https requests modify a config,
72
+ # causing an exception when operating on OPT_OUT. This ensures we don't
73
+ # try to modify the config.
74
+ request = Rack::Request.new({})
75
+ _, env = cookie_middleware.call request.env
76
+ expect(env["Set-Cookie"]).to eq("foo=bar")
77
+ end
78
+
69
79
  context "cookies should not be flagged" do
70
80
  it "does not flags cookies as secure" do
71
- capture_warning do
72
- Configuration.default { |config| config.secure_cookies = false }
73
- end
81
+ Configuration.default { |config| config.cookies = {secure: OPT_OUT, httponly: OPT_OUT, samesite: OPT_OUT} }
74
82
  request = Rack::Request.new("HTTPS" => "on")
75
83
  _, env = cookie_middleware.call request.env
76
- expect(env['Set-Cookie']).to eq("foo=bar")
84
+ expect(env["Set-Cookie"]).to eq("foo=bar")
77
85
  end
78
86
  end
79
87
  end
80
88
 
81
89
  context "cookies" do
82
90
  it "flags cookies from configuration" do
83
- Configuration.default { |config| config.cookies = { secure: true, httponly: true } }
91
+ Configuration.default { |config| config.cookies = { secure: true, httponly: true, samesite: { lax: true} } }
84
92
  request = Rack::Request.new("HTTPS" => "on")
85
93
  _, env = cookie_middleware.call request.env
86
94
 
87
- expect(env['Set-Cookie']).to eq("foo=bar; secure; HttpOnly")
95
+ expect(env["Set-Cookie"]).to eq("foo=bar; secure; HttpOnly; SameSite=Lax")
88
96
  end
89
97
 
90
98
  it "flags cookies with a combination of SameSite configurations" do
91
99
  cookie_middleware = Middleware.new(lambda { |env| [200, env.merge("Set-Cookie" => ["_session=foobar", "_guest=true"]), "app"] })
92
100
 
93
- Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } } } }
101
+ Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } }, httponly: OPT_OUT, secure: OPT_OUT} }
94
102
  request = Rack::Request.new("HTTPS" => "on")
95
103
  _, env = cookie_middleware.call request.env
96
104
 
97
- expect(env['Set-Cookie']).to match("_session=foobar; SameSite=Strict")
98
- expect(env['Set-Cookie']).to match("_guest=true; SameSite=Lax")
105
+ expect(env["Set-Cookie"]).to match("_session=foobar; SameSite=Strict")
106
+ expect(env["Set-Cookie"]).to match("_guest=true; SameSite=Lax")
99
107
  end
100
108
 
101
109
  it "disables secure cookies for non-https requests" do
102
- Configuration.default { |config| config.cookies = { secure: true } }
110
+ Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
103
111
 
104
112
  request = Rack::Request.new("HTTPS" => "off")
105
113
  _, env = cookie_middleware.call request.env
106
- expect(env['Set-Cookie']).to eq("foo=bar")
114
+ expect(env["Set-Cookie"]).to eq("foo=bar")
107
115
  end
108
116
 
109
117
  it "sets the secure cookie flag correctly on interleaved http/https requests" do
110
- Configuration.default { |config| config.cookies = { secure: true } }
118
+ Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
111
119
 
112
120
  request = Rack::Request.new("HTTPS" => "off")
113
121
  _, env = cookie_middleware.call request.env
114
- expect(env['Set-Cookie']).to eq("foo=bar")
122
+ expect(env["Set-Cookie"]).to eq("foo=bar")
115
123
 
116
124
  request = Rack::Request.new("HTTPS" => "on")
117
125
  _, env = cookie_middleware.call request.env
118
- expect(env['Set-Cookie']).to eq("foo=bar; secure")
126
+ expect(env["Set-Cookie"]).to eq("foo=bar; secure")
119
127
  end
120
128
  end
121
129
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "spec_helper"
2
3
  require "erb"
3
4
 
@@ -58,7 +59,7 @@ TEMPLATE
58
59
  end
59
60
 
60
61
  if options.is_a?(Hash)
61
- options = options.map {|k,v| " #{k}=#{v}"}
62
+ options = options.map {|k, v| " #{k}=#{v}"}
62
63
  end
63
64
  "<#{type}#{options}>#{content}</#{type}>"
64
65
  end
@@ -82,9 +83,9 @@ module SecureHeaders
82
83
  before(:all) do
83
84
  Configuration.default do |config|
84
85
  config.csp = {
85
- :default_src => %w('self'),
86
- :script_src => %w('self'),
87
- :style_src => %w('self')
86
+ default_src: %w('self'),
87
+ script_src: %w('self'),
88
+ style_src: %w('self')
88
89
  }
89
90
  end
90
91
  end