secure_headers 3.7.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 (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 +8 -4
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +7 -4
  9. data/Guardfile +1 -0
  10. data/README.md +7 -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 +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 +52 -20
  18. data/lib/secure_headers/headers/content_security_policy_config.rb +1 -0
  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 +104 -49
  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 +42 -25
  40. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  41. data/spec/lib/secure_headers/headers/policy_management_spec.rb +56 -10
  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 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
@@ -120,50 +135,52 @@ module SecureHeaders
120
135
  block_all_mixed_content: true,
121
136
  upgrade_insecure_requests: true,
122
137
  script_src: %w(script-src.com),
123
- script_nonce: 123456
138
+ script_nonce: 123456,
139
+ sandbox: %w(allow-forms),
140
+ plugin_types: %w(application/pdf)
124
141
  })
125
142
  end
126
143
 
127
144
  it "does not filter any directives for Chrome" do
128
145
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
129
- 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 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")
130
147
  end
131
148
 
132
149
  it "does not filter any directives for Opera" do
133
150
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
134
- 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 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")
135
152
  end
136
153
 
137
154
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
138
155
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
139
- 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 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")
140
157
  end
141
158
 
142
159
  it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
143
160
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
144
- 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 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")
145
162
  end
146
163
 
147
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
148
165
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
149
- 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")
150
167
  end
151
168
 
152
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
153
170
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
154
- 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")
155
172
  end
156
173
 
157
174
  it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
158
175
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
159
- 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")
160
177
  end
161
178
 
162
179
  it "falls back to standard Firefox defaults when the useragent version is not present" do
163
180
  ua = USER_AGENTS[:firefox].dup
164
181
  allow(ua).to receive(:version).and_return(nil)
165
182
  policy = ContentSecurityPolicy.new(complex_opts, ua)
166
- 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 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")
167
184
  end
168
185
  end
169
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: [] })
@@ -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
 
@@ -46,10 +47,22 @@ module SecureHeaders
46
47
 
47
48
  it "requires a :default_src value" do
48
49
  expect do
49
- ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
50
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
50
51
  end.to raise_error(ContentSecurityPolicyConfigError)
51
52
  end
52
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
+
53
66
  it "requires :report_only to be a truthy value" do
54
67
  expect do
55
68
  ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
@@ -95,28 +108,60 @@ module SecureHeaders
95
108
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
96
109
  it "performs light validation on source lists" do
97
110
  expect do
98
- 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"])))
99
136
  end.to raise_error(ContentSecurityPolicyConfigError)
100
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
101
144
  end
102
145
 
103
146
  describe "#combine_policies" do
104
147
  it "combines the default-src value with the override if the directive was unconfigured" do
105
148
  Configuration.default do |config|
106
149
  config.csp = {
107
- default_src: %w(https:)
150
+ default_src: %w(https:),
151
+ script_src: %w('self'),
108
152
  }
109
153
  end
110
- 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))
111
155
  csp = ContentSecurityPolicy.new(combined_config)
112
156
  expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
113
- 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")
114
158
  end
115
159
 
116
160
  it "combines directives where the original value is nil and the hash is frozen" do
117
161
  Configuration.default do |config|
118
162
  config.csp = {
119
163
  default_src: %w('self'),
164
+ script_src: %w('self'),
120
165
  report_only: false
121
166
  }.freeze
122
167
  end
@@ -130,6 +175,7 @@ module SecureHeaders
130
175
  Configuration.default do |config|
131
176
  config.csp = {
132
177
  default_src: %w('self'),
178
+ script_src: %w('self'),
133
179
  report_only: false
134
180
  }.freeze
135
181
  end
@@ -141,14 +187,13 @@ module SecureHeaders
141
187
  ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
142
188
  expect(combined_config[directive]).to eq(%w("http://example.org))
143
189
  end
144
-
145
- ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
146
190
  end
147
191
 
148
192
  it "overrides the report_only flag" do
149
193
  Configuration.default do |config|
150
194
  config.csp = {
151
195
  default_src: %w('self'),
196
+ script_src: %w('self'),
152
197
  report_only: false
153
198
  }
154
199
  end
@@ -161,12 +206,13 @@ module SecureHeaders
161
206
  Configuration.default do |config|
162
207
  config.csp = {
163
208
  default_src: %w(https:),
209
+ script_src: %w('self'),
164
210
  block_all_mixed_content: false
165
211
  }
166
212
  end
167
213
  combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
168
214
  csp = ContentSecurityPolicy.new(combined_config)
169
- 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'")
170
216
  end
171
217
 
172
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